Java.lang.RuntimeException: WakeLock under-locked C2DM_LIB 运行时异常:WakeLock 未正确锁定 C2DM_LIB。

67
我已经在Google Play上上传了我的应用程序,但是用户报告了以下异常:java.lang.RuntimeException: WakeLock under-locked C2DM_LIB。当我尝试释放WakeLock时会出现此异常。有人可以告诉我可能的问题是什么吗?
4个回答

197
您没有发布您的代码,所以我不知道您是否已经按照我在这里提出的建议进行操作, 但是我也遇到了同样的异常,我所添加的只是一个简单的“if”来确保WakeLock实际上被持有,然后再尝试释放它
在我的onPause中,我只添加了这个“if”语句(在“release()”之前):
if (mWakeLock.isHeld())
    mWakeLock.release();

然后异常就消失了。


10
这个解决方案在我看来比被接受的那个更加简洁。 - ottel142
1
那是因为这是正确的做法。这应该是被接受的答案。 - CompEng88
2
我的代码中没有 .release()(也没有任何 mWakeLock),但我仍然遇到了这个错误。我看到的唯一堆栈跟踪是: java.lang.RuntimeException: WakeLock under-locked GCM_LIB at [...]com.google.android.gcm.GCMBaseIntentService.onHandleIntent(GCMBaseIntentService.java:252) at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65) - Ted
4
我同意这应该是被接受的答案,但别忘了将上述代码放在一个同步语句块中。我曾遇到过一些罕见情况,在调用 isHeld 和 release 之间,唤醒锁会被另一个线程释放。 - Emanuel Moecklin
1
你有同步代码的例子吗?我认为最安全的方法是使用所有三种方法:Catch Throwable、isHeld和Synchronized.. 哈哈如果在运行时引发异常,那么代价很大.. 通过首先检查isHeld来节省电池电量,这更有效率,可以节省几微秒,哈哈。 - hamish
显示剩余3条评论

65
我在新的GCM库中追踪到了相同的异常。实际上,旧的C2DM Android库也有同样的错误和崩溃,而且Google至今没有修复它。根据我们的统计数据,约有0.1%的用户经历了这种崩溃。
我的调查表明,问题出在GCM库中释放网络WakeLock不正确,在库尝试释放保持空闲的WakeLock时(内部锁计数器变为负数)。
我对简单的解决方案感到满意——只需捕获此异常并什么也不做,因为我们不需要做任何额外的工作来保持wakelock处于未拥有状态。
为了做到这一点,您需要将GCM库源代码导入到您的项目中,而不是已编译的.jar文件。您可以在“$Android_SDK_Home$/extras/google/gcm/gcm-client/src”文件夹下找到GCM库源代码(您需要先使用Android SDK Manager下载它)。
接下来,打开GCMBaseIntentService类,找到行
sWakeLock.release();

并将其用try-catch包围。

代码应该像这样:

    synchronized (LOCK) {
        // sanity check for null as this is a public method
        if (sWakeLock != null) {
            Log.v(TAG, "Releasing wakelock");
            try {
                sWakeLock.release();
            } catch (Throwable th) {
                // ignoring this exception, probably wakeLock was already released
            }
        } else {
            // should never happen during normal workflow
            Log.e(TAG, "Wakelock reference is null");
        }
    }

更新: 同时,如@fasti在他的回答中建议的那样,您可以使用mWakeLock.isHeld()方法来检查wakelock是否实际持有此锁。


1
是的,我已经在我们所有的项目中实现了这个解决方案,它完美地运作(用户基数超过200万) - HitOdessit
3
我已经在Google的代码库的分支中进行了更改,并将其放到了Github上:https://github.com/ajlyon/gcm - Avram Lyon
酷,我能否只用这个(https://github.com/ajlyon/gcm/blob/master/gcm-client/dist/gcm-src.jar)替换GCM库jar文件并修复该错误?它是否更新为最新的GCM库版本? - Mario Lenci
4
这种解决方案太粗暴了,只是把问题藏起来罢了。下面fasti提出的那个方案才是正确应对该问题的方式。 - twig
是的,Catch Throwable,这是高质量Java的标志。 - HaMMeReD
显示剩余2条评论

5
虽然isHeld()方案看起来更好,但实际上可能会失败——因为它不是原子的(即不是线程安全的)。如果您有多个线程可能释放锁定,则在检查(isHeld)和调用relase之间,另一个线程可能会释放锁定......然后您会失败。
通过使用try/catch,您可以以线程安全的方式掩盖此错误。

1
有没有一种好的选择以可重用的方式使WakeLock释放原子化?它应该是一个原子操作。它的名字中实际上有“锁”。 - colintheshots
你确定吗?看一下PowerManager.java源代码,这些函数好像是同步的。 - Nick

1
只要我不重新初始化唤醒锁并在新对象上调用acquire,就不会出现这个问题。您应该只保留一个唤醒锁实例(将其设置为字段变量)。然后您就知道始终释放该唤醒锁。
 if (mWakeLock == null) {
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP
                | PowerManager.ON_AFTER_RELEASE, "MyWakeLock");
    }

try{
        mWakeLock.release();//always release before acquiring for safety just in case
    }
    catch(Exception e){
        //probably already released
        Log.e(TAG, e.getMessage());
    }
    mWakeLock.acquire();

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