GCM Console shows 0 (zero) requests after a succes

2019-08-08 16:50发布

问题:

From my ASP.NET web service and using my GCM Browser API key I can successfully send a GCM Push Notification getting the following success response:

{"multicast_id":4623804699821154941,"success":1,"failure":0,"canonical_ids":0,"results":[{"message_id":"0:1393876717064721%59cd098ff9fd7ecd"}

Two Problems:

1) The Google Developers Console shows 0 (zero) Requests and 0 Errors even after multiple 'Sends' and browser refreshes of the console page. Shouldn't the count change with every GCM Push Notification?

2) My Android device did not receive the push notification.

My code is modeled after: remote server returned an error: (401) unathorized in C# GCM response

The passed in Android device ID was copied from a successful Android device registration. My Server (web-service) code is below:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Net;
    using System.Text;
    using System.IO;
    using System.Security.Cryptography.X509Certificates;
    using System.Net.Security;
    using System.Collections.Specialized;

    public class AndroidGCMPushNotification
    {
        public AndroidGCMPushNotification()
        {
            //
            // TODO: Add constructor logic here
            //
        }
        public string SendNotification(string deviceId, string message)
        {
            string BrowserAPIKey = "xxxxxxxxxxxxxxxxxxxxxxxxx";     // GCM Browser Key

            string tickerText = "ticker test GCM";
            string contentTitle = "content title GCM";
            string postData = "{ \"registration_ids\": [ \"" + deviceId + "\" ], \"data\": {\"tickerText\":\"" + tickerText + "\", \"contentTitle\":\"" + contentTitle + "\", \"message\": \"" + message + "\"}}";

    string sResponseFromServer = SendGCMNotification(BrowserAPIKey, postData);
    return sResponseFromServer;
}

private string SendGCMNotification(string apiKey, string postData, string postDataContentType = "application/json")
{
    String sResponseFromServer = "";
    // from here:
    // https://stackoverflow.com/questions/11431261/unauthorized-when-calling-google-gcm
    //
    // original:
    // http://www.codeproject.com/Articles/339162/Android-push-notification-implementation-using-ASP

    ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(ValidateServerCertificate);

    //
    //  MESSAGE CONTENT
    byte[] byteArray = Encoding.UTF8.GetBytes(postData);

    //
    //  CREATE REQUEST
    HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("https://android.googleapis.com/gcm/send");

    Request.Method = "POST";
    Request.KeepAlive = false;
    Request.ContentType = postDataContentType;
    //Request.Headers.Add(string.Format("Authorization: key={0}", apiKey));
    Request.Headers.Add(HttpRequestHeader.Authorization, String.Format("key={0}", apiKey));
    Request.ContentLength = byteArray.Length;

    //Stream dataStream;
    try
    {
        Stream dataStream = Request.GetRequestStream();

        dataStream.Write(byteArray, 0, byteArray.Length);
        dataStream.Close();
    }
    catch (Exception e)
    {
        sResponseFromServer = e.Message;
    }

    //
    //  SEND MESSAGE
    try
    {
        WebResponse Response = Request.GetResponse();

        HttpStatusCode ResponseCode = ((HttpWebResponse)Response).StatusCode;

        if (ResponseCode.Equals(HttpStatusCode.Unauthorized) || ResponseCode.Equals(HttpStatusCode.Forbidden))
        {
            sResponseFromServer = "Unauthorized - need new token";

        }
        else if (!ResponseCode.Equals(HttpStatusCode.OK))
        {
            sResponseFromServer = "Response from web service isn't OK";
        }

        StreamReader Reader = new StreamReader(Response.GetResponseStream());
        sResponseFromServer = Reader.ReadToEnd();
        Reader.Close();
    }
    catch (Exception e)
    {
        sResponseFromServer = e.Message;
    }
    return sResponseFromServer;
}

public static bool ValidateServerCertificate(
                                          object sender,
                                          X509Certificate certificate,
                                          X509Chain chain,
                                          SslPolicyErrors sslPolicyErrors)
{
    return true;
}

}

On the Android client side I have the following notification-receive code:

package com.MichaelResslerFineArt.eclipmessenger;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;

import com.MichaelResslerFineArt.eclipmessenger.ProfileActivity;
import com.MichaelResslerFineArt.eclipmessenger.R;
import com.MichaelResslerFineArt.eclipmessenger.ProfileActivity.GcmBroadcastReceiver;
import com.MichaelResslerFineArt.eclipmessenger.ProfileActivity.GcmIntentService;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.gcm.GoogleCloudMessaging;

import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.app.Activity;
import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.WakefulBroadcastReceiver;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Toast;

public class ProfileActivity extends Activity {

    public static final String EXTRA_MESSAGE = "message";
    public static final String PROPERTY_REG_ID = "registration_id";
    private static final String PROPERTY_APP_VERSION = "appVersion";
    private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;

    String SENDER_ID = "594966827111";    // GCM Project Number


    public static final String LOG_MSG_TAG = "eClipMessenger";
    GoogleCloudMessaging gcm;
    AtomicInteger msgId = new AtomicInteger();
    SharedPreferences prefs;
    Context context;

    String regid;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_profile);

        context = getApplicationContext();

        // Check device for Play Services APK.
        GcmApi gcmApi = new GcmApi();
        if (gcmApi.checkPlayServices(this)) {
            // If this check succeeds, proceed with normal processing.
            // Otherwise, prompt user to get valid Play Services APK.
            Toast.makeText(this, "Success checking APK.", Toast.LENGTH_LONG).show();
            Log.i(LOG_MSG_TAG, "Success checking APK.");

            gcm = GoogleCloudMessaging.getInstance(this);
            regid = getRegistrationId(context);

            if (regid.isEmpty()) {
                registerInBackground();
            }
            else {
                Log.i(LOG_MSG_TAG, "GCM Reg ID: " + regid);
            }
        } else {
            Log.i(LOG_MSG_TAG, "No valid Google Play Services APK found.");
        }
    }

    // You need to do the Play Services APK check here too.
    @Override
    protected void onResume() {
        super.onResume();
        GcmApi gcmApi = new GcmApi();
        gcmApi.checkPlayServices(this);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.profile, menu);
        return true;
    }

    /**
     * Gets the current registration ID for application on GCM service.
     * <p>
     * If result is empty, the app needs to register.
     *
     * @return registration ID, or empty string if there is no existing
     *         registration ID.
     */
    private String getRegistrationId(Context context) {
        final SharedPreferences prefs = getGCMPreferences(context);
        String registrationId = prefs.getString(PROPERTY_REG_ID, "");
        if (registrationId.isEmpty()) {
            Log.i(LOG_MSG_TAG, "Registration not found.");
            return "";
        }
        // Check if app was updated; if so, it must clear the registration ID
        // since the existing regID is not guaranteed to work with the new
        // app version.
        int registeredVersion = prefs.getInt(PROPERTY_APP_VERSION, Integer.MIN_VALUE);
        int currentVersion = getAppVersion(context);
        if (registeredVersion != currentVersion) {
            Log.i(LOG_MSG_TAG, "App version changed.");
            return "";
        }
        return registrationId;
    }

    /**
     * @return Application's {@code SharedPreferences}.
     */
    private SharedPreferences getGCMPreferences(Context context) {
        // This sample app persists the registration ID in shared preferences, but
        // how you store the regID in your app is up to you.
        return getSharedPreferences(ProfileActivity.class.getSimpleName(),
                Context.MODE_PRIVATE);
    }

    /**
     * @return Application's version code from the {@code PackageManager}.
     */
    private static int getAppVersion(Context context) {
        try {
            PackageInfo packageInfo = context.getPackageManager()
                    .getPackageInfo(context.getPackageName(), 0);
            return packageInfo.versionCode;
        } catch (NameNotFoundException e) {
            // should never happen
            throw new RuntimeException("Could not get package name: " + e);
        }
    }

    /**
     * Registers the application with GCM servers asynchronously.
     * <p>
     * Stores the registration ID and app versionCode in the application's
     * shared preferences.
     */
    private void registerInBackground() {
        new AsyncTask<Void, Void, String>() {
            @Override
            protected String doInBackground(Void... params) {
                String msg = "";
                try {
                    if (gcm == null) {
                        gcm = GoogleCloudMessaging.getInstance(context);
                    }
                    regid = gcm.register(SENDER_ID);
                    msg = "Device registered, registration ID=" + regid;

                    // You should send the registration ID to your server over HTTP,
                    // so it can use GCM/HTTP or CCS to send messages to your app.
                    // The request to your server should be authenticated if your app
                    // is using accounts.
                    sendRegistrationIdToBackend();

                    // For this demo: we don't need to send it because the device
                    // will send upstream messages to a server that echo back the
                    // message using the 'from' address in the message.

                    // Persist the regID - no need to register again.
                    storeRegistrationId(context, regid);

                } catch (IOException ex) {
                    msg = "Error :" + ex.getMessage();
                    // If there is an error, don't just keep trying to register.
                    // Require the user to click a button again, or perform
                    // exponential back-off.
                }
                return msg;
            }

            //@Override
            protected void onPostExecute(String msg) {
                Log.i(LOG_MSG_TAG, msg);
                //mDisplay.append(msg + "\n");
            }

        }.execute(null, null, null);
    }
    /**
     * Sends the registration ID to your server over HTTP, so it can use GCM/HTTP
     * or CCS to send messages to your app. Not needed for this demo since the
     * device sends upstream messages to a server that echoes back the message
     * using the 'from' address in the message.
     */
    private void sendRegistrationIdToBackend() {
        // Your implementation here.
    }       
    /**
     * Stores the registration ID and app versionCode in the application's
     * {@code SharedPreferences}.
     *
     * @param context application's context.
     * @param regId registration ID
     */
    private void storeRegistrationId(Context context, String regId) {
        Log.i(LOG_MSG_TAG, "entered storeRegistrationId()");
        final SharedPreferences prefs = getGCMPreferences(context);
        Log.i(LOG_MSG_TAG, "retrieved preferences");
        int appVersion = getAppVersion(context);
        Log.i(LOG_MSG_TAG, "Saving regId on app version " + appVersion);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putString(PROPERTY_REG_ID, regId);
        editor.putInt(PROPERTY_APP_VERSION, appVersion);
        editor.commit();
    }       

    /*
    public void sendGcm(final View view) {
        if (view == findViewById(R.id.button2)) {
            new AsyncTask<Void, Void, String>() {
                @Override
                protected String doInBackground(Void... params) {
                    String msg = "";
                    try {
                        Bundle data = new Bundle();
                            data.putString("my_message", "Hello World");
                            data.putString("my_action",
                                    "com.google.android.gcm.demo.app.ECHO_NOW");
                            String id = Integer.toString(msgId.incrementAndGet());
                            gcm.send(SENDER_ID + "@gcm.googleapis.com", id, data);
                            msg = "Sent message";
                    } catch (IOException ex) {
                        msg = "Error :" + ex.getMessage();
                    }
                    return msg;
                }

                @Override
                protected void onPostExecute(String msg) {
                    Log.i(LOG_MSG_TAG, msg);
                    //mDisplay.append(msg + "\n");
                }
            }.execute(null, null, null);
//        } else if (view == findViewById(R.id.textView2)) {
//            mDisplay.setText("");
        }
    }
    */
    public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.i(LOG_MSG_TAG, "onReceive");

            // Explicitly specify that GcmIntentService will handle the intent.
            ComponentName comp = new ComponentName(context.getPackageName(),
                    GcmIntentService.class.getName());
            // Start the service, keeping the device awake while it is launching.
            startWakefulService(context, (intent.setComponent(comp)));
            setResultCode(Activity.RESULT_OK);
        }
    }
    public class GcmIntentService extends IntentService {
        public static final int NOTIFICATION_ID = 1;
        private NotificationManager mNotificationManager;
        NotificationCompat.Builder builder;

        public GcmIntentService() {
            super("GcmIntentService");
        }

        @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)) {
                    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(LOG_MSG_TAG, "Working... " + (i+1)
                                + "/5 @ " + SystemClock.elapsedRealtime());
                        try {
                            Thread.sleep(5000);
                        } catch (InterruptedException e) {
                        }
                    }
                    Log.i(LOG_MSG_TAG, "Completed work @ " + SystemClock.elapsedRealtime());
                    // Post notification of received message.
                    sendNotification("Received: " + extras.toString());
                    Log.i(LOG_MSG_TAG, "Received: " + extras.toString());
                }
            }
            // Release the wake lock provided by the WakefulBroadcastReceiver.
            GcmBroadcastReceiver.completeWakefulIntent(intent);
        }

        // Put the message into a notification and post it.
        // This is just one simple example of what you might choose to do with
        // a GCM message.
        private void sendNotification(String msg) {
            Log.i(LOG_MSG_TAG, "sendNotification");

            mNotificationManager = (NotificationManager)
                    this.getSystemService(Context.NOTIFICATION_SERVICE);

            PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                    new Intent(this, ProfileActivity.class), 0);

            NotificationCompat.Builder mBuilder =
                    new NotificationCompat.Builder(this)
            .setSmallIcon(R.drawable.ic_launcher)
            .setContentTitle("GCM Notification")
            .setStyle(new NotificationCompat.BigTextStyle()
            .bigText(msg))
            .setContentText(msg);

            mBuilder.setContentIntent(contentIntent);
            mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
        }
    }
}

And this separate class for checking the Play Services:

package com.MichaelResslerFineArt.eclipmessenger;

import android.app.Activity;
import android.util.Log;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;

public class GcmApi {

    public static final String EXTRA_MESSAGE = "message";
    public static final String PROPERTY_REG_ID = "registration_id";
    private static final String PROPERTY_APP_VERSION = "appVersion";
    private final static int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;

    String SENDER_ID = "594966827111";    // GCM Project Number

    /**
     * Check the device to make sure it has the Google Play Services APK. If
     * it doesn't, display a dialog that allows users to download the APK from
     * the Google Play Store or enable it in the device's system settings.
     */
    public boolean checkPlayServices(ProfileActivity currActivity) {
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(currActivity);
        if (resultCode != ConnectionResult.SUCCESS) {
            if (GooglePlayServicesUtil.isUserRecoverableError(resultCode)) {
                GooglePlayServicesUtil.getErrorDialog(resultCode, currActivity,
                        PLAY_SERVICES_RESOLUTION_REQUEST).show();
            } else {
                Log.i(ProfileActivity.LOG_MSG_TAG, "This device is not supported.");
                Toast.makeText(currActivity, "This device is not supported.", Toast.LENGTH_LONG).show();
                currActivity.finish();
            }
            return false;
        }
        return true;
    }
}

And the Android client side manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.MichaelResslerFineArt.eclipmessenger"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
    android:minSdkVersion="10"
    android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<permission     android:name="com.MichaelResslerFineArt.eclipmessenger.permission.C2D_MESSAGE"
    android:protectionLevel="signature" />
<uses-permission     android:name="com.MichaelResslerFineArt.eclipmessenger.permission.C2D_MESSAGE" />            

<application
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
        android:name="com.MichaelResslerFineArt.eclipmessenger.ProfileActivity"
        android:label="@string/app_name" >
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <meta-data android:name="com.google.android.gms.version"
               android:value="@integer/google_play_services_version" />
    <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.MichaelResslerFineArt.eclipmessenger" />
        </intent-filter>
    </receiver>
    <service android:name=".GcmIntentService" />

</application>

</manifest>

回答1:

GcmBroadcastReceiver and GcmIntentService shouldn't be inner classes of your activity class. They should be regular (top level) classes.

When you declare them as .GcmBroadcastReceiver and .GcmIntentService in your manifest, in means they should be located at com.MichaelResslerFineArt.eclipmessenger.GcmBroadcastReceiver and com.MichaelResslerFineArt.eclipmessenger.GcmIntentService, but since you implemented them as inner classes, they are actually located in com.MichaelResslerFineArt.eclipmessenger.ProfileActivity.GcmBroadcastReceiver and com.MichaelResslerFineArt.eclipmessenger.ProfileActivity.GcmIntentService, so they cannot be found when the message reaches your device.