Reputation: 7248
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.
Upvotes: 1
Views: 535
Reputation: 48871
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.
Upvotes: 1