Receiving a System Broadcast: Waking Up on Boot

PhotoGallery’s background alarm works, but it is not perfect. If the user reboots the device, the alarm will be forgotten.

Apps that perform an ongoing process for the user generally need to wake themselves up after the device is booted. You can detect when boot is completed by listening for a broadcast intent with the BOOT_COMPLETED action. The system sends out a BOOT_COMPLETED broadcast intent whenever the device is turned on. You can listen for it by creating and registering a standalone broadcast receiver that filters for the appropriate action.

Creating and registering a standalone receiver

A standalone receiver is a broadcast receiver that is declared in the manifest. Such a receiver can be activated even if your app process is dead. (Later you will learn about dynamic receivers, which can instead be tied to the lifecycle of a visible app component, like a fragment or activity.)

Just like services and activities, broadcast receivers must be registered with the system to do anything useful. If the receiver is not registered with the system, the system will not send any intents its way and, in turn, the receiver’s onReceive(…) will not get executed as desired.

Before you can register your broadcast receiver, you have to write it. Create a new Java class called StartupReceiver that is a subclass of android.content.BroadcastReceiver.

Listing 29.1  Your first broadcast receiver (StartupReceiver.java)

public class StartupReceiver extends BroadcastReceiver{
    private static final String TAG = "StartupReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i(TAG, "Received broadcast intent: " + intent.getAction());
    }
}

A broadcast receiver is a component that receives intents, just like a service or an activity. When an intent is issued to StartupReceiver, its onReceive(…) method will be called.

Next, open AndroidManifest.xml and hook up StartupReceiver as a standalone receiver:

Listing 29.2  Adding your receiver to the manifest (AndroidManifest.xml)

<manifest ...>

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application
        ...>
        <activity
            android:name=".PhotoGalleryActivity"
            android:label="@string/app_name">
            ...
        </activity>
        <service android:name=".PollService"/>

        <receiver android:name=".StartupReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>
    </application>

</manifest>

Registering a standalone receiver to respond to an implicit intent works just like registering an activity to do the same. You use the receiver tag with appropriate intent-filters. StartupReceiver will be listening for the BOOT_COMPLETED action. This action also requires a permission, so you include an appropriate uses-permission tag as well.

With your broadcast receiver declared in your manifest, it will wake up any time a matching broadcast intent is sent – even if your app is not currently running. Upon waking up, the ephemeral broadcast receiver’s onReceive(Context, Intent) method will be run, and then it will die, as shown in Figure 29.2.

Figure 29.2  Receiving BOOT_COMPLETED

Figure shows ephemeral broadcastreceiver’s onReceive(Context, Intent) method.

Time to verify that StartupReceiver’s onReceive(…) is executed when the device boots up. First, run PhotoGallery to install the most recent version on your device.

Next, shut down your device. If you are using a physical device, power it all the way off. If you are using an emulator, the easiest way to shut it down is to quit out of the emulator by closing the emulator window.

Turn the device back on. If you are using a physical device, use the power button. If you are using an emulator, either rerun your application or start the device using the AVD Manager. Make sure you are using the same emulator image you just shut down.

Now, open the Android Device Monitor by selecting ToolsAndroidAndroid Device Monitor.

Click on your device in Android Device Monitor’s Devices tab. (If you do not see the device listed, try unplugging and replugging your USB device or restarting the emulator.)

Search the Logcat results within the Android Device Monitor window for your log statement (Figure 29.3).

Figure 29.3  Searching Logcat output

Screenshot shows Android Device Monitor window.

You should see a Logcat statement showing that your receiver ran. However, if you check your device in the Devices tab, you will probably not see a process for PhotoGallery. Your process came to life just long enough to run your broadcast receiver, and then it died again.

(Testing that the receiver executed can be unreliable when you are using Logcat output, especially if you are using an emulator. If you do not see the log statement the first time through the instructions above, try a few more times. Worst case, continue through the rest of the exercise. Once you get to the part where you hook up notifications, you will have a more reliable way to check whether the receiver is working.)

Using receivers

The fact that broadcast receivers live such short lives restricts the things you can do with them. You cannot use any asynchronous APIs, for example, or register any listeners, because your receiver will not be alive any longer than the call to onReceive(Context, Intent). Also, because onReceive(Context, Intent) runs on your main thread, you cannot do any heavy lifting inside it. That means no networking or heavy work with permanent storage.

But this does not make receivers useless. They are invaluable for all kinds of little plumbing code, such as starting an activity or service (so long as you do not expect a result back) or resetting a recurring alarm when the system finishes rebooting (as you will do in this exercise).

Your receiver will need to know whether the alarm should be on or off. Add a preference constant and convenience methods to QueryPreferences to store this information in shared preferences.

Listing 29.3  Adding alarm status preference (QueryPreferences.java)

public class QueryPreferences {

    private static final String PREF_SEARCH_QUERY = "searchQuery";
    private static final String PREF_LAST_RESULT_ID = "lastResultId";
    private static final String PREF_IS_ALARM_ON = "isAlarmOn";
    ...
    public static void setLastResultId(Context context, String lastResultId) {
        ...
    }

    public static boolean isAlarmOn(Context context) {
        return PreferenceManager.getDefaultSharedPreferences(context)
                .getBoolean(PREF_IS_ALARM_ON, false);
    }

    public static void setAlarmOn(Context context, boolean isOn) {
        PreferenceManager.getDefaultSharedPreferences(context)
                .edit()
                .putBoolean(PREF_IS_ALARM_ON, isOn)
                .apply();
    }
}

Next, update PollService.setServiceAlarm(…) to write to shared preferences when the alarm is set.

Listing 29.4  Writing alarm status preference when alarm is set (PollService.java)

public class PollService extends IntentService {
    ...
    public static void setServiceAlarm(Context context, boolean isOn) {
        ...
        if (isOn) {
            alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME,
                    SystemClock.elapsedRealtime(), POLL_INTERVAL_MS, pi);
        } else {
            alarmManager.cancel(pi);
            pi.cancel();
        }

        QueryPreferences.setAlarmOn(context, isOn);
    }
    ...
}

Then your StartupReceiver can use it to turn the alarm on at boot.

Listing 29.5  Starting alarm on boot (StartupReceiver.java)

public class StartupReceiver extends BroadcastReceiver{
    private static final String TAG = "StartupReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i(TAG, "Received broadcast intent: " + intent.getAction());

        boolean isOn = QueryPreferences.isAlarmOn(context);
        PollService.setServiceAlarm(context, isOn);
    }
}

Run PhotoGallery again. (You may want to change PollService.POLL_INTERVAL_MS back to a shorter interval, such as 60 seconds, for testing purposes.) Turn polling on by pressing START POLLING in the toolbar. Reboot your device. This time, background polling should be restarted after you reboot your phone, tablet, or emulator.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
35.175.174.36