GCM + AppEngine - 应用没有收到推送通知

Posted

技术标签:

【中文标题】GCM + AppEngine - 应用没有收到推送通知【英文标题】:GCM + AppEngine - app does not receive push notifications 【发布时间】:2014-07-14 22:01:18 【问题描述】:

我正在为我的 appengine 服务器开发一个应用客户端,该服务器支持推送通知服务。 我已经设置和部署了服务器部分并且能够注册我的设备,但是一旦我发送了推送通知,它就永远不会到达应用程序。

我在应用程序和后端都使用 android Studio,并按照此处的步骤操作:https://github.com/manfredzab/gradle-appengine-templates/tree/master/GcmEndpoints。

AndroidManifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp" >

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

        <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.example.myapp" />
            </intent-filter>
        </receiver>

        <service android:name=".GcmIntentService" />
    </application>

    <permission
        android:name="com.example.myapp.permission.C2D_MESSAGE"
         />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
    <!-- <uses-permission android:name="com.example.gcm.permission.C2D_MESSAGE" /> -->
    <uses-permission android:name="com.example.myapp.permission.C2D_MESSAGE" />
    <uses-permission android:name="com.example.myapp.c2dm.permission.RECEIVE" />

</manifest>

主活动:

package com.example.myapp;

public class MyActivity extends ActionBarActivity 

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_my);
    new GcmRegistrationAsyncTask().execute(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.my, menu);
    return true;


@Override
public boolean onOptionsItemSelected(MenuItem item) 
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    if (id == R.id.action_settings) 
        return true;
    
    return super.onOptionsItemSelected(item);


广播接收器

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver

@Override
public void onReceive(Context context, Intent intent) 
    // 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);
    Log.i("blabla","notification received");


意图服务

public class GcmIntentService extends IntentService


public GcmIntentService() 
    super("GcmIntentService");


@Override
protected void onHandleIntent(Intent intent) 
    Log.i("blabla", "notification received");
    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 != null && !extras.isEmpty())   // has effect of unparcelling Bundle
        // Since we're not using two way messaging, this is all we really to check for
        if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) 
            Logger.getLogger("GCM_RECEIVED").log(Level.INFO, extras.toString());

            showToast(extras.getString("message"));
        
    
    GcmBroadcastReceiver.completeWakefulIntent(intent);


protected void showToast(final String message) 
    new Handler(Looper.getMainLooper()).post(new Runnable() 
        @Override
        public void run() 
            Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
        
    );


消息传递端点:

@Api(name = "messaging", version = "v1", namespace = @ApiNamespace(ownerDomain =     "myapp.example.com", ownerName = "myapp.example.com", packagePath=""))
public class MessagingEndpoint 
private static final Logger log = Logger.getLogger(MessagingEndpoint.class.getName());

/** Api Keys can be obtained from the google cloud console */
private static final String API_KEY = System.getProperty("gcm.api.key");

public void sendMessage(@Named("message") String message) throws IOException 
    if(message == null || message.trim().length() == 0) 
        log.warning("Not sending message because it is empty");
        return;
    
    // crop longer messages
    if (message.length() > 1000) 
        message = message.substring(0, 1000) + "[...]";
    
    Sender sender = new Sender(API_KEY);
    Message msg = new Message.Builder().addData("message", message).build();
    List<RegistrationRecord> records = ofy().load().type(RegistrationRecord.class).limit(1000).list();
    for(RegistrationRecord record : records) 
        Result result = sender.send(msg, record.getRegId(), 10);
        if (result.getMessageId() != null) 
            log.info("Message sent to " + record.getRegId());
            String canonicalRegId = result.getCanonicalRegistrationId();
            if (canonicalRegId != null) 
                // if the regId changed, we have to update the datastore
                log.info("Registration Id changed for " + record.getRegId() + " updating to " + canonicalRegId);
                record.setRegId(canonicalRegId);
                ofy().save().entity(record).now();
            
         else 
            String error = result.getErrorCodeName();
            if (error.equals(Constants.ERROR_NOT_REGISTERED)) 
                log.warning("Registration Id " + record.getRegId() + " no longer registered with GCM, removing from datastore");
                // if the device is no longer registered with Gcm, remove it from the datastore
                ofy().delete().entity(record).now();
            
            else 
                log.warning("Error when sending message : " + error);
            
        
    


OfyService:

public class OfyService 

static 
    ObjectifyService.register(RegistrationRecord.class);


public static Objectify ofy() 
    return ObjectifyService.ofy();


public static ObjectifyFactory factory() 
    return ObjectifyService.factory();


注册端点

@Api(name = "registration", version = "v1",
 namespace = @ApiNamespace(ownerDomain = "myapp.example.com", ownerName = "myapp.example.com", packagePath = ""))
public class RegistrationEndpoint


private static final Logger log = Logger.getLogger(RegistrationEndpoint.class.getName());

/**
 * Register a device to the backend
 *
 * @param regId The Google Cloud Messaging registration Id to add
 */
@ApiMethod(name = "register")
public void registerDevice(@Named("regId") String regId)

    if (findRecord(regId) != null)
    
        log.info("Device " + regId + " already registered, skipping register");
        return;
    
    RegistrationRecord record = new RegistrationRecord();
    record.setRegId(regId);
    ofy().save().entity(record).now();


/**
 * Unregister a device from the backend
 *
 * @param regId The Google Cloud Messaging registration Id to remove
 */
@ApiMethod(name = "unregister")
public void unregisterDevice(@Named("regId") String regId)

    RegistrationRecord record = findRecord(regId);
    if (record == null)
    
        log.info("Device " + regId + " not registered, skipping unregister");
        return;
    
    ofy().delete().entity(record).now();


/**
 * Return a collection of registered devices
 *
 * @param count The number of devices to list
 * @return a list of Google Cloud Messaging registration Ids
 */
@ApiMethod(name = "listDevices")
public CollectionResponse<RegistrationRecord> listDevices(@Named("count") int count)

    List<RegistrationRecord> records = ofy().load().type(RegistrationRecord.class).limit(count).list();
    return CollectionResponse.<RegistrationRecord>builder().setItems(records).build();


private RegistrationRecord findRecord(String regId)

    return ofy().load().type(RegistrationRecord.class).filter("regId", regId).first().now();



注册记录

@Entity
public class RegistrationRecord 

@Id
Long id;

@Index
private String regId;
// you can add more fields...

public RegistrationRecord() 

public String getRegId() 
    return regId;


public void setRegId(String regId) 
    this.regId = regId;


index.html

<!DOCTYPE html>
<html>
<head>
    <title>Hello, Google Cloud Messaging!</title>
    <link rel="stylesheet"     href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
    <link rel="stylesheet"     href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css">
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js" >    </script>
    <script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js">    </script>
</head>
<body role="document" style="padding-top: 70px;">
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
    <div class="container">
        <div class="navbar-header">
            <a class="navbar-brand" href="#">Hello, Google Cloud Messaging!</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li class="dropdown">
                    <a href="#" class="dropdown-toggle" data-    toggle="dropdown">Documentation <b class="caret"></b></a>
                    <ul class="dropdown-menu">
                        <li><a     href="https://developers.google.com/appengine/docs/java/">Google App Engine</a></li>
                        <li><a     href="https://developers.google.com/appengine/docs/java/endpoints/">Google Cloud     Endpoints</a></li>
                        <li><a href="http://developer.android.com/google/gcm/">Google Cloud Messaging</a></li>
                        <li><a href="https://github.com/GoogleCloudPlatform/gradle-appengine-templates/tree/master/GcmEndpoints">Connecting your Android application to this backend</a></li>
                    </ul>
                </li>
                <li><a href="/_ah/api/explorer">Google Cloud Endpoints API Explorer</a></li>
                <li><a href="https://console.developers.google.com">Google Developers Console</a></li>
            </ul>
        </div>
    </div>
</div>

<div class="container theme-showcase" role="main">
<!--
  Output from GCM call.
-->
<div class="alert alert-success" style="visibility: collapse;" id="outputAlert"></div>

<!--
  A form that takes a message text and submits it to "messaging" Endpoints API,
  access to this Endpoints API is enabled once the client is loaded below.
-->
<div class="jumbotron">
    <div class="row">
        <div class="col-lg-12">
            <h1>Hello, Google Cloud Messaging!</h1>
            <p>Enter your message below and press "Send Message" button to send it over Google Cloud Messaging to all registered devices.</p>
            <form>
                <div class="input-group">
                    <input type="text" class="form-control input-lg" placeholder="Message text" id="messageTextInput" />
                      <span class="input-group-btn">
                         <button class="btn btn-default btn-primary btn-group btn-lg" type="submit" id="sendMessageButton">Send Message &raquo;</button>
                      </span>
                </div>
            </form>
            <br/>
            <p>If you need step-by-step instructions for connecting your Android application to this backend module, see <a href="https://github.com/GoogleCloudPlatform/gradle-appengine-templates/tree/master/GcmEndpoints">"App Engine Backend with Google Cloud Messaging" template documentation</a>.</p>
            <p>
                    <small>
                    For more information about Google App Engine for Java, check out the <a href="https://developers.google.com/appengine/docs/java/">App Engine documentation</a>.<br />
                    To learn more about Google Cloud Endpoints, see <a href="https://developers.google.com/appengine/docs/java/endpoints/">Cloud Endpoints documentation</a>.<br />
                    Similarly, for more information about Google Cloud Messaging, see     <a href="http://developer.android.com/google/gcm/">Cloud Messaging documentation</a>.<br />
                    If you'd like to access your generated Google Cloud Endpoints APIs directly, see the <a href="/_ah/api/explorer">Cloud Endpoints API Explorer</a>.
                    </small>
                </p>
            </div>
        </div>
    </div>
</div>

<script type="text/javascript">
// A function that attaches a "Send Message" button click handler
function enableClick() 
  document.getElementById('sendMessageButton').onclick = function() 
    var message = document.getElementById('messageTextInput').value;
    if (!message) 
      message = '(Empty message)';
    

    gapi.client.messaging.messagingEndpoint.sendMessage('message': message).execute(
      function(response) 
        var outputAlertDiv = document.getElementById('outputAlert');
        outputAlertDiv.style.visibility = 'visible';

        if (response && response.error) 
          outputAlertDiv.className = 'alert alert-danger';
          outputAlertDiv.innerHTML = '<b>Error Code:</b> ' + response.error.code + ' [' + response.error.message +']';
        
        else 
          outputAlertDiv.className = 'alert alert-success';
          outputAlertDiv.innerHTML = '<b>Success:</b> Message \"' + message + '\" sent to all registered devices!</h2>';
        
      
    );
    return false;
  


// This is called initially
function init() 
  var apiName = 'messaging'
  var apiVersion = 'v1'
  // set the apiRoot to work on a deployed app and locally
  var apiRoot = '//' + window.location.host + '/_ah/api';
  var callback = function() 
    enableClick();
  
  gapi.client.load(apiName, apiVersion, callback, apiRoot);

  </script>
<!--
 Load the Google APIs Client Library for JavaScript
 More info here : https://developers.google.com/api-client-    library/javascript/reference/referencedocs
-->
<script src="https://apis.google.com/js/client.js?onload=init"></script>
</body>
</html>

appengine-web.xml

<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>app-name-from-developers-console</application>
<version>1</version>
<threadsafe>true</threadsafe>

<system-properties>
    <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>


    <property name="gcm.api.key" value="gcm-SERVER-api-key-from-dev-console"/>
</system-properties>

我不知道还要添加什么,后端似乎运行良好,因为注册已成功进行。我没有收到任何错误,什么都没有。我错过了什么?一定是某个地方出了点小错误,我看不出来。我假设所有的东西都已经建立好了,可以立即发送推送通知,因为它都是生成的。我的服务 api 密钥设置正确,并且已将 IPS 设置为允许的任何 IP。

【问题讨论】:

gcm 服务器是否成功响应? 推送消息?我应该去哪里看? 有几件事,你是使用本地开发服务器还是部署到 apppot。此外,在发送消息时打开浏览器开发工具并检查是否发现任何有趣的地方。 Appspot。我稍后会研究并让你知道“有趣”的部分。 确保您明确使用 https,一些旧版本的 appengine 模板的 javascript 页面不能很好地处理这个问题。您需要确保端点请求通过 https 并且端点 javascript 东西从地址栏中提取地址。 【参考方案1】:

唯一的问题是我在 GCM 的主页 url 中有 http 而不是 https。改变这个解决了我的问题。

在 index.html 中我改变了

// This is called initially
function init() 
  var apiName = 'messaging'
  var apiVersion = 'v1'
  // set the apiRoot to work on a deployed app and locally
  var apiRoot = '//' + window.location.host + '/_ah/api';

  var callback = function() 
    enableClick();
  
  gapi.client.load(apiName, apiVersion, callback, apiRoot);

// This is called initially
function init() 
  var apiName = 'messaging'
  var apiVersion = 'v1'
  // set the apiRoot to work on a deployed app and locally
  var apiRoot = 'https://' + window.location.host + '/_ah/api';

  var callback = function() 
    enableClick();
  
  gapi.client.load(apiName, apiVersion, callback, apiRoot);

【讨论】:

以上是关于GCM + AppEngine - 应用没有收到推送通知的主要内容,如果未能解决你的问题,请参考以下文章

使用 GCM 的 iOS 自定义推送通知声音

Android - GCM 在打开应用程序时收到来自 gcm 的通知

IOS 没有收到 GCM 推送通知

有时一些 android 用户没有通过 GCM 收到推送通知

PHP GCM 应用引擎

如何接收应用程序停止或设备关闭时发送的 GCM 推送通知?