سؤال

I'm trying to create a custom build of my app that I can install side-by-side with my production app.

I've successfully got my custom staging version to install side-by-side by repackaging the manifest with AAPT as per this answer. My problem is with getting GCM to work for both apps.

I call GCMRegistrar.register, but I never seem to get a response to the staging version of the app (GCM notifications still work fine for the production version of the app).

I've created a new Draft app in my Google Play developer account (I want to test in-app billing so need to host the app as a Draft in Google Play), and a new Project in my Google APIs Console for the staging app.

The domains I'm trying to use look like this:

  • production = com.mydomain.myapp
  • staging = com.mydomain.myapp.staging

The first problem I hit was that AAPT doesn't change the GCM permissions, so GCM would fail with:

Application does not define permission com.mydomain.myapp.staging.permission.C2D_MESSAGE

So I added a custom build step to update my AndroidManifest.xml further. Here are the relevant sections now:

<manifest 
    ...
    package="com.mydomain.myapp">

    <permission
        android:name="com.mydomain.myapp.staging.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />

    <uses-permission android:name="com.mydomain.myapp.staging.permission.C2D_MESSAGE" />

    <permission
        android:name="com.mydomain.myapp.staging.MESSAGING_PERMISSION"
        android:label="Blah"
        android:protectionLevel="normal" >
    </permission>

    <receiver
        android:name="com.google.android.gcm.GCMBroadcastReceiver"
        android:permission="com.google.android.c2dm.permission.SEND" >
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <action android:name="com.google.android.c2dm.intent.REGISTRATION" />

            <category android:name="com.mydomain.myapp" />
        </intent-filter>
    </receiver>

The <permission> tags seem to require the new package name to avoid the error I mentioned above.

The main <manifest> tag needs to keep the original package name, otherwise the app crashes. This is because the repackaging seems to happen dynamically at the point of install (i.e. if you decompile your app after repacking you'll see this package name is unchanged), so you shouldn't change this instance of the package name.

I'm not sure if the final <receiver> should be the original or the repackaged name, but I've tried both and neither seem to work.

Here the API Access information from the Google APIs Console:

STAGING
Key for Android apps (with certificates)
API key: <key>
Android apps: <SHA1>;com.mydomain.myapp.staging

PRODUCTION
Key for Android apps (with certificates)
API key: <key>
Android apps: <SHA1>;com.mydomain

Both also have a "Key for browser apps (with referers)".

I'm not sure if the "Key for Android apps" is actually required, but the GCM docs make it a bit hard to figure that out.

Has anybody got any ideas? I was wondering if the "Key for Android apps" is causing the problem, or perhaps the fact that my staging app is a subdomain of the production app.

One other complication is how long it takes changes from the Google APIs Console to propagate - does anybody know how long I need to wait before retesting to be sure the changes are in place?

هل كانت مفيدة؟

المحلول

I'm not sure how AAPT works, but I know how the manifest should be defined for GCM.

All the places marked below with PACKAGE should contain the same package name.

<manifest 
    ...
    package="PACKAGE">

<permission
    android:name="PACKAGE.permission.C2D_MESSAGE"
    android:protectionLevel="signature" />

<uses-permission android:name="PACKAGE.permission.C2D_MESSAGE" />

<receiver
    android:name="com.google.android.gcm.GCMBroadcastReceiver"
    android:permission="com.google.android.c2dm.permission.SEND" >
    <intent-filter>
        <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        <action android:name="com.google.android.c2dm.intent.REGISTRATION" />

        <category android:name="PACKAGE" />
    </intent-filter>
</receiver>

Another problem could be your intent service. If you are using a service class that extends GCMBaseIntentService, GCMBroadCastReceiver looks for this class in the main package of the app, so in your staging version, which has a different package name, it won't find it.

The solution in this case is to use a sub class of GCMBroadCastReceiver and override the method that specifies the path of your service class.

My answer here is related to your problem.

نصائح أخرى

Thanks to @Eran for his answer! In case it helps others I've listed my working configuration below (after running my custom build steps).

The package name in the <manifest> tag still needs to keep the original package name, but all the other instances need to use the repackaged name.

AndroidManifest.xml:

<manifest 
    ...
    package="ORIGINAL_PACKAGE_NAME">

    <permission
        android:name="NEW_PACKAGE_NAME.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />

    <uses-permission android:name="NEW_PACKAGE_NAME.permission.C2D_MESSAGE" />

    <permission
        android:name="NEW_PACKAGE_NAME.MESSAGING_PERMISSION"
        android:label="Blah"
        android:protectionLevel="normal" >
    </permission>

    <service android:name=".MyGCMIntentService" />

    <receiver
        android:name=".MyGCMBroadcastReceiver"
        android:permission="com.google.android.c2dm.permission.SEND" >
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <action android:name="com.google.android.c2dm.intent.REGISTRATION" />

            <category android:name="NEW_PACKAGE_NAME" />
        </intent-filter>
    </receiver>

Custom GCMBroadcastReceiver:

public class MyGCMBroadcastReceiver extends GCMBroadcastReceiver {

    public MyGCMBroadcastReceiver() {
        super();
        Log.d("Creating MyGCMBroadcastReceiver");
    }

    @Override
    protected String getGCMIntentServiceClassName(Context context) {
        String className = MyGCMIntentService.class.getName();
        Log.i("getGCMIntentServiceClassName", className);
        return className;
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top