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 »</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 - 应用没有收到推送通知的主要内容,如果未能解决你的问题,请参考以下文章
Android - GCM 在打开应用程序时收到来自 gcm 的通知