Android插件化系列一:開篇前言,Binder機制,ClassLoader

Android插件化系列一:開篇前言,Binder機制,ClassLoader

Android插件化是之前幾年裡的一個很火的技術概念。從2012年開始就有人在研究這門技術。從粗糙的AndroidDynamicLoader框架,到第一代的DroidPlugin等,繼而發展到第二代的VirtualApk,Replugin等,再到現如今的VirtualApp,Atlas。插件化在國內逐漸的發展和完善,卻也在近幾年出現了RN等替代品以後慢慢會走向弱勢。

儘管插件化技術的研究熱潮已經過去,但是這門技術本身還是有著大量的技術實踐,對於我們瞭解Android機制很有幫助。所以從這篇文章開始我會寫一系列的文章,加上自己對插件化的實踐,最後會去從源碼角度分析幾個優秀的插件化庫,形成一套完整的插件化的理論體系。

下面是插件化的技術框架,也是我這個系列文章的行文思路,

Android插件化系列一:開篇前言,Binder機制,ClassLoader

一. Binder機制

網上分析Binder機制的文章已經很多了,在這篇文章裡,我不會去講解Binder的使用,而是會去講解清楚Binder的設計思路,設計原理和對於插件化的使用。

為什麼需要Binder

首先我們知道,Android是基於Linux內核開發的。對於Linux來說,操作系統為一個二進制可執行文件創建了一個載有該文件自己的棧,堆、數據映射以及共享庫的內存片段,還為其分配特殊的內部管理結構。這就是一個進程。操作系統必須提供公平的使用機制,使得每個進程能正常的開始,執行和終結。

這樣呢,就引入了一個問題。一個進程能不能去操作別的進程的數據呢?我們可以想一下,這是絕對不能出現的,尤其是系統級的進程,如果被別的進程影響了可能會造成整個系統的崩塌。所以我們很自然的想到,我們應該把進程隔離起來,linux也是這樣做的,它的虛擬內存機制為每個進程分配連續的內存空間,進程只能操作自己的虛擬內存空間。同時,還必須滿足進程之間保持通信的能力,畢竟團結力量大,單憑單個進程的獨立運作是不能撐起操作系統的功能需求的。

為了解決這個問題,Linux引進了用戶空間User Space和內核空間Kernel Space的區別。用戶空間要想訪問內核空間,唯一方式就是系統調用。內核空間通過接口把應用程序請求傳給內核處理後返回給應用程序。同時,用戶空間進程如果想升級為內核空間進程,需要進行安全檢查。

補充知識:系統調用主要通過兩個方法:

  • copy_from_user():將用戶空間的數據拷貝到內核空間
  • copy_to_user():將內核空間的數據拷貝到用戶空間
Android插件化系列一:開篇前言,Binder機制,ClassLoader

以上就是linux系統的跨進程通信機制。而我們馬上要說的Binder,就是跨進程的一種方式

Binder模型

Binder是一種進程間通信(IPC)方式,Android常見的進程中通信方式有文件共享,Bundle,AIDL,Messenger,ContentProvider,Socket。其中AIDL,Messenger,ContentProvider都是基於Binder。Linux系統通過Binder驅動來管理Binder機制。

Binder的實現基於mmap()系統調用,只用拷貝一次,比常規文件頁操作更快。微信開源的MMKV等也是基於此。有興趣的可以瞭解一下。

首先Binder機制有四個參與者,Server,Client兩個進程,ServiceManager,Binder驅動(內核空間)。其中ServiceManager和Binder驅動都是系統實現的,而Server和Client是需要開發者自己實現的。四者之中只有Binder驅動是運行在內核空間的。

Android插件化系列一:開篇前言,Binder機制,ClassLoader

這裡的ServiceManager作為Manager,承擔著Binder通信的建立,Binder的註冊和傳遞的能力。Service負責創建Binder,併為他起一個字符形式的名字,然後把Binder和名字通過通過Binder驅動,藉助於ServiceManager自帶的Binder向ServiceManager註冊。注意這裡,因為Service和ServiceManager也是跨進程通信需要Binder,ServerManager是自帶Binder的,所以相對ServiceManager來說Service也就相當於Client了。

Service註冊了這個Binder以後,Client就能通過名字獲得Binder的引用了。這裡的跨進程通信雙方就變成了Client和ServiceManager,然後ServiceManager從Binder表取出Binder的引用返給Client,這樣的話如果有多個Client的話,多次返回引用就行了,但是事實上引用的都是放在ServiceManager中的Service。

當Client經過Binder驅動跟Service通信的時候,往往需要獲取到Service的某個對象object。這時候為了安全考慮,Binder會把object的代理對象proxyobject返回,這個對象擁有一模一樣的方法,但是沒有具體能力,只負責接收參數傳給真正的object使用。

所以完整的Binder通信過程是

Android插件化系列一:開篇前言,Binder機制,ClassLoader

OK,跨進程通信就講清楚了。接下來我們講講插件化中的Binder。

Binder與插件化

首先,我們先回顧一下Activity的啟動過程,Instrumentation調用了ActivityManagerNative,這個AMN是我們的本地對象,然後AMN調用getDefault拿到了ActivityManagerProxy,這個人AMP就是AMS在本地的代理。相當於binder模型中的Client,而這個AMP繼承的是IActivityManager,擁有四大組件的所有需要AMS參與的方法。本地通過調用這個AMP方法來間接地調用AMS。這樣,我們就調用到了AMS啟動了Activity。

那麼,AMS如何與Client進行通信呢?現在我們通過Launcher啟動了Activity,肯定要告訴Launcher “沒你什麼事了,你洗洗睡吧”。大家可以看到,這裡雙方的角色就發生了改變,AMS需要去發消息,承擔Client的角色,而Launcher這時候作為Service提供服務。而這次通信同樣也是使用的Binder機制。AMS這邊保存了一個ApplicationThreadProxy對象,這個對象就是Launcher的ApplicationThread的代理。AMS通過ATP給App發消息,App通過ApplicationThread處理。

Android插件化系列一:開篇前言,Binder機制,ClassLoader

以上,就是Binder機制在Android中的運用,我們後面會通過hook這個過程實現插件化。

總結

看了這麼多,可能還是很多朋友不懂Binder。我也是這樣,很長一段時間都不知道Binder到底指的是啥。後來我看到了這樣一種定義:

  • 從進程間通信的角度看,Binder 是一種進程間通信的機制;
  • 從 Server 進程的角度看,Binder 指的是 Server 中的 Binder 實體對象;
  • 從 Client 進程的角度看,Binder 指的是對 Binder 代理對象,是 Binder 實體對象的一個遠程代理
  • 從傳輸過程的角度看,Binder 是一個可以跨進程傳輸的對象;Binder 驅動會對這個跨越進程邊界的對象對一點點特殊處理,自動完成代理對象和本地對象之間的轉換。

二. ClassLoader

雙親委託模型

Java中默認有三種ClassLoader。分別是:

  • BootStrap ClassLoader:啟動類加載器,最頂層的加載器。主要負責加載JDK中的核心類。在JVM啟動後也隨著啟動,並構造Ext ClassLoader和App ClassLoader。
  • Extension ClassLoader:擴展類加載器,負責加載Java的擴展類庫。
  • App ClassLoader:系統類加載器,負責加載應用程序的所有jar和class文件。
  • 自定義ClassLoader:需要繼承自ClassLoader類。

ClassLoader默認使用雙親委託模型來搜索類。每個ClassLoader都有一個父類的引用。當ClassLoader需要加載某個類時,先判斷是否加載過,如果加載過就返回Class對象。否則交給他的父類去加載,繼續判斷是否加載過。這樣 層層判斷,就到了最頂層的BootStrap ClassLoader來試圖加載。如果連最頂層的Bootstrap ClassLoader都沒加載過,那就加載。如果加載失敗,就轉交給子ClassLoader,層層加載,直到最底層。如果還不能加載的話那就只能拋出異常了。

通過這種雙親委託模型,好處是:

  • 更高效,父類加載一次就可以避免了子類多次重複加載
  • 更安全,避免了外界偽造java核心類。

Android中的ClassLoader

android從5.0開始使用art虛擬機,這種虛擬機在程序運行時也需要ClassLoader將類加載到內存中,但是與java不同的是,java虛擬機通過讀取class字節碼來加載,但是art則是通過dex字節碼來加載。這是一種優化,可以合併多個class文件為一個classes.dex文件。

android一共有三種類加載器:

  • BootClassLoader:父類構造器
  • PathClassLoader:一般是加載指定路徑/data/app中的apk,也就是安裝到手機中的apk。所以一般作為默認的加載器。
  • DexClassLoader:從包含classes.dex的jar或者apk中,加載類的加載器,可用於動態加載。

看PathClassLoader和DexClassLoader源碼,都是繼承自BaseDexClassLoader。

public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent); //見下文
}
}
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,

String libraryPath, ClassLoader parent) {
super(parent); //見下文
//收集dex文件和Native動態庫
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
}

我們可以看到PathClassLoader的兩個參數都為null,表明只能接受固定的dex文件,而這個文件是隻能在安裝後出現的。而DexClassLoader中optimizedDirectory,和librarySearchPath都是可以自己定義的,說明我們可以傳入一個jar或者apk包,保證解壓縮後是一個dex文件就可以操作了。因此,我們通常使用DexClassLoader來進行插件化和熱修復。

可以看到,BaseDexClassLoader有一個相當重要的過程就是初始化DexPathList。初始化DexPathList的過程主要是收集dexElements和nativeLibraryPathElements。一個Classloader可以包含多個dex文件,每個dex文件被封裝到一個Element對象。這element對象在初始化和熱修復邏輯中是相當重要的。當查找某個類時,會遍歷dexElements,如果找到就返回,否則繼續遍歷。所以當多個dex中有相同的類,只會加載前面的dex中的類。下面是這段邏輯的具體實現

public Class findClass(String name, List<throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
//找到目標類,則直接返回
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}

return null;
}
/<throwable>

總結

我們先是講解了Java中類加載的雙親委託機制,然後介紹了Android中的幾種ClassLoader,從源碼角度介紹了兩種ClassLoader加載機制的不同。以後的插件化實踐中,我們會經常用到DexClassLoader。

轉載 https://mp.weixin.qq.com/s?__biz=MzU4Mjg0OTg1OA==&mid=2247483657&idx=1&sn=f0776db86fc0ce7eecace78b95c7400b&scene=21#wechat_redirect


分享到:


相關文章: