簡要回顧
先回顧一下Android系統的啟動過程:
init進程fork出Zygote進程后,Zygote進程會創建一個服務端socket,等待AMS發起socket請求。
同時,由Zygote進程fork出的SystemServer進程會啟動各項系統服務,其中就包含了AMS,AMS會啟動Launcher桌面,此時就可以等待用戶點擊App圖標來啟動應用進程了。
然后看下系統服務的啟動,不管是由init進程啟動的獨立進程的系統服務如SurfaceFlinger,還是由SystemServer進程啟動的非獨立進程的系統服務如AMS,都是在ServiceManager進程中完成注冊和獲取的,在跨進程通信上使用了Android的binder機制。
ServiceManager進程本身也是一個系統服務,經過啟動進程、啟動binder機制、發布自己和等待請求4個步驟,就可以處理其他系統服務的獲取和注冊需求了。
AMS發送socket請求
Android應用進程的啟動是被動式的,在Launcher桌面點擊圖標啟動一個應用的組件如Activity時,如果Activity所在的進程不存在,就會創建并啟動進程。
點擊App圖標后經過層層調用會來到ActivityStackSupervisor的startSpecificActivityLocked方法。
//ActivityStackSupervisor.JAVA
final ActivityManagerService mService;
void startSpecificActivityLocked(...) {
//查找Activity所在的進程,ProcessRecord是用來封裝進程信息的數據結構
ProcessRecord app = mService.getProcessRecordLocked(...);
//如果進程已啟動,并且binder句柄IApplicationThread也拿到了,那就直接啟動Activity
if (app != null && app.thread != null) {
realStartActivityLocked(r, app, andResume, checkConfig);
return;
}
//否則,讓AMS啟動進程
mService.startProcessLocked(...);
}
app.thread并不是線程,而是一個binder句柄。應用進程使用AMS需要拿到AMS的句柄IActivityManager,而系統需要通知應用和管理應用的生命周期,所以也需要持有應用進程的binder句柄IApplicationThread。
也就是說,他們互相持有彼此的binder句柄,來實現雙向通信。
那IApplicationThread句柄是怎么傳給AMS的呢?Zygote進程收到socket請求后會處理請求參數,執行ActivityThread的入口函數main。
//ActivityThread.java
public static void main(String[] args) {
//創建主線程的looper
Looper.prepareMainLooper();
//ActivityThread并不是線程,只是普通的java對象
ActivityThread thread = new ActivityThread();
//告訴AMS,應用已經啟動好了
thread.attach(false);
//運行looper,啟動消息循環
Looper.loop();
}
private void attach(boolean system) {
//獲取AMS的binder句柄IActivityManager
final IActivityManager mgr = ActivityManager.getService();
//告訴AMS應用進程已經啟動,并傳入應用進程自己的binder句柄IApplicationThread
mgr.attachApplication(mAppThread);
}
所以對于AMS來說:
1.AMS向Zygote發起啟動應用的socket請求,Zygote收到請求fork出進程,返回進程的pid給AMS;
2.應用進程啟動好后,執行入口main函數,通過attachApplication方法告訴AMS已經啟動,同時傳入應用進程的binder句柄IApplicationThread。
完成這兩步,應用進程的啟動過程才算完成。
下面看AMS的startProcessLocked啟動應用進程時都做了些什么。
//ActivityManagerService.java
final ProcessRecord startProcessLocked(...){
ProcessRecord app = getProcessRecordLocked(processName, info.uid, keepIfLarge);
//如果進程信息不為空,并且已經拿到了Zygote進程返回的應用進程pid
//說明AMS已經請求過了,并且Zygote已經響應請求然后fork出進程了
if (app != null && app.pid > 0) {
//但是app.thread還是空,說明應用進程還沒來得及注冊自己的binder句柄給AMS
//即此時進程正在啟動,那就直接返回,避免重復創建
if (app.thread == null) {
return app;
}
}
//調用重載方法
startProcessLocked(...);
}
之所以要判斷app.thread,是為了避免當應用進程正在啟動的時候,假如又有另一個組件需要啟動,導致重復拉起(創建)應用進程。
繼續看重載方法startProcessLocked:
//ActivityManagerService.java
private final void startProcessLocked(...){
//應用進程的主線程的類名
if (entryPoint == null) entryPoint = "android.app.ActivityThread";
ProcessStartResult startResult = Process.start(entryPoint, ...);
}
//Process.java
public static final ProcessStartResult start(...){
return zygoteProcess.start(...);
}
來到ZygoteProcess。
//ZygoteProcess.java
public final Process.ProcessStartResult start(...){
return startViaZygote(...);
}
private Process.ProcessStartResult startViaZygote(...){
ArrayList<String> argsForZygote = new ArrayList<String>();
//...處理各種參數
return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}
其中:
- openZygoteSocketIfNeeded打開本地socket
- zygoteSendArgsAndGetResult發送請求參數,其中帶上了ActivityThread類名
- return返回的數據結構ProcessStartResult中會有pid字段
梳理一下:
注意:Zygote進程啟動時已經創建好了虛擬機實例,所以由他fork出的應用進程可以直接繼承過來用而無需創建。
下面來看Zygote是如何處理socket請求的。
Zygote處理socket請求
從 圖解Android系統的啟動 一文可知,在ZygoteInit的main函數中,會創建服務端socket。
//ZygoteInit.java
public static void main(String argv[]) {
//Server類,封裝了socket
ZygoteServer zygoteServer = new ZygoteServer();
//創建服務端socket,名字為socketName即zygote
zygoteServer.registerServerSocket(socketName);
//進入死循環,等待AMS發請求過來
zygoteServer.runSelectLoop(abiList);
}
看到ZygoteServer。
//ZygoteServer.java
void registerServerSocket(String socketName) {
int fileDesc;
//socket真正的名字被加了個前綴,即 "ANDROID_SOCKET_" + "zygote"
final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
String env = System.getenv(fullSocketName);
fileDesc = Integer.parseInt(env);
//創建文件描述符fd
FileDescriptor fd = new FileDescriptor();
fd.setInt$(fileDesc);
//創建LocalServerSocket對象
mServerSocket = new LocalServerSocket(fd);
}
void runSelectLoop(String abiList){
//進入死循環
while (true) {
for (int i = pollFds.length - 1; i >= 0; --i) {
if (i == 0) {
//...
} else {
//得到一個連接對象ZygoteConnection,調用他的runOnce
boolean done = peers.get(i).runOnce(this);
}
}
}
}
來到ZygoteConnection的runOnce。
boolean runOnce(ZygoteServer zygoteServer){
//讀取socket請求的參數列表
String args[] = readArgumentList();
//創建應用進程
int pid = Zygote.forkAndSpecialize(...);
if (pid == 0) {
//如果是應用進程(Zygote fork出來的子進程),處理請求參數
handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
return true;
} else {
return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
}
}
handleChildProc方法調用了ZygoteInit的zygoteInit方法,里邊主要做了3件事:
- 啟動binder線程池(后面分析)
- 讀取請求參數拿到ActivityThread類并執行他的main函數,執行thread.attach告知AMS并回傳自己的binder句柄
- 執行Looper.loop()啟動消息循環(代碼前面有)
這樣應用進程就啟動起來了。梳理一下:
下面看下binder線程池是怎么啟動的。
啟動binder線程池
Zygote的跨進程通信沒有使用binder,而是socket,所以應用進程的binder機制不是繼承而來,而是進程創建后自己啟動的。
前邊可知,Zygote收到socket請求后會得到一個ZygoteConnection,他的runOnce會調用handleChildProc。
//ZygoteConnection.java
private void handleChildProc(...){
ZygoteInit.zygoteInit(...);
}
//ZygoteInit.java
public static final void zygoteInit(...){
RuntimeInit.commonInit();
//進入native層
ZygoteInit.nativeZygoteInit();
RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
}
來到AndroidRuntime.cpp:
//AndroidRuntime.cpp
static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz){
gCurRuntime->onZygoteInit();
}
來到app_main.cpp:
//app_main.cpp
virtual void onZygoteInit() {
//獲取單例
sp<ProcessState> proc = ProcessState::self();
//在這里啟動了binder線程池
proc->startThreadPool();
}
看下ProcessState.cpp:
//ProcessState.cpp
sp<ProcessState> ProcessState::self()
{
//單例模式,返回ProcessState對象
if (gProcess != NULL) {
return gProcess;
}
gProcess = new ProcessState("/dev/binder");
return gProcess;
}
//ProcessState構造函數
ProcessState::ProcessState(const char *driver)
: mDriverName(String8(driver))
, mDriverFD(open_driver(driver)) //打開binder驅動
,//...
{
if (mDriverFD >= 0) {
//mmap是一種內存映射文件的方法,把mDriverFD映射到當前的內存空間
mVMStart = mmap(0, BINDER_VM_SIZE, PROT_READ,
MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);
}
}
//啟動了binder線程池
void ProcessState::startThreadPool()
{
if (!mThreadPoolStarted) {
mThreadPoolStarted = true;
spawnPooledThread(true);
}
}
void ProcessState::spawnPooledThread(bool isMain)
{
if (mThreadPoolStarted) {
//創建線程名字"Binder:${pid}_${自增數字}"
String8 name = makeBinderThreadName();
sp<Thread> t = new PoolThread(isMain);
//運行binder線程
t->run(name.string());
}
}
ProcessState有兩個宏定義值得注意一下:
//ProcessState.cpp
//一次Binder通信最大可以傳輸的大小是 1MB-4KB*2
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
//binder驅動的文件描述符fd被限制了最大線程數15
#define DEFAULT_MAX_BINDER_THREADS 15
我們看下binder線程PoolThread長啥樣:
class PoolThread : public Thread {
public:
explicit PoolThread(bool isMain)
: mIsMain(isMain){}
protected:
virtual bool threadLoop()
{ //把binder線程注冊進binder驅動程序的線程池中
IPCThreadState::self()->joinThreadPool(mIsMain);
return false;
}
const bool mIsMain;
};
來到IPCThreadState.cpp:
//IPCThreadState.cpp
void IPCThreadState::joinThreadPool(bool isMain)
{
//向binder驅動寫數據:進入死循環
mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
status_t result;
do {
//進入死循環,等待指令的到來
result = getAndExecuteCommand();
} while (result != -ECONNREFUSED && result != -EBADF);
//向binder驅動寫數據:退出死循環
mOut.writeInt32(BC_EXIT_LOOPER);
}
status_t IPCThreadState::getAndExecuteCommand()
{
//從binder驅動讀數據,得到指令
cmd = mIn.readInt32();
//執行指令
result = executeCommand(cmd);
return result;
}
梳理一下binder的啟動過程:
- 打開binder驅動
- 映射內存,分配緩沖區
- 運行binder線程,進入死循環,等待指令
總結
綜上,Android應用進程的啟動可以總結成以下步驟:
- 點擊Launcher桌面的App圖標
- AMS發起socket請求
- Zygote進程接收請求并處理參數
- Zygote進程fork出應用進程,應用進程繼承得到虛擬機實例
- 應用進程啟動binder線程池、運行ActivityThread類的main函數、啟動Looper循環
完整流程圖:
面試前的知識梳理,儲備提升
自己的知識準備得怎么樣,這直接決定了你能否順利通過一面和二面,所以在面試前來一個知識梳理,看需不需要提升自己的知識儲備是很有必要的。
關于知識梳理,這里再分享一下我面試這段時間的復習路線:(以下體系的復習資料是我從各路大佬收集整理好的)
- 架構師筑基必備技能:深入Java泛型+注解深入淺出+并發編程+數據傳輸與序列化+Java虛擬機原理+反射與類加載+動態代理+高效IO
- Android高級UI與FrameWork源碼:高級UI晉升+Framework內核解析+Android組件內核+數據持久化
- 360°全方面性能調優:設計思想與代碼質量優化+程序性能優化+開發效率優化
- 解讀開源框架設計思想:熱修復設計+插件化框架解讀+組件化框架設計+圖片加載框架+網絡訪問框架設計+RXJava響應式編程框架設計+IOC架構設計+Android架構組件Jetpack
- NDK模塊開發:NDK基礎知識體系+底層圖片處理+音視頻開發
- 微信小程序:小程序介紹+UI開發+API操作+微信對接
- Hybrid 開發與Flutter:html5項目實戰+Flutter進階
知識梳理完之后,就需要進行查漏補缺,所以針對這些知識點,我手頭上也準備了不少的電子書和筆記,這些筆記將各個知識點進行了完美的總結。
《507頁Android開發相關源碼解析》
《379頁Android開發面試寶典》
3.項目復盤
實際上,面試的一二輪所問到的技術問題,很多都是圍繞著你的項目展開,因此在面試前最后要做好的一件事情就是項目復盤。關于項目復盤,我個人的思路如下,可供參考:
- 你在這個項目中承擔了什么樣的角色?
- 這個項目的背景是什么,如果是技術項目,為什么要做?
- 有哪些技術難點,是怎么解決的,是否還有更好的方案?
- 你認為項目中是否有可以改進的點?
- 這個項目解決了什么問題,最好用數據說話,這個數據又是怎么得出來的?
提前把思路捋一捋,上面這些問題好好思考或準備一下,做到心中有譜以后,自然能夠面試官聊得融洽,保持一個好的心態,通過的幾率就會更大一些。
以上文章中的資料,均可以免費分享給大家來學習,
資料太多,全部展示會影響篇幅,暫時就先列舉這些部分截圖;






