從源碼角度看Android_O_AMS新特性

簡介

Android O上之後對AMS的廣播與服務作了嚴格的限制,靜態註冊的廣播再也不可以無所欲爲的接收隱式的廣播,後臺的應用進程也再也不可以調用startService了。Android 做出這樣的限制主要是爲了限制住不自覺的開發者,不讓他們隨意的浪費系統資源。java

這篇文章裏,我將從源碼的角度分析Android O是如何限制靜態廣播與後臺服務的,相關的廣播和服務源碼背景知識你們能夠看我之前的文章。分析的後面會列出Android O上廣播和服務的適配指南。谷歌推薦使用的JobScheduler原理你們能夠看個人上一篇文章。android

文章的最後是個人一些對這些限制的自問自答,也歡迎你們提出問題~shell

BroadcastReceiver

沒有權限的前提下, 發出的隱式Intent,靜態的接收者不能收到廣播

查看大圖緩存

[frameworks/base/services/core/java/com/android/server/am/BroadcastQueue#processNextBroadcast]app

if (!skip) {
	final int allowed = mService.getAppStartModeLocked(
		info.activityInfo.applicationInfo.uid, info.activityInfo.packageName,
		info.activityInfo.applicationInfo.targetSdkVersion, -1, true, false);
	if (allowed != ActivityManager.APP_START_MODE_NORMAL) {
	    if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
		    || (r.intent.getComponent() == null
		        && r.intent.getPackage() == null
		        && ((r.intent.getFlags()
		                & Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND) == 0)
		        && !isSignaturePerm(r.requiredPermissions))) {
		mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
		        component.getPackageName());
		Slog.w(TAG, "Background execution not allowed: receiving "
		        + r.intent + " to "
		        + component.flattenToShortString());
		skip = true;
	    }
	}
}

 

[frameworks/base/services/core/java/com/android/server/am/ActivityManagerService#getAppStartModeLocked]ide

int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
    int callingPid, boolean alwaysRestrict, boolean disabledOnly) {
	UidRecord uidRec = mActiveUids.get(uid);
	if (uidRec == null || alwaysRestrict || uidRec.idle) {
		final int startMode = (alwaysRestrict)
		        ? appRestrictedInBackgroundLocked(uid, packageName, packageTargetSdk)
		        : appServicesRestrictedInBackgroundLocked(uid, packageName,
		                packageTargetSdk);
		return startMode;
	}
	return ActivityManager.APP_START_MODE_NORMAL;
}

 

[frameworks/base/services/core/java/com/android/server/am/ActivityManagerService#appRestrictedInBackgroundLocked]ui

int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
	// 若是targetSdkVerision設置的爲26+的話,即便是在前臺的進程,也不可以收到廣播
	// 2018年下半年Google將強制開發者將targetSdkVersion設置爲27以上
	if (packageTargetSdk >= Build.VERSION_CODES.O) {
	    return ActivityManager.APP_START_MODE_DELAYED_RIGID;
	}
}

 

隱式發送的靜態廣播不能接收解決方案

方案 建議
發送端直接將intent加入FLAG_RECEIVER_INCLUDE_BACKGROUND的flag 頻繁發送的重要廣播不推薦
將隱式廣播轉換爲顯式,調用setPackage、setComponent其中一個便可 肯定接收端組件或包名時可用,低擴展性
使用動態註冊廣播的方式替代靜態註冊 推薦
廣播發送端與接收端使用signature級別的權限 推薦
使用JobScheduler代替廣播接收事件 部分場景下,推薦

發送端實例代碼

<permission
	android:name="top.navyblue.bugsfree.permission"
	android:protectionLevel="signature"/>
<uses-permission android:name="top.navyblue.bugsfree.permission" />
Intent intent = new Intent(Utils.IMPLICT_INTENT_ACTION);
//                intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
//                intent.setPackage("com.example.yongbiaoai.bugsfreeclient");
//                intent.setComponent(new ComponentName("com.example.yongbiaoai.bugsfreeclient", "com.example.yongbiaoai.bugsfreeclient.ImplictReceiver"));
//                intent.addFlags(0x01000000);
sendBroadcast(intent, "top.navyblue.bugsfree.permission");

 

若是須要自定義權限則須要聲明一個客戶端本身的權限,而後在調用sendBroadcast時加入這個權限this

接收端實例代碼

<uses-permission android:name="top.navyblue.bugsfree.permission" />
<receiver
    android:name=".ImplictReceiver"
    android:permission="top.navyblue.bugsfree.permission">
    <intent-filter>
        <action android:name="top.navyblue.top.implict.broadcast"/>
    </intent-filter>
</receiver>

 

定義靜態接收者時,須要填入permission選項spa

Service

idle狀態下,應用啓動服務將拋異常調試

查看大圖

active與idle的變換規則

  • 若是應用變爲前臺,即procState小於PROCESS_STATE_TRANSIENT_BACKGROUND(8)時,UID狀態立刻變動爲active狀態
  • 若是應用變爲後臺,即procState大於等於PROCESS_STATE_TRANSIENT_BACKGROUND(8)時,應用持續在後臺60s後,UID狀態會變動爲idle狀態

調試方法:

  • 輸入 「adb logcat -b events | grep am_uid」 來查看uid狀態變化的LOG
  • 輸入 「adb shell am make-uid-idle 「 使指定應用的uid變爲idle狀態

idle白名單,updateWhitelistAppIdsLocked場景

場景 意義
DeviceIdleController.addPowerSaveWhitelistApp 使用系統Service 「deviceidle」,動態添加白名單
DeviceIdleController.removePowerSaveWhitelistApp 使用系統Service 「deviceidle」,動態刪除白名單
DeviceIdleController.onStart 從文件中讀取包名,初始化idle白名單

查看大圖

[frameworks/base/services/java/com/android/server/SystemServer]

public static void main(String[] args) {
	new SystemServer().run();
}
private void run() {
	try {;
	    startOtherServices();
	} catch (Throwable ex) {
	    throw ex;
	} finally {
	    traceEnd();
	}
}
private void startOtherServices() {
	mSystemServiceManager.startService(DeviceIdleController.class);
}

 

在system_server啓動時,會將DeviceIdleController的系統服務啓動

[frameworks/base/services/core/java/com/android/server/DeviceIdleController]

public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
@Override
public void onStart() {
	synchronized (this) {
	    readConfigFileLocked();
	    updateWhitelistAppIdsLocked();
	}
	mBinderService = new BinderService();
	publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
}
    
private final class BinderService extends IDeviceIdleController.Stub {
	@Override 
	public void addPowerSaveWhitelistApp(String name) {
		    getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
		    null);
		    long ident = Binder.clearCallingIdentity();
	    try {
			addPowerSaveWhitelistAppInternal(name);
	    } finally {
			Binder.restoreCallingIdentity(ident);
	    }
	}
	@Override 
	public void removePowerSaveWhitelistApp(String name) {
		    getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
			    null);
		    	long ident = Binder.clearCallingIdentity();
		    try {
				removePowerSaveWhitelistAppInternal(name);
		    } finally {
				Binder.restoreCallingIdentity(ident);
		    }
	}
}

 

在調用onStart方法時會將系統服務在ServiceManager進行publish

startForegroundService的ANR與FC:

  • 調用startForegroundService後,若是5s內沒有在Service中調用startForeground,那麼就會發生ANR; 「Context.startForegroundService() did not then call Service.startForeground()」
  • 調用startForegroundService後,直到將Service中止以前都沒有在Service中調用startForeground,那麼就會發生FC

startForegroundService ANR超時機制

[frameworks/base/services/core/java/com/android/server/am/ActivityManagerService]

@Override
public ComponentName startForegroundService(Intent service) {
	warnIfCallingFromSystemProcess();
	return startServiceCommon(service, true, mUser);
}
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
    UserHandle user) {
	try {
	    validateServiceIntent(service);
	    service.prepareToLeaveProcess(this);
	    ComponentName cn = ActivityManager.getService().startService(
		mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
		            getContentResolver()), requireForeground,
		            getOpPackageName(), user.getIdentifier());
	    return cn;
	} catch (RemoteException e) {
	    throw e.rethrowFromSystemServer();
	}
}

 

調用startForegroundService方法的區別在於requireForeground的FLAG被置爲true,和startService並無什麼區別

[frameworks/base/services/core/java/com/android/server/am/ActiveServices#sendServiceArgsLocked]

private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
            boolean oomAdjusted) throws TransactionTooLargeException {
            if (r.fgRequired && !r.fgWaiting) {
                if (!r.isForeground) {
                    scheduleServiceForegroundTransitionTimeoutLocked(r);
                } else {
                    r.fgRequired = false;
                }
            }
}
    
void scheduleServiceForegroundTransitionTimeoutLocked(ServiceRecord r) {
        if (r.app.executingServices.size() == 0 || r.app.thread == null) {
            return;
        }
        Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG);
        msg.obj = r;
        r.fgWaiting = true;
        mAm.mHandler.sendMessageDelayed(msg, SERVICE_START_FOREGROUND_TIMEOUT);
}
    
void serviceForegroundTimeout(ServiceRecord r) {
        ProcessRecord app;
        synchronized (mAm) {
            app = r.app;
            r.fgWaiting = false;
            stopServiceLocked(r);
        }
        if (app != null) {
            mAm.mAppErrors.appNotResponding(app, null, null, false,
                    "Context.startForegroundService() did not then call Service.startForeground()");
        }
}

 

調用Service.onStartCommand時會設置一個定時器,若是5s內沒有設置調用startForeground將服務置爲前臺,那麼就會出現ANR

startForegroundService FC機制

[frameworks/base/core/java/android/app/Service#stopSelf]

public final void stopSelf(int startId) {
        if (mActivityManager == null) {
            return;
        }
        try {
            mActivityManager.stopServiceToken(
                    new ComponentName(this, mClassName), mToken, startId);
        } catch (RemoteException ex) {
        }
}

 

[frameworks/base/services/core/java/com/android/server/am/ActiveServices#stopServiceTokenLocked]

boolean stopServiceTokenLocked(ComponentName className, IBinder token,
            int startId) {
    bringDownServiceIfNeededLocked(r, false, false);
}
private final void bringDownServiceLocked(ServiceRecord r) {
        if (r.fgRequired) {
            r.fgRequired = false;
            r.fgWaiting = false;
            mAm.mHandler.removeMessages(
                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
            if (r.app != null) {
                Message msg = mAm.mHandler.obtainMessage(
                        ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
                msg.obj = r.app;
                mAm.mHandler.sendMessage(msg);
            }
        }
}
void serviceForegroundCrash(ProcessRecord app) {
        mAm.crashApplication(app.uid, app.pid, app.info.packageName, app.userId,
                "Context.startForegroundService() did not then call Service.startForeground()");
}

 

在調用startForegroundService拉起服務後,若是直接stop掉Service卻沒有將Service置爲前臺的話會出現FC

ANR與FC解除

[frameworks/base/core/java/android/app/Service#startForeground]

public final void startForeground(int id, Notification notification) {
        try {
            mActivityManager.setServiceForeground(
                    new ComponentName(this, mClassName), mToken, id,
                    notification, 0);
        } catch (RemoteException ex) {
        }
}

 

[frameworks/base/services/core/java/com/android/server/am/ActiveServices#setServiceForegroundInnerLocked]

private void setServiceForegroundInnerLocked(ServiceRecord r, int id,
            Notification notification, int flags) {
        if (r.fgRequired) {
                r.fgRequired = false;
                r.fgWaiting = false;
                mAm.mHandler.removeMessages(
                        ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        }
}

 

當Service被啓動後,客戶端須要調用Service.startForeground解除ANR和FC

常見問題

問:爲何Android O只限制靜態廣播而不限制動態廣播?
答:靜態註冊的廣播在沒有被限制的前提下,甚至能夠拉起應用進程。雖然在原生3.1以後,Intent若是沒有攜帶FLAG_INCLUDE_STOPPED_PACKAGES,是不能發送廣播給被force-stop的應用的靜態廣播,可是若是應用進程只是被殺死而沒有被force-stop,仍是能夠經過註冊隱式廣播來接收到與本身無關的廣播,從而拉起本身的進程或者提高自身進程的優先級,浪費用戶的資源。反觀動態廣播,一不能拉起進程,二生命週期每每很短,相比靜態廣播的權限威力小的。

問:爲何Android O只限制了後臺Service而不限制前臺和bounded Service? 答:一言以蔽之,就是大部分的應用開發者太不自覺了,後臺Service對比前臺Service,實現的成本低了不少,能夠在用戶絕不知情的前提下耗費手機的資源。不少Service被應用在後臺偷偷拉起,這樣就躲避了AMS的一些檢查機制,好比說,不會被看成空進程與緩存進程殺死。經過這類保活手段,雖然能夠延長應用的壽命,可是卻使手機電池壽命與內存佔用惡化。

相關文章
相關標籤/搜索