如果持有wakelock的应用程序或服务被终止,Android操作系统是否会释放该wakelock?

48
我有一个关于 Wakelock 的问题。在下面的情况中,Android 操作系统是否会释放 Wakelock(如果需要指定,则为 PARTIAL_WAKE_LOCK),以防止 Wakelock 被保留并浪费电池,直到关闭电源(而不是休眠)。
情况1-a: 应用程序在其中一个线程中获取了 Wakelock(没有超时选项),并设计了在关键任务完成时释放 Wakelock。应用程序可能会被任务管理器或臭名昭著的任务杀手杀死,并且应用程序无法让其线程释放 Wakelock。那个 Wakelock 会发生什么?
情况1-b: (如果情况1-a的答案是“是,请不要担心”,则请忽略此情况。) 与情况1-a相同,但应用程序向 Wakelock 给出了超时选项,比如3秒钟。这个超时选项还有效吗?
情况2-a: 请想象一下,有一个服务是通过 AlarmManager(通过广播接收器)启动的,并且该服务获取了 Wakelock(没有超时选项)。这个服务被设计为使 Wakelock 获取时间最短。但不幸的是,由于内存不足,Android OS 选择终止这个服务。(我不知道当 Wakelock 被获取时操作系统是否会杀死服务,但我猜操作系统不关心。但我希望操作系统稍后会释放 Wakelock。)那个 Wakelock 会发生什么?
情况2-b: (如果情况2-a的答案是“是,请不要担心”,则请忽略此情况。) 与情况2-a相同,但服务向 Wakelock 给出了超时选项,比如3秒钟。这个超时选项还有效吗?
2个回答

49

WakeLock实现概述

当我们使用pm.newWakeLock创建一个新的Wakelock时,PowerManager会简单地创建一个新的WakeLock对象并返回。 WakeLock对象不是binder对象,因此它不能通过多个进程使用。但是,在该WakeLock对象中,它包含一个名为mToken的Binder对象。

    WakeLock(int flags, String tag) {
        mFlags = flags;
        mTag = tag;
        mToken = new Binder();
    }

因此,当您在此 WakeLock 对象上调用 acquire 或 release 时,它实际上会将该令牌传递给 PowerManagerService

    private void acquireLocked() {
        if (!mRefCounted || mCount++ == 0) {
            mHandler.removeCallbacks(mReleaser);
            try {
                mService.acquireWakeLock(mToken, mFlags, mTag, mWorkSource);
            } catch (RemoteException e) {
            }
            mHeld = true;
        }
    }

查看 PowerManagerService 在获取或释放 wakelock 时的工作方式,将有助于回答您的问题。

void acquireWakeLockInternal(IBinder lock, int flags, String tag, WorkSource ws,
        int uid, int pid) {
    synchronized (mLock) {
        ...
        WakeLock wakeLock;
        int index = findWakeLockIndexLocked(lock);
        if (index >= 0) {
            ...
            // Update existing wake lock.  This shouldn't happen but is harmless.
            ...
        } else {
            wakeLock = new WakeLock(lock, flags, tag, ws, uid, pid);
            try {
                lock.linkToDeath(wakeLock, 0);
            } catch (RemoteException ex) {
                throw new IllegalArgumentException("Wake lock is already dead.");
            }
            notifyWakeLockAcquiredLocked(wakeLock);
            mWakeLocks.add(wakeLock);
        }
        ...
    }
    ...
}

关键语句是 lock.linkToDeath(wakeLock, 0);。这里的 lock 正是我们之前提到的 mToken。该方法会为接收方(即 wakeLock)注册一个通知,以便在此 binder 对象消失时得到通知。如果该 binder 对象意外消失(通常是因为其所在进程被杀死),则接收方将调用 binderDied 方法。
注意,PowerManagerService 中的 WakeLock 与 PowerManager 中的 WakeLock 不同,它是 IBinder.DeathRecipient 接口的实现。因此,请查看其 binderDied 方法。
    @Override
    public void binderDied() {
        PowerManagerService.this.handleWakeLockDeath(this);
    }
handleWakeLockDeath会释放该wakelock。
private void handleWakeLockDeath(WakeLock wakeLock) {
    synchronized (mLock) {
        ...
        int index = mWakeLocks.indexOf(wakeLock);
        if (index < 0) {
            return;
        }

        mWakeLocks.remove(index);
        notifyWakeLockReleasedLocked(wakeLock);

        applyWakeLockFlagsOnReleaseLocked(wakeLock);
        mDirty |= DIRTY_WAKE_LOCKS;
        updatePowerStateLocked();
    }
}

所以我认为在你的问题两种情况下,答案都是不用担心。至少在Android 4.2(代码来源)中是这样的。此外,在PowerManager类中存在finalize方法,但这不是你问题的关键。


4
感谢您对这个两年前的问题进行了详细、清晰的回答。您的答案确实帮助了许多开发人员,包括我在内。 - Tomcat
1
非常愉快,我不知道为什么这个老问题会跳到按有趣程度排序的问题列表中的第一位。@Tomcat - StarPinkER

6
我猜测(不确定)Android系统不会为已结束进程保留唤醒锁。很可能在使用sigkill杀死进程时,系统也会删除该进程持有的任何唤醒锁。
否则,如你所说,崩溃将导致手机始终处于唤醒状态,但我并没有观察到这种情况发生。

我认为这听起来很合理。我猜你是对的。我希望SDK能清楚地描述这种行为。 - Tomcat
我也发现了wakelock的超时存在漏洞[链接]http://code.google.com/p/android/issues/detail?id=14184,因此我们无法以有效的方式使用它。(我在OS2.2上尝试过,但失败了,然后谷歌引导我到了那个链接。) - Tomcat
1
一个简单的测试是创建一个应用程序,设置一个保持屏幕唤醒锁定,然后在应用程序中放置一个按钮,故意引起FC。然后只需等待并观察屏幕是否关闭。 - Joseph Earl

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