摘要:方法,是一個(gè)對(duì)象是從構(gòu)造函數(shù)中賦值。上面我們分析到會(huì)執(zhí)行構(gòu)造函數(shù),在構(gòu)造函數(shù)會(huì)將的賦值給的。傳入的是返回對(duì)象也是繼承,其是。參考插件化技術(shù)原理篇中詳解你所不知道的更深層次的理解
Android插件化在國(guó)內(nèi)已不再是幾個(gè)巨頭公司團(tuán)隊(duì)在玩了,陸續(xù)有團(tuán)隊(duì)開(kāi)源其解決方案,例如 Small,VirtualAPK,RePlugin,Atlas,甚至Lody開(kāi)發(fā)的VirtualApp。另外我司也在玩,方案與Replugin類(lèi)似。
借用Atlas Github上的總結(jié),Android上動(dòng)態(tài)加載方案,始終都繞不過(guò)三個(gè)關(guān)鍵的點(diǎn):
動(dòng)態(tài)加載資源
動(dòng)態(tài)加載class
處理四大組件 能夠讓動(dòng)態(tài)代碼中的四大組件在Android上正常跑起來(lái)
本文詳解如何Hook Resource,追溯Application,Activity,Service和Broadcast是如何與Resource綁定的。
Resource使用追溯插件化Resource Hook有兩種解決方案[1]:
合并式:addAssetPath時(shí)加入所有插件和主工程的路徑
獨(dú)立式:各個(gè)插件只添加自己apk路徑
合并式解決資源沖突有重寫(xiě)appt,arsc文件等方案,獨(dú)立式一個(gè)典型的實(shí)現(xiàn)是Replugin,資源要通過(guò)提供的API來(lái)共享訪問(wèn)。
本文分析的是合并方式。獨(dú)立放至另一篇文章分析。另外本文的源碼均摘至7.0Android系統(tǒng)源碼。
獲取resource不外乎在Application,Activity,Service和Broadcast中通過(guò)getResource方法,而這幾個(gè)場(chǎng)景都會(huì)走到ContextImpl類(lèi)中[2]
public class ContextWrapper extends Context { Context mBase; //mBase是ContextImpl實(shí)例 public ContextWrapper(Context base) { mBase = base; } @Override public Resources getResources() { return mBase.getResources(); } }
到這里,我們看到Resource都是在ContextImpl實(shí)例中獲取的?,F(xiàn)在我們要考慮Application,Activity,Service和Broadcast是在什么時(shí)機(jī)注入ContextImpl實(shí)例的,以及Resource實(shí)例如何注入ContextImpl中。
Application與mBase關(guān)聯(lián)流程分析下面我們來(lái)倒推Application與ContextImpl關(guān)聯(lián)流程
//ContextWrapper的attachBaseContext方法關(guān)聯(lián)了mBase,這里的mBase就是ContextImpl實(shí)例,我們往下看 protected void attachBaseContext(Context base) { if (mBase != null) { throw new IllegalStateException("Base context already set"); } mBase = base; }
//Application的attach方法調(diào)用了attachBaseContext方法,和context關(guān)聯(lián)了,這里的context就是ContextImpl實(shí)例,我們往下看 /* package */ final void attach(Context context) { attachBaseContext(context); mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; }
public class Instrumentation { //LoadedApk.makeApplication會(huì)調(diào)用 public Application newApplication(ClassLoader cl, String className, Context context) throws InstantiationException, IllegalAccessException, ClassNotFoundException { return newApplication(cl.loadClass(className), context); } //Application在這里被創(chuàng)建 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被創(chuàng)建了,并通過(guò)Instrumentation.newApplication與Application關(guān)聯(lián)起來(lái)了 //另外這里createAppContext是ContextImpl的mResource與LoadApk的mResource關(guān)聯(lián)的核心代碼 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) { // ...... // 獲取應(yīng)用信息LoadedApk data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); // 實(shí)例化Application Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; } }
從上面的倒推代碼調(diào)用,了解了Application與ContextImpl的關(guān)聯(lián)時(shí)機(jī)?,F(xiàn)在來(lái)分析正序的代碼調(diào)用流程
Luancher APP處理點(diǎn)擊,會(huì)調(diào)用到AMS。ActivityManagerService發(fā)送BIND_APPLICATION消息致ActivityThread,ActivityThread.handleBindApplication中調(diào)用了LoadedApk.makeApplication方法
ActivityThread.makeApplication方法創(chuàng)建了ContextImpl實(shí)例,并作為參數(shù)調(diào)用Instrumentation.newApplication方法
Instrumentation.newApplication方法完成Application實(shí)例創(chuàng)建,并在application.attach方法完成Application實(shí)例與ContextImpl的關(guān)聯(lián)
當(dāng)然,這只是正向的代碼分析流程,具體細(xì)節(jié)和各版本差異會(huì)有所不同。
mBase與Resource關(guān)聯(lián)流程分析上面流程分析到ContextImpl.createAppContext方法是ContextImpl實(shí)例的mResource與LoadApk實(shí)例的mResource關(guān)聯(lián)的核心代碼,接下來(lái)我們看下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創(chuàng)建Resources 實(shí)例 Resources resources = packageInfo.getResources(mainThread); //... mResources = resources; //... } }
//LoadedApk類(lèi) 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, 這樣就向跨宿主和插件資源訪問(wèn)前進(jìn)了一步。
資源合并流程分析如何將插件的資源與宿主合并,照舊,我們先來(lái)逆向分析代碼調(diào)用.
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等方法獲取資源類(lèi)似,都會(huì)調(diào)用mAssets。getResourcexxx方法,mAssets是一個(gè)AssetManager對(duì)象是從Resource構(gòu)造函數(shù)中賦值。如以下代碼
/** * 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入?yún)⒁酝獾膮?shù),AssetManager有一個(gè)關(guān)鍵方法 addAssetPath,可以把額外的apk或目錄的資源加入到AssetManager實(shí)例中。并且額外的一個(gè)關(guān)鍵點(diǎn),AssetManager是一個(gè)單例。
/** * 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單例加入插件的資源或宿主的資源,那資源共享就解決了一大半。
資源共享另一半問(wèn)題是我們要解決資源id突沖問(wèn)題,這篇我們不細(xì)說(shuō),解決方案目前有重寫(xiě)aapt,arsc等方案。
前面我們看到ContextWrapper是在attachBaseContext中關(guān)聯(lián)ContextImpl對(duì)象的。先看下Activity.attachBaseContext在什么方法中調(diào)用。
//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方法執(zhí)行了attachBaseContext。Instrumentation管理Activity創(chuàng)建和生命周期回調(diào)。下面看下Instrumentation.performLaunchActivity方法。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { //...... Activity activity = null; //...... activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); //...... //createBaseContextForActivity返回了ContextImpl實(shí)例 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實(shí)例 ContextImpl appContext = ContextImpl.createActivityContext( this, r.packageInfo, displayId, r.overrideConfig); appContext.setOuterContext(activity); Context baseContext = appContext; //..... return baseContext; }
轉(zhuǎn)至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構(gòu)造函數(shù)會(huì)將LoadApk的mResource賦值給ContextImpl的mResource。至此,我們可以確認(rèn)Activity和Application一樣,mBase.mResource就是LoadApk的mResource。
Service與mBase關(guān)聯(lián)代碼分析Service與Activity類(lèi)似,Service.attach在ActivityThread.handleCreateService調(diào)用。
//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會(huì)執(zhí)行構(gòu)造函數(shù),在構(gòu)造函數(shù)會(huì)將LoadedApk的mResource賦值給ContextImpl的mResource。至此,我們可以確認(rèn)Service和Application一樣,mBase.mResource就是LoadApk的mResource。
Broadcast與mBase關(guān)聯(lián)代碼分析Broadcast與Service類(lèi)似,Broadcast.onReceive在ActivityThread.handleReceiver調(diào)用。
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返回對(duì)象 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。
總結(jié)至此,我們看到Application,Activity,Service和Broadcast均會(huì)通過(guò)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插件化技術(shù)——原理篇》: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...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/68370.html
摘要:什么樣的對(duì)象容易找到靜態(tài)變量和單例。在一個(gè)進(jìn)程之內(nèi),靜態(tài)變量和單例變量是相對(duì)不容易發(fā)生變化的,因此非常容易定位,而普通的對(duì)象則要么無(wú)法標(biāo)志,要么容易改變。 前言 為了實(shí)現(xiàn) App 的快速迭代更新,基于 H5 Hybrid 的解決方案有很多,由于 webview 本身的性能問(wèn)題,也隨之出現(xiàn)了很多基于 JS 引擎實(shí)現(xiàn)的原生渲染的方案,例如 React Native、weex 等,而國(guó)內(nèi)一線...
閱讀 3268·2021-10-27 14:20
閱讀 2536·2021-10-08 10:05
閱讀 1635·2021-09-09 09:33
閱讀 2909·2019-08-30 13:16
閱讀 1445·2019-08-29 18:34
閱讀 1180·2019-08-29 10:58
閱讀 1233·2019-08-28 18:22
閱讀 1231·2019-08-26 13:33