【Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )
Android 插件化系列文章目錄
【Android 插件化】插件化簡介 ( 組件化與插件化 )
【Android 插件化】插件化原理 ( JVM 內(nèi)存數(shù)據(jù) | 類加載流程 )
【Android 插件化】插件化原理 ( 類加載器 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 原理與實現(xiàn)思路 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 類加載器創(chuàng)建 | 資源加載 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 注入上下文的使用 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 獲取插件入口 Activity 組件 | 加載插件 Resources 資源 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 運(yùn)行應(yīng)用 | 代碼整理 )
【Android 插件化】Hook 插件化框架 ( Hook 技術(shù) | 代理模式 | 靜態(tài)代理 | 動態(tài)代理 )
【Android 插件化】Hook 插件化框架 ( Hook 實現(xiàn)思路 | Hook 按鈕點擊事件 )
【Android 插件化】Hook 插件化框架 ( Hook Activity 啟動過程 | 靜態(tài)代理 )
【Android 插件化】Hook 插件化框架 ( 從 Hook 應(yīng)用角度分析 Activity 啟動流程 一 | Activity 進(jìn)程相關(guān)源碼 )
【Android 插件化】Hook 插件化框架 ( 從 Hook 應(yīng)用角度分析 Activity 啟動流程 二 | AMS 進(jìn)程相關(guān)源碼 | 主進(jìn)程相關(guān)源碼 )
【Android 插件化】Hook 插件化框架 ( hook 插件化原理 | 插件包管理 )
【Android 插件化】Hook 插件化框架 ( 通過反射獲取 “插件包“ 中的 Element[] dexElements )
【Android 插件化】Hook 插件化框架 ( 通過反射獲取 “宿主“ 應(yīng)用中的 Element[] dexElements )
【Android 插件化】Hook 插件化框架 ( 合并 “插件包“ 與 “宿主“ 中的 Element[] dexElements | 設(shè)置合并后的 Element[] 數(shù)組 )
【Android 插件化】Hook 插件化框架 ( 創(chuàng)建插件應(yīng)用 | 拷貝插件 APK | 初始化插件包 | 測試插件 DEX 字節(jié)碼 )
【Android 插件化】Hook 插件化框架 ( Hook Activity 啟動流程 | Hook 點分析 )
【Android 插件化】Hook 插件化框架 ( Hook Activity 啟動流程 | 反射獲取 IActivityManager 對象 )
【Android 插件化】Hook 插件化框架 ( Hook Activity 啟動流程 | AMS 啟動前使用動態(tài)代理替換掉插件 Activity 類 )
【Android 插件化】Hook 插件化框架 ( Hook Activity 啟動流程 | 主線程創(chuàng)建 Activity 實例之前使用插件 Activity 類替換占位的組件 )
【Android 插件化】Hook 插件化框架 ( 反射工具類 | 反射常用操作整理 )
【Android 插件化】Hook 插件化框架 ( 插件包資源加載 )
【Android 插件化】Hook 插件化框架 ( 從源碼角度分析加載資源流程 | Hook 點選擇 | 資源沖突解決方案 )
文章目錄
- Android 插件化系列文章目錄
- 前言
- 一、從源碼角度分析加載資源流程
- 1、ActivityThread 入口
- 2、LaunchActivityItem
- 3、ActivityThread.performLaunchActivity
- 4、ContextImpl
- 二、Hook 點選擇
- 三、資源沖突解決方案
- 四、博客資源
前言
在之前的博客 【Android 插件化】Hook 插件化框架 ( 插件包資源加載 ) 中 , 實現(xiàn)了從插件包中獲取資源 ;
但是這種方法對代碼的侵入性較大 , 使用這種方式開發(fā) , 插件應(yīng)用 和 宿主應(yīng)用 , 都需要對 Resources 進(jìn)行特別處理 , 如重寫 Activity 和 Application 的 public Resources getResources() 方法 ;
最好是使用 Hook 方式加載資源文件 , 實現(xiàn)插件包代碼 0 侵入 , 開發(fā)插件應(yīng)用 與 開發(fā)普通應(yīng)用 , 基本一致 ;
一、從源碼角度分析加載資源流程
在插件包中的 Activity , 如果加載 R.layout.activity_main , 拿到的是 “宿主” 應(yīng)用中的資源 , 無法拿到插件包中的資源 ;
1、ActivityThread 入口
在 【Android 插件化】Hook 插件化框架 ( Hook Activity 啟動流程 | 主線程創(chuàng)建 Activity 實例之前使用插件 Activity 類替換占位的組件 ) 博客中 , 分析了 Activity 在主線程啟動前的一些操作 ;
在 ActivityThread 中的 mH 處理 EXECUTE_TRANSACTION 信號時 , 其中 ClientTransaction 中有 LaunchActivityItem ;
public final class ActivityThread extends ClientTransactionHandler {final H mH = new H();/** Reference to singleton {@link ActivityThread} */private static volatile ActivityThread sCurrentActivityThread;class H extends Handler {public static final int EXECUTE_TRANSACTION = 159;public void handleMessage(Message msg) {if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));switch (msg.what) {case EXECUTE_TRANSACTION:final ClientTransaction transaction = (ClientTransaction) msg.obj;mTransactionExecutor.execute(transaction);if (isSystem()) {// Client transactions inside system process are recycled on the client side// instead of ClientLifecycleManager to avoid being cleared before this// message is handled.transaction.recycle();}// TODO(lifecycler): Recycle locally scheduled transactions.break;}}} }源碼路徑 : /frameworks/base/core/java/android/app/ActivityThread.java
2、LaunchActivityItem
LaunchActivityItem 中的 execute 方法中的 ClientTransactionHandler client 參數(shù) 就是 ActivityThread ;
public class LaunchActivityItem extends ClientTransactionItem {@Overridepublic void execute(ClientTransactionHandler client, IBinder token,PendingTransactionActions pendingActions) {Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,mPendingResults, mPendingNewIntents, mIsForward,mProfilerInfo, client);client.handleLaunchActivity(r, pendingActions, null /* customIntent */);Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);} }源碼路徑 : /frameworks/base/core/java/android/app/servertransaction/LaunchActivityItem.java
3、ActivityThread.performLaunchActivity
在 LaunchActivityItem 的 execute 方法中 , 調(diào)用 client.handleLaunchActivity 方法就是執(zhí)行的 ActivityThread 的 handleLaunchActivity 方法 ;
在 ActivityThread 中的 handleLaunchActivity 方法中 , 調(diào)用了 performLaunchActivity 方法 ;
在 ActivityThread 中的 performLaunchActivity 方法中 , 調(diào)用
ContextImpl appContext = createBaseContextForActivity(r);方法 , 創(chuàng)建了 ContextImpl 對象 ;
public final class ActivityThread extends ClientTransactionHandler {private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {final int displayId;try {displayId = ActivityManager.getService().getActivityDisplayId(r.token);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();// For debugging purposes, if the activity's package name contains the value of// the "debug.use-second-display" system property as a substring, then show// its content on a secondary display if there is one.String pkgName = SystemProperties.get("debug.second-display.pkg");if (pkgName != null && !pkgName.isEmpty()&& r.packageInfo.mPackageName.contains(pkgName)) {for (int id : dm.getDisplayIds()) {if (id != Display.DEFAULT_DISPLAY) {Display display =dm.getCompatibleDisplay(id, appContext.getResources());appContext = (ContextImpl) appContext.createDisplayContext(display);break;}}}return appContext;} }源碼路徑 : /frameworks/base/core/java/android/app/ActivityThread.java
4、ContextImpl
在 ActivityThread 中調(diào)用了
ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);方法 , 創(chuàng)建了 ContextImpl ;
下面分析 ContextImpl 的 createActivityContext 方法 ;
class ContextImpl extends Context {static ContextImpl createActivityContext(ActivityThread mainThread,LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,Configuration overrideConfiguration) {if (packageInfo == null) throw new IllegalArgumentException("packageInfo");String[] splitDirs = packageInfo.getSplitResDirs();ClassLoader classLoader = packageInfo.getClassLoader();if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies");try {classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName);splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);} catch (NameNotFoundException e) {// Nothing above us can handle a NameNotFoundException, better crash.throw new RuntimeException(e);} finally {Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);}}ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,activityToken, null, 0, classLoader);// Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)? packageInfo.getCompatibilityInfo(): CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;final ResourcesManager resourcesManager = ResourcesManager.getInstance();// Create the base resources for which all configuration contexts for this Activity// will be rebased upon.context.setResources(resourcesManager.createBaseActivityResources(activityToken,packageInfo.getResDir(),splitDirs,packageInfo.getOverlayDirs(),packageInfo.getApplicationInfo().sharedLibraryFiles,displayId,overrideConfiguration,compatInfo,classLoader));context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,context.getResources());return context;} }源碼路徑 : /frameworks/base/core/java/android/app/ContextImpl.java
在上述方法中 , 就有創(chuàng)建 Resources 資源的方法 :
context.setResources(resourcesManager.createBaseActivityResources(activityToken,packageInfo.getResDir(),splitDirs,packageInfo.getOverlayDirs(),packageInfo.getApplicationInfo().sharedLibraryFiles,displayId,overrideConfiguration,compatInfo,classLoader));二、Hook 點選擇
修改應(yīng)用 Resources 的 Hook 點有很多 , 通過改變 Activity 的 Resources , 甚至修改 ActivityThread 中創(chuàng)建 Resources 的流程 都可以實現(xiàn) ;
在插件包中 , 使用了 AppCompatActivity , 可以直接替換 AppCompatActivity 中的 private Resources mResources 成員 ;
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {private static final String DELEGATE_TAG = "androidx:appcompat";private AppCompatDelegate mDelegate;private Resources mResources; }只要可以拿到 AppCompatActivity 實例 , 就可以通過反射 , 替換掉 private Resources mResources 成員 ;
在 Instrumentation 中 , 調(diào)用了 newActivity 創(chuàng)建 Activity 實例 ;
public Activity newActivity(Class<?> clazz, Context context, IBinder token, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id,Object lastNonConfigurationInstance) throws InstantiationException, IllegalAccessException {Activity activity = (Activity)clazz.newInstance();ActivityThread aThread = null;// Activity.attach expects a non-null Application Object.if (application == null) {application = new Application();}activity.attach(context, aThread, this, token, 0 /* ident */, application, intent,info, title, parent, id,(Activity.NonConfigurationInstances)lastNonConfigurationInstance,new Configuration(), null /* referrer */, null /* voiceInteractor */,null /* window */, null /* activityConfigCallback */);return activity;}源碼路徑 : /frameworks/base/core/java/android/app/Instrumentation.java
在 ActivityThread 中 , 有 Instrumentation mInstrumentation 成員變量 , ActivityThread 本身就是單例 , 通過獲取其靜態(tài)成員 private static volatile ActivityThread sCurrentActivityThread , 就可以獲取到 ActivityThread ;
/** Reference to singleton {@link ActivityThread} */private static volatile ActivityThread sCurrentActivityThread;Instrumentation mInstrumentation;三、資源沖突解決方案
資源的 ID 在 AAPT 編譯資源階段就確定了 ;
固定類型的資源 , 編號是從一定的編號段開始的 , 如 layout 布局資源 , 第一個布局資源總是 2131361820 ;
不同類型的資源 , 布局 , 字符串 , 數(shù)值 , 主題 , 圖片 , drawable 等 , 都有對應(yīng)的資源編號范圍 , 同時也要兼容系統(tǒng)的資源編號范圍 ;
如果宿主應(yīng)用啟動 , 加載第一個布局資源 , 那么編號是 2131361820 ;
如果插件應(yīng)用啟動 , 加載第一個布局資源 , 那么編號也是 2131361820 ;
這樣就出現(xiàn)了資源沖突 ;
使用不同的 AssetManager 加載不同的資源 , 可以解決資源沖突問題 ;
四、博客資源
博客資源 :
- GitHub : https://github.com/han1202012/Plugin_Hook
總結(jié)
以上是生活随笔為你收集整理的【Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Android 插件化】Hook 插件
- 下一篇: 【错误记录】Android Studio