(android-12)app进入后台,关掉杀死serivce、停止摄像头、停止录音

在android-12,当app切到后台,(大概)一分钟后,app正运行的service会被杀死;如果正播放摄像头,摄像头会被停掉;如果正在录音,录音会被停掉。当然,一分钟后,可能还有其它模块也被关了。本文只说通过修改aosp源码如何关掉这三种限制,其它模块可以类推。

可以确定,引发停止摄像头和停止录音是同一个原因,但不清楚和杀死service是不是同一个来源。至于是不是准时的一分钟,杀死service来自ActivityManagerConstants.DEFAULT_BACKGROUND_SETTLE_TIME,值是“60*1000”,即1分钟。停止摄像头、录音则不确定,实测下来大概一分钟。

 

一、关掉杀死service

app进入后台,一分钟后杀死service,logcat会输出类似以下日志。

Stopping service due to app idle: u0a77 -2m30s208ms com.kos.launcher/org.libsdl.app.ForOnDestroyService

探讨8.0版本下后台service存活机制及保活”,这篇文章详细叙述了一分钟出处,以及android如何判断何样service会被杀死。虽然是andorid-8.0,到12时,一些逻辑变了,一些函数名变了,像getAppStartModeLocked变成getAppStartModeLOSP,但大致逻辑没变。

app被切到后台,经过一分钟后,ActiveServices会调用该app的stopInBackgroundLocked,参数uid是该app的uid。

<aosp>/frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
------
public final class ActiveServices {
    void stopInBackgroundLocked(int uid) {
        // Stop all services associated with this uid due to it going to the background
        // stopped state.
        ServiceMap services = mServiceMap.get(UserHandle.getUserId(uid));
        ArrayList<ServiceRecord> stopping = null;
        if (services != null) {
            for (int i = services.mServicesByInstanceName.size() - 1; i >= 0; i--) {
                ServiceRecord service = services.mServicesByInstanceName.valueAt(i);
                if (service.appInfo.uid == uid && service.startRequested) {
                    if (mAm.getAppStartModeLOSP(service.appInfo.uid, service.packageName,
                            service.appInfo.targetSdkVersion, -1, false, false, false)
                            != ActivityManager.APP_START_MODE_NORMAL) {
                        if (stopping == null) {
                            stopping = new ArrayList<>();
                        }
                        ......此个service满足停止条件,加入stopping列表
                        stopping.add(service);
                    }
                }
            }
            if (stopping != null) {
                final int size = stopping.size();
                // 停止stopping列表中那些个service
                for (int i = size - 1; i >= 0; i--) {
                    ServiceRecord service = stopping.get(i);
                    service.delayed = false;
                    services.ensureNotStartingBackgroundLocked(service);
                    stopServiceLocked(service, true);
                }
                if (size > 0) {
                    mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE);
                }
            }
        }
    }
}

stopInBackgroundLocked首先遍历service,经过一些条件判断,将满足条件的service放入stopping列表中,然后遍历这个列表停止service。在判断的条件中,一个是mAm.getAppStartModeLOSP方法返回值不能是ActivityManager.APP_START_MODE_NORMAL。于是要阻止放入stopping,自然想到了让getAppStartModeLOSP返回APP_START_MODE_NORMAL。

<aosp>/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
------
    @GuardedBy(anyOf = {"this", "mProcLock"})
    int getAppStartModeLOSP(int uid, String packageName, int packageTargetSdk,
            int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby) {
        if (mInternal.isPendingTopUid(uid)) {
            return ActivityManager.APP_START_MODE_NORMAL;
        }
        // 增加下面这个if判断,希望关闭限制的apk直接返回APP_START_MODE_NORMAL。
        if (packageName != null && packageName.equals("com.kos.launcher")) {             
             Slog.d(TAG, "{leagor}checkAllowBackground: uid=" + uid + " pkg=" + packageName + " it is in whitelist");
             return ActivityManager.APP_START_MODE_NORMAL;
        }
        .....
    }

上面例代码中,com.kos.launcher是需要关闭限制的apk,原理很简单,增加个if判断,希望关闭限制的apk直接返回APP_START_MODE_NORMAL。

 

二、关掉停止摄像头

app进入后台,大概一分钟停止摄像头,aosp方面没有明显的logcat输出,如果app基于webrtc,会输出类似以下日志。

E/org.webrtc.Logging: Camera2Session: Error: Camera device could not be opened due to a device policy.

在webrtc源码搜“Camera device could not be opened due to a device policy”,它对应的是错误码CameraDevice.StateCallback.ERROR_CAMERA_DISABLED

<webrtc>/Camera2Session.java
------
  private class CameraStateCallback extends CameraDevice.StateCallback {
    private String getErrorDescription(int errorCode) {
      switch (errorCode) {
        ......
        case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED:
          return "Camera device could not be opened due to a device policy.";
        ......
      }
    }

Android 9.0 对后台程序使用Mic、Camera做了限制?”,这篇文章有在说这个问题。按那写的,除摄像头外,卖克风也有类似限制。

不同于杀死service是java代码,停止摄像头是C++。

app切到后台,大致一分钟后,会调用binder本地对象的BnUidObserver::onTransact,code参数值是ON_UID_IDLE_TRANSACTION。

<aosp>/frameworks/native/libs/binder/IUidObserver.cpp
------
status_t BnUidObserver::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch(code) {
        case ON_UID_GONE_TRANSACTION: {
            ......
            return NO_ERROR;
        } break;

        case ON_UID_ACTIVE_TRANSACTION: {
            // app切回前台,会进入这里
            CHECK_INTERFACE(IUidObserver, data, reply);
            uid_t uid = data.readInt32();
            onUidActive(uid);
            return NO_ERROR;
        } break;

        case ON_UID_IDLE_TRANSACTION: {
            // app切到后台,大概一分钟后,会进入这里
            CHECK_INTERFACE(IUidObserver, data, reply);
            uid_t uid = data.readInt32();
            bool disabled = data.readInt32() == 1;
            onUidIdle(uid, disabled);
            return NO_ERROR;
        } break;
        case ON_UID_STATE_CHANGED_TRANSACTION: {
            ......
        } break;
        default:
            return BBinder::onTransact(code, data, reply, flags);
    }
}

收到ON_UID_IDLE_TRANSACTION后,那些个从BnUidObserver派生的binder服务,会调用onUidIdle方法,当中一个就是UidPolicy

class CameraService :
    public BinderService<CameraService>,
    public virtual ::android::hardware::BnCameraService,
    public virtual IBinder::DeathRecipient,
    public virtual CameraProviderManager::StatusListener
{
    class UidPolicy : public BnUidObserver, public virtual IBinder::DeathRecipient {
    public:
        ......

        void onUidActive(uid_t uid);
        void onUidIdle(uid_t uid, bool disabled);

        ......
    };
};

<aosp>/frameworks/av/services/camera/libcameraservice/CameraService.cpp
------
void CameraService::UidPolicy::onUidActive(uid_t uid) {
    Mutex::Autolock _l(mUidLock);
    mActiveUids.insert(uid);
}

void CameraService::UidPolicy::onUidIdle(uid_t uid, bool /* disabled */) {
    // 官方代码没有下面5行, 是我加的,目的是关掉停止摄像头。
    // 一开始理解时忽略这5行。
    bool enable_background = true;
    if (enable_background) {
        ALOGI("{leagor}[UidPolicy::onUidIdle]uid: %i, enable background, do nothing", (int)uid);
        return;
    }
    
    bool deleted = false;
    {
        Mutex::Autolock _l(mUidLock);
        if (mActiveUids.erase(uid) > 0) {
            // 要是正常的话,mActiveUids会包含uid,会进这个入口
            deleted = true;
        }
    }
    if (deleted) {
        sp<CameraService> service = mService.promote();
        if (service != nullptr) {
            service->blockClientsForUid(uid);
        }
    }
}

要是uid对应的app正在使用摄像头,那么mActiveUids会包含这个uid。onUidIdle调用service->blockClientsForUid(uid),后者会调用basicClient->block。

<aosp>/frameworks/av/services/camera/libcameraservice/CameraService.cpp
------
void CameraService::BasicClient::block() {
    ATRACE_CALL();

    // Reset the client PID to allow server-initiated disconnect,
    // and to prevent further calls by client.
    mClientPid = CameraThreadState::getCallingPid();
    CaptureResultExtras resultExtras; // a dummy result (invalid)
    notifyError(hardware::camera2::ICameraDeviceCallbacks::ERROR_CAMERA_DISABLED, resultExtras);
    disconnect();
}

在block(),通过notifyError报告错误:ERROR_CAMERA_DISABLED(3),也就是上面webrtc报出的错误码,然后断开。

要关掉停止摄像头,修改方法就是上面已写的,在UidPolicy::onUidIdle,什么都不做,直接返回。我的理解,mActiveUids存储着正在播放摄像头的uid,既然不停止播放,也就不再“mActiveUids.erase(uid)”删除了。顺便说下ON_UID_ACTIVE_TRANSACTION和onUidActive。app切换回前台后,那些个BnUidObserver会收到code值ON_UID_ACTIVE_TRANSACTION,然后调用onUidActive方法。UidPolicy::onUidActive会执行“mActiveUids.insert(uid)”,经过上面修改后,uid没被从mActiveUids删除,这里的insert会返回false。

在CameraService,除onUidIdle外,还有其它地方在调用basicClient->block(),不清楚它们使用场景。

 

三、关掉停止录音

<aosp>/frameworks/av/services/audiopolicy/service/AudioPolicyService.cpp
------
void AudioPolicyService::UidPolicy::onUidIdle(uid_t uid, __unused bool disabled) {
    // 官方代码没有下面5行, 是我加的,目的是关掉停止录音。
    bool enable_background = true;
    if (enable_background) {
        // The output is a bit much, remark it
        ALOGI("{leagor}[AudioPolicyService::UidPolicy::onUidIdle]uid: %i, enable background, do nothing", (int)uid);
        return;
    }
    updateUid(&mCachedUids, uid, false, ActivityManager::PROCESS_STATE_UNKNOWN, true);
}

Android P系统禁止闲置APP在后台使用麦克风的问题”有描述AudioPolicyService大致逻逻。

全部评论: 0

    写评论: