Sticky Intents & acquiring System Service status

As mentioned in the Broadcast Receiver post, updates will only be applied when the app is visible (between onResume and onPause). Therefore system changes which occur outside this window could leave the app with outdated information on resume. Therefore we should also check at onResume what the current system status is.

Depending on the version of Android the device is running you may be able to acquire a status directly from e.g. BatteryManager:

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // If using a modern enough version of Android...
            BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE); // ...ask the appropriate service for a status check
            doSomething(batteryManager.isCharging()); // Returns a boolean you can feed back into your own methods
        } else // Otherwise use sticky intents (see below)

Certain system broadcast intents do not disappear the moment they have fired and are processed. Instead they remain available to the system until superceded by a more up-to-date intent. These are known as sticky intents, and are the way of determining system status in older Android versions:

   {
            IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); // Create a new IntentFilter for the appropriate sticky Intent
            Intent chargingStatus = registerReceiver(null, intentFilter); // The same code you would use to register a Broadcast Receiver, but leave the first parameter null
            int batteryChargingStatus = chargingStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); // Get the info you want from the sticky Intent, with a default value
            boolean isCharging = batteryChargingStatus == BatteryManager.BATTERY_STATUS_CHARGING || batteryChargingStatus == BatteryManager.BATTERY_STATUS_FULL; // This will return a boolean of true if the phone is charging or fully charged
            doSomething(isCharging); // Feed the boolean into your own methods
   }

ud851-Exercises-student\Lesson10-Hydration-Reminder\T10.06

Broadcast Receiver

Listens out for designated System Broadcast Intents via an IntentFilter (declared in the AndroidManifest.xml) so that your app can respond to changes in the system. It can be triggered even when the app is not running. There are two types:

  • Static (Manifest-declared) – triggered whenever the broadcast intent occurs, even if the app is offline
  • Dynamic (Context-registered) – tied to the app’s lifecycle

It is preferred that dynamic broadcast receivers or job scheduling is used over static broadcast receivers, as abuse of statics could result in multiple apps responding to a system event. For this reason, some broadcast intents will not let you create a corresponding static receiver. These intents have FLAG_RECEIVER_REGISTERED_ONLY flag set.

For a static broadcast receiver you will register the receiver in the AndroidManifest.xml:

<receiver android:name=".MyBroadcastReceiver"  android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
    </intent-filter>
</receiver>

Then implement the receiver like so:

public class MyBroadcastReceiver extends BroadcastReceiver {
        private static final String TAG = "MyBroadcastReceiver";
        @Override
        public void onReceive(Context context, Intent intent) {
            // Do something
        }
    }

For a dynamic receiver you register and un-register the receiver in the onResume() and onPause method overrides respectively:

public class MyBroadcastReceiver extends BroadcastReceiver {
        private static final String TAG = "MyBroadcastReceiver";
        BroadcastReceiver br = new MyBroadcastReceiver();
        @Override
        public void onReceive(Context context, Intent intent) {
            //Do something
        }
    
    @Override
    protected void onResume() {
    super.onResume();
    IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
    filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
    this.registerReceiver(br, filter);
    @Override
    protected void onPause() {
    super.onPause();
    this.unRegisterReceiver(br);
    }
}

The potential issue with the above example of a Dynamic receiver is that it will only check for changes once onResume has initiated, and so if the system status changes while the app is not visible, it may hold the incorrect status when the app starts. Adding the code to the onCreate and onDestroy methods would result in unnecessary actions occurring in the background when the app is not visible. To overcome this, check for the current system status on onResume in addition to registering the broadcast receiver. See an example here.

ud851-Exercises-student\Lesson10-Hydration-Reminder\T10.05