简体   繁体   中英

BroadcastReceiver for SCREEN_ON and SCREEN_OFF even after quitting the app

I'm trying to make an app that monitors the users phone usage by tracking time of screen lock and unlock. I tried to setup a BroadcastReceiver which works fine when the app is running the background. But won't work when I close the app. Is there a solution for this.

The code I'm using now is as follows :

public class MainActivity extends AppCompatActivity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);

       Intent intent = new Intent(this, ScreenListenerService.class);
       startService(intent);
   }

}

ScreenListenerService class is as follows..

public class ScreenListenerService extends Service {

   private BroadcastReceiver mScreenStateBroadcastReceiver = new BroadcastReceiver() {

       @Override
       public void onReceive(Context context, Intent intent) {

           if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {

               // Save something to the server

           } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {

               // Save something to the server

           }

       }

   };

   @Override
   public void onCreate() {
       super.onCreate();
       IntentFilter intentFilter = new IntentFilter();
       intentFilter.addAction(Intent.ACTION_SCREEN_ON);
       intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
       registerReceiver(mScreenStateBroadcastReceiver, intentFilter);
   }

   @Override
   public void onDestroy() {
       unregisterReceiver(mScreenStateBroadcastReceiver);
       super.onDestroy();
   }

   @Override
   public int onStartCommand(Intent intent, int flags, int startId) {
       super.onStartCommand(intent, flags, startId);
       return START_STICKY;
   }

   @Override
   public IBinder onBind(Intent intent) {
       return null;
   }

}

My AndroidManifest file is as follows :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.abbinvarghese.calculu">

   <application
       android:allowBackup="true"
       android:icon="@mipmap/ic_launcher"
       android:label="@string/app_name"
       android:roundIcon="@mipmap/ic_launcher_round"
       android:supportsRtl="true"
       android:theme="@style/AppTheme">
       <service android:name=".ScreenListenerService" />
       <activity android:name=".MainActivity">
           <intent-filter>
               <action android:name="android.intent.action.MAIN" />
               <category android:name="android.intent.category.LAUNCHER" />
           </intent-filter>
       </activity>
   </application>

</manifest>

As Background Execution Limit imposes on Android 8.0 (API level 26) so now it's not possible to listen SCREEN_OFF and SCREEN_ON action in background by running the service.

I have found a work around for same with the help of JobScheduler which works fine for listen broadcast in background without running any service.

Please check on this: Screen OFF/ON broadcast listener without service on Android Oreo

To overcome the imposed limitations of 8.0 you could run a foreground service. Just like a service but a notification is posted to the foreground.

Then the service code would be like this (remember to unregister the receiver onDestory):

BroadcastReceiver screenReceiver;

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}


@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    startRunningInForeground();
    detectingDeterminateOfServiceCall(intent.getExtras());
    registerBroadcastReceivers();
    return START_STICKY;
}

private void startRunningInForeground() {

    //if more than or equal to 26
    if (Build.VERSION.SDK_INT >= 26) {

        //if more than 26
        if(Build.VERSION.SDK_INT > 26){
            String CHANNEL_ONE_ID = "sensor.example. geyerk1.inspect.screenservice";
            String CHANNEL_ONE_NAME = "Screen service";
            NotificationChannel notificationChannel = null;
            notificationChannel = new NotificationChannel(CHANNEL_ONE_ID,
                    CHANNEL_ONE_NAME, NotificationManager.IMPORTANCE_MIN);
            notificationChannel.enableLights(true);
            notificationChannel.setLightColor(Color.RED);
            notificationChannel.setShowBadge(true);
            notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
            NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            if (manager != null) {
                manager.createNotificationChannel(notificationChannel);
            }

            Bitmap icon = BitmapFactory.decodeResource(getResources(), R.drawable.background_running);
            Notification notification = new Notification.Builder(getApplicationContext())
                    .setChannelId(CHANNEL_ONE_ID)
                    .setContentTitle("Recording data")
                    .setContentText("ActivityLog is logging data")
                    .setSmallIcon(R.drawable.background_running)
                    .setLargeIcon(icon)
                    .build();

            Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
            notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
            notification.contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, 0);

            startForeground(101, notification);
        }
        //if version 26
        else{
            startForeground(101, updateNotification());

        }
    }
    //if less than version 26
    else{
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("Activity logger")
                .setContentText("data recording on going")
                .setSmallIcon(R.drawable.background_running)
                .setOngoing(true).build();

        startForeground(101, notification);
    }
}

private Notification updateNotification() {

    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
            new Intent(this, MainActivity.class), 0);

    return new NotificationCompat.Builder(this)
            .setContentTitle("Activity log")
            .setTicker("Ticker")
            .setContentText("recording of data is on going")
            .setSmallIcon(R.drawable.activity_log_icon)
            .setContentIntent(pendingIntent)
            .setOngoing(true).build();
}

private void detectingDeterminateOfServiceCall(Bundle b) {
    if(b != null){
        Log.i("screenService", "bundle not null");
        if(b.getBoolean("phone restarted")){
            storeInternally("Phone restarted");
        }
    }else{
        Log.i("screenService", " bundle equals null");
    }
    documentServiceStart();
}


private void documentServiceStart() {
    Log.i("screenService", "started running");
}


private void registerBroadcastReceivers() {
    screenReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            switch (Objects.requireNonNull(intent.getAction())){
                case Intent.ACTION_SCREEN_ON:
                    //or do something else
                    storeInternally("Screen on");
                    break;
                case Intent.ACTION_SCREEN_OFF:
                    //or do something else
                    storeInternally("Screen off");
                    break;
            }
        }
    };

    IntentFilter screenFilter = new IntentFilter();
    screenFilter.addAction(Intent.ACTION_SCREEN_ON);
    screenFilter.addAction(Intent.ACTION_SCREEN_OFF);

    registerReceiver(screenReceiver, screenFilter);
}
@Override
public void onDestroy() {
    super.onDestroy();
    unregisterReceiver(screenReceiver);
}

and call it from the main activity:

private void startServiceRunning() {
    if(!isMyServiceRunning(Background.class)){
        if(Build.VERSION.SDK_INT >25){
            startForegroundService(new Intent(this, Background.class));
        }else{
            startService(new Intent(this, Background.class));
        }
    }
}

Instead of creating a new service for broadcast receiver, you can directly create a broadcast receiver class that will listen to system broadcasts even when the app is not running. Create a new class which extends BroadcastReceiver .

public class YourReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        //Do your stuff
    }
}

And register it in manifest.

<receiver
    android:name=".YourReceiver"
    android:enabled="true"
    android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.ACTION_SCREEN_ON" />
            <action android:name="android.intent.action. ACTION_SCREEN_OFF" />
            <category android:name="android.intent.category.DEFAUL" />
        </intent-filter>
</receiver>

Read about Manifest-declared receivers here .

Above solution won't work , here is the reason why. Problem is that your service is getting killed when the app is killed, so your receiver instance is removed from memory. Here is a little trick to re-start the service in background. Add the following code to your service.

@Override
public void onTaskRemoved(Intent rootIntent){
    Intent restartServiceIntent = new Intent(getApplicationContext(), this.getClass());
    restartServiceIntent.setPackage(getPackageName());

    PendingIntent restartServicePendingIntent = PendingIntent.getService(getApplicationContext(), 1, restartServiceIntent, PendingIntent.FLAG_ONE_SHOT);
    AlarmManager alarmService = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
    alarmService.set(
    AlarmManager.ELAPSED_REALTIME,
    SystemClock.elapsedRealtime() + 1000,
    restartServicePendingIntent);

    super.onTaskRemoved(rootIntent);
 }

Although this is not the right way to do it. Also in Android 26+ you won't be able to do this and you'd go for foreground service . https://developer.android.com/about/versions/oreo/background

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM