开发者

C2DM Push notifications What causes 401 unauthorisation errors?

UPDATE

SOLVED - Thank's to @MusiGenesis persistence with this I solved the problem by registering a new Google mail account and a new C2DM account. After updating the relevant credentials in the web server and the android app all started working like magic.

END OF UPDATE

I'm looking for a definitive list for causes of 401 unauthorised errors when sending a push notification so I can try to eliminate my problem.

I have a google email registered with C2DM I can use curl to get an authorisation code

I have the auth token from the registered user on my android app

Using the 2 auth tokens (refreshed) I get 401 unauthorised errors from my web server when sending a push notification request from my web server.

As far as I can tell I am doing everything I need to do so I'm looking for what I may be missing. I have scoured the internet and lots of people seem to be having the same problem with no definite answer. Any help greatly appreciated

UPDATE

As mentioned in the answer below it seems there is a second stage required to get a registration ID which appears to be different from the auth token received by the registered user on the android app. Having looked at the jumpnote code and these two resources

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

and

http://marakana.com/forums/android/general/272.html

I see no info regarding a second registration call to get a regisration ID in addition to the auth token. I'm obviously missing something and would be gratefull if someone could spell this out for me.

** UPDATE 2 **

My C2DM Receiver looks like this

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!!!");
    }
}

C2DMBaseReceiver taken from jumpnote app looks like this

/**
 * 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);

             开发者_如何学JAVA   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());
            }
        }
    }
}


You say you "have the auth token from the registered user on my android app". You may have just written that incorrectly, but if you mean literally that you're using the registered user's auth token and not the registration id the user got back from the C2DM server, then there's your problem right there.

Edit: Your client app (running on the device) uses a 3-step process for C2DM: 1) call the C2DM server passing the client's gmail account id and password, get back an auth token; 2) call the C2DM server again using the auth token from step 1, get back a registration id (which is 96-120 characters of ASCII splooge); 3) call your server app and pass the registration id obtained in step 2 (not the auth token obtained in step 1).

When your server app then wants to push something to the client, it makes a call to the C2DM server to get an auth token (passing the email and password you used to sign up for the C2DM server, not the client user's email and password), then uses that auth token along with the client's registration id to execute the push.

Edit 2: my description here of what happens on the client is wrong - the client code does not involve getting an oauth token at any point. All of that stuff is handled by the Android OS itself. This tutorial:

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

shows nicely how everything works for C2DM.

Edit 3: The most common mistake I've seen with C2DM comes from the documentation's use of the phrase "sender's email". This term refers to the gmail account that was "registered" for use with C2DM, and does not refer to the gmail account of the Android user. This gmail account is used by your web server app (along with the matching password) to get an oauth token from C2DM. This same account needs to be used by the Android client app (without the matching password, which it doesn't know) to make the call that gets back a registrationId that it then sends to your web server.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜