圖解 | 一圖摸清Android應(yīng)用進(jìn)程的啟動

一圖摸清Android應(yīng)用進(jìn)程的啟動~

大綱:

  • 簡要回顧
  • AMS發(fā)送socket請求
  • Zygote處理socket請求
  • 啟動binder線程池
  • 總結(jié)
  • 細(xì)節(jié)補(bǔ)充
  • 參考資料

本文約2.5k字寺鸥,閱讀大約11分鐘丈甸。

Android源碼基于8.0。

簡要回顧

先回顧一下Android系統(tǒng)的啟動過程:

init進(jìn)程fork出Zygote進(jìn)程后巡蘸,Zygote進(jìn)程會創(chuàng)建一個服務(wù)端socket乃坤,等待AMS發(fā)起socket請求旬迹。

同時,由Zygote進(jìn)程fork出的SystemServer進(jìn)程會啟動各項(xiàng)系統(tǒng)服務(wù)液斜,其中就包含了AMS累贤,AMS會啟動Launcher桌面叠穆,此時就可以等待用戶點(diǎn)擊App圖標(biāo)來啟動應(yīng)用進(jìn)程了。

image

然后看下系統(tǒng)服務(wù)的啟動臼膏,不管是由init進(jìn)程啟動的獨(dú)立進(jìn)程的系統(tǒng)服務(wù)如SurfaceFlinger硼被,還是由SystemServer進(jìn)程啟動的非獨(dú)立進(jìn)程的系統(tǒng)服務(wù)如AMS,都是在ServiceManager進(jìn)程中完成注冊和獲取的渗磅,在跨進(jìn)程通信上使用了Android的binder機(jī)制嚷硫。

image

ServiceManager進(jìn)程本身也是一個系統(tǒng)服務(wù),經(jīng)過啟動進(jìn)程始鱼、啟動binder機(jī)制仔掸、發(fā)布自己等待請求4個步驟,就可以處理其他系統(tǒng)服務(wù)的獲取和注冊需求了风响。

AMS發(fā)送socket請求

Android應(yīng)用進(jìn)程的啟動是被動式的嘉汰,在Launcher桌面點(diǎn)擊圖標(biāo)啟動一個應(yīng)用的組件如Activity時,如果Activity所在的進(jìn)程不存在状勤,就會創(chuàng)建并啟動進(jìn)程鞋怀。

點(diǎn)擊App圖標(biāo)后經(jīng)過層層調(diào)用會來到ActivityStackSupervisor的startSpecificActivityLocked方法,

//ActivityStackSupervisor.java
final ActivityManagerService mService;

void startSpecificActivityLocked(...) {
    //查找Activity所在的進(jìn)程持搜,ProcessRecord是用來封裝進(jìn)程信息的數(shù)據(jù)結(jié)構(gòu)
    ProcessRecord app = mService.getProcessRecordLocked(...);
    //如果進(jìn)程已啟動密似,并且binder句柄IApplicationThread也拿到了,那就直接啟動Activity
    if (app != null && app.thread != null) {
        realStartActivityLocked(r, app, andResume, checkConfig);
        return;
    }
    //否則葫盼,讓AMS啟動進(jìn)程
    mService.startProcessLocked(...);
}

app.thread并不是線程残腌,而是一個binder句柄。應(yīng)用進(jìn)程使用AMS需要拿到AMS的句柄IActivityManager贫导,而系統(tǒng)需要通知應(yīng)用和管理應(yīng)用的生命周期抛猫,所以也需要持有應(yīng)用進(jìn)程的binder句柄IApplicationThread。

也就是說孩灯,他們互相持有彼此的binder句柄闺金,來實(shí)現(xiàn)雙向通信

image

那IApplicationThread句柄是怎么傳給AMS的呢峰档?

Zygote進(jìn)程收到socket請求后會處理請求參數(shù)败匹,執(zhí)行ActivityThread的入口函數(shù)main,

//ActivityThread.java
public static void main(String[] args) {
    //創(chuàng)建主線程的looper
    Looper.prepareMainLooper();
    //ActivityThread并不是線程讥巡,只是普通的java對象
    ActivityThread thread = new ActivityThread();
    //告訴AMS掀亩,應(yīng)用已經(jīng)啟動好了
    thread.attach(false);
    //運(yùn)行l(wèi)ooper,啟動消息循環(huán)
    Looper.loop();
}

private void attach(boolean system) {
    //獲取AMS的binder句柄IActivityManager
    final IActivityManager mgr = ActivityManager.getService();
    //告訴AMS應(yīng)用進(jìn)程已經(jīng)啟動欢顷,并傳入應(yīng)用進(jìn)程自己的binder句柄IApplicationThread
    mgr.attachApplication(mAppThread);
}

所以對于AMS來說槽棍,

  1. AMS向Zygote發(fā)起啟動應(yīng)用的socket請求,Zygote收到請求fork出進(jìn)程,返回進(jìn)程的pid給AMS刹泄;
  2. 應(yīng)用進(jìn)程啟動好后外里,執(zhí)行入口main函數(shù),通過attachApplication方法告訴AMS已經(jīng)啟動特石,同時傳入應(yīng)用進(jìn)程的binder句柄IApplicationThread盅蝗。

完成這兩步,應(yīng)用進(jìn)程的啟動過程才算完成姆蘸。

下面看AMS的startProcessLocked啟動應(yīng)用進(jìn)程時都做了些什么墩莫。

//ActivityManagerService.java
final ProcessRecord startProcessLocked(...){
    ProcessRecord app = getProcessRecordLocked(processName, info.uid, keepIfLarge);
    //如果進(jìn)程信息不為空,并且已經(jīng)拿到了Zygote進(jìn)程返回的應(yīng)用進(jìn)程pid
    //說明AMS已經(jīng)請求過了逞敷,并且Zygote已經(jīng)響應(yīng)請求然后fork出進(jìn)程了
    if (app != null && app.pid > 0) {
        //但是app.thread還是空狂秦,說明應(yīng)用進(jìn)程還沒來得及注冊自己的binder句柄給AMS
        //即此時進(jìn)程正在啟動,那就直接返回推捐,避免重復(fù)創(chuàng)建
        if (app.thread == null) {
            return app;
        }
    }
    //調(diào)用重載方法
    startProcessLocked(...);
}

之所以要判斷app.thread裂问,是為了避免當(dāng)應(yīng)用進(jìn)程正在啟動的時候,假如又有另一個組件需要啟動牛柒,導(dǎo)致重復(fù)拉起(創(chuàng)建)應(yīng)用進(jìn)程堪簿。

繼續(xù)看重載方法startProcessLocked,

//ActivityManagerService.java
private final void startProcessLocked(...){
    //應(yīng)用進(jìn)程的主線程的類名
    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>();
    //...處理各種參數(shù)
    return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}

其中:

  1. openZygoteSocketIfNeeded打開本地socket
  2. zygoteSendArgsAndGetResult發(fā)送請求參數(shù)椭更,其中帶上了ActivityThread類名
  3. return返回的數(shù)據(jù)結(jié)構(gòu)ProcessStartResult中會有pid字段

梳理一下:

image

注意:Zygote進(jìn)程啟動時已經(jīng)創(chuàng)建好了虛擬機(jī)實(shí)例,所以由他fork出的應(yīng)用進(jìn)程可以直接繼承過來用而無需創(chuàng)建蛾魄。

下面來看Zygote是如何處理socket請求的虑瀑。

Zygote處理socket請求

圖解Android系統(tǒng)的啟動 一文可知,在ZygoteInit的main函數(shù)中滴须,會創(chuàng)建服務(wù)端socket舌狗,

//ZygoteInit.java
public static void main(String argv[]) {
    //Server類,封裝了socket
    ZygoteServer zygoteServer = new ZygoteServer();
    //創(chuàng)建服務(wù)端socket扔水,名字為socketName即zygote
    zygoteServer.registerServerSocket(socketName);
    //進(jìn)入死循環(huán)痛侍,等待AMS發(fā)請求過來
    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);

    //創(chuàng)建文件描述符fd
    FileDescriptor fd = new FileDescriptor();
    fd.setInt$(fileDesc);
    //創(chuàng)建LocalServerSocket對象
    mServerSocket = new LocalServerSocket(fd);
}

void runSelectLoop(String abiList){
    //進(jìn)入死循環(huán)
    while (true) {
        for (int i = pollFds.length - 1; i >= 0; --i) {
            if (i == 0) {
                //...
            } else {
                //得到一個連接對象ZygoteConnection恋日,調(diào)用他的runOnce
                boolean done = peers.get(i).runOnce(this);
            }
        }
    }
}

來到ZygoteConnection的runOnce膀篮,

//ZygoteConnection.java
boolean runOnce(ZygoteServer zygoteServer){
    //讀取socket請求的參數(shù)列表
    String args[] = readArgumentList();
    //創(chuàng)建應(yīng)用進(jìn)程
    int pid = Zygote.forkAndSpecialize(...);
    if (pid == 0) {
        //如果是應(yīng)用進(jìn)程(Zygote fork出來的子進(jìn)程)嘹狞,處理請求參數(shù)
        handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
        return true;
    } else {
        return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
    }
}

handleChildProc方法調(diào)用了ZygoteInit的zygoteInit方法,里邊主要做了3件事:

  1. 啟動binder線程池(后面分析)
  2. 讀取請求參數(shù)拿到ActivityThread類并執(zhí)行他的main函數(shù)誓竿,執(zhí)行thread.attach告知AMS并回傳自己的binder句柄
  3. 執(zhí)行Looper.loop()啟動消息循環(huán)(代碼前面有)

這樣應(yīng)用進(jìn)程就啟動起來了磅网。梳理一下,

image

下面看下binder線程池是怎么啟動的筷屡。

啟動binder線程池

Zygote的跨進(jìn)程通信沒有使用binder涧偷,而是socket簸喂,所以應(yīng)用進(jìn)程的binder機(jī)制不是繼承而來,而是進(jìn)程創(chuàng)建后自己啟動的燎潮。

前邊可知喻鳄,Zygote收到socket請求后會得到一個ZygoteConnection,他的runOnce會調(diào)用handleChildProc确封,

//ZygoteConnection.java
private void handleChildProc(...){
    ZygoteInit.zygoteInit(...);
}

//ZygoteInit.java
public static final void zygoteInit(...){
    RuntimeInit.commonInit();
    //進(jìn)入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構(gòu)造函數(shù)
ProcessState::ProcessState(const char *driver)
    : mDriverName(String8(driver))
        , mDriverFD(open_driver(driver)) //打開binder驅(qū)動
        ,//...
{
    if (mDriverFD >= 0) {
        //mmap是一種內(nèi)存映射文件的方法,把mDriverFD映射到當(dāng)前的內(nèi)存空間
        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) {
        //創(chuàng)建線程名字"Binder:${pid}_${自增數(shù)字}"
        String8 name = makeBinderThreadName();
        sp<Thread> t = new PoolThread(isMain);
        //運(yùn)行binder線程
        t->run(name.string());
    }
}

ProcessState有兩個宏定義值得注意一下秉剑,感興趣可以看 一次Binder通信最大可以傳輸多大的數(shù)據(jù) 這篇文章泛豪,

//ProcessState.cpp
//一次Binder通信最大可以傳輸?shù)拇笮∈?1MB-4KB*2
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
//binder驅(qū)動的文件描述符fd被限制了最大線程數(shù)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線程注冊進(jìn)binder驅(qū)動程序的線程池中
        IPCThreadState::self()->joinThreadPool(mIsMain);
        return false;
    }
    
    const bool mIsMain;
};

來到IPCThreadState.cpp侦鹏,

//IPCThreadState.cpp
void IPCThreadState::joinThreadPool(bool isMain)
{
    //向binder驅(qū)動寫數(shù)據(jù):進(jìn)入死循環(huán)
    mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);
    status_t result;
    do {
        //進(jìn)入死循環(huán)诡曙,等待指令的到來
        result = getAndExecuteCommand();
    } while (result != -ECONNREFUSED && result != -EBADF);
    //向binder驅(qū)動寫數(shù)據(jù):退出死循環(huán)
    mOut.writeInt32(BC_EXIT_LOOPER);
}

status_t IPCThreadState::getAndExecuteCommand()
{
    //從binder驅(qū)動讀數(shù)據(jù),得到指令
    cmd = mIn.readInt32();
    //執(zhí)行指令
    result = executeCommand(cmd);
    return result;
}

梳理一下binder的啟動過程:

  1. 打開binder驅(qū)動
  2. 映射內(nèi)存种柑,分配緩沖區(qū)
  3. 運(yùn)行binder線程岗仑,進(jìn)入死循環(huán),等待指令

總結(jié)

綜上聚请,Android應(yīng)用進(jìn)程的啟動可以總結(jié)成以下步驟:

  1. 點(diǎn)擊Launcher桌面的App圖標(biāo)
  2. AMS發(fā)起socket請求
  3. Zygote進(jìn)程接收請求并處理參數(shù)
  4. Zygote進(jìn)程fork出應(yīng)用進(jìn)程荠雕,應(yīng)用進(jìn)程繼承得到虛擬機(jī)實(shí)例
  5. 應(yīng)用進(jìn)程啟動binder線程池、運(yùn)行ActivityThread類的main函數(shù)驶赏、啟動Looper循環(huán)

完整流程圖:

image

可見binder用得還是非常多的炸卑,下篇就補(bǔ)一補(bǔ)binder吧~

系列文章:

細(xì)節(jié)補(bǔ)充

  • 拋異常清空堆棧幀:Zygote不是直接執(zhí)行ActivityThread的main函數(shù)的,而是通過拋出一個異常進(jìn)行捕獲煤傍,捕獲后再執(zhí)行盖文,這樣可以清除初始化過程產(chǎn)生的調(diào)用堆棧,讓ActivityThread的main函數(shù)看起來像個應(yīng)用程序進(jìn)程的入口函數(shù)蚯姆。

參考資料


更多性感文章五续,關(guān)注原創(chuàng)技術(shù)公眾號:哈利迪ei

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市龄恋,隨后出現(xiàn)的幾起案子疙驾,更是在濱河造成了極大的恐慌,老刑警劉巖郭毕,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件它碎,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)扳肛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門傻挂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挖息,你說我怎么就攤上這事金拒。” “怎么了套腹?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵殖蚕,是天一觀的道長。 經(jīng)常有香客問我沉迹,道長睦疫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任鞭呕,我火速辦了婚禮蛤育,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘葫松。我一直安慰自己瓦糕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布腋么。 她就那樣靜靜地躺著咕娄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪珊擂。 梳的紋絲不亂的頭發(fā)上圣勒,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天,我揣著相機(jī)與錄音摧扇,去河邊找鬼圣贸。 笑死,一個胖子當(dāng)著我的面吹牛扛稽,可吹牛的內(nèi)容都是我干的吁峻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼在张,長吁一口氣:“原來是場噩夢啊……” “哼用含!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起帮匾,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤啄骇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后辟狈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肠缔,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年哼转,在試婚紗的時候發(fā)現(xiàn)自己被綠了明未。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡壹蔓,死狀恐怖趟妥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情佣蓉,我是刑警寧澤披摄,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站勇凭,受9級特大地震影響疚膊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜虾标,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一寓盗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧璧函,春花似錦傀蚌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至库继,卻和暖如春箩艺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宪萄。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工舅桩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人雨膨。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓擂涛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親聊记。 傳聞我的和親對象是個殘疾皇子撒妈,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355