- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
更新
已解决 - 感谢@MusiGenesis 的坚持,我通过注册一个新的 Google 邮件帐户和一个新的 C2DM 帐户解决了这个问题。在网络服务器和 Android 应用程序中更新相关凭据后,所有这些都开始像魔术一样工作。
更新结束
我正在寻找发送推送通知时 401 未经授权错误原因的明确列表,以便我可以尝试消除我的问题。
我有一个用 C2DM 注册的谷歌邮箱我可以使用 curl 获取授权码
我的 android 应用程序上有注册用户的授权 token
使用 2 个身份验证 token (已刷新),当从我的网络服务器发送推送通知请求时,我从我的网络服务器收到 401 未经授权的错误。
据我所知,我正在做我需要做的一切,所以我正在寻找我可能遗漏的东西。我已经搜索了互联网,很多人似乎都遇到了同样的问题,但没有明确的答案。非常感谢任何帮助
更新
正如下面的答案中提到的,似乎需要第二个阶段才能获得注册 ID,该 ID 似乎与注册用户在 Android 应用程序上收到的授权 token 不同。看了jumpnote代码和这两个资源
和
http://marakana.com/forums/android/general/272.html
除了授权 token 之外,我没有看到有关获取注册 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 应用程序中获取的 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());
}
}
}
}
最佳答案
您说您“拥有来 self 的 Android 应用程序注册用户的授权 token ”。您可能刚刚写错了,但如果您的意思是您使用的是注册用户的 auth token 而不是用户从 C2DM 服务器返回的 registration id ,那么你的问题就在那里。
编辑:您的客户端应用程序(在设备上运行)使用 C2DM 的 3 步过程:1) 调用 C2DM 服务器传递客户端的 gmail 帐户 ID 和密码,取回身份验证 token ; 2) 使用步骤 1 中的 auth token 再次调用 C2DM 服务器,取回注册 ID(96-120 个字符的 ASCII spooge); 3) 调用您的服务器应用程序并传递在第 2 步中获得的注册 ID(而不是在第 1 步中获得的auth token)。
当您的服务器应用程序想要向客户端推送内容时,它会调用 C2DM 服务器以获取身份验证 token (传递您用于注册 C2DM 服务器的电子邮件和密码,不是 客户端用户的电子邮件和密码),然后使用该身份验证 token 以及客户端的注册 ID 来执行推送。
编辑 2: 我在此处对客户端发生的情况的描述是错误的 - 客户端代码在任何时候都不涉及获取 oauth token 。所有这些东西都由 Android 操作系统本身处理。本教程:
http://www.vogella.de/articles/AndroidCloudToDeviceMessaging/article.html
很好地展示了 C2DM 的一切运作方式。
编辑 3:我见过的 C2DM 最常见的错误是文档中使用了短语“发件人的电子邮件”。该术语是指“注册”用于 C2DM 的 gmail 帐户,而不是指 Android 用户的 gmail 帐户。您的 Web 服务器应用程序使用此 gmail 帐户(以及匹配的密码)从 C2DM 获取 oauth token 。 Android 客户端应用程序需要使用相同的帐户(没有匹配的密码,它不知道该密码)进行调用以返回一个 registrationId,然后将其发送到您的网络服务器。
关于android - C2DM 推送通知 401 未授权错误的原因是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7703680/
我正在尝试将多个值放入数组中。 当我使用时: csvData.push('data[0][index],data[1][index],data[2][index],data[3][index]');
我想在数组声明中直接使用函数 push(),但它不能正常工作。在我的示例中,我的数组返回值 2 : var j = ["b"].push("a"); document.write(j); // ret
我编写了以下Powershell,它为所选文件夹中的所有驱动程序创建了一个bat安装程序,然后应重新启动PC。 New-Item C:\Tools\Drivers\DellLatitude3450.b
例: $ git clone git@gitlab:carlos/test.git Cloning into 'asd'... ssh: connect to host gitlab port 22:
我正在构建一个具有数组类型属性的对象数组: 这里是一些简化的代码: var _data = []; for(var i=0;i<10;i++) { var element = {
我有一个简单的 PHP/MySql 应用程序,它通常会选择几个数据库之一(假设每个客户一个)进行操作。但是,经常调用访问公共(public)数据库的实用程序函数。 我不想在我的代码中散布 USE 子句
我在推送 View Controller 时遇到问题。这就是我所做的:单击一个按钮,我使用这段代码添加了一个模态视图,我工作正常: - (void)addAction:(id)sender {
我想为socket can写一个android系统服务器。我目前正在设计这个,想知道是否有任何方法可以在 Linux/POSIX 套接字上的数据是否可用而无需调用 read() 并随时轮询结果的情况下
我正在编写一个 Bootstrap 站点,我想知道这是否可以接受。该网站看起来像我想要的那样,但我想知道这是否是最佳做法? 我采用的方法是对每两个缺失的列使用 1 个偏
删除远程分支是通过: git push origin :master 如果本地在远程之后,则需要完成: git push --force origin :master 但是强制删除例如master 基
假设我有一个 git 服务器。在每次推送时,我都需要启动一个进程,我可以通过一个钩子(Hook)来完成。 需要将进程的标准输出写入执行推送的 git 客户端。这与 Heroku 或 Openshift
我刚刚开始学习 Git,有些事情我无法解决。在我的 Mac 上本地创建和使用 git 存储库后,我可以将副本推送到其他地方的另一台服务器吗?我在防火墙后面,所以不幸的是我无法从另一台机器运行 git
这个问题在这里已经有了答案: warning: remote HEAD refers to nonexistent ref, unable to checkout (13 个答案) 关闭 7 年前。
我已经安装了 SCM Sync 配置插件(0.0.10)来将我的 jenkins 设置保存在我的 git 存储库中。 我已经设置了 git url 存储库但插件没有提交/推送,请看截图 我试过: 私钥
这可能看起来很矛盾,我知道 secret 变更集是私有(private)的,但是如果我想备份这些 secret 变更集怎么办? 我与一些分支并行工作,有时我想插入一个,而不是其他的。为了实现这一点,我
我正在使用 TortoiseHg用于版本控制。提交到本地后,我推送到远程存储库。如何撤消到特定的提交点? 有三个不同的插入,我想恢复到第一个插入。我读到了 Mercurial 回滚和 hg 撤销 命令
我知道以前有人问过这个问题,但我似乎无法理解这件事...... git checkout master git pull git git checkout feature git rebase ori
下面的代码片段中 return { Push:function ..... 的含义是什么?当我用谷歌搜索时,我发现push()方法将新项目添加到数组的末尾,并返回新的长度。所以我不确定什么是push:
我正在使用 Mercurial 1.6。我有一个带有几个子存储库的存储库 (11)。我想将父存储库推送到默认远程存储库,而不推送子存储库。想要这样做的原因包括: 我使用的是 SSH 存储库,需要很长时
我分配了一个按钮来将 segue 推送到另一个 View Controller ,但是当我执行这部分代码时,我得到以下信息: 2014-02-20 10:44:29.357 nar[20244:70b
我是一名优秀的程序员,十分优秀!