android service外掛化之一
1, 概述
本文將探討Android四大元件之一——Service元件的外掛化方式。
與Activity, BroadcastReceiver相比,Service元件的不同點在哪裡呢?能否用與之相同的方式實現Service的外掛化?
如果不行,它們的差別在哪裡,應該如何實現Service的外掛化?
接下來將圍繞這幾個問題展開,最終給出Service元件的外掛化方式;
閱讀本文之前,可以先clone一份understand-plugin-framework,參考此專案的service-management模組。本編文章的原始碼基於android 6.0.
2, Service工作原理
連Service的工作原理都不瞭解,談何外掛化?知己知彼。
Service分為兩種形式:以startService啟動的服務和用bindService繫結的服務;
由於這兩個過程大體相似,這裡以稍複雜的bindService為例分析Service元件的工作原理。
繫結Service的過程是通過Context類的bindService完成的,這個方法需要三個引數:
第一個引數代表想要繫結的Service的Intent,第二個引數是一個ServiceConnetion,
可以通過這個物件接收到Service繫結成功或者失敗的回撥;第三個引數則是繫結時候的一些FLAG;
Context的具體實現在ContextImpl類,ContextImpl中的bindService方法直接呼叫了bindServiceCommon方法,此方法原始碼如下:
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags, UserHandle user) { IServiceConnection sd; if (conn == null) { throw new IllegalArgumentException("connection is null"); } if (mPackageInfo != null) { // important sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), mMainThread.getHandler(), flags); } else { throw new RuntimeException("Not supported in system context"); } validateServiceIntent(service); try { IBinder token = getActivityToken(); if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null && mPackageInfo.getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { flags |= BIND_WAIVE_PRIORITY; } service.prepareToLeaveProcess(); int res = ActivityManagerNative.getDefault().bindService( mMainThread.getApplicationThread(), getActivityToken(), service, service.resolveTypeIfNeeded(getContentResolver()), sd, flags, getOpPackageName(), user.getIdentifier()); if (res < 0) { throw new SecurityException( "Not allowed to bind to service " + service); } return res != 0; } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } }
大致觀察就能發現這個方法最終通過ActivityManagerNative藉助AMS進而完成Service的繫結過程,
在跟蹤AMS的bindService原始碼之前,關注一下這個方法開始處建立的sd變數。這個變數的型別是IServiceConnection,
如果讀者還有印象,在廣播的管理一文中也遇到過類似的處理方式——IIntentReceiver;
所以,這個IServiceConnection與 IApplicationThread以及IIntentReceiver相同,
都是ActivityThread給AMS提供的用來與之進行通訊的 Binder物件;這個介面的實現類為LoadedApk.ServiceDispatcher。
這個方法最終呼叫了ActivityManagerNative的bindService,而這個方法的真正實現在AMS裡面,原始碼如下:
public int bindService(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, IServiceConnection connection, int flags, String callingPackage,
int userId) throws TransactionTooLargeException {
enforceNotIsolatedCaller("bindService");
// 略去引數校檢
synchronized(this) {
return mServices.bindServiceLocked(caller, token, service,
resolvedType, connection, flags, callingPackage, userId);
}
}
bindService這個方法相當簡單,只是做了一些引數校檢之後直接呼叫了ActivityServices類的bindServiceLocked方法:
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, IServiceConnection connection, int flags,
String callingPackage, int userId) throws TransactionTooLargeException {
final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
// 引數校檢,略
ServiceLookupResult res =
retrieveServiceLocked(service, resolvedType, callingPackage,
Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg);
// 結果校檢, 略
ServiceRecord s = res.record;
final long origId = Binder.clearCallingIdentity();
try {
// ... 不關心, 略
mAm.startAssociationLocked(callerApp.uid, callerApp.processName,
s.appInfo.uid, s.name, s.processName);
AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
ConnectionRecord c = new ConnectionRecord(b, activity,
connection, flags, clientLabel, clientIntent);
IBinder binder = connection.asBinder();
ArrayList<ConnectionRecord> clist = s.connections.get(binder);
// 對connection進行處理, 方便存取,略
clist.add(c);
if ((flags&Context.BIND_AUTO_CREATE) != 0) {
s.lastActivity = SystemClock.uptimeMillis();
if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) {
return 0;
}
}
// 與BIND_AUTO_CREATE不同的啟動FLAG,原理與後續相同,略
} finally {
Binder.restoreCallingIdentity(origId);
}
return 1;
}
這個方法比較長,這裡省去了很多無關程式碼,只列出關鍵邏輯;首先它通過retrieveServiceLocked方法
獲取到了intent匹配到的需要bind到的Service元件res;然後把ActivityThread傳遞過來的IServiceConnection
使用ConnectionRecord進行了包裝,方便接下來使用;最後如果啟動的FLAG為BIND_AUTO_CREATE,
那麼呼叫bringUpServiceLocked開始建立Service,跟蹤這個方法:(非這種FLAG的程式碼已經省略,可以自行跟蹤)
private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
boolean whileRestarting) throws TransactionTooLargeException {
// 略。。
final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
final String procName = r.processName;
ProcessRecord app;
if (!isolated) {
app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
if (app != null && app.thread != null) {
try {
app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
// 1. important !!!
realStartServiceLocked(r, app, execInFg);
return null;
} catch (TransactionTooLargeException e) {
throw e;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when starting service " + r.shortName, e);
}
}
} else {
app = r.isolatedProc;
}
// Not running -- get it started, and enqueue this service record
// to be executed when the app comes up.
if (app == null) {
// 2. important !!!
if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
"service", r.name, false, isolated, false)) == null) {
bringDownServiceLocked(r);
return msg;
}
if (isolated) {
r.isolatedProc = app;
}
}
// 略。。
return null;
}
這個方案同樣也很長,但是實際上非常簡單:注意註釋的兩個important的地方,
如果Service所在的程序已經啟動,那麼直接呼叫realStartServiceLocked方法來真正啟動Service元件;
如果Service所在的程序還沒有啟動,那麼先在AMS中記下這個要啟動的Service元件,然後通過startProcessLocked啟動新的程序。
先看Service程序已經啟動的情況,也即realStartServiceLocked分支:
private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException {
// 略。。
boolean created = false;
try {
synchronized (r.stats.getBatteryStats()) {
r.stats.startLaunchedLocked();
}
mAm.ensurePackageDexOpt(r.serviceInfo.packageName);
app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
r.postNotification();
created = true;
} catch (DeadObjectException e) {
mAm.appDiedLocked(app);
throw e;
} finally {
// 略。。
}
requestServiceBindingsLocked(r, execInFg);
// 不關心,略。。
}
這個方法首先呼叫了app.thread的scheduleCreateService方法,知道,這是一個IApplicationThread物件,
它是App所在程序提供給AMS的用來與App程序進行通訊的Binder物件,這個Binder的 Server端在
ActivityThread的ApplicationThread類,因此,跟蹤ActivityThread類,這個方法的實現如下:
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
s.compatInfo = compatInfo;
sendMessage(H.CREATE_SERVICE, s);
}
它不過是轉發了一個訊息給ActivityThread的H這個Handler,H類收到這個訊息之後,
直接呼叫了ActivityThread類的handleCreateService方法,如下:
private void handleCreateService(CreateServiceData data) {
unscheduleGcIdler();
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
}
try {
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
service.onCreate();
mServices.put(data.token, service);
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
// nothing to do.
}
} catch (Exception e) {
}
}
看到這段程式碼,是不是似曾相識?!沒錯,這裡與Activity元件的建立過程如出一轍!
所以這裡就不贅述了,可以參閱 Activity生命週期管理。
需要注意的是,這裡Service類的建立過程與Activity是略微有點不同的,雖然都是通過ClassLoader通過反射建立,
但是Activity卻把建立過程委託給了Instrumentation類,而Service則是直接進行。
OK,現在ActivityThread裡面的handleCreateService方法成功創建出了Service物件,並且呼叫了它的onCreate方法;
到這裡的Service已經啟動成功。scheduleCreateService這個Binder呼叫過程結束,
程式碼又回到了AMS程序的realStartServiceLocked方法。這裡不得不感嘆Binder機制的精妙,
如此簡潔方便高效的跨程序呼叫,在程序之間來回穿梭,遊刃有餘。
realStartServiceLocked方法的程式碼如下:
private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException {
// 略。。
boolean created = false;
try {
synchronized (r.stats.getBatteryStats()) {
r.stats.startLaunchedLocked();
}
mAm.ensurePackageDexOpt(r.serviceInfo.packageName);
app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
r.postNotification();
created = true;
} catch (DeadObjectException e) {
mAm.appDiedLocked(app);
throw e;
} finally {
// 略。。
}
requestServiceBindingsLocked(r, execInFg);
// 不關心,略。。
}
這個方法在完成scheduleCreateService這個binder呼叫之後,執行了一個requestServiceBindingsLocked方法;
看方法名好像於「繫結服務」有關,它簡單地執行了一個遍歷然後呼叫了另外一個方法:
private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
boolean execInFg, boolean rebind) throws TransactionTooLargeException {
if (r.app == null || r.app.thread == null) {
return false;
}
if ((!i.requested || rebind) && i.apps.size() > 0) {
try {
bumpServiceExecutingLocked(r, execInFg, "bind");
r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
r.app.repProcState);
// 不關心,略。。
}
return true;
}
可以看到,這裡又通過IApplicationThread這個Binder進行了一次IPC呼叫,
跟蹤ActivityThread類裡面的ApplicationThread的scheduleBindService方法,
發現這個方法不過通過Handler轉發了一次訊息,真正的處理程式碼在handleBindService裡面:
private void handleBindService(BindServiceData data) {
Service s = mServices.get(data.token);
if (s != null) {
try {
data.intent.setExtrasClassLoader(s.getClassLoader());
data.intent.prepareToEnterProcess();
try {
if (!data.rebind) {
IBinder binder = s.onBind(data.intent);
ActivityManagerNative.getDefault().publishService(
data.token, data.intent, binder);
} else {
s.onRebind(data.intent);
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
}
ensureJitEnabled();
} catch (RemoteException ex) {
}
} catch (Exception e) {
}
}
}
要Bind的Service終於在這裡完成了繫結!繫結之後又通過ActivityManagerNative這個Binder進行一次IPC呼叫,
檢視AMS的publishService方法,這個方法簡單第呼叫了publishServiceLocked方法,原始碼如下:
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
final long origId = Binder.clearCallingIdentity();
try {
if (r != null) {
Intent.FilterComparison filter
= new Intent.FilterComparison(intent);
IntentBindRecord b = r.bindings.get(filter);
if (b != null && !b.received) {
b.binder = service;
b.requested = true;
b.received = true;
for (int conni=r.connections.size()-1; conni>=0; conni--) {
ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni);
for (int i=0; i<clist.size(); i++) {
ConnectionRecord c = clist.get(i);
if (!filter.equals(c.binding.intent.intent)) {
continue;
}
try {
c.conn.connected(r.name, service);
} catch (Exception e) {
}
}
}
}
serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false);
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
還記得之前提到的那個IServiceConnection嗎?在bindServiceLocked方法裡面,
把這個IServiceConnection放到了一個ConnectionRecord的List中存放在ServiceRecord裡面,
這裡所做的就是取出已經被Bind的這個Service對應的IServiceConnection物件,然後呼叫它的connected方法;
這個IServiceConnection也是一個Binder物件,它的Server端在LoadedApk.ServiceDispatcher裡面。
程式碼到這裡已經很明確了,接下來的過程非常簡單,感興趣的讀者自行查閱LoadedApk.ServiceDispatcher的connected方法,
一路跟蹤弄清楚ServiceConnection回撥過程,完成最後的拼圖!
最後提一點,以上分析了Service所在程序已經存在的情況,如果Service所在程序不存在,
那麼會呼叫startProcessLocked方法建立一個新的程序,並把需要啟動的Service放在一個佇列裡面;
建立程序的過程通過Zygote fork出來,程序建立成功之後會呼叫ActivityThread的main方法,
在這個main方法裡面間接呼叫到了AMS的 attachApplication方法,在AMS的attachApplication裡面
會檢查剛剛那個待啟動Service佇列裡面的內容,並執行 Service的啟動操作;
之後的啟動過程與程序已經存在的情況下相同;可以自行分析。