GcmBroadcastReceiver / GcmIntentService 崩溃

3

我正在使用 GCM 以获取图片发布的通知,然后下载并处理它:

public class GcmBroadcastReceiver extends WakefulBroadcastReceiver
{
    @Override
    public void onReceive(Context context, Intent intent)
    {
        DataUtils.log("In GcmBroadcastReceiver! threadname is " + Thread.currentThread().getName());

        // Explicitly specify that GcmIntentService will handle the intent.
        ComponentName comp = new ComponentName(context.getPackageName(), GcmIntentService.class.getName());
        // Start the service, keeping the device awake while it is launching.
        startWakefulService(context, (intent.setComponent(comp)));
        setResultCode(Activity.RESULT_OK);
    }
}

这是我GcmIntentService的开头:
public class GcmIntentService extends IntentService
{
    public static final int NOTIFICATION_ID = 1;

    public GcmIntentService()
    {
        super("GcmIntentService");
    }


    @Override
    protected void onHandleIntent(Intent intent)
    {

        DataUtils.log("In GcmIntentService onHandleIntent(), threadname is " + Thread.currentThread().getName());

        Bundle extras = intent.getExtras();
        GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
        // The getMessageType() intent parameter must be the intent you received in your BroadcastReceiver.
        String messageType = gcm.getMessageType(intent);

        if (!extras.isEmpty()) // has effect of unparcelling Bundle
        {
            /*
             * Filter messages based on message type. Since it is likely that GCM will be
             * extended in the future with new message types, just ignore any message types you're
             * not interested in, or that you don't recognize.
             */
            if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR.equals(messageType))
            {
                DataUtils.log("In GcmIntentService - Send error: " + extras.toString());
            } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED.equals(messageType))
            {
                DataUtils.log("In GcmIntentService - Deleted messages on server: " + extras.toString());
            // If it's a regular GCM message, do some work.
            } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType))
            {

                String notificationType = extras.getString(MyAppApi.GCM_MSG_TYPE_KEY);

                if(DataUtils.isEmpty(notificationType)) {

                    DataUtils.log("In GcmIntentService - notificationType is empty!");

                } else if(notificationType.equalsIgnoreCase(MyAppApi.GCM_IS_NEW_WALLPAPER)) {

                    //We're about to receive a new image!
                    DataUtils.log("In GcmIntentService - Receiving a new image!");
                    processNewWallpaper();

                } else if(notificationType.equalsIgnoreCase(MyAppApi.GCM_IS_FRIEND_NOTIFICATION)) {

                    //We're about to receive a friend notification
                    DataUtils.log("In GcmIntentService - Receiving a friend notification!");
                    processFriendNotification();

                } else {
                    //Unknown
                    DataUtils.log("In GcmIntentService - Receiving unknown message type! " + notificationType);
                }

            } else {

                DataUtils.log("In GcmIntentService - Unknown GCM message: " + extras.toString());
            }
        }

        //Release the wake lock provided by the WakefulBroadcastReceiver.
        GcmBroadcastReceiver.completeWakefulIntent(intent);

    }
}

看起来服务会随机死亡。从日志中可以看到:

01-13 20:00:44.436: I/ActivityManager(375): Process com.grakk.android (pid 23227) has died.
01-13 20:00:44.444: W/ActivityManager(375): Scheduling restart of crashed service com.grakk.android/.GcmIntentService in 11426ms

当代码收到GCM消息时,它会下载一张图片,然后向用户显示通知(这类似于普通聊天应用程序)。
有一个测试人员告诉我他收到了一张图片,但没有收到通知,这意味着服务本身已经启动并完成了部分工作,但没有完成所有工作。
通知代码在processNewWallpaper()中运行,与图像的下载和处理一起。以下是代码:
...

if(senderContact == null) {
    sendNotification(null, message, true);
} else {
    sendNotification(senderContact.getName(), message.trim(), false);
}

...

通知方式:

...

// Put the message into a notification and post it. This is just one simple example
// of what you might choose to do with a GCM message.
@SuppressWarnings("deprecation")
@TargetApi(16)
private void sendNotification(String name, String message, boolean isAnonymous)
{
    Context context = GcmIntentService.this;
    NotificationManager mNotificationManager = (NotificationManager)this.getSystemService(Context.NOTIFICATION_SERVICE);

    PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, ContactsActivity.class), 0);

    Notification.Builder mBuilder = new Notification.Builder(this)
        .setSmallIcon(R.drawable.ic_launcher)
        .setContentTitle(context.getString(R.string.app_name));

    String textToShow = null;
    if(DataUtils.isEmpty(message))
    {
        if(isAnonymous) {
            textToShow = context.getString(R.string.notification_text_anonymous);
        } else {
            textToShow = String.format(getResources().getString(R.string.notification_text_friend), name);
        }
    } else {
        textToShow = message;
    }

    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        mBuilder.setStyle(new Notification.BigTextStyle().bigText(textToShow));
    }

    mBuilder.setContentText(textToShow);
    mBuilder.setAutoCancel(true);

    Uri alarmSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
    mBuilder.setSound(alarmSound);
    mBuilder.setContentIntent(contentIntent);

    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
        mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
    } else {
        mNotificationManager.notify(NOTIFICATION_ID, mBuilder.getNotification());
    }
}

我可以通过向自己发送一张图片,然后反复按Android返回按钮直到我不再在应用程序中来重现这个问题。我可以跟踪显示图像已下载的日志消息,但是在通知显示之前,它就死亡了。
这种情况并非总是发生。有时会显示通知,有时不会。
我不确定可能的原因或如何调试。有什么提示吗?

你尝试过创建一个独立的、新的“Intent”实例来调用服务,而不是修改传递给“GcmBroadcastReceiver”的实例吗? - matiash
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Pararth
在你完成了大量的工作后,你是否通过GcmReceiver.completeWakefulIntent(intent);来释放唤醒锁? - Dhir Pratap
更新的代码!@DhirPratap 是的,我正在释放唤醒锁(请参见更新)。user2450263 它基本上是下载图像并根据屏幕大小调整大小。 - Sandy
3个回答

1
你是否调用了 GcmIntentService 类中的 OnCreate() 方法?
以下是一些示例代码:
public class GcmIntentService extends IntentService {

    String mes;
    private Handler mHandler;

    public GcmIntentService() {
        super("GcmIntentService");
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mHandler = new Handler();
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        Bundle extras = intent.getExtras();

        GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);

        String messageType = gcm.getMessageType(intent);
        mes = extras.getString("title");
        showToast();
        Log.i("GCM", "Recevied: (" + messageType + ") " + extras.getString("title"));

        GcmReceiver.completeWakefulIntent(intent);
    }

    public void showToast() {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getApplicationContext(), mes, Toast.LENGTH_LONG).show();
            }
        });
    }
}

编辑: 在此处添加有用的Youtube教程,涉及GCM这里


我没有初始化mHandler = new Handler();,谢谢。 - saqibahmad
@saqibahmad 谢谢,我刚刚在这里添加了GCM的YouTube教程(https://www.youtube.com/watch?v=Vd8bIkE29U4)。 - bjiang
@saqibahmad 你可以参考我的另一个答案这里。或者直接在SO上搜索,会很有帮助的。 - bjiang
@saqibahmad 请查看此处的官方文档 [here](https://developer.android.com/google/gcm/client.html#sample-receive),并搜索“onHandleIntent”。顺便说一下,我认为在Google I / O之前,谷歌将在Udacity [here](https://www.udacity.com/courses/android)上发布大量的Android课程,包括“GCM”。 - bjiang
@bjiang 谢谢,这正是我在寻找的。我是一名网站开发人员,最近开始从事移动应用程序开发。感谢您的指导,它很有帮助。 - saqibahmad
显示剩余4条评论

0

非常抱歉,我正在使用回答(我还不能评论)。

我建议将sendNotification的调用从processNewWallpaper中提取到processNewWallpaper()后面。如果这样做不起作用,你应该在processNewWallpaper()中发布你的代码。我的猜测是,在某些情况下,你的代码会在processNewWallpaper内部崩溃并跳过sendNotification,但由于它被处理,所以不会抛出任何异常。

另外,我注意到如果应用程序在后台或完全关闭(使用运行应用程序键并在那里关闭应用程序),应用程序的行为会有所不同。如果你能够一致地重现问题,那么解决问题就会更容易。


嗨@kayvan。你说的“since its being handled”是什么意思?代码中有一些在try {} catch结构内的部分,但如果遇到异常,我会将其记录到logcat输出中。但是没有显示任何这些警告。 - Sandy
我所说的“被处理”是指与try和catch相同,因为你的代码的一部分正在工作,而另一部分则没有。尝试将代码稍微移动一下,比如先推送通知,然后再尝试处理图像。 - Kayvan N
每当遇到并捕获异常且日志中没有消息时,我都会记录日志。我可以移动通知,但是有可能出现通知发生但图像处理失败的问题。相比之下,我更愿意保存图像而不是通知。 - Sandy
有道理,但如果移动通知解决了问题,你就会知道到底是什么导致了这个问题。然后你可以修复它并将其移回原位 ;) - Kayvan N

0

你的logcat就只有这些吗?"crashed"服务中是否有任何异常或堆栈跟踪信息?

不过,我有一个想法,你是在异步下载图片并在回调中创建通知吗?

你在onHandleIntent的结尾释放了唤醒锁,这将在执行任何异步代码之前调用。如果屏幕关闭,释放唤醒锁将会杀死服务。

你需要在onHandleIntent中有条件地释放唤醒锁,仅当不需要执行任何异步工作时才释放。在任何异步工作的回调中释放唤醒锁。只要确保没有执行路径不释放唤醒锁即可!

希望这就是问题所在!


嗨@darnmason。是的,这就是logcat上显示的全部内容。没有异常通知或类似的东西。由于onHandleIntent()不在UI线程中运行,我没有创建任何额外的线程来下载或解析图像,因此一切都应该按顺序执行,并在最后释放唤醒锁。 - Sandy
啊,很酷,这个有点奇怪,一定有办法找到导致服务崩溃的异常。你尝试过调试并在异常处中断吗? - darnmason

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