Parse.com push notifications not shown in backgrou

2019-05-19 05:30发布

问题:

I'm using Parse.com with an Ionic app and the PushPlugin and trying to implement Parse's Push Notifications through GCM with a custom Sender ID.

When I'm sending a message to all devices, or using the REST API with cURL, iOS notifications trigger fine when the app is in the background but android notifications are not.

Here's what I tried with the rest API First for iOS, which is working well:

curl -X POST \
  -H "X-Parse-Application-Id: APP-ID" \
  -H "X-Parse-REST-API-Key: API-KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "where": {
          "deviceType": "ios"
        },
        "data": {
          "alert": "Hello World!"
        }
      }' \
  https://api.parse.com/1/push

The iOS notification is received and displayed even when the app is closed or open in the background.

Now, when I try the same targeting for android devices:

curl -X POST \
  -H "X-Parse-Application-Id: APP-ID" \
  -H "X-Parse-REST-API-Key: API-KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "where": {
          "deviceType": "android"
        },
        "data": {
          "alert": "Hello World!"
        }
      }' \
  https://api.parse.com/1/push

The notification is received by the device and partially logged in adb logcat but is not displayed in the notification bar or otherwise acknowledged. I tried changing 'alert' to 'message' but that had no effect.

However, if I try to use the GCM HTTP API with Postman or cURL, everything works well:

curl 'https://android.googleapis.com/gcm/send' \
  -H 'authorization: key=API-KEY' \
  -H 'content-type: application/json' \
  -d '{
        "registration_ids" : [
          "DEVICE-REGISTRATION-ID"
        ],
        "data" : {
          "message": "You Go I Go, Buddy!"
        }
      }'

When logging adb logcat the logs are different when using the GCM API and Parse's Push Notifications API:

With Parse:

I/GCM     (10319): GCM message co.yougoigo.mobile 0:1423318254669687%bd9ff524f9fd7ecd
V/GCMBroadcastReceiver(11506): onReceive: com.google.android.c2dm.intent.RECEIVE
V/GCMBroadcastReceiver(11506): GCM IntentService class: com.plugin.gcm.GCMIntentService
V/GCMBaseIntentService(11506): Acquiring wakelock
V/GCMBaseIntentService(11506): Intent service name: GCMIntentService-GCMIntentService-3
D/GCMIntentService(11506): onMessage - context: android.app.Application@251ee33b

And with GCM cURL call:

I/GCM     (10319): GCM message co.yougoigo.mobile 0:1423318321652064%bd9ff524f9fd7ecd
I/ActivityManager(  745): Start proc co.yougoigo.mobile for broadcast co.yougoigo.mobile/com.plugin.gcm.CordovaGCMBroadcastReceiver: pid=11788 uid=10187 gids={50187, 9997, 3003} abi=armeabi-v7a
V/GCMBroadcastReceiver(11788): onReceive: com.google.android.c2dm.intent.RECEIVE
V/GCMRegistrar(11788): Setting the name of retry receiver class to com.plugin.gcm.CordovaGCMBroadcastReceiver
V/GCMBroadcastReceiver(11788): GCM IntentService class: com.plugin.gcm.GCMIntentService
V/GCMBaseIntentService(11788): Acquiring wakelock
V/GCMBaseIntentService(11788): Intent service name: GCMIntentService-GCMIntentService-1
D/GCMIntentService(11788): onMessage - context: android.app.Application@251ee33b
E/GCMIntentService(11788): Number format exception - Error parsing Notification ID: Invalid int: "null"
V/GCMBaseIntentService(11788): Releasing wakelock

The current manifest is as follows:

<?xml version='1.0' encoding='utf-8'?>
<manifest android:hardwareAccelerated="true" android:versionCode="4" android:versionName="0.0.4" package="co.yougoigo.mobile" xmlns:android="http://schemas.android.com/apk/res/android">
    <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
    <uses-permission android:name="android.permission.INTERNET" />
    <application android:hardwareAccelerated="true" android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale" android:label="@string/activity_name" android:launchMode="singleTop" android:name="CordovaApp" android:theme="@android:style/Theme.Black.NoTitleBar" android:windowSoftInputMode="adjustResize">
            <intent-filter android:label="@string/launcher_name">
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:exported="true" android:name="com.plugin.gcm.PushHandlerActivity" />
        <receiver android:name="com.plugin.gcm.CordovaGCMBroadcastReceiver" android:permission="com.google.android.c2dm.permission.SEND">
            <intent-filter>
                <action android:name="com.google.android.c2dm.intent.RECEIVE" />
                <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
                <category android:name="co.yougoigo.mobile" />
            </intent-filter>
        </receiver>
        <service android:name="com.plugin.gcm.GCMIntentService" />
        <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/fb_app_id" />
        <activity android:label="@string/fb_app_name" android:name="com.facebook.LoginActivity" android:theme="@android:style/Theme.Translucent.NoTitleBar" />
    </application>
    <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="19" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <permission android:name="co.yougoigo.mobile.permission.C2D_MESSAGE" android:protectionLevel="signature" />
    <uses-permission android:name="co.yougoigo.mobile.permission.C2D_MESSAGE" />
</manifest>

Does anyone have any experience in getting GCM notifications to work on phonegap apps?

回答1:

In order to use the phonegap/cordova PushPlugin with Parse Push you have to make the realization that Parse actually wraps it's content in a non standard way. This results in PushPlugin receiving the push notification when the app is in the foreground, but not when it is in the background.

Source code snippet from GCMIntentService.java

@Override
protected void onMessage(Context context, Intent intent) {
    Log.d(TAG, "onMessage - context: " + context);

    // Extract the payload from the message
    Bundle extras = intent.getExtras();
    if (extras != null)
    {
        // if we are in the foreground, just surface the payload, else post it to the statusbar
        if (PushPlugin.isInForeground()) {
            extras.putBoolean("foreground", true);
            PushPlugin.sendExtras(extras);
        }
        else {
            extras.putBoolean("foreground", false);

            // Send a notification if there is a message
            if (extras.getString("message") != null && extras.getString("message").length() != 0) {
                createNotification(context, extras);
            }
        }
    }
}

As you can see when the check for PushPlugin.isInForeground() fails it checks for a 'message' property on the extras bundle.

2 Observations:

  1. Parse does not include a 'message' on the notification so the PushPlugin doesn't call createNotification(content, extras)
  2. If you look at the 'payload' property of the bundle it also wraps it one layer deeper than "traditional" GCM notifications by putting the notification an object with the key 'data'.

Here's what the bundle looks like in JSON

{ ...
  "payload" : {
    "data" : { 
      "message" : "your message here",
      "title": "your title here",
      ...,
      "customData": { ... }
    }
}

You, therefore, have to do some custom logic to extract the payload from the bundle extras.getString("payload") and then create a new JSONObject (documentation here: http://developer.android.com/reference/org/json/JSONObject.html).

Instead of check bundle.getString("message") you need to check for check for the existence of payload.data and then parse out message and title and make sure they are in the correct locations for the createNotification function to work by repackaging the bundle (or creating your own).

My recommendation is to leave the existing code as is, but make a separate check for the parse formatted notification and call a custom createNotification function.

As in:

if (PushPlugin.isInForeground()) {
  extras.putBoolean("foreground", true);
  PushPlugin.sendExtras(extras);
} else if (extras.getString("message") && extras.getString("message").length() != 0) {
  extras.putBoolean("foreground", false);
  createNotification(context, extras);
} else {
  JSONObject payload = extras.getString("payload");
  JSONObject data = payload.getJSONObject("data");
  String message = data.getString("alert") //parse puts the message as an alert if you don't use custom json payload
  extras.putString("message", alert)
  createNotification(context, extras)
}

Hopefully this helps. Honestly, I wish parse just sent the data like everyone else so that it was compatible with PushPlugin out of the box.

Source code for PushPlugin GCMIntentService.java found here (official github repo): https://github.com/phonegap-build/PushPlugin/blob/master/src/android/com/plugin/gcm/GCMIntentService.java