Push notification using jar is not working

2019-04-12 00:22发布

问题:

First of all please don't make this question as duplicate or anything else because all other don't cover my issue.

I have an issue with push notification. I have implemented push notification in my app using gcm and make a jar with its source code. Now I have distributed it with my res folder for integration. Its working fine if host app don't implement push notification its own. If host app implement push notification its own then my integrated app doesn't receive push.

I went through this post : Register GCM from a library project

I have used below addition in the app in which I have integrated my jar:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />        
<!-- GCM requires a Google account. -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<!-- Keeps the processor from sleeping when a message is received. -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!-- Creates a custom permission so only this app can receive its messages. -->
<permission
    android:name="HOST_APP_PACKAGE.permission.C2D_MESSAGE"
    android:protectionLevel="signature" />
<uses-permission android:name="HOST_APP_PACKAGE.permission.C2D_MESSAGE" />
<!-- This app has permission to register and receive data message. -->
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<!-- Network State Permissions to detect Internet status -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Permission to vibrate -->
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

And below is my receiver:

<receiver
      android:name="MY_JAR_APP_PACKAGE.PushLibraryBroadcastReceiver"
      android:permission="com.google.android.c2dm.permission.SEND" >
      <intent-filter>
          <!-- Receives the actual messages. -->
          <action android:name="com.google.android.c2dm.intent.RECEIVE" />
          <!-- Receives the registration id. -->
          <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
          <category android:name="HOST_APP_PACKAGE" />
      </intent-filter>
  </receiver>

My PushLibraryBroadcastReceiver class code in jar:

public class PushLibraryBroadcastReceiver extends GCMBroadcastReceiver
{
    /**
     * Gets the class name of the intent service that will handle GCM messages.
     */
    @Override
    protected String getGCMIntentServiceClassName(Context context) {
        return "MY_JAR_APP_PACKAGE.GCMIntentService";
    }
}

回答1:

Based on your clarification above, you need each broadcast receiver (that of your library and that of the host app) to take care of its own messages and ignore messages intended for the other broadcast receiver.

Since you are using different sender IDs to register to GCM in your library and in your host app, you can use that to determine which message should be handled by which broadcast receiver.

First of all, I'd suggest that you stop extending the deprecated GCMBroadcastReceiver class. My solution relies on not using it (though you might be able to make it work with the old receiver by changing its code).

Then following receiver is based on the new version of the official GCM Demo App.

public class PushLibraryBroadcastReceiver extends WakefulBroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getExtras ().get("from").equals (SENDER_ID_OF_LIBRARY) {
          // Explicitly specify that GcmIntentService will handle the intent.
          ComponentName comp = new ComponentName(
            GcmIntentService.class.getPackage().getName(),
            GcmIntentService.class.getName());
          // Start the service, keeping the device awake while it is launching.
          startWakefulService(context, (intent.setComponent(comp)));
          setResultCode(Activity.RESULT_CANCEL);
        } else
          setResultCode(Activity.RESULT_OK);
        }
    }
}

I made two changes from the Demo's implementation :

  1. get the package name of the intent service explicitly (since using context.getPackageName() will return the main package of the host app, which is not what you need).
  2. compare the "from" field of the message to the sender ID of the library and handle the message only if it comes from that sender. Once the message is handled, the result is set to Activity.RESULT_CANCEL, to prevent the broadcast from being handled by the broadcast receiver of the host app.

If you stop using the old GCMBroadcastReceiver, you should change your intent service to something like this (again, this is taken from the demo) :

protected void onHandleIntent(Intent intent) {
    Bundle extras = intent.getExtras();
    GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
    // The getMessageType() intent parameter must be the intent you received
    // in your BroadcastReceiver.
    String messageType = gcm.getMessageType(intent);

    if (!extras.isEmpty()) {  // has effect of unparcelling Bundle
        /*
         * Filter messages based on message type. Since it is likely that GCM will be
         * extended in the future with new message types, just ignore any message types you're
         * not interested in, or that you don't recognize.
         */
        if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType)) {
            sendNotification("Send error: " + extras.toString());
        } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {
            sendNotification("Deleted messages on server: " + extras.toString());
        // If it's a regular GCM message, do some work.
        } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
            // This loop represents the service doing some work.
            for (int i = 0; i < 5; i++) {
                Log.i(TAG, "Working... " + (i + 1)
                        + "/5 @ " + SystemClock.elapsedRealtime());
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                }
            }
            Log.i(TAG, "Completed work @ " + SystemClock.elapsedRealtime());
            // Post notification of received message.
            sendNotification("Received: " + extras.toString());
            Log.i(TAG, "Received: " + extras.toString());
        }
    }
    // Release the wake lock provided by the WakefulBroadcastReceiver.
    GcmBroadcastReceiver.completeWakefulIntent(intent);
}

I'm assuming your GCMIntentService class extends the deprecated GCMBaseIntentService. You should extend IntentService instead, and move the logic from onMessage to onHandleIntent.

You should also switch to the new way of registering to GCM, using GoogleCloudMessaging.register, which doesn't require any handling in the intent service class. All the handling will be done in the activity that performs the registration, as demonstrated here.

Finally, if the broadcast receiver of the host app doesn't behave similarly to your library's broadcast receiver (i.e. handling only the messages it is supposed to handle), you would still have a problem if the host app's broadcast receiver is triggered before your library's broadcast receiver. You can avoid that by adding the android:priority attribute to the intent-filter of both receivers, and giving your library's receiver a higher priority. That would ensure that the library's broadcast receiver is always triggered first.

I must say that I never tested an app with two broadcast receivers, so I can't guarantee that using the priority attribute works, but it should work based on the documentation I read :

Ordered broadcasts (sent with Context.sendOrderedBroadcast) are delivered to one receiver at a time. As each receiver executes in turn, it can propagate a result to the next receiver, or it can completely abort the broadcast so that it won't be passed to other receivers. The order receivers run in can be controlled with the android:priority attribute of the matching intent-filter; receivers with the same priority will be run in an arbitrary order.