在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大致逻逻。