外掛化-外掛Service的執行管理
本文出自:Android" target="_blank" rel="nofollow,noindex">https://github.com/SusionSuc/AdvancedAndroid
在繼續看VirtualApk中如何啟動一個外掛的Service
之前,先簡單的看一下Android
如何啟動一個Service
, 主要是有個印象。
下面的原始碼參考自Android8.0。 貼的原始碼只是包含一些關鍵點。
Service啟動的大體流程
我們從ContextImpl.startService()
開始看。 為什麼從這裡開始看呢? 如果你看過前面的文章外掛Activity的啟動
, 你應該知道在Activity建立時,其Context
的例項就是ContextImpl
,因此我們看一下它的startService()
。
public ComponentName startService(Intent service) { ..... return startServiceCommon(service, false, mUser); } private ComponentName startServiceCommon(Intent service, boolean requireForeground,UserHandle user) { .. ActivityManager.getService().startService(mMainThread.getApplicationThread(), service, ....); ...... }
即直接向ActivityManager
請求啟動Service。這是一次通過Binder
跨程序呼叫呼叫。最後呼叫到ActivityManagerService.startService()
,然後它又呼叫了ActiveServices.startServiceLocked()
, 來看一下關鍵步驟:
ComponentName startServiceLocked(....){ //建立 ServiceRecord, 並快取起來 ServiceLookupResult res = retrieveServiceLocked(.....); //這裡會檢測Service是否在 manifest檔案中註冊 ServiceRecord r = res.record; ....一系列許可權相關檢查 //繼續啟動流程 ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting); }
在後續流程中,真正啟動Service的方法是ActiveServices.bringUpServiceLocked()
:
private String bringUpServiceLocked(ServiceRecord r, .....) { ProcessRecord app; .... //是不是要單獨開了一個程序來啟動這個Service final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0; ... if (!isolated) { app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false); ..... realStartServiceLocked(r, app, execInFg); //真正啟動Service return null; } //啟動一個對應的程序 app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, hostingType, r.name, false, isolated, false) ..... //等會啟動 if (!mPendingServices.contains(r)) { mPendingServices.add(r); } }
ActiveServices.realStartServiceLocked()
這個方法做的事情就很熟悉了:
app.thread.scheduleCreateService(r, r.serviceInfo,....);//這個方法會導致 service.onCreate()呼叫 .... sendServiceArgsLocked(r, execInFg, true);//這個方法會導致 service.onStartCommand()呼叫
即切換到了我們應用的程序,接下來就是在ActivityThread
中啟動Service了:
//ActivityThread.java private void handleCreateService(CreateServiceData data) { ...... java.lang.ClassLoader cl = packageInfo.getClassLoader(); service = (Service) cl.loadClass(data.info.name).newInstance(); ...... ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); Application app = packageInfo.makeApplication(false, mInstrumentation);//不為null, 直接返回 service.attach(context, this, data.info.name, data.token, app, ActivityManager.getService()); service.onCreate(); mServices.put(data.token, service); ...... }
ok分析到這裡,我們大致知道了Service
是如何啟動的。 至於Service
的繫結這裡先不做分析。 那分析這一遍原始碼有什麼意義呢?當然是為了方便接下來的分析外掛化Service的啟動
。
VirtualApk-外掛化Service的啟動
- 服務端對於Service的啟動有校驗機制嗎 ?
前面在分析原始碼時註釋已經標註了。ActiveServices.retrieveServiceLocked()
這個方法在構造ServiceRecord
的時候會做這個校驗。即必須在manifest檔案中註冊。 所以啟動一個外掛的Service,我們也需要類似外掛Activity
來啟動一個佔坑的Service
。
動態代理ActivityManagerService
所以VirtualApk
對於外掛Service啟動的第一步就是需要繞過系統校驗,原理類似於外掛Activity的啟動,只不過由於Service
的啟動和Instrumentation
關係不大,它hook的是ActivityManagerService
。
protected void hookSystemServices() { Singleton<IActivityManager> defaultSingleton; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { defaultSingleton = Reflector.on(ActivityManager.class).field("IActivityManagerSingleton").get(); } else { defaultSingleton = Reflector.on(ActivityManagerNative.class).field("gDefault").get(); } //通過java動態代理 hook IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class }, createActivityManagerProxy(defaultSingleton.get())); Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy); // hook for android8.0以下版本 }
hook之後目的當然是為了繞過系統對外掛Service
的校驗。類似於Activity
的啟動,在外掛Service啟動時會把佔坑Service
給ActivityManagerService
校驗。在VirtualApk中也定義了兩個佔坑Service
:
<service android:exported="false" android:name="com.didi.virtualapk.delegate.LocalService" /> <service android:exported="false" android:name="com.didi.virtualapk.delegate.RemoteService" android:process=":daemon"/>
現在我們看VirtualApk
是如何在Service啟動時替換外掛Service
為這兩個佔坑的Service
:
public class ActivityManagerProxy implements InvocationHandler { //前面已經標註,對ActivityManagerService的hook是通過java的動態代理 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("startService".equals(method.getName())) { return startService(proxy, method, args); } if ("stopService".equals(method.getName())) { return stopService(proxy, method, args); } }
即如果呼叫的是ActivityManagerService.startService
,那就做一些處理:
protected Object startService(Object proxy, Method method, Object[] args) throws Throwable { Intent target = (Intent) args[1]; ...如果這個intent不是外掛的Service直接返回 Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, RemoteService.EXTRA_COMMAND_START_SERVICE); return mPluginManager.getHostContext().startService(wrapperIntent); } protected Object stopService(Object proxy, Method method, Object[] args) throws Throwable { ....流程同startService startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_STOP_SERVICE); } protected Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) { target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name)); String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation(); //外掛啟動的service是 本地的還是遠端的 Class<? extends Service> delegate = PluginUtil.isLocalService(serviceInfo) ? LocalService.class : RemoteService.class; Intent intent = new Intent(); intent.setClass(mPluginManager.getHostContext(), delegate); intent.putExtra(RemoteService.EXTRA_TARGET, target); intent.putExtra(RemoteService.EXTRA_COMMAND, command); // 注意這個command。 這裡是: EXTRA_COMMAND_START_SERVICE intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation); ..... return intent; }
即如果啟動的是外掛Service
, 就把要啟動的外掛Serivice
的資訊儲存在intent中,然後啟動佔坑Service
。並且對於不同的操作在intent中標記為一個不同的command
,比如上面的EXTRA_COMMAND_START_SERVICE
和EXTRA_COMMAND_STOP_SERVICE
。其餘還有EXTRA_COMMAND_BIND_SERVICE
和EXTRA_COMMAND_UNBIND_SERVICE
。
接下來還要像啟動外掛Activity
那樣再把Activity
換回來嗎?VirtualApk
並沒有這麼做,它就是直接啟動了LocalService
orRemoteService
, 只不過在這兩個Service中對不同的command
做了不同的處理,比如EXTRA_COMMAND_START_SERVICE
:
public class LocalService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { int command = intent.getIntExtra(EXTRA_COMMAND, 0); switch (command) { case EXTRA_COMMAND_START_SERVICE: { ActivityThread mainThread = ActivityThread.currentActivityThread(); IApplicationThread appThread = mainThread.getApplicationThread(); Service service; if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) { service = this.mPluginManager.getComponentsHandler().getService(component); //如果這個Service已經執行 } else { //例項化外掛Service service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance(); Application app = plugin.getApplication(); IBinder token = appThread.asBinder(); Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class); IActivityManager am = mPluginManager.getActivityManager(); attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am); service.onCreate(); //快取Service,避免重複例項化 this.mPluginManager.getComponentsHandler().rememberService(component, service); } //呼叫外掛Service的 onStartCommand service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement()); break; } } }
即它在LocalService
的onStartCommand
中,模擬了Service的生命週期方法的呼叫,具體生命週期方法呼叫步驟可以回顧前面看過的Service
原始碼。對於其他stopService
,bindService
也是由LocalService
在onStartCommand
中來模擬的。對於RemoteService
這裡就不列了,處理其實和LocalService
是一樣的,只不過RemoteService
是跑在另一個程序中的。
所以這裡來總結一下VirtualApk
中的外掛Serivice
的真正執行情況吧,這裡以主程序中的Service為例:
-
外掛Service
都是執行在LocalService
中的。其實這無所謂,都是跑在主程序中的。 -
外掛Service
的相關生命週期方法都是LocalService
來模擬呼叫的(在主執行緒中)。 -
即真正在後臺一直跑的Service是
LocalService
。
所以從原始碼來看,VirtualApk
對於外掛Service
通過依賴於系統Service
,建立了一套自己的外掛Service
執行模型。有沒有什麼缺點呢?當然有
多程序Service
好,對於VirtualApk
的外掛Service
的管理原始碼的解讀就看到這裡。 歡迎關注我的Android進階計劃 :https://github.com/SusionSuc/AdvancedAndroid