Saturday, April 25, 2009

Registering for TIME_TICK After a Reboot in Android

Creating a service that will receive TIME_TICK, but also start on boot isn't immediately obvious. It took me a bit to figure out the best way to do it, so I figure it's worth sharing.

To start, you can't register your receiver to get TIME_TICK in the android manifest. The registration for TIME_TICK has to happen in code using Intent.registerReceiver. You can, however, define that your receiver has android.permission.RECEIVE_BOOT_COMPLETED and set your receiver to handle it, which will give you some control on boot. Here's the relevant portion of AndroidManifest.xml:

<application android:icon="@drawable/icon" android:label="@string/app_name"
 android:enabled="true" android:debuggable="false">
 <activity android:name=".AndroidMain" android:label="@string/app_name">
  <intent-filter>
   <action android:name="android.intent.action.MAIN" />
   <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
 </activity>
 <service android:name="DemoService"></service>
 <receiver android:name="DemoReceiver"
  android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
  <intent-filter>
   <action android:name="android.intent.action.TIME_TICK"></action>
   <action android:name="android.intent.action.BOOT_COMPLETED"></action>
  </intent-filter>
 </receiver>
</application>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"></uses-permission>

This isn't enough, however, since your receiver can't register itself to listen to TIME_TICK. Intent.registerReceiver registers for the lifetime of the Intent it's called from. If we register it using the intent created for BOOT_COMPLETED, it's dead as soon as it exits the handler. Therefore, we need to start a background service that will keep running. This service then creates a new instance of the receiver and registers that for TIME_TICK.

DemoReceiver.java:
public class DemoReceiver extends BroadcastReceiver {
 static final String LOGGING_TAG = "MyDemo";

 @Override
 public void onReceive(Context context, Intent intent) {
  if (intent.getAction().compareTo(Intent.ACTION_BOOT_COMPLETED) == 0){   
   Log.v(LOGGING_TAG, "DemoReceiver.onReceive(ACTION_BOOT_COMPLETED)");   
   context.startService(new Intent(context, DemoService.class));   
  }else if(intent.getAction().compareTo(Intent.ACTION_TIME_TICK) == 0)
   Log.v(LOGGING_TAG, "DemoReceiver.onReceive(ACTION_TIME_TICK)");
  else
   Log.v(LOGGING_TAG, "DemoReceiver.onReceive(" + intent.getAction() + ")");
 }
}


DemoService.java:
public class DemoService extends Service {
 static final String LOGGING_TAG = "MyDemo";
 
 @Override
 public IBinder onBind(Intent intent) {
  return null;
 }
 
 @Override
 public void onStart(Intent intent, int startId){
  super.onStart(intent, startId);
  Log.v(LOGGING_TAG, "DemoService.onStart()");
 }
 
 @Override
 public void onCreate(){
  super.onCreate();
  Log.v(LOGGING_TAG, "DemoService.onCreate()");
  
  registerReceiver(
    new DemoReceiver(), 
    new IntentFilter(Intent.ACTION_TIME_TICK));  
 }
}


If this all works correctly, we'll see log messages that look something like:
04-25 21:04:22.580: VERBOSE/MyDemo(182): DemoReceiver.onReceive(ACTION_BOOT_COMPLETED)
04-25 21:04:22.630: VERBOSE/MyDemo(182): DemoService.onCreate()
04-25 21:04:22.650: VERBOSE/MyDemo(182): DemoService.onStart()
04-25 21:05:00.140: VERBOSE/MyDemo(182): DemoReceiver.onReceive(ACTION_TIME_TICK)

9 comments:

  1. curious why you register the TIME_TICK action on the receiver in the manifest and not on the service?

    ReplyDelete
  2. BTW is it just me (as a newbie to Android) or is the whole programming model just a bit "clunky" and non-intuitive?

    :)

    ReplyDelete
  3. Doh ... ignore my 1st comment, I get it now ...
    it's the broadcast receiver that gets the ticks ... hence its the target of the intent filter ...

    I should RTFC ... I still think my 2nd comment is germain ... :)

    ReplyDelete
  4. intent.getAction().compareTo(Intent.ACTION_BOOT_COMPLETED) == 0
    should be written as
    intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)

    ReplyDelete
  5. Nice, but how do you stop the service and the notifications that keep coming every minute without uninstalling the application?
    Once the service is bound to the registered receiver, I could not stop the service once started because it was bound to the registered receiver even when I called stopService(intent) with a static intent from the same app that started the service.

    ReplyDelete
  6. when I use TIME_TICK for minute then missing one minute each hour
    why missing one minute log report in each hour ?

    ReplyDelete
  7. I have a error? help me please
    java.lang.ClassNotFoundException: Didn't find class "com.example.AndroidMain" on path: DexPathList[[zip file "/data/app/com.example-1.apk"],nativeLibraryDirectories=[/data/app-lib/com.example-1, /vendor/lib, /system/lib]]

    ReplyDelete
  8. Same problem for me. AndroidMain does not exist in my example:

    ```
    08-20 01:35:28.660 4494 4494 E AndroidRuntime: java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.example.wifi/com.example.wifi.AndroidMain}: java.lang.ClassNotFoundException: Didn't find class "com.example.wifi.AndroidMain" on path: DexPathList[[zip file "/data/app/com.example.wifi-wfD48gjtZGqsPqHSkwOI-w==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.wifi-wfD48gjtZGqsPqHSkwOI-w==/lib/arm64, /system/lib64]]
    ```

    ReplyDelete