前言
作為一個應用工程師,除了寫一些業務代碼,性能優化也是我們需要關注的點!
如果想要去做啟動優化,那么去了解啟動程序就是一個繞不過去的坎兒,
那么除了關于啟動程序的那些代碼,我們還應該去知道什么呢?

一、多行程那些事兒
在大家很早學習 Android 的時候,想必就知道,每一個 Android App 就代表著一個行程,
1. 為什么要開啟行程?
為什么要開啟一個新的行程呢?
在 Linux 中,執行緒和行程可沒多大區別,內核并沒有給執行緒準備特別的調度演算法或者特殊的資料結構,相反,執行緒被視為一個與其他行程共享某些資源的行程,
看到了嗎?執行緒之間可是會共享資源的,比如地址空間,你肯定不想發送微信的時候,其他應用都能知道,你發了什么吧,
2. 如何開啟一個應用行程
想要開啟一個應用行程可不容易,大家可能都聽說過,Linux 行程的創建都通過 fork() 方法,Android 自然也是同理,所有的應用行程的父行程都是 Zygote 行程,它是 Android 系統的兩大之主行程之一,

點擊 App 圖示后,Launcher 行程會通知 AMS(ActivityManagerService),AMS 就會呼叫自身的 startProcessLocked 方法,接著會呼叫 Process#start() 方法,透過層層嵌套,我們可以看到連接 Socket 的地方:
public static ZygoteState connect(LocalSocketAddress address) throws IOException {
//...
final LocalSocket zygoteSocket = new LocalSocket();
try {
zygoteSocket.connect(address);
zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
zygoteWriter = new BufferedWriter(new OutputStreamWriter(zygoteSocket.getOutputStream()), 256);
} catch (IOException ex) {
//...
}
//...
return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter, Arrays.asList(abiListString.split(",")));
}
Zygote 行程會開啟 Socket 等待連接,連上了以后,最侄訓觸發它的 Zygote#forkAndSpecialize() 方法:
static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
boolean isTopApp, String[] pkgDataInfoList, String[] whitelistedDataInfoList,
boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
ZygoteHooks.preFork();
int pid = nativeForkAndSpecialize(
uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
pkgDataInfoList, whitelistedDataInfoList, bindMountAppDataDirs,
bindMountAppStorageDirs);
//...
ZygoteHooks.postForkCommon();
return pid;
}
可以看到,一個新的行程就創建好了,并回傳行程ID,
應用行程創建完了,應用還得和 AMS、PMS 等其他服務或者行程通信啊,所以還得創建 Binder 執行緒池,不然就沒法和外界交流,
接下來,可到了重點部分了,系統會呼叫 RuntimeInit#findStaticMain() 方法,該方法最侄訓呼叫一個 Runnable 方法:
static class MethodAndArgsCaller implements Runnable {
//...
public void run() {
try {
mMethod.invoke(null, new Object[] { mArgs });
} catch (IllegalAccessException ex) {
// ...
} catch (InvocationTargetException ex) {
// ...
}
}
}
mMethod 就是 ActivityThread#main() 方法,引數都是之前傳遞進去的,最終通過反射去啟動我們的 ActivityThread,
3. Android特有的多行程傳輸方式
你以為要分析 ActivityThread#main(),不,我們沒有,
上面我們談到了 Socket,但 Binder 才是 Android 中特有的多行程通信方式,并且在下面會被我多次提及,
往簡單了說,因為 Android 中的行程的記憶體地址是各自獨立的,但是它們都得通過內核的限制與硬體層進行交流,
如果不存在內核,就會發生很多糟糕的情況,比如說,一共有8GB的運行記憶體,起點讀書說我全要了,剩下的其他應用還怎么玩!

多行程通信也得通過內核,分給行程的一般都是虛擬地址,每一段虛擬地址都有實際的物理地址與之對應,當發生行程通信時,一般都會將資料從行程A的用戶空間(用戶可以操作的記憶體空間)復制到內核的緩沖區,再從內核的緩沖區復制到行程B的用戶空間,

但是 Binder 就不同了,Binder 會通過 mmap 將內核中的虛擬地址和用戶空間的虛擬地址做映射,如果行程B使用了 Binder,當資料從行程A復制到記憶體快取區的時候,整個通信程序就結束了,因為使用了記憶體映射,資料直接映射到了行程B的記憶體空間,

看到這兒,相信大家對 Binder 有了基礎的印象了,再來看一下它的使用流程即可,

Binder的實作是典型的 C\S 的結構:
Client:服務的請求方,Server:服務的提供方,Service Manager:為Server提供Binder的注冊服務,為Client提供Binder的查詢服務,Server、Client和Service Manager的通訊都是通過Binder,Binder驅動:負責Binder通信機制的建立,提供一系列底層支持,
從上圖中,Binder 通信的程序是這樣的:
Server在Service Manager中注冊:Server行程在創建的時候,也會創建對應的Binder物體,如果要提供服務給Client,就必須為Binder物體注冊一個名字,Client通過Service Manager獲取服務:Client知道服務中Binder物體的名字后,通過名字從Service Manager獲取Binder物體的參考,Client使用服務與Server進行通信:Client通過呼叫Binder物體與Server進行通信,
以上的知識可能有點淺顯,權當拋磚引玉,如果想要深入的學習,大家可自行了解,
看到這里,相信大家可能會有一個疑惑,Binder 是 Android 中特有的高效跨行程傳輸方式,Zygote 為什么沒有選擇 Binder 而是選擇 Socket 作為跨行程通信方式呢?
其實我也有點疑惑,大家可以看看下面的解答:
?
《知乎:zygote行程為什么不啟用binder機制?》
?
二、dex那些事兒
我們的知道,解壓一個應用的 apk 檔案,里面可能會有若干個 dex 檔案,

它是經工具軟體將所有的 Java 位元組碼檔案(.class)轉化形成的,

這些 dex 檔案怎么就可以直接運行了呢?

從圖片中我們也可以看出來了,需要根據情況討論,因為 Android 的虛擬機分為 Dalvik 和 ART,
對于 Dalvik,安裝程序會提取出 .odex,不過這個只是優化過的位元組碼檔案,最后,在運行程序,Dalvik 還要通過 Jit(即時編譯) 還需要轉化成機器可以識別的機器碼,
對于 ART,它會在安裝程序中,決議成 .oat 檔案,這個檔案裝的可不是位元組碼,而是實實在在的機器碼,少了 Jit,整個運行速度和啟動速度都大大加快了,
不過呢,在ART中,整個安裝程序和應用升級都比較耗時,所以,在 Android 7.0 以后,既采用了 JIT 又采用了 AOT,簡單來說就是:
- 第一次啟動采用 JIT,將熱點函式包含的 dex 決議成位元組碼,
- 等到應用空閑的時候,再執行 AOT 程序,進行編譯,
經過了這一大串,dex 檔案就變成了機器可以識別的機器碼了,這里再提醒一下,Android 系統可不是直接識別 .class 檔案的,它需要將 .dex 映射到記憶體中,并通過 PathClassLoader 和 DexClassLoader 將 APP 中的類加載到記憶體里,
三、進入Android啟動程序
從上面我們已經知道了,我們的應用啟動入口在 ActivityThread#main() 方法,
1. 啟動程序中的通信機制
在正式了解啟動程序之前,我想我們還得了解一下啟動程序的通信機制,這也是啟動程序的主線,雖然我知道你們已經迫不及待了!

稍微了解過啟動程序的同學應該都知道 ActivityThread、ApplicationThread 和 ActivityManagerService 這三個角色:
| 角色 | 作用 |
|---|---|
ActivityThread | (下稱AT):應用的啟動入口,進行應用啟動的作業, |
ActivityManagerService | (下稱AMS):Android系統中最重要的系統服務之一,負責四大組件的啟動、管理和調度,同時也管理應用行程, |
ApplicationThread | AMS 與 AT 通信的橋梁,AT 的內部類, |
上面我們了解過 Android 系統的兩大支柱行程之一的 Zygote 行程,另外一個就是 SystemServer 行程,
AMS 就處于 SystemServer 行程,還有很多大家耳熟能詳的服務都在這里邊,AMS 基于 Binder 實作的,所以 ActivityThread 能夠在應用行程聯系遠在 SystemServer 行程的 AMS,
那 AMS 如何聯系 ActivityThread 呢?
巧了,也是 Binder,它的實作類是 ApplicationThread,不過它是一個匿名 Binder(沒有在 Server Manager 注冊的 Binder),之所以這么設計,我想更多的是基于安全方面考慮的,

AT 和 AMS 的通信正如圖上所描述的那樣,
2. 入口ActivityThread
終于到了代碼解釋環節了!
進入 main 方法:
public static void main(String[] args) {
//...
Looper.prepareMainLooper();
//... 省略
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
//...
Looper.loop();
}
重點是 ActivityThread#attach() 方法,system 變數傳遞的是 false:
private void attach(boolean system, long startSeq) {
// ...
if (!system) {
//. ...
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManager.getService();
try {
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
// ...
}
// Watch for getting close to heap limit.
BinderInternal.addGcWatcher(new Runnable() {
@Override public void run() {
//...
}
});
} else {
//...
}
//...
}
我們可以看到這個方法先是直接獲取 AMS,之后就直接將 ApplicationThread 物件傳了過去,
2. 進入AMS
點進 ActivityManagerService#attachApplication() 方法,該方法又呼叫了 ActivityManagerService#attachApplicationLocked() 方法,簡化一下:
private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
ProcessRecord app;
if (pid != MY_PID && pid >= 0) {
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid); // 根據pid獲取ProcessRecord
}
}
...
ApplicationInfo appInfo = app.instrumentationInfo != null
? app.instrumentationInfo : app.info;
thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(mConfiguration), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked());
...
return true;
}
注意一下,上面的方法在 AMS 中,就直接呼叫了 ApplicationThread#bindApplication() 方法了,這可是跨行程,
之前我們也提過了,ApplicationThread 也是 Binder,并且我們也沒有發現 ApplicationThread 向 Service Manager 注冊 Binder 的任何代碼,這也更加證實它是一個匿名 Binder,不信,我們可以看一下 ApplicationThread 的代碼:
private class ApplicationThread extends IApplicationThread.Stub {
//...
}
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
// ...
}
ApplicationThread 和 ActivityManagerService 貌似都改成 AIDL 去實作 Binder 了,沒毛病!
3. 進入ApplicationThread
ApplicationThread 自己并沒有處理,而是交給了 H 的實體,從下面的代碼中,我們就能看出 H 是一個 Handler:
public final void bindApplication(
//... 引數省略
) {
//...
AppBindData data = new AppBindData();
//...
sendMessage(H.BIND_APPLICATION, data);
}
H 也是 ActivityThread 的內部類,于是直接呼叫 ActivityThread#handleBindApplication() 方法,簡化一下:
private void handleBindApplication(AppBindData data) {
mBoundApplication = data;
Process.setArgV0(data.processName);//設定行程名
// ...
//獲取LoadedApk物件
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
// ...
// 創建ContextImpl背景關系
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
//...
try {
// 此處data.info是指LoadedApk, 通過反射創建目標應用Application物件
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
// 初始化ContentProvider
installContentProviders(app, data.providers);
mInitialApplication = app;
// ...
mInstrumentation.onCreate(data.instrumentationArgs);
//回呼onCreate
mInstrumentation.callApplicationOnCreate(app);
} finally {
StrictMode.setThreadPolicy(savedPolicy);
}
}
我們可以看見,主要做了以下幾件事:
- 創建
ContextImpl - 創建
Application - 初始化
ContentProvider - 回呼
Application#onCreate()
4. 創建Application
data.info 的型別是 LoadedApk,它保存了很多跟 Apk 相關的資訊,
進入 LoadedApk#makeApplication() 方法,這是 Application 的創建方法,注意第二個引數傳了一個 null:
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
Application app = null;
//...
try {
final java.lang.ClassLoader cl = getClassLoader();
// ...
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
// ...
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
} catch (Exception e) {
//...
}
if (instrumentation != null) {
// instrumentation為空,所以走不進這個方法
try {
instrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
//...
}
}
return app;
}
實際的創建 Application 方法又交給了 mActivityThread.mInstrumentation,它的型別 Instrumentation,這個類可是一個大管家,無論是 Application 還是 Activity,最后都會交給它來處理:
public Application newApplication(ClassLoader cl, String className, Context context) {
// 利用反射創建的Application
Application app = getFactory(context.getPackageName())
.instantiateApplication(cl, className);
app.attach(context);
return app;
}
Application 創建完成后還會呼叫 Application#attach() 方法,在這個方法,我們看到了熟悉的方法:
final void attach(Context context) {
attachBaseContext(context);
// ...
}
常用的方法 Application#attachBaseContext() 就是這個時候回呼的,
5. 初始化ContentProvider
這里沒什么好說的,只有一點,ContentProvider 的初始化時機要早于 Application#onCreate() 方法,
因為之前我就出現過在 ContentProvider#onCreate() 呼叫了某個 SDK,而這個 SDK 在 Application#onCreate() 才回完成初始化,結果就閃退了,

6. 呼叫Application#oncreate()
回到步驟3中的方法,mInstrumentation 的型別是 Instrumentation,它也是通過反射創建的,最侄訓執行 Instrumentation#callApplicationOnCreate 方法:
public void callApplicationOnCreate(Application app) {
app.onCreate();
}
在這個方法中,就可以看到我們的 Application#onCreate() 方法得以執行,這也是我們通常用來初始化 SDK 的地方,
在這完成以后,就會通過 AMS 啟動第一個 Activity,還是同樣的通信方式,就不和大伙繼續展開了,
總結
看到這兒,相信大伙兒對應用已經有了初步的認識,如果有什么爭議的地方,評論區見!
再談一些關于知識點的事,可能大家會認為學習一個個知識點是比較枯燥的事,
比如去了解Linux內核的一些知識、去了解Apk的組成、去看應用的啟動流程,但是當你把這些看似不連貫的點都能夠連貫起來的時候,你就會發現這還是挺有意思的!

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/378232.html
標籤:其他
