内容简介:Activity启动过程重点是应用进程跟AMS进行通信,处理完成后AMS再交给应用进程继续处理。需要Hook的点就是在AMS调用之前跟MAS调用完成之后。在Activity启动时,通过Instrumentation的checkStartActivityResult去检查启动的Activity的结果,如果插件的Activity未在清单文件中注册,则会抛出ActivityNotFoundException。需要解决的就是如何通过验证?需要解决的是将需要加载的插件Activity创建出来
- 将特定功能打包为插件,当用户需要使用某个特定功能时,才进行下载并开启
- 发版更灵活,可随时发版
- 组织架构更灵活,每个团队负责自身的插件开发
- 开发中调试速度更快,直接将插件推入手机运行
2、局限性
- 稳定性不够,通过hook方式,存在兼容问题
- 插件化开发如果改动过大可能就需要发版
二、Activity启动Hook点分析
Activity启动过程重点是应用进程跟AMS进行通信,处理完成后AMS再交给应用进程继续处理。需要Hook的点就是在AMS调用之前跟MAS调用完成之后。
1、execStartActivity
#Instrumentation public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, String resultWho, Intent intent, int requestCode, Bundle options, UserHandle user) { ..... try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(who); //调用AMS继续启动Activity int result = ActivityManager.getService() .startActivityAsUser(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, resultWho, requestCode, 0, null, options, user.getIdentifier()); //检查启动Activity的结果 checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } return null; } 复制代码
在Activity启动时,通过Instrumentation的checkStartActivityResult去检查启动的Activity的结果,如果插件的Activity未在清单文件中注册,则会抛出ActivityNotFoundException。需要解决的就是如何通过验证?
2、ActivityThread
#ActivityThread private class H extends Handler { ... public void handleMessage(Message msg) { if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); switch (msg.what) { case LAUNCH_ACTIVITY: { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); final ActivityClientRecord r = (ActivityClientRecord) msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo, r.compatInfo); //调用了performLaunchActivity方法 handleLaunchActivity(r, null, "LAUNCH_ACTIVITY"); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); } break; ... } ... } 复制代码
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ... //创建要启动Activity的上下文环境 ContextImpl appContext = createBaseContextForActivity(r); Activity activity = null; try { java.lang.ClassLoader cl = appContext.getClassLoader(); //用类加载器来创建Activity的实例 activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent);//1 ... } catch (Exception e) { ... } ... return activity; } 复制代码
需要解决的是将需要加载的插件Activity创建出来
三、VirtualApk原理分析
1、初始化
在Application进行初始化操作
PluginManager.getInstance(base).init(); 复制代码
在初始化操作时Hook了Instrumentation、ActivityThread的mH类的Callback、IActivityManager、DataBindingUtil
#PluginManager protected PluginManager(Context context) { ...... hookCurrentProcess(); } protected void hookCurrentProcess() { hookInstrumentationAndHandler(); hookSystemServices(); hookDataBindingUtil(); } 复制代码
(1)hookInstrumentationAndHandler
#PluginManager protected void hookInstrumentationAndHandler() { try { ActivityThread activityThread = ActivityThread.currentActivityThread(); Instrumentation baseInstrumentation = activityThread.getInstrumentation(); final VAInstrumentation instrumentation = createInstrumentation(baseInstrumentation); Reflector.with(activityThread).field("mInstrumentation").set(instrumentation); Handler mainHandler = Reflector.with(activityThread).method("getHandler").call(); Reflector.with(mainHandler).field("mCallback").set(instrumentation); this.mInstrumentation = instrumentation; Log.d(TAG, "hookInstrumentationAndHandler succeed : " + mInstrumentation); } catch (Exception e) { Log.w(TAG, e); } } public class VAInstrumentation extends Instrumentation implements Handler.Callback {......} 复制代码
- 创建VAInstrumentation,是Instrumentation的子类,实现了Handler.Callback方法
- 通过反射将VAInstrumentation设置给ActivityThread, hook住了Instrumentation
- 通过反射设置了Handler.Callback,拦截了ActivityThread的H的Callback
(2)hookSystemServices
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)); // Hook IActivityManager from ActivityManagerNative 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); } } public class ActivityManagerProxy implements InvocationHandler {......} 复制代码
- 创建了IActivityManager的动态代理对象ActivityManagerProxy
- 通过反射来替换掉AMS的代理对象IActivityManager,来接管Activity启动等操作
2、插件加载
(1)loadPlugin
一般会将某个功能插件生成jar或者apk文件,然后交给主工程通过PluginManager的loadPlugin进行加载
#PluginManager public void loadPlugin(File apk) throws Exception { ...... //将插件文件转换为一个LoadedPlugin对象 LoadedPlugin plugin = createLoadedPlugin(apk); if (null == plugin) { throw new RuntimeException("Can't load plugin which is invalid: " + apk.getAbsolutePath()); } //将插件LoadedPlugin存入 this.mPlugins.put(plugin.getPackageName(), plugin); ...... } 复制代码
(2)构建LoadedPlugin对象
#LoadedPlugin public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception { this.mPluginManager = pluginManager; this.mHostContext = context; this.mLocation = apk.getAbsolutePath(); this.mPackage = PackageParserCompat.parsePackage(context, apk, PackageParser.PARSE_MUST_BE_APK); this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData; //创建PackageInfo对象 this.mPackageInfo = new PackageInfo(); this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo; this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath(); ...... this.mPackageManager = createPluginPackageManager(); this.mPluginContext = createPluginContext(null); this.mNativeLibDir = getDir(context, Constants.NATIVE_DIR); this.mPackage.applicationInfo.nativeLibraryDir = this.mNativeLibDir.getAbsolutePath(); //创建Resource this.mResources = createResources(context, getPackageName(), apk); //创建ClassLoader this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader()); //拷贝so tryToCopyNativeLib(apk); // 缓存instrumentations Map<ComponentName, InstrumentationInfo> instrumentations = new HashMap<ComponentName, InstrumentationInfo>(); for (PackageParser.Instrumentation instrumentation : this.mPackage.instrumentation) { instrumentations.put(instrumentation.getComponentName(), instrumentation.info); } this.mInstrumentationInfos = Collections.unmodifiableMap(instrumentations); this.mPackageInfo.instrumentation = instrumentations.values().toArray(new InstrumentationInfo[instrumentations.size()]); // 缓存activities Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>(); for (PackageParser.Activity activity : this.mPackage.activities) { activity.info.metaData = activity.metaData; activityInfos.put(activity.getComponentName(), activity.info); } this.mActivityInfos = Collections.unmodifiableMap(activityInfos); this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]); // 缓存services Map<ComponentName, ServiceInfo> serviceInfos = new HashMap<ComponentName, ServiceInfo>(); for (PackageParser.Service service : this.mPackage.services) { serviceInfos.put(service.getComponentName(), service.info); } this.mServiceInfos = Collections.unmodifiableMap(serviceInfos); this.mPackageInfo.services = serviceInfos.values().toArray(new ServiceInfo[serviceInfos.size()]); // 缓存providers Map<String, ProviderInfo> providers = new HashMap<String, ProviderInfo>(); Map<ComponentName, ProviderInfo> providerInfos = new HashMap<ComponentName, ProviderInfo>(); for (PackageParser.Provider provider : this.mPackage.providers) { providers.put(provider.info.authority, provider.info); providerInfos.put(provider.getComponentName(), provider.info); } this.mProviders = Collections.unmodifiableMap(providers); this.mProviderInfos = Collections.unmodifiableMap(providerInfos); this.mPackageInfo.providers = providerInfos.values().toArray(new ProviderInfo[providerInfos.size()]); // Register broadcast receivers dynamically Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>(); for (PackageParser.Activity receiver : this.mPackage.receivers) { receivers.put(receiver.getComponentName(), receiver.info); BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance()); for (PackageParser.ActivityIntentInfo aii : receiver.intents) { this.mHostContext.registerReceiver(br, aii); } } this.mReceiverInfos = Collections.unmodifiableMap(receivers); this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]); // try to invoke plugin's application invokeApplication(); } 复制代码
创建PackageInfo、Resouces、ClassLoader对象,存储Instrumentation、Activity、Service、Content Provider等信息
(3)创建ClassLoader对象
#LoadedPlugin protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception { File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR); String dexOutputPath = dexOutputDir.getAbsolutePath(); //创建DexClassLoader用来加载插件 DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent); if (Constants.COMBINE_CLASSLOADER) { DexUtil.insertDex(loader, parent, libsDir); } return loader; } #DexUtil public static void insertDex(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception { Object baseDexElements = getDexElements(getPathList(baseClassLoader)); Object newDexElements = getDexElements(getPathList(dexClassLoader)); //将宿主自身的dex文件和插件的dex文件合并 Object allDexElements = combineArray(baseDexElements, newDexElements); Object pathList = getPathList(baseClassLoader); //通过反射将合并后的dex文件赋值给dexElements Reflector.with(pathList).field("dexElements").set(allDexElements); insertNativeLibrary(dexClassLoader, baseClassLoader, nativeLibsDir); } 复制代码
- 创建DexClassLoader对象
- 将宿主和插件Dex文件合并,并通过反射赋值给dexElements
- 然后插件中的Activity等文件就可以被加载了
3、定义占位Activity
<activity android:exported="false" android:name="com.didi.virtualapk.delegate.StubActivity" android:launchMode="standard"/> <!-- Stub Activities --> <activity android:exported="false" android:name=".A$1" android:launchMode="standard"/> <activity android:exported="false" android:name=".A$2" android:launchMode="standard" android:theme="@android:style/Theme.Translucent" /> ...... <!-- Local Service running in main process --> <service android:exported="false" android:name="com.didi.virtualapk.delegate.LocalService" /> <!-- Daemon Service running in child process --> <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> <provider android:exported="false" android:name="com.didi.virtualapk.delegate.RemoteContentProvider" android:authorities="${applicationId}.VirtualAPK.Provider" android:process=":daemon" /> 复制代码
在清单文件中定了各种启动模式的占位Activity、Service、ContentProvider
4、将插件Activity替换为占位的Activity
(1)启动Activity时会走到VAInstrumentation的execStartActivity方法
#VAInstrumentation @Override public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode) { //替换为占坑的Activity injectIntent(intent); //继续走Instrumentation的execStartActivity方法 return mBase.execStartActivity(who, contextThread, token, target, intent, requestCode); } protected void injectIntent(Intent intent) { //通过intent去匹配PluginManager中Activity的坑位 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())); // resolve intent with Stub Activity if needed this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent); } } 复制代码
(2)将插件Activity的相关信息进行存储
public void markIntentIfNeeded(Intent intent) { if (intent.getComponent() == null) { return; } String targetPackageName = intent.getComponent().getPackageName(); String targetClassName = intent.getComponent().getClassName(); // search map and return specific launchmode stub 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); } } 复制代码
(3)将插件Activity替换为占位的Activity进行启动
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); //通过launchMode等信息找到合适的占位Activity String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj); Log.i(TAG, String.format("dispatchStubActivity,[%s -> %s]", targetClassName, stubActivity)); intent.setClassName(mContext, stubActivity); } 复制代码
接下来拿着占位的Activity继续跟AMS进行通信
5、替换回目标插件的Activity
(1)VAInstrumentation接收到ApplicationThread发送的消息
#VAInstrumentation @Override public boolean handleMessage(Message msg) { if (msg.what == LAUNCH_ACTIVITY) { // ActivityClientRecord r Object r = msg.obj; try { Reflector reflector = Reflector.with(r); Intent intent = reflector.field("intent").get(); intent.setExtrasClassLoader(mPluginManager.getHostContext().getClassLoader()); //获取ActivityInfo ActivityInfo activityInfo = reflector.field("activityInfo").get(); if (PluginUtil.isIntentFromPlugin(intent)) { int theme = PluginUtil.getTheme(mPluginManager.getHostContext(), intent); if (theme != 0) { Log.i(TAG, "resolve theme, current theme:" + activityInfo.theme + " after :0x" + Integer.toHexString(theme)); //更换thme activityInfo.theme = theme; } } } catch (Exception e) { Log.w(TAG, e); } } return false; } 复制代码
(2)通过VAInstrumentation的newActivity创建一个Activity对象
#VAInstrumentation @Override public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { try { cl.loadClass(className); Log.i(TAG, String.format("newActivity[%s]", className)); } catch (ClassNotFoundException e) { //占位的Activity不存在,进入catch处理 ComponentName component = PluginUtil.getComponent(intent); if (component == null) { return newActivity(mBase.newActivity(cl, className, intent)); } //获取目标插件的Activity String targetClassName = component.getClassName(); LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component); if (plugin == null) { // Not found then goto stub activity. boolean debuggable = false; try { Context context = this.mPluginManager.getHostContext(); debuggable = (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; } catch (Throwable ex) { } if (debuggable) { throw new ActivityNotFoundException("error intent: " + intent.toURI()); } Log.i(TAG, "Not found. starting the stub activity: " + StubActivity.class); return newActivity(mBase.newActivity(cl, StubActivity.class.getName(), intent)); } //通过Instrumentation的newActivity实现目标Activity的创建 Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent); activity.setIntent(intent); // for 4.1+ Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources()); return newActivity(activity); } return newActivity(mBase.newActivity(cl, className, intent)); } 复制代码
获取目标Activity的ComponentName
#PluginUtil public static ComponentName getComponent(Intent intent) { if (intent == null) { return null; } if (isIntentFromPlugin(intent)) { return new ComponentName(intent.getStringExtra(Constants.KEY_TARGET_PACKAGE), intent.getStringExtra(Constants.KEY_TARGET_ACTIVITY)); } return intent.getComponent(); } 复制代码
获取之前存储到占位Activity的相关参数信息,并返回ComponentName,继续执行Activity启动操作
6、callActivityOnCreate
@Override public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) { injectActivity(activity); mBase.callActivityOnCreate(activity, icicle, persistentState); } protected void injectActivity(Activity activity) { final Intent intent = activity.getIntent(); if (PluginUtil.isIntentFromPlugin(intent)) { Context base = activity.getBaseContext(); try { LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent); Reflector.with(base).field("mResources").set(plugin.getResources()); Reflector reflector = Reflector.with(activity); reflector.field("mBase").set(plugin.createPluginContext(activity.getBaseContext())); reflector.field("mApplication").set(plugin.getApplication()); // set screenOrientation ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent)); if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { activity.setRequestedOrientation(activityInfo.screenOrientation); } // for native activity ComponentName component = PluginUtil.getComponent(intent); Intent wrapperIntent = new Intent(intent); wrapperIntent.setClassName(component.getPackageName(), component.getClassName()); activity.setIntent(wrapperIntent); } catch (Exception e) { Log.w(TAG, e); } } } 复制代码
设置了修改了mResources、mBase(Context)、mApplication对象,最终执行了Activity的onCreate方法
7、Activity插件化总结
(1)初始化时Hook住Instrumentation、ActivityThread.mH的Callback回调 (2)在宿主工程的清单文件中定义占位Activity (3)加载插件时,将插件dex文件和宿主dex文件合并,反射赋值给PathList的dexElements,以便被ClassLoader加载 (4)启动目标Activity过程中,VAInstrumentation将目标Activity替换为占位Activity,并将目标Activity信息作为参数存储。从而通过对Activity的校验,继而继续与AMS进行通信。 (5)AMS处理完成后,传递到ApplicationThread中,通过VAInstrumentation拦截到该消息,将占位Activity替换为目标Activity,并将目标Activity进行创建,继而进行后续操作。 (6)设置mResources、mBase(Context)、mApplication对象,最终调用到了Activity的onCreate方法
参考资料:
- 浅析Android插件化
- 360开源的插件化框架Replugin深度剖析
- 滴滴插件化方案 VirtualApk 源码解析
- 《Android插件化开发指南》
- 《Android进阶解密》
以上所述就是小编给大家介绍的《Android进阶(九)Activity插件化和VirtualApk分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- WP AMP插件漏洞分析
- Android 插件化框架 DynamicLoadApk 源码分析
- 性能分析工具SkyWalking插件开发指南
- Android插件化原理分析(基于Neptune框架)
- 每日一博 | 深入分析 源码级别解读 MyBatis 插件
- SkyWalking 源码分析 —— Agent 插件(一)之 Tomcat
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Convergence Culture
Henry Jenkins / NYU Press / 2006-08-01 / USD 30.00
"Convergence Culture" maps a new territory: where old and new media intersect, where grassroots and corporate media collide, where the power of the media producer, and the power of the consumer intera......一起来看看 《Convergence Culture》 这本书的介绍吧!