天天看點

Android插件化原理(三):Service的插件化

  上一節Activity的插件化中我們解決了四大元件中最重要的元件Activity的插件化問題。四大元件中,Service同樣是使用相對頻繁的元件,是以Service的插件化也是比較重要的。本節我們就跟着VirtualApk源碼看一下Service插件化的實作。

Service插件化思路

  在Activity的插件化中我們看到對于Activity的插件化,VirtualApk采用了Hook及在宿主中注冊占位Activity的方式解決,那麼Service是否可用這種方式解決呢?是可以的,我們同樣可以在宿主中注冊一些占位Service,再通過Hook相關類,當啟動一個Service時,在向AMS請求之前将目标Service替換成占位Service,在AMS回調以後再将目标Service替換回插件Service。但是Activity與Service啟動有以下幾個不同之處:

  1. 啟動一個标準啟動模式Activity是可以重複建立執行個體的,是以标準啟動模式的占位Activity隻需一個即可,但Service如果已經啟動,是不會重複建立的,是以目标Service和占位Service隻能是一對一的關系。
  2. Activity随着使用者操作有複雜生命周期處理,而Service的生命周期比較簡單,可以手動進行管理。

  是以占位Service可以解決Service插件化問題,但是需要在宿主中注冊多個占位Service保證有足夠的Service可用。VirtualApk采用了另一種思路,基于代理分發的方式實作,在啟動插件Service時,會啟動一個在宿主中注冊過的代理Service,這裡是真正的啟動而非繞過AMS檢查,是以會調用代理Service的

onStartCommand()

,在該方法中會建立插件Service執行個體并手動調用它的相關方法完成Service請求的分發。可以看到這種方式下就無需在宿主中注冊大量的占位Service就能實作Service的插件化了。下面我們跟着具體源碼看下VirtualApk是如何實作的,Service包括了啟動和綁定兩個過程,我們分别看下這兩個過程。

Service的啟動過程

  我們先看一下Service啟動流程的時序圖:

Android插件化原理(三):Service的插件化

  當啟動一個Service時,會調用ContextWrapper的

startService()

,Activity、Service以及Application都繼承了ContextWrapper,其内部會調用ContextImpl的

startService()

,最後會調用到

startServiceCommon()

,其内部又會調用IActivityManager的

startService()

向AMS發起請求;AMS收到請求并處理完成後,會調用ApplicationThread的

scheduleCreateService()

回調到應用程序,接着通過主線程Handler發送一條CREATE_SERVICE類型的消息,Handler處理這條消息時會調用ActivityThread的

handleCreateService()

,在它内部會建立Service執行個體,并調用

attach()

完成Service初始化,最後調用Service的

onCreate()

完成Service啟動。

  根據Service的啟動流程以及Service插件化實作的思路,我們需要在插件啟動Service時先啟動代理Service,是以我們同樣可以采用Hook的方式,在向AMS發送請求之前将目标Service替換成代理Service,這樣AMS收到的就是啟動代理Service的請求了,是以我們看看VirtualApk是如何實作的。

  在Activity的插件化中我們說到了VirtualApk初始化的時候會hook多個對象,其中會調用

hookSystemServices()

hook AMS在應用程序的代理,我們看下這個方法的實作:

protected void hookSystemServices() {
    try {
        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();
        }
        IActivityManager origin = defaultSingleton.get();
        IActivityManager activityManagerProxy = (IActivityManager) Proxy.newProxyInstance(mContext.getClassLoader(), new Class[] { IActivityManager.class },
            createActivityManagerProxy(origin));
        Reflector.with(defaultSingleton).field("mInstance").set(activityManagerProxy);
        if (defaultSingleton.get() == activityManagerProxy) {
            this.mActivityManager = activityManagerProxy;
            Log.d(TAG, "hookSystemServices succeed : " + mActivityManager);
        }
    } catch (Exception e) {
        Log.w(TAG, e);
    }
}
           

  因為AMS在應用程序的代理在8.0版本前後的形式有所不同,是以首先根據版本調用不同的擷取方式,最終都會得到一個實作了IActivityManager的對象,這是一個單例對象。接着建立了IActivityManager接口的一個動态代理對象,其中調用

createActivityManagerProxy()

建立了代理的處理對象,最後通過反射将代理對象指派給單例的容器中,

createActivityManagerProxy()

内部就直接new了一個ActivityManagerProxy對象,ActivityManagerProxy實作了InvocationHandler接口,是以我們看看ActivityManagerProxy的

invoke()

做了什麼處理:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if ("startService".equals(method.getName())) {
        try {
            return startService(proxy, method, args);
        } catch (Throwable e) {
            Log.e(TAG, "Start service error", e);
        }
    } else if ("stopService".equals(method.getName())) {
        try {
            return stopService(proxy, method, args);
        } catch (Throwable e) {
            Log.e(TAG, "Stop Service error", e);
        }
    } else if ("bindService".equals(method.getName())) {
        try {
            return bindService(proxy, method, args);
        } catch (Throwable e) {
            Log.w(TAG, e);
        }
    } else if ("unbindService".equals(method.getName())) {
        try {
            return unbindService(proxy, method, args);
        } catch (Throwable e) {
            Log.w(TAG, e);
        }
    }
    ...
}
           

  

invoke()

方法根據調用的方法名做不同的處理,上面看到了啟動、停止、綁定、解綁服務的處理,稍後我們再看綁定的處理,這裡我們先看下

startService()

protected Object startService(Object proxy, Method method, Object[] args) throws Throwable {
    IApplicationThread appThread = (IApplicationThread) args[0];
    Intent target = (Intent) args[1];
    ResolveInfo resolveInfo = mPluginManager.resolveService(target, 0);
    if (null == resolveInfo || null == resolveInfo.serviceInfo) {
        return method.invoke(this.mActivityManager, args);
    }
    return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);
}
           

  方法首先從參數中取出Intent,調用

PluginManager.resolveService()

根據Intent中的目标Service資訊看是否比對一個插件Service,如果不是則不做處理,否則則調用

startDelegateServiceForTarget()

,我們看下這個方法的實作:

protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
    Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
    return mPluginManager.getHostContext().startService(wrapperIntent);
}
           

  可以看到方法調用

wrapperTargetIntent()

将Intent進行轉化,接着通過轉化後的Intent調用宿主的Context的

startService()

啟動Service,是以我們看下

wrapperTargetIntent()

是如何處理Intent的,方法如下所示:

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();
    boolean local = PluginUtil.isLocalService(serviceInfo);
    Class<? extends Service> delegate = local ? LocalService.class : RemoteService.class;  // 1
    Intent intent = new Intent();
    intent.setClass(mPluginManager.getHostContext(), delegate);  // 2
    intent.putExtra(RemoteService.EXTRA_TARGET, target);  // 3
    intent.putExtra(RemoteService.EXTRA_COMMAND, command);  // 4
    intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);
    if (extras != null) {
        intent.putExtras(extras);
    }
    return intent;
}
           

  在注釋1處會根據插件Service運作在的程序名判斷該服務是運作在本地還是遠端,如果是本地就會用LocalService作為代理Service,否則會使用RemoteService,RemoteService繼承自LocalService,但它運作在子程序中,我們可以看一下LocalService以及RemoteService的聲明:

<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">
    <intent-filter>
        <action android:name="${applicationId}.intent.ACTION_DAEMON_SERVICE" />
    </intent-filter>
</service>
           

  可以看到LocalService沒有指定程序而RemoteService運作在:daemon程序中。

wrapperTargetIntent()

方法在注釋2處會建立一個新的Intent,并且将目标Service設定成剛標明的代理Service;在注釋3會把原本的包含插件Service資訊的Intent作為參數添加到新建立的Intent中;在注釋4處會向Intent中添加一個參數辨別目前執行的操作,用來和綁定服務進行區分,這個參數我們後面還會看到。這樣就會啟動我們標明的一個代理Service,因為RemoteService繼承自LocalService,處理邏輯都在LocalService中,是以我們這裡以LocalService為例。LocalService啟動以後,它的

onStartCommand()

會被調用,且即使LocalService已經被建立,

onStartCommand()

也是會被調用的,是以不會因為LocalService已經啟動而影響代理分發邏輯的執行,我們看下

onStartCommand()

是如何處理的,代碼如下:

public int onStartCommand(Intent intent, int flags, int startId) {
    if (null == intent || !intent.hasExtra(EXTRA_TARGET) || !intent.hasExtra(EXTRA_COMMAND)) {
        return START_STICKY;
    }
    Intent target = intent.getParcelableExtra(EXTRA_TARGET);  // 1
    int command = intent.getIntExtra(EXTRA_COMMAND, 0);  // 2
    if (null == target || command <= 0) {
        return START_STICKY;
    }
    ComponentName component = target.getComponent();  // 3
    LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);
    if (plugin == null) {
        Log.w(TAG, "Error target: " + target.toURI());
        return START_STICKY;
    }
    target.setExtrasClassLoader(plugin.getClassLoader());
    switch (command) {  // 4
        ...
    }
    return START_STICKY;
}
           

  可以看到方法在注釋1和注釋2處分别從Intent中取出我們前面作為參數添加到Intent中的目标Intent以及操作辨別,在注釋3處從目标Intent中取出實際要啟動的Service的包名類名,在注釋4處根據操作辨別做不同處理,這裡我們看一下對啟動服務的處理,處理邏輯如下所示:

ActivityThread mainThread = ActivityThread.currentActivityThread();
IApplicationThread appThread = mainThread.getApplicationThread();
Service service;
if (mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
    service = mPluginManager.getComponentsHandler().getService(component);  // 1
} else {
    try {
        service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();  // 2
        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);  // 3
        IActivityManager am = mPluginManager.getActivityManager();
        attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);  // 4
        service.onCreate();  // 5
        mPluginManager.getComponentsHandler().rememberService(component, service);
    } catch (Throwable t) {
        return START_STICKY;
    }
}
service.onStartCommand(target, 0, mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());  // 6
           

  上面代碼在注釋1處先判斷要啟動的插件Service是否已經啟動,如果是則從緩存取出,不再重複建立執行個體,直接在注釋6調用它的

onStartCommand()

,否則在注釋2處通過插件Service所在的插件的DexClassLoader加載Service類并建立執行個體,在注釋3處通過反射獲得Service類的

attach()

,并在注釋4處對剛剛建立的Service執行個體調用

attach()

進行初始化,在注釋5處調用Service的

onCreate()

,最後在注釋6處調用Service的

onStartCommand()

,這樣就完成了插件Service的啟動。可以看出插件Service的生命周期是由代理Service手動調用的,這就是代理分發的思想,這種思路我們在下面的ContentProvider插件化中還會再次看到。

  上面就是Service啟動的插件化實作,接下來看一下Service綁定的插件化實作,綁定和啟動的思路都是一緻的,是以了解了啟動的原理後,綁定也就很簡單了。

Service的綁定過程

  我們還是先看一下Service綁定流程的時序圖:

Android插件化原理(三):Service的插件化

  綁定Service的流程和啟動Service基本一樣,隻是調用的方法從create變成了bind,大家看時序圖基本都能明白,我就不做解釋了。但需要注意的一點是,綁定Service的時候,如果Service還沒有啟動,AMS會先回調應用程序觸發Service的啟動。

  綁定服務時會調用IActivityManager的

bindService()

與AMS通信,我們前面已經知道VirtualApk hook了IActivityManager,是以調用

bindService()

依然會調用到ActivityManagerProxy的

invoke()

,現在我們看下

invoke()

對bindService的處理,代碼如下:

protected Object bindService(Object proxy, Method method, Object[] args) throws Throwable {
    Intent target = (Intent) args[2];
    ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
    if (null == resolveInfo || null == resolveInfo.serviceInfo) {
        return method.invoke(this.mActivityManager, args);
    }
    Bundle bundle = new Bundle();
    PluginUtil.putBinder(bundle, "sc", (IBinder) args[4]);  // 1
    startDelegateServiceForTarget(target, resolveInfo.serviceInfo, bundle, RemoteService.EXTRA_COMMAND_BIND_SERVICE);  // 2
    mPluginManager.getComponentsHandler().remberIServiceConnection((IBinder) args[4], target);
    return 1;
}
           

  可以看到對綁定的處理和啟動基本一緻,不一樣的地方在注釋1處從參數中取出了一個ServiceDispatcher對象,它内部封裝了我們調用

bindService()

傳遞的ServiceConnection對象;并在注釋2處調用

startDelegateServiceForTarget()

啟動代理Service時作為參數傳入,同時傳遞了表示綁定Service的辨別符,是以在代理Service啟動後就會執行綁定Service的邏輯。注意注釋2我們執行

startDelegateServiceForTarget()

啟動代理服務,雖然我們這裡是綁定一個插件Service,但我們依舊是啟動代理Service而非綁定,因為無論是啟動還是綁定Service,代理Service的分發邏輯都在

onStartCommand()

中,是以我們這裡還是需要啟動代理Service。

startDelegateServiceForTarget()

的邏輯我們前面已經看過了,這裡就不再贅述了。我們依舊是以LocalService為例,在LocalService啟動以後,還是會調用到LocalService的

onStartCommand()

,這個方法我們前面也看到過了,是從Intent中取出目标Intent以及辨別符command,并根據command決定執行的邏輯,這裡我們會執行到綁定Service的邏輯,代碼如下所示:

ActivityThread mainThread = ActivityThread.currentActivityThread();
IApplicationThread appThread = mainThread.getApplicationThread();
Service service = null;
if (mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
    service = mPluginManager.getComponentsHandler().getService(component);  // 1
} else {
    try {
        service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();  // 2
        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);  // 3
        IActivityManager am = mPluginManager.getActivityManager();
        attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);  // 4
        service.onCreate();  // 5
        mPluginManager.getComponentsHandler().rememberService(component, service);
    } catch (Throwable t) {
        Log.w(TAG, t);
    }
}
try {
    IBinder binder = service.onBind(target);  // 6
    IBinder serviceConnection = PluginUtil.getBinder(intent.getExtras(), "sc");  // 7
    IServiceConnection iServiceConnection = IServiceConnection.Stub.asInterface(serviceConnection);
    if (Build.VERSION.SDK_INT >= 26) {
        iServiceConnection.connected(component, binder, false);  // 8
    } else {
        Reflector.QuietReflector.with(iServiceConnection).method("connected", ComponentName.class, IBinder.class).call(component, binder);
    }
} catch (Exception e) {
    Log.w(TAG, e);
}
           

  在注釋1處判斷是否已經建立過Service執行個體,如果是則從緩存中取出,否則在注釋2處通過插件Service所在的插件的DexClassLoader加載Service類并建立執行個體,注釋3處反射擷取到

attach()

方法并在注釋4處調用進行Service初始化,在注釋5處手動調用插件Service的

onCreate()

,到這裡Service的建立及初始化就完成了。接着在注釋6處手動調用插件Service的

onBind()

進行綁定請求的分發,

onBind()

會傳回一個Binder對象,在注釋7處取出我們之前作為作為參數添加到Intent的ServiceDispatcher對象,并将其轉化成ServiceConnection類型,在注釋8處将

onBind()

擷取到的Binder對象作為參數調用ServiceConnection的

connected()

,這樣我們調用

bindService()

時傳遞的ServiceConnection的監聽就能收到回調,并且通過拿到的Binder對象與插件Service進行通信了。

  到這裡Service的啟動以及綁定的插件化原理都分析清楚了,可以看出Service插件化采用了與Activity插件化不一樣的思路,采用了代理分發的思想,通過代理分發的思路可以保證宿主隻需要注冊少量的代理Service就可以完成全部插件Service的啟動,我們在ContentProvider的插件化上還會再次看到這種思路。我們主要講了啟動和綁定的流程,還有停止和解綁流程,但是相信了解了前兩個流程以後,剩下兩個流程也不是問題了。下一節會介紹一下剩下兩個元件—BroadcastReceiver和ContentProvider的插件化實作原理。