2020 Android 大厂面试-插件化、模块化、组件化,android开发环境的搭建视频
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 resource
《Android學習筆記總結+最新移動架構視頻+大廠安卓面試真題+項目實戰源碼講義》
【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整內容開源分享
s) {
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> map = (Map<Object, WeakReference>) 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、可以多團隊并行開發,測試。
原因:每個團隊負責不同的模塊,提升開發,測試效率。
組件化與模塊化
組件化是指以重用化為目的,將一個系統拆分為一個個單獨的組件
避免重復造輪子,節省開發維護成本;
降低項目復雜性,提升開發效率;
多個團隊公用同一個組件,在一定層度上確保了技術方案的統一性。
模塊化業務分層:由下到上
基礎組件層:
底層使用的庫和封裝的一些工具庫(libs),比如okhttp,rxjava,rxandroid,glide等
業務組件層:
與業務相關,封裝第三方sdk,比如封裝后的支付,即時通行等
業務模塊層:
按照業務劃分模塊,比如說IM模塊,資訊模塊等
Library Module開發問題
在把代碼抽取到各個單獨的Library Module中,會遇到各種問題。
最常見的就是R文件問題,Android開發中,各個資源文件都是放在res目錄中,在編譯過程中,會生成R.java文件。
R文件中包含有各個資源文件對應的id,這個id是靜態常量,但是在Library Module中,這個id不是靜態常量,那么在開發時候就要避開這樣的問題。
舉個常見的例子,同一個方法處理多個view的點擊事件,有時候會使用switch(view.getId())這樣的方式,
然后用case R.id.btnLogin這樣進行判斷,這時候就會出現問題,因為id不是經常常量,那么這種方式就用不了。
4.熱修復、插件化
www.jianshu.com/p/704cac3eb…
宿主: 就是當前運行的APP
插件: 相對于插件化技術來說,就是要加載運行的apk類文件
補丁: 相對于熱修復技術來說,就是要加載運行的.patch,.dex,*.apk等一系列包含dex修復內容的文件。
QQ 空間超級補丁方案
Tinker
HotFix
當然就熱修復的實現,各個大廠還有各自的實現,比如餓了嗎的Amigo,美團的Robust,實現及優缺點各有差異,但總的來說就是兩大類
ClassLoader 加載方案
Native層替換方案
或者是參考Android Studio Instant Run 的思路實現代碼整體的增量更新。但這樣勢必會帶來性能的影響。
Sophix
www.jianshu.com/p/4d30ce3e5…
底層替換方案
原理:在已經加載的類中直接替換掉原有方法,是在原有類的結構基礎上進行修改的。在hook方法入口ArtMethod時,通過構造一個新的ArtMethod實現替換方法入口的跳轉。
應用:能即時生效,Andfix采用此方案。
缺點:底層替換穩定性不好,適用范圍存在限制,通過改造代碼繞過限制既不優雅也不方便,并且還沒提供資源及so的修復。
類加載方案
原理:讓app重新啟動后讓ClassLoader去加載新的類。如果不重啟,原來的類還在虛擬機中無法重復加載。
優點:修復范圍廣,限制少。
應用:騰訊系包括QQ空間,手QFix,Tinker采用此方案。
QQ空間會侵入打包流程。
QFix需要獲取底層虛擬機的函數,不穩定。
Tinker是完整的全量dex加載。
Tinker與Sophix方案不同之處
Tinker采用dex merge生成全量DEX方案。反編譯為smali,然后新apk跟基線apk進行差異對比,最后得到補丁包。
Dalvik下Sophix和Tinker相同,在Art下,Sophix不需要做dex merge,因為Art下本質上虛擬機已經支持多dex的加載,要做的僅僅是把補丁dex作為主dex(classes.dex)加載而已:
將補丁dex命名為classes.dex,原apk中的dex依次命名為classes(2, 3, 4…).dex就好了,然后一起打包為一個壓縮文件。然后DexFile.loadDex得到DexFile對象,最后把該DexFile對象整個替換舊的dexElements數組就好了。
資源修復方案
基本參考InstantRun的實現:構造一個包含所有新資源的新的AssetManager。并在所有之前引用到原來的AssetManager通過反射替換掉。
Sophix不修改AssetManager的引用,構造的補丁包中只包含有新增或有修改變動的資源,在原AssetManager中addAssetPath這個包就可以了。資源包不需要在運行時合成完整包。
so庫修復方案
本質是對native方法的修復和替換。類似類修復反射注入方式,將補丁so庫的路徑插入到nativeLibraryDirectories數據最前面。
Method Hook
www.jianshu.com/p/7dcb32f8a… pqpo.me/2017/07/07/…
5.項目組件化的理解
juejin.im/post/5b5f17…
總結
組件化相較于單一工程,在組件模式下可以提高編譯速度,方便單元測試,提高開發效率。
開發人員分工更加明確,基本上做到互不干擾。
業務組件的架構也可以自由選擇,不影響同伴之間的協作。
降低維護成本,代碼結構更加清晰。
6.描述清點擊 Android Studio 的 build 按鈕后發生了什么
blog.csdn.net/u011026779/… blog.csdn.net/github_3713…
apply plugin : ‘com.android.application’
apply plugin : ‘com.android.library’
編譯五階段
1.準備依賴包 Preparation of dependecies
2.合并資源并處理清單 Merging resources and proccesssing Manifest
3.編譯 Compiling
4.后期處理 Postprocessing
5.包裝和出版 Packaging and publishing
簡單構建流程:
1. Android編譯器(5.0之前是Dalvik,之后是ART)將項目的源代碼(包括一些第三方庫、jar包和aar包)轉換成DEX文件,將其他資源轉換成已編譯資源。
2. APK打包器將DEX文件和已編譯資源在使用秘鑰簽署后打包。
3. 在生成最終 APK 之前,打包器會使用zipalign 等工具對應用進行優化,減少其在設備上運行時的內存占用。
構建流程結束后獲得測試或發布用的apk。
復制代碼
圖中的矩形表示用到或者生成的文件,橢圓表示工具。
1. 通過aapt打包res資源文件,生成R.java、resources.arsc和res文件
2. 處理.aidl文件,生成對應的Java接口文件
3. 通過Java Compiler編譯R.java、Java接口文件、Java源文件,生成.class文件
4. 通過dex命令,將.class文件和第三方庫中的.class文件處理生成classes.dex
5. 通過apkbuilder工具,將aapt生成的resources.arsc和res文件、assets文件和classes.dex一起打包生成apk
6. 通過Jarsigner工具,對上面的apk進行debug或release簽名
7. 通過zipalign工具,將簽名后的apk進行對齊處理。
這樣就得到了一個可以安裝運行的Android程序。
復制代碼
7.徹底搞懂Gradle、Gradle Wrapper與Android Plugin for Gradle的區別和聯系
zhuanlan.zhihu.com/p/32714369 blog.csdn.net/LVXIANGAN/a…
Offline work時可能出現"No cached version of com.android.tools.build:gradle:xxx available for offline mode"問題
Gradle: gradle-wrapper.properties中的distributionUrl=https/😕/services.gradle.org/distributions/gradle-2.10-all.zip
Gradle插件:build.gradle中依賴的classpath ‘com.android.tools.build:gradle:2.1.2’
Gradle:
一個構建系統,構建項目的工具,用來編譯Android app,能夠簡化你的編譯、打包、測試過程。
Gradle是一個基于Apache Ant和Apache Maven概念的項目自動化建構工具。它使用一種基于Groovy的特定領域語言來聲明項目設置,而不是傳統的XML。當前其支持的語言限于Java、Groovy和Scala
Gradle插件:
我們在AS中用到的Gradle被叫做Android Plugin for Gradle,它本質就是一個AS的插件,它一邊調用 Gradle本身的代碼和批處理工具來構建項目,一邊調用Android SDK的編譯、打包功能。
Gradle插件跟 Android SDK BuildTool有關聯,因為它還承接著AS里的編譯相關的功能,在項目的 local.properties 文件里寫明 Android SDK 路徑、在build.gradle 里寫明 buildToolsVersion 的原因。
| 插件版本 | Gradle 版本 |
| — | — |
| 1.0.0 - 1.1.3 | 2.2.1 - 2.3 |
| 1.2.0 - 1.3.1 | 2.2.1 - 2.9 |
| 1.5.0 | 2.2.1 - 2.13 |
| 2.0.0 - 2.1.2 | 2.10 - 2.13 |
| 2.1.3 - 2.2.3 | 2.14.1+ |
| 2.3.0+ | 3.3+ |
| 3.0.0+ | 4.1+ |
| 3.1.0+ | 4.4+ |
| 3.2.0 - 3.2.1 | 4.6+ |
總結
以上是生活随笔為你收集整理的2020 Android 大厂面试-插件化、模块化、组件化,android开发环境的搭建视频的全部內容,希望文章能夠幫你解決所遇到的問題。