简体   繁体   中英

Mobile Backend Starter Notification when app not running

In my Android app, I use the Mobile Backend Starter from Google. I'd like to get a notification when the CloudEntities on the server get updated, and this notification should contain some data from the updated Entity. It works when the app is running in the background, but when I close the app (by swiping it away in the multitasking view), I can't make such a notification because I haven't got access to the CloudBackendAsync in the GCMIntentService .

I already saw this question: Mobile Backend handle continuous queries in background

But it doesn't have a solution for the problem of accessing the cloud data in the GCMIntentService.

EDIT: My current code in GCMIntentService.java

protected void onHandleIntent(Intent intent) {

    //... (Check if the GCM Message is about an update of the Mobile Backend)

            // dispatch message
            if (GCM_TYPEID_QUERY.equals(typeId)) {
                // Here, a broadcast is sent to the Main Activity of the app, which then downloads
                // the new content and shows a notification in the CloudCallbackHandler. That 
                // only works when the Activity is running.
                // So I would like to get access to the CloudBackendAsync instance from
                // the app here to download data in the background and show a notification.

                Intent messageIntent = new Intent(BROADCAST_ON_MESSAGE);
                messageIntent.putExtras(intent);
                messageIntent.putExtra("token", tokens[2]);
                LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent);
            }
    //...
}

The Android client does not receive the message content through the push notification event from the backend (only the subId token is sent from the demo backend which is enough to notify the client that some new message has been received for the given topic and refresh it).

So as I understand, it is not possible to directly get the entity data within the client GCMIntentService.onHandleIntent() method unless we change the backend code. I have made the following changes in the backend class ProspectiveSearchServlet so that it includes as well the message content within the push notification:

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    // Return if push notification is not enabled
    if (!backendConfigManager.isPushEnabled()) {
      log.info("ProspectiveSearchServlet: couldn't send push notification because it is disabled.");
      return;
    }

    // dispatch GCM messages to each subscribers
    String[] subIds = req.getParameterValues("id");
    // Each subId has this format "<regId>:query:<clientSubId>"
    for (String subId : subIds) {
      String regId = SubscriptionUtility.extractRegId(subId);
      if (isSubscriptionActive(regId)) {
        Entity matchedEntity = ProspectiveSearchServiceFactory.getProspectiveSearchService().getDocument(req);
        if(matchedEntity != null) {
            log.info(String.format("ProspectiveSearchServlet: matchedEntity.toString: " + matchedEntity.toString())); 
        } else {
            log.info(String.format("ProspectiveSearchServlet: matchedEntity is null.")); 
        }
        //Add the matchedEntity object.
        sendPushNotification(regId, subId, matchedEntity);
      } else {
        SubscriptionUtility.clearSubscriptionAndDeviceEntity(Arrays.asList(regId));
      }
    }

  }

  private void sendPushNotification(String regId, String subId, Entity matchedEntity) throws IOException {
    SubscriptionUtility.MobileType type = SubscriptionUtility.getMobileType(subId);

    if (SubscriptionUtility.MobileType.ANDROID == type) {
      sendGcmAlert(subId, regId, matchedEntity);
    } else if (SubscriptionUtility.MobileType.IOS == type) {
      sendIosAlert(subId, new String[] {regId}, matchedEntity);
    }
  }


  private void sendGcmAlert(String subId, String regId, Entity matchedEntity)
      throws IOException {
    String gcmKey = backendConfigManager.getGcmKey();
    boolean isGcmKeySet = !(gcmKey == null || gcmKey.trim().length() == 0);

    // Only attempt to send GCM if GcmKey is available
    if (isGcmKeySet) {
      Sender sender = new Sender(gcmKey);

      if(matchedEntity != null) {
          Message message = new Message.Builder().addData(SubscriptionUtility.GCM_KEY_SUBID, subId)
                    //extra data.<key> elements can be added here
                    .addData("data.message", (String) matchedEntity.getProperty("message"))
                    .addData("data.updatedBy", (String) matchedEntity.getProperty("_updatedBy"))
                    .addData("data.owner", (String) matchedEntity.getProperty("_owner"))
                    .addData("data.kindName", (String) matchedEntity.getProperty("_kindName"))
                    .build();

          Result r = sender.send(message, regId, GCM_SEND_RETRIES);
          if (r.getMessageId() != null) {
            log.info("ProspectiveSearchServlet: GCM sent: subId: " + subId);
          } else {
            log.warning("ProspectiveSearchServlet: GCM error for subId: " + subId +
                ", senderId: " + gcmKey + ", error: " + r.getErrorCodeName());
            ArrayList<String> deviceIds = new ArrayList<String>();
            deviceIds.add(regId);
            SubscriptionUtility.clearSubscriptionAndDeviceEntity(deviceIds);
          }
      }  
    } else {
      // Otherwise, just write a log entry
      log.info(String.format("ProspectiveSearchServlet: GCM is not sent: GcmKey: %s ", 
          isGcmKeySet));
    }
  }

Now on the client side you can make the following changes in the GCMIntentService to display a proper push notification (with the message body and the user name):

@Override
    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)) {
                Log.i(Consts.TAG, "onHandleIntent: message error");
            } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType)) {              
              Log.i(Consts.TAG, "onHandleIntent: message deleted");
        // If it's a regular GCM message, do some work.
        } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
            String subId = intent.getStringExtra(GCM_KEY_SUBID);
            Log.i(Consts.TAG, "onHandleIntent: subId: " + subId);
            String[] tokens = subId.split(":");
            String typeId = tokens[1];

            // dispatch message
            if (GCM_TYPEID_QUERY.equals(typeId)) {
                Intent messageIntent = new Intent(BROADCAST_ON_MESSAGE);
                messageIntent.putExtras(intent);
                messageIntent.putExtra("token", tokens[2]);
                boolean isReceived = LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent);
                //Check if the broadcast has been handled, if not show the notification.
                if (!isReceived) {
                    Log.i(Consts.TAG, "A message has been recieved but no broadcast was handled.");
                    generateNotification(this, intent, tokens[2]);
                } else {
                    Log.i(Consts.TAG, "A message has been recieved, broadcasted and handled.");
                }
            }
        }
    }
    // Release the wake lock provided by the WakefulBroadcastReceiver.
    GCMBroadcastReceiver.completeWakefulIntent(intent);
}

public static void generateNotification(Context context, Intent intent, String message) {
    //Event keys
    HashMap data = new HashMap();
    for (String key : intent.getExtras().keySet()) {
        Log.d(Consts.TAG, "Message key: " + key + " value: " + intent.getExtras().getString(key));
        String eventKey = key.startsWith("data.") ? key.substring(5) : key;
        data.put(eventKey, intent.getExtras().getString(key));
    }

    CharSequence contentTitle = (CharSequence) data.get("updatedBy");
    if (contentTitle == null) contentTitle = "New Message";

    CharSequence contentText = (CharSequence) data.get("message");
    if (contentText == null) contentText = "";

    CharSequence userId = (CharSequence) data.get("updatedBy");
    Bitmap iconBitmap = getUserIcon(context, userId.toString());
    if (iconBitmap == null) iconBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);

    // Creates an Intent for the Activity
    Intent resultIntent = new Intent(context, GuestbookActivity.class);
    // The stack builder object will contain an artificial back stack for the started Activity.
    // This ensures that navigating backward from the Activity leads out of
    // your application to the Home screen.
    TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
    // Adds the back stack for the Intent (but not the Intent itself)
    stackBuilder.addParentStack(IntroductionActivity.class);
    // Adds the Intent that starts the Activity to the top of the stack
    stackBuilder.addNextIntent(resultIntent);
    PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);


    Notification.Builder mBuilder = new Notification.Builder(context);
    mBuilder.setContentIntent(resultPendingIntent);

    Notification notification = mBuilder
    .setContentTitle(contentTitle)
    .setContentText(contentText)
    .setSmallIcon(R.drawable.notification_icon)
    .setLargeIcon(iconBitmap)
    .setTicker(contentTitle + ": " + contentText)
    .setWhen(System.currentTimeMillis())
    .setAutoCancel(true)
    .build();

    ///Get the notification ID, /it allows to update the notification later on.
    int notifyID = 1;
    String contentID = (String) data.get("id");
    if(contentID != null) {
        notifyID = Integer.parseInt(contentID);
    }

    NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    mNotificationManager.notify(notifyID, notification);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM