概述

滴滴出行公司的首个对外开源项目 - VirtualAPK。地址:github.com/didi/Virtua…

滴滴自行研发了这款插件化框架,功能全面、兼容性好,还能够适用于有耦合的业务插件,这就是VirtualAPK存在的意义。业内认为,在加载耦合插件方面,VirtualAPK可以说是开源方案的首选。据说滴滴打车里面已经用上了,所以还是有必要一探究竟的~~

VirtualAPK 的工作流程如图所示:

这里写图片描述

VirtualAPK 对于插件没有额外的约束,原生的 apk 即可作为一个插件。插件工程编译生成 apk 后,通过宿主 App 加载,每个插件 apk 被加载后,都会在宿主中创建一个单独的 LoadedPlugin 对象。如上图所示,通过这些 LoadedPlugin 对象,VirtualAPK 就可以管理插件并赋予插件新的意义,使其可以像手机中安装过的App一样运行。

Activity 支持

Hook ActivityManagerService

插件化支持首先要解决的一点就是插件里的Activity并未在宿主程序的 AndroidMainfest.xml 注册,常规方法肯定无法直接启动插件的Activity,这个时候就需要去了解Activity的启动流程,关于启动过程主要的几个步骤请参考:浅析Android Activity的启动过程

从上文中可知,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++) { //先查找一遍看是否存在这个activityfinal 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 最终通过 ActivityManagerNative.getDefault() 远程调用了AMS的startActivity方法, ActivityManagerNative 实际上就是 ActivityManagerService 这个远程对象的 Binder 代理对象,每次需要与AMS交互时,需要通过这个 Binder 对象完成远程IPC调用。

还不了解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;}
};

从这我们可以知道,ActivityManagerNative.getDefault() 实际上是返回了一个 IActivityManager 的单例对象。

那么,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 替换为刚创建的 activityManagerProxyReflectUtil.setField(defaultSingleton.getClass().getSuperclass(), defaultSingleton, "mInstance", activityManagerProxy);if (defaultSingleton.get() == activityManagerProxy) {// 两者一样,保存下来this.mActivityManager = activityManagerProxy;}} catch (Exception e) {e.printStackTrace();}
}

实际上除了 startActivity 是调用 AMS 的方法以外,startServicebindService 等方法,最终调用到AMS的里的方法,这个我们在动态代理类 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 binderIBinder 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;}}

所以实际上就等同于我们重写了一些 ActivityService 的相关操作。具体做些什么,后面会提到~

Hook Instrumentation

回过头去看看 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 注册,要如何才能绕过这一层检测?

前文中提到,com.didi.virtualapk.PluginManager 这个类的初始化的时候,除了 Hook 出一个 AMS 代理对象以外,还 Hook 出一个 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 里面的 instrumentationReflectUtil.setInstrumentation(activityThread, instrumentation);ReflectUtil.setHandlerCallback(this.mContext, instrumentation);this.mInstrumentation = instrumentation;} catch (Exception e) {e.printStackTrace();}
}

既然 Activity 的启动,中间走了 Instrumentation.execStartActivity 这个方法,那么我们大概可以知道,Hook 出一个 Instrumentation 对象用来做什么了,实际上就是用来帮助启动插件的 Activity

启动插件Activity

我们 Hook 了一个 VAInstrumentation 以替代系统的 Instrumentation,这样当系统通过 ActivityThread 调用 它的的成员变量 mInstrumentation 的 newActivity() 等方法的时候,实际是调用我们 VAInstrumentationnewActivity()

实际上对于插件 Activity 启动,采用的是宿主 manifest 中占坑的方式来绕过系统校验,然后再加载真正的activity。

什么是占坑?就是构造一系列假的 Activity 替身,在 AndroidMainfest.xml 里面进行注册,以绕过检测,然后到了真正启动 Activity 的时候,再把它变回,去启动真正的目标 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>

可以发现,在清单里面注册了一堆假的 StubActivity。 ABCD分别对应不同的启动模式,那么,我们启动插件的 Activity 的时候,是如何把它改为清单里面已注册的这些假的 Activity 名呢?

VAInstrumentation 里面,重写了 startActivity 的必经之路,就是 execStartActivity() 方法:

public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {// 这里面做了一系列操作,实际上就是查找插件里面第一个符合隐式条件的第一个ResolveInfo,并设置进intentmPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);// null component is an implicitly intentif (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();// 判断是否是启动插件的Activityif (!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 启动流程分析,就基本结束了。细节方面,没法一步到位,还需要大家边看源码边理解,这样才能看得更透彻。

Service 支持

对于 Service 的支持,采用动态代理AMS,拦截 Service 相关的请求,将其中转给Service Runtime去处理,Service Runtime会接管系统的所有操作。

对于我们动态代理AMS,在上一节 Activity支持 中已经介绍过了,那么,简单的来看一下 ActivityManagerProxy 是如何启动一个Service的。

在执行 startService 等方法的时候,AMS 代理对象会相应的来执行以下这些方法:

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 servicereturn 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 ComponentNametarget.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();// 这里进行判断,看是交给 LocalService,还是 RemoteService 处理// LocalService 和 RemoteService 分别对应是否在新的进程中启动Activityboolean 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;
}

实际上包括我们调用 stopService(),AMS 代理对象最后变换后的意图,同样也是上面代码的最后两个个方法 startDelegateServiceForTargetwrapperTargetIntent(),只不过 command 不一样。

所以本质上 AMS 作为代理,不管你执行启动或者关闭插件里面的 Service,他都是调用 LocalService 或者 RemoteService 的 startService 方法,在 LocalService 或者 RemoteService 的 onStartCommand() 下,根据 command 进行相应的操作。那么我们来看一下 LocalService 的 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的生命周期。具体代码就留给大家分析吧~~

ContentProvider 支持

动态代理 IContentProvider,拦截provider相关的请求,将其中转给Provider Runtime去处理,Provider Runtime会接管系统的所有操作。

我们来看一下 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;}@Overridepublic 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 支持

官方解释是将插件中静态注册的receiver重新注册一遍。在代码里貌似没找到相应的支持,Demo 里也没有,或许这部分还没完成吧??

小结

本文重点在于分析插件的 Activity 启动流程,其他包括主题、资源,并没有详细分析,因为说细了内容还是有点多了,主要是让大伙儿在阅读代码时,有个大致的方向。有疑问欢迎一起探讨哟~~

感谢阅读!

原文链接:blog.csdn.net

滴滴开源Android插件化框架VirtualAPK原理分析相关推荐

  1. 滴滴开源Android插件框架

     登录 | 注册 收藏成功 确定 收藏失败,请重新收藏 确定 查看所有私信查看所有通知 暂没有新通知 返回通知列表 下一条 上一条 分享资讯传PPT/文档提问题写博客传资源创建项目创建代码片 wz ...

  2. 淘宝开源Android容器化框架Atlas开发者指南

    Atlas 由阿里巴巴移动团队自研,以容器化思路解决大规模团队协作问题,实现并行开发.快速迭代和动态部署,适用于 Android 4.x 以上系统版本的大小型 App 开发. 该框架于2017年3月1 ...

  3. Android 插件化框架DroidPlugin

    上一次项目迭代中,接触到了插件化框架. 使用场景:我们的app需要集成某一直播app.即在不安装第三方直播app到手机的情况下,点击我们app内部的某一连接能跳转到直播app中,运行里面原有的所有功能 ...

  4. 解密Android插件化的实现原理

    插件化技术在国内已经是相对比较完善,而且也有比较广泛的应用了,各种插件化的开源框架也是层出不穷.虽然我个人是不太喜欢这种钻操作系统空子,或者可以说是和原生系统对着干的黑科技,但是在国内特殊的环境下,插 ...

  5. 开源Android容器化框架Atlas开发者指南

    课程介绍 随着手机淘宝业务的快速增长,协作研发团队的不断扩大,技术也面临着更多挑战:一方面代码量快速上升导致方法过多,限制打包的正常输出:另一个方面各业务线的开发和集成都需要到一个 apk上,业务间相 ...

  6. Android插件化之DroidPlugin原理解析

    文章目录 DroidPlugin原理解析 插件包安装 插件包解析和加载 插件Activity启动解析 插件service启动分析 插件receiver分析 插件provider分析 插件加载独立性 插 ...

  7. VirtualAPK:滴滴 Android 插件化的实践之路

    一.前言 在 Android 插件化技术日新月异的今天,开发并落地一款插件化框架到底是简单还是困难,这个问题不同人会有不同的答案.但是我相信,完成一个插件化框架的 Demo 并不是多难的事儿,然而要开 ...

  8. Android 插件化原理 完胜360插件框架 技术实战

    性能优化 Android 性能优化 (一)APK高效瘦身 http://blog.csdn.net/whb20081815/article/details/70140063 Android 性能优化 ...

  9. 《Android插件化技术——原理篇》

    | 导语 插件化技术最早从2012年诞生至今,已经走过了5个年头.从最初只支持Activity的动态加载发展到可以完全模拟app运行时的沙箱系统,各种开源项目层出不穷,在此挑选了几个代表性的框架,总结 ...

最新文章

  1. 江苏“超牛”女博导:16岁考入北大,跨专业读博,成为全球第4个获此奖项的学者!...
  2. Microsoft Azure Express Route (专线直连)到云端数据中心(2)
  3. 三分频的Verilog实现
  4. python3 读写json文件,python3没有读取JSON文件righ
  5. IDC 监控技术介绍
  6. 《多媒体技术与应用》练习题
  7. Windows 上安装vmware到Ubuntu到python到umake到Pip 到pycharm
  8. 百度网盘目录搜索引擎上线了 百度网盘目录搭建系统
  9. 电脑常见问题之-右键无新建文件夹选项
  10. python怎么编写口算题_用Python开发小学二年级口算自动出题程序
  11. PCAN-USB FD选型使用比较
  12. Android 安卓修改程序图标
  13. Zabbix -- 客户端安装以及如何添加数据库的动态监控视图
  14. 有关振动试验夹具的问题
  15. 给折腾ramdisk的朋友们一点建议
  16. java 枚举 总结,java枚举总结
  17. 矩形广告是什么?矩形广告如何设置?
  18. 什么是决策支持系统?
  19. mysql月份加减_MySQL 日期加减
  20. 《灰色系统理论及其应用》第7版 刘思峰 P193 9.3基于Captuo模型分数阶导数的灰色模型

热门文章

  1. 判断字符串NSString是否是整形或者浮点型
  2. 现代谱估计:多窗口谱
  3. liblbfgs简介
  4. 以LeNet为例分析CNN中的参数量
  5. 科大星云诗社动态20210819
  6. 通过History Trends Unlimited通过统计服务器上Chrome浏览器Top10网页历史访问量(2021.11.23)
  7. [MATLAB粒子模拟笔记]初始化半个时间步的位置
  8. 编写代码的若干个基本规则(以Java为例)
  9. 如何利用Delphi释放所占的内存
  10. delphi编码规范文档