摘要:方法,是一个对象是从构造函数中赋值。上面我们分析到会执行构造函数,在构造函数会将的赋值给的。传入的是返回对象也是继承,其是。参考插件化技术原理篇中详解你所不知道的更深层次的理解
Android插件化在国内已不再是几个巨头公司团队在玩了,陆续有团队开源其解决方案,例如 Small,VirtualAPK,RePlugin,Atlas,甚至Lody开发的VirtualApp。另外我司也在玩,方案与Replugin类似。
借用Atlas Github上的总结,Android上动态加载方案,始终都绕不过三个关键的点:
动态加载资源
动态加载class
处理四大组件 能够让动态代码中的四大组件在Android上正常跑起来
本文详解如何Hook Resource,追溯Application,Activity,Service和Broadcast是如何与Resource绑定的。
Resource使用追溯插件化Resource Hook有两种解决方案[1]:
合并式:addAssetPath时加入所有插件和主工程的路径
独立式:各个插件只添加自己apk路径
合并式解决资源冲突有重写appt,arsc文件等方案,独立式一个典型的实现是Replugin,资源要通过提供的API来共享访问。
本文分析的是合并方式。独立放至另一篇文章分析。另外本文的源码均摘至7.0Android系统源码。
获取resource不外乎在Application,Activity,Service和Broadcast中通过getResource方法,而这几个场景都会走到ContextImpl类中[2]
public class ContextWrapper extends Context { Context mBase; //mBase是ContextImpl实例 public ContextWrapper(Context base) { mBase = base; } @Override public Resources getResources() { return mBase.getResources(); } }
到这里,我们看到Resource都是在ContextImpl实例中获取的。现在我们要考虑Application,Activity,Service和Broadcast是在什么时机注入ContextImpl实例的,以及Resource实例如何注入ContextImpl中。
Application与mBase关联流程分析下面我们来倒推Application与ContextImpl关联流程
//ContextWrapper的attachBaseContext方法关联了mBase,这里的mBase就是ContextImpl实例,我们往下看 protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = base; }
//Application的attach方法调用了attachBaseContext方法,和context关联了,这里的context就是ContextImpl实例,我们往下看 /* package */ final void attach(Context context) { attachBaseContext(context); mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; }
public class Instrumentation { //LoadedApk.makeApplication会调用 public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return newApplication(cl.loadClass(className), context); } //Application在这里被创建 static public Application newApplication(Class> clazz, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { Application app = (Application)clazz.newInstance(); app.attach(context); return app; } }
public final class LoadedApk { public Application makeApplication(boolean forceDefaultAppClass,Instrumentation instrumentation) { // ...... //这里终于看到ContextImpl被创建了,并通过Instrumentation.newApplication与Application关联起来了 //另外这里createAppContext是ContextImpl的mResource与LoadApk的mResource关联的核心代码 ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext); // ...... mApplication = app; // ...... return app; } }
public final class ActivityThread { private void handleBindApplication(AppBindData data) { // ...... // 获取应用信息LoadedApk data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); // 实例化Application Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; } }
从上面的倒推代码调用,了解了Application与ContextImpl的关联时机。现在来分析正序的代码调用流程
Luancher APP处理点击,会调用到AMS。ActivityManagerService发送BIND_APPLICATION消息致ActivityThread,ActivityThread.handleBindApplication中调用了LoadedApk.makeApplication方法
ActivityThread.makeApplication方法创建了ContextImpl实例,并作为参数调用Instrumentation.newApplication方法
Instrumentation.newApplication方法完成Application实例创建,并在application.attach方法完成Application实例与ContextImpl的关联
当然,这只是正向的代码分析流程,具体细节和各版本差异会有所不同。
mBase与Resource关联流程分析上面流程分析到ContextImpl.createAppContext方法是ContextImpl实例的mResource与LoadApk实例的mResource关联的核心代码,接下来我们看下createAppContext方法
class ContextImpl extends Context { static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); return new ContextImpl(null, mainThread, packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY); } private ContextImpl(ContextImpl container, ActivityThread mainThread, LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags, Display display, Configuration overrideConfiguration, int createDisplayWithId) { //... //从LoadApk创建Resources 实例 Resources resources = packageInfo.getResources(mainThread); //... mResources = resources; //... } }
//LoadedApk类 public Resources getResources(ActivityThread mainThread) { if (mResources == null) { mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this); } return mResources; }
从上面分析得知,如果我们把LoadedApk.mResource Hook成我们的插件框架Resource, 这样就向跨宿主和插件资源访问前进了一步。
资源合并流程分析如何将插件的资源与宿主合并,照旧,我们先来逆向分析代码调用.
public class Resources { public CharSequence getText(@StringRes int id) throws NotFoundException { CharSequence res = mAssets.getResourceText(id); //...... } public String[] getStringArray(@ArrayRes int id) throws NotFoundException { String[] res = mAssets.getResourceStringArray(id); //...... } }
与getText,getStringArray等方法获取资源类似,都会调用mAssets。getResourcexxx方法,mAssets是一个AssetManager对象是从Resource构造函数中赋值。如以下代码
/** * Create a new Resources object on top of an existing set of assets in an * AssetManager. * * @param assets Previously created AssetManager. * @param metrics Current display metrics to consider when * selecting/computing resource values. * @param config Desired device configuration to consider when * selecting/computing resource values (optional). */ public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) { this(assets, metrics, config, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO); } /** * Creates a new Resources object with CompatibilityInfo. * * @param assets Previously created AssetManager. * @param metrics Current display metrics to consider when * selecting/computing resource values. * @param config Desired device configuration to consider when * selecting/computing resource values (optional). * @param compatInfo this resource"s compatibility info. Must not be null. * @hide */ public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config, CompatibilityInfo compatInfo) { mAssets = assets; mMetrics.setToDefaults(); if (compatInfo != null) { mCompatibilityInfo = compatInfo; } updateConfiguration(config, metrics); assets.ensureStringBlocks(); }
我们先忽略除assets入参以外的参数,AssetManager有一个关键方法 addAssetPath,可以把额外的apk或目录的资源加入到AssetManager实例中。并且额外的一个关键点,AssetManager是一个单例。
/** * Add an additional set of assets to the asset manager. This can be * either a directory or ZIP file. Not for use by applications. Returns * the cookie of the added asset, or 0 on failure. * {@hide} */ public final int addAssetPath(String path) { synchronized (this) { int res = addAssetPathNative(path); makeStringBlocks(mStringBlocks); return res; } }
分析到这里,我们可以想下,如果我们把AssetManager单例加入插件的资源或宿主的资源,那资源共享就解决了一大半。
资源共享另一半问题是我们要解决资源id突冲问题,这篇我们不细说,解决方案目前有重写aapt,arsc等方案。
前面我们看到ContextWrapper是在attachBaseContext中关联ContextImpl对象的。先看下Activity.attachBaseContext在什么方法中调用。
//Activity.attach方法 final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor) { attachBaseContext(context); //..... }
从代码看到,Activity.attach方法执行了attachBaseContext。Instrumentation管理Activity创建和生命周期回调。下面看下Instrumentation.performLaunchActivity方法。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { //...... Activity activity = null; //...... activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); //...... //createBaseContextForActivity返回了ContextImpl实例 Context appContext = createBaseContextForActivity(r, activity); //...... activity.attach(appContext, this, getInstrumentation(), r.token, r.ident, app, r.intent, r.activityInfo, title, r.parent, r.embeddedID, r.lastNonConfigurationInstances, config, r.referrer, r.voiceInteractor); //...... return activity; }
Instrumentation.createBaseContextForActivity方法
private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) { //..... //ContextImpl.createActivityContext返回了ContextImpl实例 ContextImpl appContext = ContextImpl.createActivityContext( this, r.packageInfo, displayId, r.overrideConfig); appContext.setOuterContext(activity); Context baseContext = appContext; //..... return baseContext; }
转至ContextImpl.createActivityContext方法
static ContextImpl createActivityContext(ActivityThread mainThread, LoadedApk packageInfo, int displayId, Configuration overrideConfiguration) { if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); return new ContextImpl(null, mainThread, packageInfo, null, null, false, null, overrideConfiguration, displayId); }
上面我们分析到ContextImpl构造函数会将LoadApk的mResource赋值给ContextImpl的mResource。至此,我们可以确认Activity和Application一样,mBase.mResource就是LoadApk的mResource。
Service与mBase关联代码分析Service与Activity类似,Service.attach在ActivityThread.handleCreateService调用。
//ActivityThread.handleCreateService private void handleCreateService(CreateServiceData data) { //...... service = (Service) cl.loadClass(data.info.name).newInstance(); //...... ContextImpl context = ContextImpl.createAppContext(this, packageInfo); context.setOuterContext(service); Application app = packageInfo.makeApplication(false, mInstrumentation); service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault()); //...... }
上面我们分析到ContextImpl.createAppContext会执行构造函数,在构造函数会将LoadedApk的mResource赋值给ContextImpl的mResource。至此,我们可以确认Service和Application一样,mBase.mResource就是LoadApk的mResource。
Broadcast与mBase关联代码分析Broadcast与Service类似,Broadcast.onReceive在ActivityThread.handleReceiver调用。
private void handleReceiver(ReceiverData data) { //...... BroadcastReceiver receiver; try { java.lang.ClassLoader cl = packageInfo.getClassLoader(); data.intent.setExtrasClassLoader(cl); data.intent.prepareToEnterProcess(); data.setExtrasClassLoader(cl); receiver = (BroadcastReceiver)cl.loadClass(component).newInstance(); } catch (Exception e) { //...... } //...... ContextImpl context = (ContextImpl)app.getBaseContext(); sCurrentBroadcastIntent.set(data.intent); receiver.setPendingResult(data); //receiver.onReceive传入的是ContextImpl.getReceiverRestrictedContext返回对象 receiver.onReceive(context.getReceiverRestrictedContext(), data.intent); //...... }
//ContextImpl.getReceiverRestrictedContext final Context getReceiverRestrictedContext() { if (mReceiverRestrictedContext != null) { return mReceiverRestrictedContext; } return mReceiverRestrictedContext = new ReceiverRestrictedContext(getOuterContext()); }
ReceiverRestrictedContext也是继承ContextWrapper,其mBase是Application。
总结至此,我们看到Application,Activity,Service和Broadcast均会通过LoadedApk.mResource去获取资源,我们只要HOOK LoadedApk的mResource替换我们的Resource即可。比如VirtualApk[4]的处理。
//ResourcesManager.hookResources public static void hookResources(Context base, Resources resources) { try { ReflectUtil.setField(base.getClass(), base, "mResources", resources); Object loadedApk = ReflectUtil.getPackageInfo(base); ReflectUtil.setField(loadedApk.getClass(), loadedApk, "mResources", resources); //...... }参考
[1] 《Android插件化技术——原理篇》:https://mp.weixin.qq.com/s/Uw...
[2] Android中Context详解 ---- 你所不知道的Context:http://blog.csdn.net/qinjunin...
[3] 更深层次的理解Context:http://www.jcodecraeer.com/a/...
[4] VirtualApk:https://github.com/didi/Virtu...
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/68370.html
摘要:什么样的对象容易找到静态变量和单例。在一个进程之内,静态变量和单例变量是相对不容易发生变化的,因此非常容易定位,而普通的对象则要么无法标志,要么容易改变。 前言 为了实现 App 的快速迭代更新,基于 H5 Hybrid 的解决方案有很多,由于 webview 本身的性能问题,也随之出现了很多基于 JS 引擎实现的原生渲染的方案,例如 React Native、weex 等,而国内一线...
阅读 3241·2021-10-27 14:20
阅读 2524·2021-10-08 10:05
阅读 1624·2021-09-09 09:33
阅读 2901·2019-08-30 13:16
阅读 1434·2019-08-29 18:34
阅读 1168·2019-08-29 10:58
阅读 1227·2019-08-28 18:22
阅读 1225·2019-08-26 13:33