android - C2DM 推送通知 401 未授权错误的原因是什么?

标签 android android-c2dm

更新

已解决 - 感谢@MusiGenesis 的坚持,我通过注册一个新的 Google 邮件帐户和一个新的 C2DM 帐户解决了这个问题。在网络服务器和 Android 应用程序中更新相关凭据后,所有这些都开始像魔术一样工作。

更新结束

我正在寻找发送推送通知时 401 未经授权错误原因的明确列表,以便我可以尝试消除我的问题。

我有一个用 C2DM 注册的谷歌邮箱 我可以使用 curl 获取授权码

我的 android 应用程序上有注册用户的授权 token

使用 2 个身份验证 token (已刷新),当从我的网络服务器发送推送通知请求时,我从我的网络服务器收到 401 未经授权的错误。

据我所知,我正在做我需要做的一切,所以我正在寻找我可能遗漏的东西。我已经搜索了互联网,很多人似乎都遇到了同样的问题,但没有明确的答案。 非常感谢任何帮助

更新

正如下面的答案中提到的,似乎需要第二个阶段才能获得注册 ID,该 ID 似乎与注册用户在 Android 应用程序上收到的授权 token 不同。看了jumpnote代码和这两个资源

http://www.vogella.de/articles/AndroidCloudToDeviceMessaging/article.html#implementation_mobileregistration

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/

相关文章:

android - 如何调试 BOOT_COMPLETED 广播接收器 "Force Close"崩溃?

java - 有没有办法在 Android 中使用没有按钮的 Twitter Digits?

java - uri 获取错误的文件路径

android - 获取在 Android 中使用 C2DM 的主要电子邮件

android - 有没有人在无根的 Nexus 1 上安装了 JumpNote Android C2DM 演示应用程序?

android - 如何使用 smack api 在 android 中进行语音/视频聊天

Android FrameLayouts 并排与独立的内部滚动

android - C2DM注册需要多长时间

php - 使用 OAuth2.0 的 C2DM 和 PHP(不推荐使用 ClientLogin!)

java - 未找到 GCM 接收器包