2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

閱讀前請點擊右上角“關注”,每天免費獲取Android知識解析及面試解答。Android架構解析,只做職場乾貨,完全免費分享!

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)


目錄

  • 1.對熱修復和插件化的理解
  • 2.插件化原理分析
  • 3.模塊化實現(好處,原因)
  • 4.熱修復、插件化
  • 5.項目組件化的理解
  • 6.描述請點擊 Android Studio 的 build 按鈕後發生了什麼

參考答案:
1.對熱修復和插件化的理解

blog.csdn.net/github_3713…

<code>

Android

類加載器

PathClassLoader

.java

DexClassLoader

.java

BaseDexClassLoader

.java

DexPathList

.java

/<code>

(1)PathClassLoader:只能加載已經安裝到Android系統中的apk文件(/data/app目錄),是Android默認使用的類加載器。

(2)DexClassLoader:可以加載任意目錄下的dex/jar/apk/zip文件,比PathClassLoader更靈活,是實現熱修復的重點。

<code> 

public

class

PathClassLoader

extends

BaseDexClassLoader

{

public

PathClassLoader

(String dexPath, ClassLoader parent)

{

super

(dexPath,

null

,

null

, parent); }

public

PathClassLoader

(String dexPath, String librarySearchPath, ClassLoader parent)

{

super

(dexPath,

null

, librarySearchPath, parent); } }

class

DexClassLoader

extends

BaseDexClassLoader

{

public

DexClassLoader

(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent)

{

super

(dexPath,

new

File(optimizedDirectory), librarySearchPath, parent); } }/<code>
2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

BaseDexClassLoaderdexPath:要加載的程序文件(一般是dex文件,也可以是jar/apk/zip文件)所在目錄。

optimizedDirectory:dex文件的輸出目錄(因為在加載jar/apk/zip等壓縮格式的程序文件時會解壓出其中的dex文件,該目錄就是專門用於存放這些被解壓出來的dex文件的)。

libraryPath:加載程序文件時需要用到的庫路徑。

parent:父加載器

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

阿里系: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.插件化原理分析

cloud.tencent.com/developer/a…

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

DexClassLoader和PathClassLoader,它們都繼承於BaseDexClassLoader。

DexClassloader多傳了一個optimizedDirectory

DexPathList

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

多DexClassLoader

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

每個插件單獨一個DexClassLoader,相對隔離,RePlugin採用該方案單DexClassLoader將插件的DexClassLoader中的pathList合併到主工程的DexClassLoader中。方便插件與宿主(插件)之間的調用,Small採用該方案插件調用主工程

主工程的ClassLoader作為插件ClassLoader的父加載器

主工程調用插件

若使用多ClassLoader機制,通過插件的ClassLoader先加載類,再通過反射調用

若使用單ClassLoader機制,直接通過類名去訪問插件中的類,弊端是庫的版本可能不一致,需要規範

資源加載

<code> 
AssetManager assets = 

new

AssetManager();

if

(assets.addAssetPath(resDir) ==

0

){

return

null

; } r =

new

Resources(assets, metrics, getConfiguration(), compInfo);/<code>

插件apk的路徑加入到AssetManager中

通過反射去創建,並且部分Rom對創建的Resource類進行了修改,所以需要考慮不同Rom的兼容性。資源路徑的處理

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

<code>Context的處理

 

if

(Constants.COMBINE_RESOURCES) { Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath()); ResourcesManager.hookResources(context, resources);

return

resources; }

else

{ Resources hostResources = context.getResources(); AssetManager assetManager = createAssetManager(context, apk);

return

new

Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration()); } 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> map = (

Map

<

Object

, WeakReference>) ReflectUtil.getField(resManager.getClass(), resManager,

"mActiveResources"

);

Object

key = map.keySet().iterator().next(); map.put(key,

new

WeakReference<>(resources)); }

else

{

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(); }/<code>

替換了主工程context中LoadedApk的mResource對象將新的Resource添加到主工程ActivityThread的mResourceManager中,並且根據Android版本做了不同處理

<code> 

Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);
 

ReflectUtil.setField(ContextThemeWrapper

.

class

,

activity

,

"mResources"

,

plugin.getResources

());/<code>

資源衝突

資源id是由8位16進制數表示,表示為0xPPTTNNNN, 由三部分組成:PackageId+TypeId+EntryId修改aapt源碼,編譯期修改PP段。

修改resources.arsc文件,該文件列出了資源id到具體資源路徑的映射。blog.csdn.net/jiangwei091…

<code> 
result = handleCommand(&bundle);

case

kCommandPackage:

return

doPackage(bundle);

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

; }/<code>

首先找到入口類:Main.cpp:main函數,解析參數,然後調用handleCommand函數處理參數對應的邏輯,我們看到了有一個函數doPackage。

然後就搜索到了Command.cpp:在他內部的doPackage函數中進行編譯工具的一個函數:buildResources函數,在全局搜索,發現了Resource.cpp:發現這裡就是處理編譯工作,構建ResourceTable的邏輯,在ResourceTable.cpp中,也是獲取PackageId的地方,下面我們就來看看如何修改呢?

其實最好的方法是,能夠修改aapt源碼,添加一個參數,把我們想要編譯的PackageId作為輸入值,傳進來最好了,那就是Bundle類型,他是從Main.cpp中的main函數傳遞到了最後的buildResources函數中,那麼我們就可以把這個參數用Bundle進行攜帶。

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

juejin.im/entry/5c008… www.jianshu.com/p/8d691b6bf…————————————————————————————————————————————————cloud.tencent.com/developer/a…在整個過程中,需要修改到R文件、resources.arsc和二進制的xml文件四大組件支持ProxyActivity代理

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

代理方式的關鍵總結起來有下面兩點:

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的過程

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

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修復內容的文件。

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

QQ 空間超級補丁方案Tinker

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

HotFix

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

當然就熱修復的實現,各個大廠還有各自的實現,比如餓了嗎的Amigo,美團的Robust,實現及優缺點各有差異,但總的來說就是兩大類

ClassLoader 加載方案

Native層替換方案

或者是參考Android Studio Instant Run 的思路實現代碼整體的增量更新。但這樣勢必會帶來性能的影響。

Sophixwww.jianshu.com/p/4d30ce3e5…底層替換方案

原理:在已經加載的類中直接替換掉原有方法,是在原有類的結構基礎上進行修改的。在hook方法入口ArtMethod時,通過構造一個新的ArtMethod實現替換方法入口的跳轉。

應用:能即時生效,Andfix採用此方案。

缺點:底層替換穩定性不好,適用範圍存在限制,通過改造代碼繞過限制既不優雅也不方便,並且還沒提供資源及so的修復。

類加載方案

原理:讓app重新啟動後讓ClassLoader去加載新的類。如果不重啟,原來的類還在虛擬機中無法重複加載。

優點:修復範圍廣,限制少。

應用:騰訊系包括QQ空間,手QFix,Tinker採用此方案。

QQ空間會侵入打包流程。

QFix需要獲取底層虛擬機的函數,不穩定。

Tinker是完整的全量dex加載。

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

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 Hookwww.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…

<code>

apply

plugin :

'com.android.application'

apply plugin :

'com.android.library'

/<code>

編譯五階段

1.準備依賴包 Preparation of dependecies

2.合併資源並處理清單 Merging resources and proccesssing Manifest

3.編譯 Compiling

4.後期處理 Postprocessing

5.包裝和出版 Packaging and publishing

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

簡單構建流程:

1. Android編譯器(5.0之前是Dalvik,之後是ART)將項目的源代碼(包括一些第三方庫、jar包和aar包)轉換成DEX文件,將其他資源轉換成已編譯資源。

2. APK打包器將DEX文件和已編譯資源在使用秘鑰簽署後打包。

3. 在生成最終 APK 之前,打包器會使用zipalign 等工具對應用進行優化,減少其在設備上運行時的內存佔用。

構建流程結束後獲得測試或發佈用的apk。

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

圖中的矩形表示用到或者生成的文件,橢圓表示工具。

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程序。

2020 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/://http://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 的原因。

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

最後

其實Android開發的知識點就那麼多,面試問來問去還是那麼點東西。所以面試沒有其他的訣竅,只看你對這些知識點準備的充分程度。so,出去面試時先看看自己複習到了哪個階段就好。上面分享的2020 Android 大廠面試(之)插件化、模塊化、組件化、熱修復、增量更新、Gradle知識其中的一個面試專題。

小編還將騰訊、頭條、阿里、美團、字節跳動等公司2019-2020年的高頻面試題解析分享出來,這是我們集合了牛客網、掘金、簡書、知乎、CSDN等網站的幾十篇面經和群友自己面試的經歷的合集,希望大家喜歡。

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

小編還把這些技術點整理成了視頻和PDF(實際上比預期多花了不少精力),包含知識脈絡 + 諸多細節,由於篇幅有限,上面只是以圖片的形式給大家展示一部分。

【Android學習PDF+學習視頻+面試文檔+知識點筆記】

【Android思維腦圖(技能樹)】

知識不體系?這裡還有整理出來的Android進階學習的思維腦圖,給大家參考一個方向。

2020 Android 面試(之)插件化、模塊化、組件化,(含答案)

【Android高級架構視頻學習資源】

Android部分精講視頻領取學習後更加是如虎添翼!進軍BATJ大廠等(備戰)!現在都說互聯網寒冬,其實無非就是你上錯了車,且穿的少(技能),要是你上對車,自身技術能力夠強,公司換掉的代價大,怎麼可能會被裁掉,都是淘汰末端的業務Curd而已!現如今市場上初級程序員氾濫,這套教程針對Android開發工程師1-6年的人員、正處於瓶頸期,想要年後突破自己漲薪的,進階Android中高級、架構師對你更是如魚得水,趕快領取吧!

【Android進階學習視頻】、【全套Android面試秘籍】可以評論或者私信我【學習】查看免費領取方式!


分享到:


相關文章: