摘要:方法,是一個對象是從構造函數中賦值。上面我們分析到會執行構造函數,在構造函數會將的賦值給的。傳入的是返回對象也是繼承,其是。參考插件化技術原理篇中詳解你所不知道的更深層次的理解
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...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/68370.html
摘要:什么樣的對象容易找到靜態變量和單例。在一個進程之內,靜態變量和單例變量是相對不容易發生變化的,因此非常容易定位,而普通的對象則要么無法標志,要么容易改變。 前言 為了實現 App 的快速迭代更新,基于 H5 Hybrid 的解決方案有很多,由于 webview 本身的性能問題,也隨之出現了很多基于 JS 引擎實現的原生渲染的方案,例如 React Native、weex 等,而國內一線...
閱讀 3242·2021-10-27 14:20
閱讀 2525·2021-10-08 10:05
閱讀 1625·2021-09-09 09:33
閱讀 2902·2019-08-30 13:16
閱讀 1435·2019-08-29 18:34
閱讀 1170·2019-08-29 10:58
閱讀 1228·2019-08-28 18:22
閱讀 1226·2019-08-26 13:33