Question

I have a Service that runs alongside my main application. The purpose of this service is to synchronize with a main server in the background. In order to avoid constantly polling the main server for updates, I have implemented google cloud messaging to notify the app when there are changes to be downloaded.

When I place the GcmBroadcastReceiver class in my main package, it receives the notification fine. However, I'd like this to be received by my service, so it can then trigger a downloadChanges() function also inside the serivce. I don't really want the app to pick up the notification, then have to use IPC to communicate with the service - that seems a bit overcomplicated.

Is there a way for my service to receive the GCM notification, instead of the main app?

Currently my code is like this:

public class SyncService extends Service {

// lots of other stuff in this class

    public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i(TAG, "GCM received!");
            downloadChanges();
        }
    }
}

However, I get a ClassNotFoundException when receiving the GCM notification. What's the correct way for my background service to handle a GCM notification?

Manifest:

<application
    android:name="com.appnl.myapp.CustomApp"
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:screenOrientation="portrait"
    android:theme="@style/AppTheme" >

    <meta-data android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />

    <!-- android.name=".GcmBroadcastReceiver" -->

    <receiver
        android:name=".GcmBroadcastReceiver"
        android:permission="com.google.android.c2dm.permission.SEND" >
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <category android:name="com.appnl.gcm" />
        </intent-filter>
    </receiver>

    <service
        android:name="service.SyncService"
        android:enabled="true"
        android:exported="false"
        android:icon="@drawable/ic_launcher"
        android:label="@string/service_name" >
        <intent-filter>
            <action android:name="service.SyncService" />
        </intent-filter>
    </service>

<!-- activity list -->

Logcat:

04-11 12:40:19.474: E/AndroidRuntime(28044): FATAL EXCEPTION: main
04-11 12:40:19.474: E/AndroidRuntime(28044): java.lang.RuntimeException: Unable to instantiate receiver com.appnl.myapp.GcmBroadcastReceiver: java.lang.ClassNotFoundException: Didn't find class "com.appnl.myapp.GcmBroadcastReceiver" on path: /data/app/com.appnl.myapp-1.apk
04-11 12:40:19.474: E/AndroidRuntime(28044):    at android.app.ActivityThread.handleReceiver(ActivityThread.java:2493)
04-11 12:40:19.474: E/AndroidRuntime(28044):    at android.app.ActivityThread.access$1600(ActivityThread.java:159)
04-11 12:40:19.474: E/AndroidRuntime(28044):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1392)
04-11 12:40:19.474: E/AndroidRuntime(28044):    at android.os.Handler.dispatchMessage(Handler.java:99)
04-11 12:40:19.474: E/AndroidRuntime(28044):    at android.os.Looper.loop(Looper.java:137)
04-11 12:40:19.474: E/AndroidRuntime(28044):    at android.app.ActivityThread.main(ActivityThread.java:5419)
04-11 12:40:19.474: E/AndroidRuntime(28044):    at java.lang.reflect.Method.invokeNative(Native Method)
04-11 12:40:19.474: E/AndroidRuntime(28044):    at java.lang.reflect.Method.invoke(Method.java:525)
04-11 12:40:19.474: E/AndroidRuntime(28044):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1187)
04-11 12:40:19.474: E/AndroidRuntime(28044):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
04-11 12:40:19.474: E/AndroidRuntime(28044):    at dalvik.system.NativeStart.main(Native Method)
04-11 12:40:19.474: E/AndroidRuntime(28044): Caused by: java.lang.ClassNotFoundException: Didn't find class "com.appnl.myapp.GcmBroadcastReceiver" on path: /data/app/com.appnl.myapp-1.apk
04-11 12:40:19.474: E/AndroidRuntime(28044):    at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:64)
04-11 12:40:19.474: E/AndroidRuntime(28044):    at java.lang.ClassLoader.loadClass(ClassLoader.java:501)
04-11 12:40:19.474: E/AndroidRuntime(28044):    at java.lang.ClassLoader.loadClass(ClassLoader.java:461)
04-11 12:40:19.474: E/AndroidRuntime(28044):    at android.app.ActivityThread.handleReceiver(ActivityThread.java:2488)
04-11 12:40:19.474: E/AndroidRuntime(28044):    ... 10 more

Edit: I should add that my service is in a separate package to my main package. I have a feeling that is what is causing the problem, but I could be wrong.

Was it helpful?

Solution

Your main issue is the naming of the receiver in your manifest. Because it is an inner class it needs to include the outer class name and have its name appended separated by $. Example...

com.appnl.myapp.SyncService$GcmBroadcastReceiver

In saying that however, I'm not sure this is a good way to do things.

Firstly the life-cycle of a BroadcastReceiver is as long as the onReceive(...) method executes and that method should only do minimal work. Unless your downloadChanges() method simply starts an asynchronous operation of some sort (and so returns immediately) you'll block the onReceive(...) method.

Secondly your approach assumes the Service is running permanently. If that is actually the case and you can guarantee it will continue to run then that's fine but you may be risking a lot of battery drain.

If you don't need a permanently running Service then I'd personally keep the BroadcastReceiver separate and use an IntentService instead. You'd then simply receive the broadcast and then in onReceive(...) use startService(...). This would ensure the BroadcastReceiver has a short life and an IntentService uses its own worker thread (thus avoiding a NetworkOnMainThreadException) and it will also self-terminate after it has completed its work.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top