滴滴出行公司的首个对外开源项目 - VirtualAPK。地址:https://github.com/didi/VirtualAPK
滴滴自行研发了这款插件化框架,功能全面、兼容性好,还能够适用于有耦合的业务插件,这就是 VirtualAPK 存在的意义。业内认为,在加载耦合插件方面,VirtualAPK 可以说是开源方案的首选。据说滴滴打车里面已经用上了,所以还是有必要一探究竟的~~
VirtualAPK 的工作流程如图所示:
VirtualAPK 对于插件没有额外的约束,原生的 apk 即可作为一个插件。插件工程编译生成 apk 后,通过宿主 App 加载,每个插件 apk 被加载后,都会在宿主中创建一个单独的
对象。如上图所示,通过这些
- LoadedPlugin
对象,VirtualAPK 就可以管理插件并赋予插件新的意义,使其可以像手机中安装过的 App 一样运行。
- LoadedPlugin
插件化支持首先要解决的一点就是插件里的 Activity 并未在宿主程序的
注册,常规方法肯定无法直接启动插件的 Activity,这个时候就需要去了解 Activity 的启动流程,关于启动过程主要的几个步骤请参考:浅析 Android Activity 的启动过程
- AndroidMainfest.xml
从上文中可知,
启动实际上是调用了
- Activity
这个方法。源码如下:
- Instrumentation.execStartActivity
- public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,
- Intent intent, int requestCode, Bundle options) {
- IApplicationThread whoThread = (IApplicationThread) contextThread;
- if (mActivityMonitors != null) {
- synchronized (mSync) {
- final int N = mActivityMonitors.size();
- for (int i=0; i<N; i++) { //先查找一遍看是否存在这个activity
- final ActivityMonitor am = mActivityMonitors.get(i);
- if (am.match(who, null, intent)) {
- am.mHits++;
- if (am.isBlocking()) {
- return requestCode >= 0 ? am.getResult() : null;
- }
- break;
- }
- }
- }
- }
- try {
- intent.migrateExtraStreamToClipData();
- intent.prepareToLeaveProcess();
- //这里才是真正打开activity的地方,其核心功能在whoThread中完成。
- int result = ActivityManagerNative.getDefault().startActivity(whoThread, who.getBasePackageName(), intent,
- intent.resolveTypeIfNeeded(who.getContentResolver()),token, target != null ? target.mEmbeddedID : null,
- requestCode, 0, null, options);
- checkStartActivityResult(result, intent); // 处理各种异常,如ActivityNotFound
- } catch (RemoteException e) {
- }
- return null;
- }
可见,
最终通过
- startActivity
远程调用了 AMS 的 startActivity 方法,
- ActivityManagerNative.getDefault()
实际上就是
- ActivityManagerNative
这个远程对象的
- ActivityManagerService
代理对象,每次需要与 AMS 交互时,需要通过这个
- Binder
对象完成远程 IPC 调用。
- Binder
还不了解 Binder 的童鞋,可以看看老罗的 Android 进程间通信(IPC)机制 Binder 简要介绍和学习计划
- // ActivityManagerNative.getDefault()
- static public IActivityManager getDefault() {
- return gDefault.get();
- }
- private static final Singleton<iactivitymanager> gDefault =
- new Singleton<iactivitymanager>() {
- protected IActivityManager create() {
- IBinder b = ServiceManager.getService("activity");
- if (false) {
- Log.v("ActivityManager", "default service binder = " + b);
- }
- IActivityManager am = asInterface(b);
- if (false) {
- Log.v("ActivityManager", "default service = " + am);
- }
- return am;
- }
- };
从这我们可以知道,
实际上是返回了一个 IActivityManager 的单例对象。
- ActivityManagerNative.getDefault()
那么,VirtualApk 所要做的第一件事,就是把这个 AMS 代理对象保存起来。首先,我们可以看一下 VirtualApk 核心库里面
这个类的初始化:
- com.didi.virtualapk.PluginManager
- // 构造方法
- private PluginManager(Context context) {
- Context app = context.getApplicationContext();
- if (app == null) {
- this.mContext = context;
- } else {
- this.mContext = ((Application)app).getBaseContext();
- }
- prepare();
- }
- // 初始化
- private void prepare() {
- Systems.sHostContext = getHostContext();
- this.hookInstrumentationAndHandler();
- this.hookSystemServices();
- }
- /**
- * Hook 出一个IActivityManager,也就是 AMS 的代理对象
- */
- private void hookSystemServices() {
- try {
- // 反射调用 ActivityManagerNative.getDefault(),实际上这在6.0中是公开的静态方法,反射可能是考虑到版本兼容性吧?
- Singleton<IActivityManager> defaultSingleton = (Singleton<IActivityManager>) ReflectUtil.getField(ActivityManagerNative.class, null, "gDefault");
- // 通过动态代理的方式去创建代理对象,之后所有ActivityManagerNative中的方法被调用的时候都会经过这个代理
- IActivityManager activityManagerProxy = ActivityManagerProxy.newInstance(this, defaultSingleton.get());
- // Hook IActivityManager from ActivityManagerNative,实际上就是把 ActivityManagerNative 替换为刚创建的 activityManagerProxy
- ReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy);
- if (defaultSingleton.get() == activityManagerProxy) {
- // 两者一样,保存下来
- this.mActivityManager = activityManagerProxy;
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
实际上除了
是调用 AMS 的方法以外,
- startActivity
,
- startService
等方法,最终调用到 AMS 的里的方法,这个我们在动态代理类
- bindService
也可以找到:
- com.didi.virtualapk.delegate.ActivityManagerProxy
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if ("startService".equals(method.getName())) {
- try {
- // 执行自定义的 startService 过程,后面会提到
- 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 ("stopServiceToken".equals(method.getName())) {
- try {
- return stopServiceToken(proxy, method, args);
- } catch (Throwable e) {
- Log.e(TAG, "Stop service token error", e);
- }
- } else if ("bindService".equals(method.getName())) {
- try {
- return bindService(proxy, method, args);
- } catch (Throwable e) {
- e.printStackTrace();
- }
- } else if ("unbindService".equals(method.getName())) {
- try {
- return unbindService(proxy, method, args);
- } catch (Throwable e) {
- e.printStackTrace();
- }
- } else if ("getIntentSender".equals(method.getName())) {
- try {
- getIntentSender(method, args);
- } catch (Exception e) {
- e.printStackTrace();
- }
- } else if ("overridePendingTransition".equals(method.getName())){
- try {
- overridePendingTransition(method, args);
- } catch (Exception e){
- e.printStackTrace();
- }
- }
- try {
- // sometimes system binder has problems.
- return method.invoke(this.mActivityManager, args);
- } catch (Throwable th) {
- Throwable c = th.getCause();
- if (c != null && c instanceof DeadObjectException) {
- // retry connect to system binder
- IBinder ams = ServiceManager.getService(Context.ACTIVITY_SERVICE);
- if (ams != null) {
- IActivityManager am = ActivityManagerNative.asInterface(ams);
- mActivityManager = am;
- }
- }
- Throwable cause = th;
- do {
- if (cause instanceof RemoteException) {
- throw cause;
- }
- } while ((cause = cause.getCause()) != null);
- throw c != null ? c : th;
- }
- }
所以实际上就等同于我们重写了一些
、
- Activity
的相关操作。具体做些什么,后面会提到~
- Service
回过头去看看
这个方法,在最后有这么一句代码:
- Instrumentation.execStartActivity
- checkStartActivityResult(result, intent); // 处理各种异常,如ActivityNotFound
- static void checkStartActivityResult(int res, Object intent) {
- if (res >= ActivityManager.START_SUCCESS) {
- return;
- }
- switch (res) {
- case ActivityManager.START_INTENT_NOT_RESOLVED:
- case ActivityManager.START_CLASS_NOT_FOUND:
- if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
- throw new ActivityNotFoundException(
- "Unable to find explicit activity class "
- + ((Intent)intent).getComponent().toShortString()
- + "; have you declared this activity in your AndroidManifest.xml?");
- throw new ActivityNotFoundException(
- "No Activity found to handle " + intent);
- case ActivityManager.START_PERMISSION_DENIED:
- throw new SecurityException("Not allowed to start activity "
- + intent);
- case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
- throw new AndroidRuntimeException(
- "FORWARD_RESULT_FLAG used while also requesting a result");
- case ActivityManager.START_NOT_ACTIVITY:
- throw new IllegalArgumentException(
- "PendingIntent is not an activity");
- default:
- throw new AndroidRuntimeException("Unknown error code "
- + res + " when starting " + intent);
- }
- }
相信大家对上面的这些异常信息不陌生吧,其中最熟悉的非
莫属了,如果
- Unable to find explicit activity class
没有在
- Activity
注册,将会抛出此异常。
- AndroidMainfest.xml
那么就得思考一个问题了,插件的
并未在宿主程序的
- Activity
注册,要如何才能绕过这一层检测?
- AndroidMainfest.xml
前文中提到,
这个类的初始化的时候,除了 Hook 出一个 AMS 代理对象以外,还 Hook 出一个
- com.didi.virtualapk.PluginManager
对象。代码如下:
- Instrumentation
- private void hookInstrumentationAndHandler() {
- try {
- Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext);
- if (baseInstrumentation.getClass().getName().contains("lbe")) {
- // reject executing in paralell space, for example, lbe.
- System.exit(0);
- }
- // 创建自定义的 instrumentation,重写了 newActivity() 等一些方法
- // baseInstrumentation 后面还会用到,也保存下来
- final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);
- // 获取 ActivityThread 的实例
- Object activityThread = ReflectUtil.getActivityThread(this.mContext);
- // 用自定义的 instrumentation 替换掉 ActivityThread 里面的 instrumentation
- ReflectUtil.setInstrumentation(activityThread, instrumentation);
- ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
- this.mInstrumentation = instrumentation;
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
既然 Activity 的启动,中间走了
这个方法,那么我们大概可以知道,Hook 出一个
- Instrumentation.execStartActivity
对象用来做什么了,实际上就是用来帮助启动插件的
- Instrumentation
。
- Activity
我们 Hook 了一个
以替代系统的
- VAInstrumentation
,这样当系统通过
- Instrumentation
调用 它的的成员变量 mInstrumentation 的
- ActivityThread
等方法的时候,实际是调用我们
- newActivity()
的
- VAInstrumentation
。
- newActivity()
实际上对于插件
启动,采用的是宿主 manifest 中占坑的方式来绕过系统校验,然后再加载真正的 activity。
- Activity
什么是占坑?就是构造一系列假的
替身,在
- Activity
里面进行注册,以绕过检测,然后到了真正启动 Activity 的时候,再把它变回,去启动真正的目标
- AndroidMainfest.xml
。那么这一步是怎么做的呢?
- Activity
我们可以打开核心库里面的
看看:
- AndroidMainfest.xml
- <application>
- <!-- Stub Activities -->
- <activity android:name=".A$1" android:launchMode="standard"/>
- <activity android:name=".A$2" android:launchMode="standard"
- android:theme="@android:style/Theme.Translucent" />
- <!-- Stub Activities -->
- <activity android:name=".B$1" android:launchMode="singleTop"/>
- <activity android:name=".B$2" android:launchMode="singleTop"/>
- <activity android:name=".B$3" android:launchMode="singleTop"/>
- <activity android:name=".B$4" android:launchMode="singleTop"/>
- <activity android:name=".B$5" android:launchMode="singleTop"/>
- <activity android:name=".B$6" android:launchMode="singleTop"/>
- <activity android:name=".B$7" android:launchMode="singleTop"/>
- <activity android:name=".B$8" android:launchMode="singleTop"/>
- <!-- Stub Activities -->
- <activity android:name=".C$1" android:launchMode="singleTask"/>
- <activity android:name=".C$2" android:launchMode="singleTask"/>
- <activity android:name=".C$3" android:launchMode="singleTask"/>
- <activity android:name=".C$4" android:launchMode="singleTask"/>
- <activity android:name=".C$5" android:launchMode="singleTask"/>
- <activity android:name=".C$6" android:launchMode="singleTask"/>
- <activity android:name=".C$7" android:launchMode="singleTask"/>
- <activity android:name=".C$8" android:launchMode="singleTask"/>
- <!-- Stub Activities -->
- <activity android:name=".D$1" android:launchMode="singleInstance"/>
- <activity android:name=".D$2" android:launchMode="singleInstance"/>
- <activity android:name=".D$3" android:launchMode="singleInstance"/>
- <activity android:name=".D$4" android:launchMode="singleInstance"/>
- <activity android:name=".D$5" android:launchMode="singleInstance"/>
- <activity android:name=".D$6" android:launchMode="singleInstance"/>
- <activity android:name=".D$7" android:launchMode="singleInstance"/>
- <activity android:name=".D$8" android:launchMode="singleInstance"/>
- </application>
可以发现,在清单里面注册了一堆假的
。 ABCD 分别对应不同的启动模式,那么,我们启动插件的 Activity 的时候,是如何把它改为清单里面已注册的这些假的
- StubActivity
名呢?
- Activity
在
里面,重写了
- VAInstrumentation
的必经之路,就是
- startActivity
方法:
- execStartActivity()
- public ActivityResult execStartActivity(
- Context who, IBinder contextThread, IBinder token, Activity target,
- Intent intent, int requestCode, Bundle options) {
- // 这里面做了一系列操作,实际上就是查找插件里面第一个符合隐式条件的第一个ResolveInfo,并设置进intent
- mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
- // null component is an implicitly intent
- if (intent.getComponent() != null) {
- Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(),
- intent.getComponent().getClassName()));
- // !!! 重头戏在这里,用那些注册的假的StubActivity来替换真实的Activity,以绕过检测 !!!
- this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
- }
- ActivityResult result = realExecStartActivity(who, contextThread, token, target,
- intent, requestCode, options);
- return result;
- }
- private ActivityResult realExecStartActivity(
- Context who, IBinder contextThread, IBinder token, Activity target,
- Intent intent, int requestCode, Bundle options) {
- ActivityResult result = null;
- try {
- Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class,
- int.class, Bundle.class};
- result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase,
- "execStartActivity", parameterTypes,
- who, contextThread, token, target, intent, requestCode, options);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return result;
- }
那么,是如何替换
的呢? 跟进代码:
- StubActivity
- public void markIntentIfNeeded(Intent intent) {
- if (intent.getComponent() == null) {
- return;
- }
- String targetPackageName = intent.getComponent().getPackageName();
- String targetClassName = intent.getComponent().getClassName();
- // 判断是否是启动插件的Activity
- if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {
- // 做标记
- intent.putExtra(Constants.KEY_IS_PLUGIN, true);
- // 保存真实的意图
- intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
- intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
- dispatchStubActivity(intent);
- }
- }
- /**
- * 真正的转换就在这里。根据启动模式,转换对应的 StubActivity
- */
- private void dispatchStubActivity(Intent intent) {
- ComponentName component = intent.getComponent();
- String targetClassName = intent.getComponent().getClassName();
- LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent);
- ActivityInfo info = loadedPlugin.getActivityInfo(component);
- if (info == null) {
- throw new RuntimeException("can not find " + component);
- }
- int launchMode = info.launchMode;
- // 临时替换主题
- Resources.Theme themeObj = loadedPlugin.getResources().newTheme();
- themeObj.applyStyle(info.theme, true);
- // 实际上就是这一句,完成转换
- String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj);
- Log.i(TAG, String.format("dispatchStubActivity,[%s -> %s]", targetClassName, stubActivity));
- intent.setClassName(mContext, stubActivity);
- }
继续跟进代码:
- class StubActivityInfo {
- public static final int MAX_COUNT_STANDARD = 1;
- public static final int MAX_COUNT_SINGLETOP = 8;
- public static final int MAX_COUNT_SINGLETASK = 8;
- public static final int MAX_COUNT_SINGLEINSTANCE = 8;
- public static final String corePackage = "com.didi.virtualapk.core";
- // 这个格式,就是那些假的Activity的名字
- public static final String STUB_ACTIVITY_STANDARD = "%s.A$%d";
- public static final String STUB_ACTIVITY_SINGLETOP = "%s.B$%d";
- public static final String STUB_ACTIVITY_SINGLETASK = "%s.C$%d";
- public static final String STUB_ACTIVITY_SINGLEINSTANCE = "%s.D$%d";
- public final int usedStandardStubActivity = 1;
- public int usedSingleTopStubActivity = 0;
- public int usedSingleTaskStubActivity = 0;
- public int usedSingleInstanceStubActivity = 0;
- private HashMap < String,
- String > mCachedStubActivity = new HashMap < >();
- /**
- * 在这里根据启动模式及主题构造 StubActivity
- */
- public String getStubActivity(String className, int launchMode, Theme theme) {
- String stubActivity = mCachedStubActivity.get(className);
- if (stubActivity != null) {
- return stubActivity;
- }
- TypedArray array = theme.obtainStyledAttributes(new int[] {
- android.R.attr.windowIsTranslucent,
- android.R.attr.windowBackground
- });
- boolean windowIsTranslucent = array.getBoolean(0, false);
- array.recycle();
- if (Constants.DEBUG) {
- Log.d("StubActivityInfo", "getStubActivity, is transparent theme ? " + windowIsTranslucent);
- }
- stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);
- switch (launchMode) {
- case ActivityInfo.LAUNCH_MULTIPLE:
- {
- stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);
- if (windowIsTranslucent) {
- stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, 2);
- }
- break;
- }
- case ActivityInfo.LAUNCH_SINGLE_TOP:
- {
- usedSingleTopStubActivity = usedSingleTopStubActivity % MAX_COUNT_SINGLETOP + 1;
- stubActivity = String.format(STUB_ACTIVITY_SINGLETOP, corePackage, usedSingleTopStubActivity);
- break;
- }
- case ActivityInfo.LAUNCH_SINGLE_TASK:
- {
- usedSingleTaskStubActivity = usedSingleTaskStubActivity % MAX_COUNT_SINGLETASK + 1;
- stubActivity = String.format(STUB_ACTIVITY_SINGLETASK, corePackage, usedSingleTaskStubActivity);
- break;
- }
- case ActivityInfo.LAUNCH_SINGLE_INSTANCE:
- {
- usedSingleInstanceStubActivity = usedSingleInstanceStubActivity % MAX_COUNT_SINGLEINSTANCE + 1;
- stubActivity = String.format(STUB_ACTIVITY_SINGLEINSTANCE, corePackage, usedSingleInstanceStubActivity);
- break;
- }
- default:
- break;
- }
- mCachedStubActivity.put(className, stubActivity);
- return stubActivity;
- }
- }
到这一步,就基本清晰了。同样的,既然变为了
,那么真正启动的时候还得变回来才行。来看一下重写后的
- StubActivity
方法:
- newActivity()
- @Override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException,
- IllegalAccessException,
- ClassNotFoundException {
- try {
- cl.loadClass(className);
- } catch(ClassNotFoundException e) {
- // 根据 intent 类型,去获取相应的插件
- LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
- // 这里就是从Intent中取出我们刚才保存的真正的意图
- String targetClassName = PluginUtil.getTargetActivity(intent);
- Log.i(TAG, String.format("newActivity[%s : %s]", className, targetClassName));
- if (targetClassName != null) {
- // mBase 是未替换之前的 Instrumentation 对象,所以这个实际上是交给系统原先的 Instrumentation 对象去执行,所以这个模式其实也可以理解为与动态代理等同
- // plugin.getClassLoader() 是自己构造的一个 DexClassLoader,专门用于加载对应的apk里面的类
- Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
- activity.setIntent(intent);
- try {
- // for 4.1+
- ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());
- } catch(Exception ignored) {
- // ignored.
- }
- return activity;
- }
- }
- return mBase.newActivity(cl, className, intent);
- }
到这里,插件的 Activity 启动流程分析,就基本结束了。细节方面,没法一步到位,还需要大家边看源码边理解,这样才能看得更透彻。
对于
的支持,采用动态代理 AMS,拦截
- Service
相关的请求,将其中转给 Service Runtime 去处理,Service Runtime 会接管系统的所有操作。
- Service
对于我们动态代理 AMS,在上一节 Activity 支持 中已经介绍过了,那么,简单的来看一下
是如何启动一个 Service 的。
- ActivityManagerProxy
在执行
等方法的时候,AMS 代理对象会相应的来执行以下这些方法:
- startService
- private Object startService(Object proxy, Method method, Object[] args) throws Throwable {
- IApplicationThread appThread = (IApplicationThread) args[0];
- Intent target = (Intent) args[1];
- ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
- if (null == resolveInfo || null == resolveInfo.serviceInfo) {
- // is host service
- return method.invoke(this.mActivityManager, args);
- }
- return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);
- }
- private ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
- Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
- return mPluginManager.getHostContext().startService(wrapperIntent);
- }
- private Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
- // fill in service with ComponentName
- target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));
- String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();
- // 这里进行判断,看是交给 LocalService,还是 RemoteService 处理
- // LocalService 和 RemoteService 分别对应是否在新的进程中启动Activity
- boolean local = PluginUtil.isLocalService(serviceInfo);
- Class<? extends Service> delegate = local ? LocalService.class : RemoteService.class;
- Intent intent = new Intent();
- intent.setClass(mPluginManager.getHostContext(), delegate);
- intent.putExtra(RemoteService.EXTRA_TARGET, target);
- // 保存一下这个的Command,对应执行不同操作
- intent.putExtra(RemoteService.EXTRA_COMMAND, command);
- intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);
- if (extras != null) {
- intent.putExtras(extras);
- }
- return intent;
- }
实际上包括我们调用
,AMS 代理对象最后变换后的意图,同样也是上面代码的最后两个个方法
- stopService()
和
- startDelegateServiceForTarget
,只不过 command 不一样。
- wrapperTargetIntent()
所以本质上 AMS 作为代理,不管你执行启动或者关闭插件里面的 Service,他都是调用 LocalService 或者 RemoteService 的 startService 方法,在 LocalService 或者 RemoteService 的
下,根据 command 进行相应的操作。那么我们来看一下 LocalService 的
- onStartCommand()
方法:
- onStartCommand()
- @Override 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);
- int command = intent.getIntExtra(EXTRA_COMMAND, 0);
- if (null == target || command <= 0) {
- return START_STICKY;
- }
- ComponentName component = target.getComponent();
- LoadedPlugin plugin = mPluginManager.getLoadedPlugin(component);
- switch (command) {
- case EXTRA_COMMAND_START_SERVICE:
- {
- ActivityThread mainThread = (ActivityThread) ReflectUtil.getActivityThread(getBaseContext());
- IApplicationThread appThread = mainThread.getApplicationThread();
- Service service;
- if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
- service = this.mPluginManager.getComponentsHandler().getService(component);
- } else {
- try {
- 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();
- this.mPluginManager.getComponentsHandler().rememberService(component, service);
- } catch(Throwable t) {
- return START_STICKY;
- }
- }
- service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());
- break;
- }
- case EXTRA_COMMAND_BIND_SERVICE:
- {
- ActivityThread mainThread = (ActivityThread) ReflectUtil.getActivityThread(getBaseContext());
- IApplicationThread appThread = mainThread.getApplicationThread();
- Service service = null;
- if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
- service = this.mPluginManager.getComponentsHandler().getService(component);
- } else {
- try {
- 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();
- this.mPluginManager.getComponentsHandler().rememberService(component, service);
- } catch(Throwable t) {
- t.printStackTrace();
- }
- }
- try {
- IBinder binder = service.onBind(target);
- IBinder serviceConnection = PluginUtil.getBinder(intent.getExtras(), "sc");
- IServiceConnection iServiceConnection = IServiceConnection.Stub.asInterface(serviceConnection);
- iServiceConnection.connected(component, binder);
- } catch(Exception e) {
- e.printStackTrace();
- }
- break;
- }
- case EXTRA_COMMAND_STOP_SERVICE:
- {
- Service service = this.mPluginManager.getComponentsHandler().forgetService(component);
- if (null != service) {
- try {
- service.onDestroy();
- } catch(Exception e) {
- Log.e(TAG, "Unable to stop service " + service + ": " + e.toString());
- }
- } else {
- Log.i(TAG, component + " not found");
- }
- break;
- }
- case EXTRA_COMMAND_UNBIND_SERVICE:
- {
- Service service = this.mPluginManager.getComponentsHandler().forgetService(component);
- if (null != service) {
- try {
- service.onUnbind(target);
- service.onDestroy();
- } catch(Exception e) {
- Log.e(TAG, "Unable to unbind service " + service + ": " + e.toString());
- }
- } else {
- Log.i(TAG, component + " not found");
- }
- break;
- }
- }
- return START_STICKY;
- }
很显然,在这里面才对应去控制了插件 Service 的生命周期。具体代码就留给大家分析吧~~
动态代理
,拦截 provider 相关的请求,将其中转给 Provider Runtime 去处理,Provider Runtime 会接管系统的所有操作。
- IContentProvider
我们来看一下
这个类:
- com.didi.virtualapk.internal.PluginContentResolver
- public class PluginContentResolver extends ContentResolver {
- private ContentResolver mBase;
- private PluginManager mPluginManager;
- private static Method sAcquireProvider;
- private static Method sAcquireExistingProvider;
- private static Method sAcquireUnstableProvider;
- static {
- try {
- sAcquireProvider = ContentResolver.class.getDeclaredMethod("acquireProvider", new Class[] {
- Context.class,
- String.class
- });
- sAcquireProvider.setAccessible(true);
- sAcquireExistingProvider = ContentResolver.class.getDeclaredMethod("acquireExistingProvider", new Class[] {
- Context.class,
- String.class
- });
- sAcquireExistingProvider.setAccessible(true);
- sAcquireUnstableProvider = ContentResolver.class.getDeclaredMethod("acquireUnstableProvider", new Class[] {
- Context.class,
- String.class
- });
- sAcquireUnstableProvider.setAccessible(true);
- } catch(Exception e) {
- //ignored
- }
- }
- public PluginContentResolver(Context context) {
- super(context);
- mBase = context.getContentResolver();
- mPluginManager = PluginManager.getInstance(context);
- }
- protected IContentProvider acquireProvider(Context context, String auth) {
- try {
- if (mPluginManager.resolveContentProvider(auth, 0) != null) {
- // 在这里,去 hook 一个 IContentProvider 代理对象
- return mPluginManager.getIContentProvider();
- }
- return (IContentProvider) sAcquireProvider.invoke(mBase, context, auth);
- } catch(Exception e) {
- e.printStackTrace();
- }
- return null;
- }
- // ...
- }
这个类是在构造
的时候创建的
- LoadedPlugin
对象里面的
- PluginContext
里面创建的。
- getContentResolver()
- class PluginContext extends ContextWrapper {
- private final LoadedPlugin mPlugin;
- public PluginContext(LoadedPlugin plugin) {
- super(plugin.getPluginManager().getHostContext());
- this.mPlugin = plugin;
- }
- @Override
- public ContentResolver getContentResolver() {
- // 创建代理支持
- return new PluginContentResolver(getHostContext());
- }
- }
那么,上面 Hook 的 IContentProvider 代理对象,实际上是在 PluginManager 做的。
- private void hookIContentProviderAsNeeded() {
- Uri uri = Uri.parse(PluginContentResolver.getUri(mContext));
- mContext.getContentResolver().call(uri, "wakeup", null, null);
- try {
- Field authority = null;
- Field mProvider = null;
- ActivityThread activityThread = (ActivityThread) ReflectUtil.getActivityThread(mContext);
- Map mProviderMap = (Map) ReflectUtil.getField(activityThread.getClass(), activityThread, "mProviderMap");
- Iterator iter = mProviderMap.entrySet().iterator();
- while (iter.hasNext()) {
- Map.Entry entry = (Map.Entry) iter.next();
- Object key = entry.getKey();
- Object val = entry.getValue();
- String auth;
- if (key instanceof String) {
- auth = (String) key;
- } else {
- if (authority == null) {
- authority = key.getClass().getDeclaredField("authority");
- authority.setAccessible(true);
- }
- auth = (String) authority.get(key);
- }
- if (auth.equals(PluginContentResolver.getAuthority(mContext))) {
- if (mProvider == null) {
- mProvider = val.getClass().getDeclaredField("mProvider");
- mProvider.setAccessible(true);
- }
- IContentProvider rawProvider = (IContentProvider) mProvider.get(val);
- IContentProvider proxy = IContentProviderProxy.newInstance(mContext, rawProvider);
- mIContentProvider = proxy;
- Log.d(TAG, "hookIContentProvider succeed : " + mIContentProvider);
- break;
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
这一块的内容,最好根据滴滴提供的 Demo,再来看,比较容易理解。
- Uri bookUri = Uri.parse("content://com.ryg.chapter_2.book.provider/book");
- ContentValues values = new ContentValues();
- values.put("_id", 6);
- values.put("name", "程序设计的艺术");
哈哈,作者有点懒,用了任玉刚的《Android 开发艺术探索》 改的,被发现了
官方解释是将插件中静态注册的 receiver 重新注册一遍。在代码里貌似没找到相应的支持,Demo 里也没有,或许这部分还没完成吧??
本文重点在于分析插件的
启动流程,其他包括主题、资源,并没有详细分析,因为说细了内容还是有点多了,主要是让大伙儿在阅读代码时,有个大致的方向。有疑问欢迎一起探讨哟~~
- Activity
来源: http://blog.csdn.net/yyh352091626/article/details/74852390