更新
已解决 - 感谢@MusiGenesis持久性,我通过注册新的Google邮件帐户和新的C2DM帐户解决了这个问题。更新了Web服务器和Android应用程序中的相关凭据后,所有工作都开始像魔术一样。
END OF UPDATE
我正在寻找一个确定的列表,列出发送推送通知时401个未经授权的错误的原因,以便我可以尝试消除我的问题。
我在C2DM上注册了一封谷歌电子邮件 我可以使用curl获取授权码
我在Android应用程序上有注册用户的身份验证令牌
使用2个身份验证令牌(刷新)当我从我的网络服务器发送推送通知请求时,我的Web服务器会收到401个未经授权的错误。
据我所知,我正在做我需要做的一切,所以我正在寻找我可能缺少的东西。我在互联网上搜索过,很多人似乎都遇到了同样的问题而没有明确的答案。 任何帮助非常感谢
更新
如下面的答案所述,似乎需要第二阶段才能获得注册ID,该注册ID似乎与注册用户在Android应用程序上收到的身份验证令牌不同。看了跳转代码和这两个资源
和
http://marakana.com/forums/android/general/272.html
除了auth令牌之外,我没有看到关于第二次注册调用以获取注册ID的信息。我显然错过了一些东西,如果有人能为我拼出来,我会感激不尽。
** 更新2 **
我的C2DM接收器看起来像这样
public class C2DMReceiver extends C2DMBaseReceiver {
public C2DMReceiver() {
super(REGISTERED_GOOGLE_MAIL_ADDRESS);
}
@Override
public void onRegistered(Context context, String registrationId)
throws java.io.IOException {
// The registrationId should be sent to your application server.
Log.e("C2DM", "Registration ID arrived!");
Log.e("C2DM", registrationId);
Intent webSeverReg = new Intent(this, RegService.class);
startService(webServerReg);
};
@Override
protected void onMessage(Context context, Intent intent) {
Log.e("C2DM", "Message: Fantastic!!!");
// Extract the payload from the message
Bundle extras = intent.getExtras();
if (extras != null) {
System.out.println(extras.get("payload"));
// Now do something smart based on the information
}
}
@Override
public void onError(Context context, String errorId) {
Log.e("C2DM", "Error occured!!!");
}
}
取自jumpnote app的C2DMBaseReceiver看起来像这样
/**
* Base class for C2D message receiver. Includes constants for the
* strings used in the protocol.
*/
public abstract class C2DMBaseReceiver extends IntentService {
private static final String C2DM_RETRY = "com.google.android.c2dm.intent.RETRY";
public static final String REGISTRATION_CALLBACK_INTENT = "com.google.android.c2dm.intent.REGISTRATION";
private static final String C2DM_INTENT = "com.google.android.c2dm.intent.RECEIVE";
// Logging tag
private static final String TAG = "C2DM";
// Extras in the registration callback intents.
public static final String EXTRA_UNREGISTERED = "unregistered";
public static final String EXTRA_ERROR = "error";
public static final String EXTRA_REGISTRATION_ID = "registration_id";
public static final String ERR_SERVICE_NOT_AVAILABLE = "SERVICE_NOT_AVAILABLE";
public static final String ERR_ACCOUNT_MISSING = "ACCOUNT_MISSING";
public static final String ERR_AUTHENTICATION_FAILED = "AUTHENTICATION_FAILED";
public static final String ERR_TOO_MANY_REGISTRATIONS = "TOO_MANY_REGISTRATIONS";
public static final String ERR_INVALID_PARAMETERS = "INVALID_PARAMETERS";
public static final String ERR_INVALID_SENDER = "INVALID_SENDER";
public static final String ERR_PHONE_REGISTRATION_ERROR = "PHONE_REGISTRATION_ERROR";
// wakelock
private static final String WAKELOCK_KEY = "C2DM_LIB";
private static PowerManager.WakeLock mWakeLock;
private final String senderId;
/**
* The C2DMReceiver class must create a no-arg constructor and pass the
* sender id to be used for registration.
*/
public C2DMBaseReceiver(String senderId) {
// senderId is used as base name for threads, etc.
super(senderId);
this.senderId = senderId;
}
/**
* Called when a cloud message has been received.
*/
protected abstract void onMessage(Context context, Intent intent);
/**
* Called on registration error. Override to provide better
* error messages.
*
* This is called in the context of a Service - no dialog or UI.
*/
public abstract void onError(Context context, String errorId);
/**
* Called when a registration token has been received.
*/
public void onRegistered(Context context, String registrationId) throws IOException {
// registrationId will also be saved
}
/**
* Called when the device has been unregistered.
*/
public void onUnregistered(Context context) {
}
@Override
public final void onHandleIntent(Intent intent) {
Log.d(TAG, "@@@@ - onHandleIntent Messaging request received");
try {
Context context = getApplicationContext();
if (intent.getAction().equals(REGISTRATION_CALLBACK_INTENT)) {
handleRegistration(context, intent);
} else if (intent.getAction().equals(C2DM_INTENT)) {
onMessage(context, intent);
} else if (intent.getAction().equals(C2DM_RETRY)) {
C2DMessaging.register(context, senderId);
}
} finally {
// Release the power lock, so phone can get back to sleep.
// The lock is reference counted by default, so multiple
// messages are ok.
// If the onMessage() needs to spawn a thread or do something else,
// it should use it's own lock.
mWakeLock.release();
}
}
/**
* Called from the broadcast receiver.
* Will process the received intent, call handleMessage(), registered(), etc.
* in background threads, with a wake lock, while keeping the service
* alive.
*/
static void runIntentInService(Context context, Intent intent) {
if (mWakeLock == null) {
// This is called from BroadcastReceiver, there is no init.
PowerManager pm =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
WAKELOCK_KEY);
}
mWakeLock.acquire();
// Use a naming convention, similar with how permissions and intents are
// used. Alternatives are introspection or an ugly use of statics.
String receiver = context.getPackageName() + ".C2DMReceiver";
intent.setClassName(context, receiver);
context.startService(intent);
}
private void handleRegistration(final Context context, Intent intent) {
final String registrationId = intent.getStringExtra(EXTRA_REGISTRATION_ID);
Log.d(TAG, "@@@@ - HandleRegistration Messaging request received");
String error = intent.getStringExtra(EXTRA_ERROR);
String removed = intent.getStringExtra(EXTRA_UNREGISTERED);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "dmControl: registrationId = " + registrationId +
", error = " + error + ", removed = " + removed);
}
if (removed != null) {
// Remember we are unregistered
C2DMessaging.clearRegistrationId(context);
onUnregistered(context);
return;
} else if (error != null) {
// we are not registered, can try again
C2DMessaging.clearRegistrationId(context);
// Registration failed
Log.e(TAG, "Registration error " + error);
onError(context, error);
if ("SERVICE_NOT_AVAILABLE".equals(error)) {
long backoffTimeMs = C2DMessaging.getBackoff(context);
Log.d(TAG, "Scheduling registration retry, backoff = " + backoffTimeMs);
Intent retryIntent = new Intent(C2DM_RETRY);
PendingIntent retryPIntent = PendingIntent.getBroadcast(context,
0 /*requestCode*/, retryIntent, 0 /*flags*/);
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.ELAPSED_REALTIME,
backoffTimeMs, retryPIntent);
// Next retry should wait longer.
backoffTimeMs *= 2;
C2DMessaging.setBackoff(context, backoffTimeMs);
}
} else {
try {
onRegistered(context, registrationId);
C2DMessaging.setRegistrationId(context, registrationId);
} catch (IOException ex) {
Log.e(TAG, "Registration error " + ex.getMessage());
}
}
}
}
答案 0 :(得分:3)
你说你“在我的Android应用程序上拥有来自注册用户的身份验证令牌”。您可能刚刚写错了,但如果您的字面意思是您使用的是注册用户的身份验证令牌而不是注册ID ,则用户从C2DM服务器返回,那就是你的问题。
编辑:您的客户端应用(在设备上运行)对C2DM使用3个步骤:1)调用C2DM服务器传递客户端的gmail帐户ID和密码,获取身份验证令牌; 2)使用步骤1中的auth令牌再次调用C2DM服务器,返回注册ID(ASCII splooge的96-120个字符); 3)调用您的服务器应用程序并传递在步骤2中获得的注册ID (而不是步骤1中获得的身份验证令牌)。
当您的服务器应用程序想要将某些内容推送到客户端时,它会调用C2DM服务器来获取身份验证令牌(传递用于注册C2DM服务器的电子邮件和密码,不< / em>客户端用户的电子邮件和密码),然后使用该身份验证令牌和客户端的注册ID 来执行推送。
编辑2:我在这里描述客户端上发生的事情是错误的 - 客户端代码不涉及在任何时候获取oauth令牌。所有这些都是由Android OS本身处理的。本教程:
http://www.vogella.de/articles/AndroidCloudToDeviceMessaging/article.html
很好地展示了C2DM的所有功能。
编辑3:我在C2DM中遇到的最常见错误来自文档使用“发件人电子邮件”这一短语。此术语是指“已注册”与C2DM一起使用的Gmail帐户,并不是指Android用户的Gmail帐户。您的Web服务器应用程序(以及匹配的密码)使用此gmail帐户从C2DM获取oauth令牌。 Android客户端应用程序需要使用相同的帐户(没有匹配的密码,它不知道)来进行调用,该调用将返回一个registrationId然后发送到您的Web服务器。