如何修复Google云消息传递注册错误:SERVICE_NOT_AVAILABLE?

30
我遇到了一个奇怪的问题 - 我已经在我的应用程序中使用GCM相当长的时间,并且一切都运行得很完美。然而,在发布到Google Play之前,我将应用程序包名称从 com.android.testapp 改为 com.android.recognition,此后GCM停止工作。起初我收到了错误消息 GCM sender id not set on constructor,通过重写getSenderIds(Context context)方法解决了这个问题,但现在我无法获取注册ID。以下是来自logcat的消息: enter image description here 我该如何解决这个问题?当我切换到新包时,我将清单文件中所有内容都更改为新包的名称。
<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.android.recognition" />
        </intent-filter>
    </receiver>

那么这背后的问题是什么?重命名应用程序包是否会导致这种情况,或者还有其他原因?


你在使用模拟器工作吗?用的是哪个模拟器? - Pankaj Kumar
@PankajKumar 我正在使用真实设备:Kindle Fire和LG NEXUS 4 进行开发,但是什么都不起作用。 - MainstreamDeveloper00
对我来说,这看起来很奇怪。我必须尝试几次。有时候,我需要强制关闭应用程序。然后它就可以工作了。 - K.Sopheak
请检查您的互联网连接是否正常... FCM需要在运行设备上使用互联网来访问网络服务并生成设备令牌。 - Sayed Mohd Ali
17个回答

44

问题已得到解答,但在我的情况下要复杂一些。

  1. 检查您是否有有效的互联网连接
  2. 检查您在清单文件中是否拥有互联网权限
  3. 确保包名正确,如Eran所提到的
  4. 设备时间设置正确。即使一切都完美无缺,如果设备时钟不正确,也将失败。

对我来说,错误的时钟导致了问题。 :)


1
我忘记开启WiFi了!谢谢。 - JCarlosR
是的,我失去了WiFi连接,而且没有意识到。谢谢。 - nland
我会添加检查Google Play商店是否正常工作,如果不正常,则该设备使用的Google帐户存在某些问题,可能导致此行为。同时,请检查 Google Play服务是否已更新。 - Ofek Ron
太好了!你能否更新答案并加入一些代码片段呢? - Aman Gautam
系统时间...该死 - zozelfelfo

33

这个 SERVICE_NOT_AVAILABLE 错误表示当前 GCM 服务 不可用。请等待一段时间后重试。

据我的经验,这种情况经常发生,所以不用担心。


参见 GCM Lib 的 GCMConstants 类。

/**
     * The device can't read the response, or there was a 500/503 from the
     * server that can be retried later. The application should use exponential
     * back off and retry.
     */
    public static final String ERROR_SERVICE_NOT_AVAILABLE =
            "SERVICE_NOT_AVAILABLE";

如需进一步了解,请查看GCMBaseIntentServicehandleRegistration()函数。

private void handleRegistration(final Context context, Intent intent) {
        String registrationId = intent.getStringExtra(EXTRA_REGISTRATION_ID);
        String error = intent.getStringExtra(EXTRA_ERROR);
        String unregistered = intent.getStringExtra(EXTRA_UNREGISTERED);
        Log.d(TAG, "handleRegistration: registrationId = " + registrationId +
                ", error = " + error + ", unregistered = " + unregistered);

        // registration succeeded
        if (registrationId != null) {
            GCMRegistrar.resetBackoff(context);
            GCMRegistrar.setRegistrationId(context, registrationId);
            onRegistered(context, registrationId);
            return;
        }

        // unregistration succeeded
        if (unregistered != null) {
            // Remember we are unregistered
            GCMRegistrar.resetBackoff(context);
            String oldRegistrationId =
                    GCMRegistrar.clearRegistrationId(context);
            onUnregistered(context, oldRegistrationId);
            return;
        }

        // last operation (registration or unregistration) returned an error;
        Log.d(TAG, "Registration error: " + error);
        // Registration failed
        if (ERROR_SERVICE_NOT_AVAILABLE.equals(error)) {
            boolean retry = onRecoverableError(context, error);
            if (retry) {
                int backoffTimeMs = GCMRegistrar.getBackoff(context);
                int nextAttempt = backoffTimeMs / 2 +
                        sRandom.nextInt(backoffTimeMs);
                Log.d(TAG, "Scheduling registration retry, backoff = " +
                        nextAttempt + " (" + backoffTimeMs + ")");
                Intent retryIntent =
                        new Intent(INTENT_FROM_GCM_LIBRARY_RETRY);
                retryIntent.putExtra(EXTRA_TOKEN, TOKEN);
                PendingIntent retryPendingIntent = PendingIntent
                        .getBroadcast(context, 0, retryIntent, 0);
                AlarmManager am = (AlarmManager)
                        context.getSystemService(Context.ALARM_SERVICE);
                am.set(AlarmManager.ELAPSED_REALTIME,
                        SystemClock.elapsedRealtime() + nextAttempt,
                        retryPendingIntent);
                // Next retry should wait longer.
                if (backoffTimeMs < MAX_BACKOFF_MS) {
                  GCMRegistrar.setBackoff(context, backoffTimeMs * 2);
                }
            } else {
                Log.d(TAG, "Not retrying failed operation");
            }
        } else {
            // Unrecoverable error, notify app
            onError(context, error);
        }
    }

3
请稍后再尝试。在不同设备上我已经遇到了三天的这个错误。 - MainstreamDeveloper00
2
我没有遇到过这个问题超过一天。如果你已经遇到了这个问题三天,那可能是其他问题。你之前有在同样的代码上成功过吗? - Pankaj Kumar
我在我的应用程序中使用GCM已经有一段时间了,一切都运行得非常完美。我在我的问题中写到了这个。 - MainstreamDeveloper00
1
嘿,你在GCM项目页面上更改了包名吗? - Pankaj Kumar
1
也许有人可以分享一下他处理这个错误的代码,但是使用新的GCM API?因为这里的代码已经过时了。谢谢。 - MichalK

23

SERVICE_NOT_AVAILABLE 是 Google Cloud Messaging 中最令人沮丧的问题之一。它是由 GoogleCloudMessaging.register(SENDER_ID) 抛出的异常,这个函数调用用于注册设备以接收推送通知并返回注册 ID。

  1. SERVICE_NOT_AVAILABLE可能意味着用户设备无法读取注册请求的响应,或者从服务器返回了500/503错误代码。开发人员无法修复此错误,因为这是在Google端,所以我们可以建议用户在几个小时后再试一次。
  2. 即使注册成功,某些设备上也可能会出现SERVICE_NOT_AVAILABLE。这可以通过实现解决方案广播接收器来捕获调用失败时的令牌来解决。我实施了这个解决方法,可能已经为一些用户解决了问题,但我仍然收到许多其他SERVICE_NOT_AVAILABLE的投诉。
  3. 如果设备上的Google Play服务库过时或丢失,则可能会出现SERVICE_NOT_AVAILABLE。在这种情况下,应用程序理论上可以通过打开相应的Google Play应用程序列表来通知用户更新Google Play服务。但是,应用程序不知道这就是SERVICE_NOT_AVAILABLE被抛出的原因,因此无法盲目地将用户重定向到Google Play上的Google Play服务应用程序页面。
  4. 如果设备的时钟与网络未同步,则可能会出现SERVICE_NOT_AVAILABLE。同样,开发人员无法知道这是确切的问题,因此我们可以盲目地建议用户检查其系统时钟同步,希望他们是极少数时钟未同步的用户。
  5. 如果已经root的用户已从设备中删除了Hangouts/GTalk应用程序(因为他们认为它是多余的软件),则可能会出现SERVICE_NOT_AVAILABLE。 GCM由Hangouts/GTalk实施和处理,因此无法在没有它的情况下使用GCM。
  6. 如果用户正在运行未安装Google API的设备(例如Amazon Kindle),则可能会出现SERVICE_NOT_AVAILABLE。这里无事可做,这些用户将永远无法从您的应用程序接收推送通知。

阅读更多: http://eladnava.com/google-cloud-messaging-extremely-unreliable/

这些问题足以让我开始寻找GCM的替代方案。我的应用每隔一两天就会收到一个1星评价,评论中包含了SERVICE_NOT_AVAILABLE抛出时显示的错误信息。由于大部分用户接收到此错误是由于无法控制的原因,所以我无法帮助这些用户。

Google Cloud Messaging的替代方案

Pushy (https://pushy.me/) 是一个独立的推送通知网关,完全独立于GCM。它维护自己的后台套接字连接,就像GCM一样,用于接收推送通知。底层协议是MQTT,这是一种非常轻量级的发布/订阅协议,使用极少的网络带宽和电池。

Pushy的巨大优势在于,发送推送通知的代码(来自服务器)和注册设备以接收推送通知的代码实际上可以在GCM和Pushy之间互换。这使得在实现GCM并因其不稳定而放弃后切换到Pushy变得非常容易。

(完全透明披露:我为自己的项目创建了Pushy,并意识到许多应用程序将从这样的服务中受益)

Pushy.me能否告诉我设备何时与您的服务器建立了活动连接以及何时断开连接。我只想向已连接的设备发送推送。 - akshay202
当然可以!我们的设备信息 API 提供了设备的 last_active 设备在线状态:https://pushy.me/docs/api/device - Elad Nava

18

请确保在清单文件的权限部分更改了包名:

<permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE"
    android:protectionLevel="signature" />
<uses-permission android:name="YOUR_PACKAGE_NAME.permission.C2D_MESSAGE" />

我由于那个部分中包名称不正确而遇到类似的错误。


如果在清单文件中有package属性,那么使用<permission android:name=".C2D_MESSAGE" android:protectionLevel="signature" /> <uses-permission android:name=".C2D_MESSAGE" />不就足够了吗? - Bootstrapper
@user2104070 不,权限必须包含包名。 - Eran
那么这是否意味着,如果我有不同的构建(在Android Studio中),我需要为每个构建维护单独的AndroidManifest版本(因为包名称是不同的)? - Bootstrapper
@user2104070 每个版本都有不同的包名,这不是已经要求在AndroidManifest.xml中指定不同的包名了吗?我对Android Studio不太熟悉。如果包名在构建过程中自动插入到清单中,也许可以通过某种方式在构建过程中插入特定于包名的C2D_MESSAGE权限。 - Eran
如果您正在使用Gradle,可以使用"${applicationId}.permission.C2D_MESSAGE"来支持按构建类型或风味变量包名。 - Stephen Kidson

13

对于我来说 - 设备时间不正确。我将设备设置更改为使用“自动日期和时间”,再次尝试,一切正常。

干杯


2
与那些踩你的人无关,这也是我遇到的问题。 - Janis Peisenieks

3
在我的情况下,解决方案是在清单文件中添加一个新的intent-filter动作REGISTRATION,参考https://snowdog.co/blog/dealing-with-service_not_available-google-cloud-messaging/
    <receiver
        android:name=".RemoteNotificationReceiver"
        android:permission="com.getset.getset.c2dm.permission.SEND" >
        <intent-filter>
            <action android:name="com.getset.getset.c2dm.intent.RECEIVE" />
            <action android:name="com.getset.getset.c2dm.intent.REGISTRATION" />
            <category android:name="com.getset.getset" />
        </intent-filter>
    </receiver>

我必须承认,这个能够成功工作让我感到惊讶,因为在教程中没有提及它。但是如果把它移除,一个成功的注册ID就会变成异常。

注意:使用 Nexus 5 API 21 (Lollipop) 模拟器。


1
它已列在新文档中:https://developers.google.com/cloud-messaging/android/client - Almo

3

我曾经遇到同样的问题,但是以上解决方案都不能解决我的问题。幸运的是,我最近解决了这个问题,并且我想解释一下如何解决它,希望能帮助其他人:

在我的情况下,我在自定义应用程序类中注册推送服务(在任何活动之前执行),我认为这是导致某些事情未被正确初始化的原因。将其更改为主要活动即可解决问题。

public class MyCustomApp extends Application {

    @Override
    public void onCreate() {
         super.onCreate();
         PushService.register(this); //BAD IDEA, don't register pushes in Application Class
    }

}

3

我遇到了连接问题。更换网络连接解决了我的问题。


1
这也解决了我的问题。我认为这是一个好的提示,当你看到SERVICE_NOT_AVAILABLE错误时,应该确保你的网络正常工作。 - superarts.org

3

对于我来说,我通过在我的Galaxy S4的数据使用选项中勾选“限制后台数据”来关闭了谷歌服务的“后台数据访问”。当我打开它时,在移动网络上的问题得到了解决。在 WiFi 上它是正常工作的。


1
对我来说,问题在于我没有连接到本地WIFI。我知道这听起来很愚蠢,但我的意思是网络可能是问题所在。 - superarts.org

3

我曾遇到类似的问题。在谷歌Nexus(Android 4.4.2)上可以正常工作,但在三星Galaxy S3(Android 4.1.2)上却无法使用。

在Samsung手机上,我一直收到“ SERVICE_NOT_AVAILABLE”注册错误信息。后来发现,三星手机的时间设置不正确,没有自动更新网络时间。一旦解决了这个问题,Google云消息传递(GCM)就像魔术般地正常工作了。感谢-Umesh!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接