IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 2020Android大厂面试(五)插件化,重磅 -> 正文阅读

[移动开发]2020Android大厂面试(五)插件化,重磅

阿里系:DeXposed、andfix:從底層二進制入手(c語言)。阿里andFix hook 方法在native的具體字段。
art虛擬機上是一個叫ArtMethod的結構體。通過修改該結構體上有bug的字段來達到修復bug方法的目的,
但這個artMethod是根據安卓原生的結構寫死的,國內很多第三方廠家會改寫ArtMethod結構,導致替換失效。
騰訊系:tinker:從java加載機制入手。qq的dex插裝就類似上面分析的那種。通過將修復的dex文件插入到app的dexFileList的前面,達到更新bug的效果,但是不能及時生效,需要重啟。
但虛擬機在安裝期間會為類打上CLASS_ISPREVERIFIED標誌,是為了提高性能的,我們強制防止類被打上標誌是否會有些影響性能
美團robust:是在編譯器為每個方法插入了一段邏輯代碼,併為每個類創建了一個ChangeQuickRedirect靜態成員變量,當它不為空會轉入新的代碼邏輯達到修復bug的目的。
優點是兼容性高,但是會增加應用體積

PathClassLoader和DexClassLoader都繼承自BaseDexClassLoader
1、Android使用PathClassLoader作為其類加載器,只能去加載已經安裝到Android系統中的apk文件;
2、DexClassLoader可以從.jar和.apk類型的文件內部加載classes.dex文件就好了。熱修復也用到這個類。
(1)動態改變BaseDexClassLoader對象間接引用的dexElements;
(2)在app打包的時候,阻止相關類去打上CLASS_ISPREVERIFIED標誌。
(3)我們使用 hook 思想代理 startActivity 這個方法,使用佔坑的方式。

1\. startActivity 的時候最終會走到 AMS 的 startActivity 方法
2\. 系統會檢查一堆的信息驗證這個 Activity 是否合法。
3\. 然後會回調 ActivityThread 的 Handler 裡的 handleLaunchActivity
4\. 在這裡走到了 performLaunchActivity 方法去創建 Activity 並回調一系列生命週期的方法
5\. 創建 Activity 的時候會創建一個 LoaderApk對象,然後使用這個對象的 getClassLoader 來創建 Activity
6\. 我們查看 getClassLoader() 方法發現返回的是 PathClassLoader,然後他繼承自 BaseDexClassLoader
7\. 然後我們查看 BaseDexClassLoader 發現他創建時創建了一個 DexPathList 類型的 pathList對象,然後在 findClass 時調用了 pathList.findClass 的方法
8\. 然後我們查看 DexPathList類 中的 findClass 發現他內部維護了一個 Element[] dexElements的dex 數組,findClass 時是從數組中遍歷查找的

2.插件化原理分析

DexClassLoader和PathClassLoader,它們都繼承於BaseDexClassLoader。
DexClassloader多傳了一個optimizedDirectory

DexPathList

多DexClassLoader

每個插件單獨一個DexClassLoader,相對隔離,RePlugin採用該方案

單DexClassLoader

將插件的DexClassLoader中的pathList合併到主工程的DexClassLoader中。方便插件與宿主(插件)之間的調用,Small採用該方案

插件調用主工程
主工程的ClassLoader作為插件ClassLoader的父加載器
主工程調用插件
若使用多ClassLoader機制,通過插件的ClassLoader先加載類,再通過反射調用
若使用單ClassLoader機制,直接通過類名去訪問插件中的類,弊端是庫的版本可能不一致,需要規範

資源加載

//創建AssetManager對象 
AssetManager assets = new AssetManager();
//將apk路徑添加到AssetManager中
if (assets.addAssetPath(resDir) == 0){              
return null;  
}
//創建Resource對象
r = new Resources(assets, metrics, getConfiguration(), compInfo);
插件apk的路徑加入到AssetManager中
通過反射去創建,並且部分Rom對創建的Resource類進行了修改,所以需要考慮不同Rom的兼容性。

資源路徑的處理

Context的處理

// 第一步:創建Resource
if (Constants.COMBINE_RESOURCES) {
//插件和主工程資源合併時需要hook住主工程的資源
Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());
ResourcesManager.hookResources(context, resources);  
return resources;
} else {  
//插件資源獨立,該resource只能訪問插件自己的資源
Resources hostResources = context.getResources();
AssetManager assetManager = createAssetManager(context, apk);  
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}

//第二步:hook主工程的Resource
//對於合併式的資源訪問方式,需要替換主工程的Resource,下面是具體替換的代碼。
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);
Object activityThread = ReflectUtil.getActivityThread(base);
Object resManager = ReflectUtil.getField(activityThread.getClass(), activityThread, "mResourcesManager");       
if (Build.VERSION.SDK_INT < 24) {
Map<Object, WeakReference<Resources>> map = (Map<Object, WeakReference<Resources>>) ReflectUtil.getField(resManager.getClass(), resManager, "mActiveResources");
Object key = map.keySet().iterator().next();
map.put(key, new WeakReference<>(resources));
} else {                
// still hook Android N Resources, even though it's unnecessary, then nobody will be strange.
Map map = (Map) ReflectUtil.getFieldNoException(resManager.getClass(), resManager, "mResourceImpls");
Object key = map.keySet().iterator().next();
Object resourcesImpl = ReflectUtil.getFieldNoException(Resources.class, resources, "mResourcesImpl");
map.put(key, new WeakReference<>(resourcesImpl));
}
} catch (Exception e) {
e.printStackTrace();
}

替換了主工程context中LoadedApk的mResource對象

將新的Resource添加到主工程ActivityThread的mResourceManager中,並且根據Android版本做了不同處理

//第三步:關聯resource和Activity
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);
//設置Activity的mResources屬性,Activity中訪問資源時都通過mResources
ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());

資源衝突

資源id是由8位16進制數表示,表示為0xPPTTNNNN,
由三部分組成:PackageId+TypeId+EntryId

修改aapt源碼,編譯期修改PP段。
修改resources.arsc文件,該文件列出了資源id到具體資源路徑的映射。

[blog.csdn.net/jiangwei091…](

)

// Main.cpp
result = handleCommand(&bundle);
case kCommandPackage: return doPackage(bundle);
// Command.cpp
int doPackage(Bundle* bundle) {
if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) {
err = buildResources(bundle, assets, builder);
if (err != 0) {
goto bail;
}
}
}
Resource.cpp
buildResources
ResourceTable.cpp
switch(mPackageType) {
case App:
case AppFeature:
packageId = 0x7f;
break;
case System:
packageId = 0x01;
break;
case SharedLibrary:
packageId = 0x00;
break;    
}

首先找到入口類:Main.cpp:main函數,解析參數,然後調用handleCommand函數處理參數對應的邏輯,我們看到了有一個函數doPackage。
然後就搜索到了Command.cpp:在他內部的doPackage函數中進行編譯工具的一個函數:buildResources函數,在全局搜索,發現了Resource.cpp:發現這裡就是處理編譯工作,構建ResourceTable的邏輯,在ResourceTable.cpp中,也是獲取PackageId的地方,下面我們就來看看如何修改呢?
其實最好的方法是,能夠修改aapt源碼,添加一個參數,把我們想要編譯的PackageId作為輸入值,傳進來最好了,那就是Bundle類型,他是從Main.cpp中的main函數傳遞到了最後的buildResources函數中,那麼我們就可以把這個參數用Bundle進行攜帶。

[juejin.im/entry/5c008…](

)
[www.jianshu.com/p/8d691b6bf…](

)

————————————————————————————————————————————————

[cloud.tencent.com/developer/a…](

)

在整個過程中,需要修改到R文件、resources.arsc和二進制的xml文件

四大組件支持

ProxyActivity代理

代理方式的關鍵總結起來有下面兩點:
ProxyActivity中需要重寫getResouces,getAssets,getClassLoader方法返回插件的相應對象。生命週期函數以及和用戶交互相關函數,如onResume,onStop,onBackPressedon,KeyUponWindow,FocusChanged等需要轉發給插件。
PluginActivity中所有調用context的相關的方法,如setContentView,getLayoutInflater,getSystemService等都需要調用ProxyActivity的相應方法。
該方式有幾個明顯缺點:
插件中的Activity必須繼承PluginActivity,開發侵入性強。
如果想支持Activity的singleTask,singleInstance等launchMode時,需要自己管理Activity棧,實現起來很繁瑣。
插件中需要小心處理Context,容易出錯。
如果想把之前的模塊改造成插件需要很多額外的工作。

預埋StubActivity,hook系統啟動Activity的過程

VirtualAPK通過替換了系統的Instrumentation,hook了Activity的啟動和創建,省去了手動管理插件Activity生命週期的繁瑣,讓插件Activity像正常的Activity一樣被系統管理,並且插件Activity在開發時和常規一樣,即能獨立運行又能作為插件被主工程調用。
其他插件框架在處理Activity時思想大都差不多,無非是這兩種方式之一或者兩者的結合。在hook時,不同的框架可能會選擇不同的hook點。如360的RePlugin框架選擇hook了系統的ClassLoader,即構造Activity2的ClassLoader,在判斷出待啟動的Activity是插件中的時,會調用插件的ClassLoader構造相應對象。另外RePlugin為了系統穩定性,選擇了儘量少的hook,因此它並沒有選擇hook系統的startActivity方法來替換intent,而是通過重寫Activity的startActivity,因此其插件Activity是需要繼承一個類似PluginActivity的基類的。不過RePlugin提供了一個Gradle插件將插件中的Activity的基類換成了PluginActivity,用戶在開發插件Activity時也是沒有感知的。

[www.jianshu.com/p/ac96420fc…](

)

Service插件化總結
初始化時通過ActivityManagerProxy Hook住了IActivityManager。
服務啟動時通過ActivityManagerProxy攔截,判斷是否為遠程服務,如果為遠程服務,啟動RemoteService,如果為同進程服務則啟動LocalService。
如果為LocalService,則通過DexClassLoader加載目標Service,然後反射調用attach方法綁定Context,然後執行Service的onCreate、onStartCommand方法
如果為RemoteService,則先加載插件的遠程Service,後續跟LocalService一致。

3.模塊化實現(好處,原因)

[www.cnblogs.com/Jackie-zhan…](

)

1、模塊間解耦,複用。
(原因:對業務進行模塊化拆分後,為了使各業務模塊間解耦,因此各個都是獨立的模塊,它們之間是沒有依賴關係。
每個模塊負責的功能不同,業務邏輯不同,模塊間業務解耦。模塊功能比較單一,可在多個項目中使用。)
2、可單獨編譯某個模塊,提升開發效率。
(原因:每個模塊實際上也是一個完整的項目,可以進行單獨編譯,調試)
3、可以多團隊並行開發,測試。
原因:每個團隊負責不同的模塊,提升開發,測試效率。

組件化與模塊化

組件化是指以重用化為目的,將一個系統拆分為一個個單獨的組件

避免重複造輪子,節省開發維護成本;
降低項目複雜性,提升開發效率;
多個團隊公用同一個組件,在一定層度上確保了技術方案的統一性。



#### **【附】相关架构及资料**

![](https://img-blog.csdnimg.cn/img_convert/121e07c8cc1aa5f2cd14e430e6bf5c77.png)

![](https://img-blog.csdnimg.cn/img_convert/706e390728dec4d2b1049d57dc3d1de8.png)

##### **[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](

)**

> **往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。**

> **本文已被[腾讯CODING开源托管项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://ali1024.coding.net/public/P7/Android/git)收录,自学资源及系列文章持续更新中...**
:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](

)**

> **往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter全方面的Android进阶实践技术,群内还有技术大牛一起讨论交流解决问题。**

> **本文已被[腾讯CODING开源托管项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://ali1024.coding.net/public/P7/Android/git)收录,自学资源及系列文章持续更新中...**
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-09-10 10:58:28  更:2021-09-10 10:59:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 17:20:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码