Android從開機到點擊Icon(桌面圖標)中間發生了什么?

系列文章

前言

上文Android Activity生命周期,啟動模式,啟動過程詳解我們講解了Activity的生命周期,以及Activity啟動過程的詳細步驟,Activity承載著App對用戶的內容展示,當我們在手機桌面上點開一個圖標便打開了一個App,App上顯示的界面對應背后的Activity,那么為什么點擊圖標便能打開App呢,其實桌面應用程序(Launcher)我們也可以理解為一個App,此Activity上承載著各種App圖標,點擊圖標便有一個響應事件,這個響應事件就是打開圖標對應的App。那么Launcher程序又是如何啟動的呢?自然是和手機開機有關,因為我們知道手機開機后便是默認桌面,所以本文我們將從手機開機說起,梳理一下從手機開機,到啟動Launcher程序,到點擊Icon打開APP這個過程,下面分別介紹以上三個過程。

本文源碼基于Android-25版本

開機過程分析

在看Android系統啟動流程前,我們先了解下計算機PC啟動過程,大概分為以為四步:

  • BIOS:主要做硬件自檢,轉移控制權等工作;
  • 主引導記錄:BIOS把控制權轉交給排在第一位的儲存設備(主引導記錄),共512字節;
  • 硬盤啟動:計算機的控制權由硬盤的某個分區控制;
  • 操作系統:計算機的控制權轉交給操作系統,內核首先被載入內存,以Linux為例,其第一個運行的程序是/sbin/init。它根據配置文件產生init進程,init是Linux啟動后的第一個進程,pid進程編號為1,其他進程都是它的后代。然后,init線程加載系統的各個模塊,比如窗口程序和網絡程序,直至執行/bin/login程序,跳出登錄界面,等待用戶輸入用戶名和密碼。

以上摘自 計算機是如何啟動的?[阮一峰]

因為Android系統是基于Linux內核的,所以系統的開機過程肯定涉及到Linux內核的啟動。但是Android系統屬于嵌入式系統,沒有計算機那樣的BIOS引導程序,取而代之的是系統引導Bootloader,為啟動系統內核做好準備。Android中也沒有硬盤,取而代之的是ROM,類似硬盤存放操作系統和應用程序等。整體開機流程如下圖所示:

Android開機流程

第一步:上電復位

開機時,通電產生一個CPU復位信號,CPU開始執行指令,第一條指令是設定好的(固化在ROM上),將引導程序(Bootloader)加載到RAM中;

第二步:Bootloader

Bootloader啟動,引導進入Linux內核;引導程序是運行的第一個程序,不同的主板和芯片具有不同引導程序,不同的手機廠商使用不同的程序,這部分不屬于Android操作系統,因此可能是廠商加鎖的地方;

第三步:Linux內核

Linux內核啟動后主要做一些初始化工作,比如初始化軟硬件環境,加載驅動程序,掛載根文件系統,內核加載的最后階段啟動第一個進程init進程;

第四步:init進程

init進程(/system/core/init/*)是系統第一個進程,進程號為1,該進程會首先加載一個init.rc配置文件,init.rc文件是Android系統的重要配置文件,位于(/system/core/rootdir/init.rc),其主要功能是定義了系統啟動時需要執行的一系列動作,設置環境變量,生成系統運行所需要的文件或目錄,執行特定Services等。

Android針對init.rc有特定的格式和規則,它由Android Init Language語言編寫而成,Android Init Language主要包含四種聲明:Actions(動作),Commands(命令),Services(服務),Options(選項)。

通過init.rc腳本主要啟動了以下服務:

Action/Service 描述
on early-init 設置init進程以及它創建的子進程的優先級,設置init進程的安全環境
on init 設置全局環境,為cpu accounting創建cgroup(資源控制)掛載點
on fs 掛載mtd分區
on post-fs 改變系統目錄的訪問權限
on post-fs-data 改變/data目錄以及它的子目錄的訪問權限
on boot 基本網絡的初始化,內存管理等等
service servicemanager 啟動系統管理器管理所有的本地服務,比如位置、音頻、Shared preference等等…
service zygote 啟動zygote作為應用進程

本表來自Android啟動過程深入解析

init.rc最重要的的任務是啟動一個Zygote(孵化器)進程,此進程負責啟動Android應用進程的啟動工作。init.rc通過include引入init.zygote.rc創建Zygote進程。

init.rc的具體語法可以參考深入分析AIL語言及init.rc文件

第五步:Zygote進程

Zygote進程孵化了所有Android應用進程,是Android Framework的基礎。在Java中,不同的虛擬機實例會為不同的應用分配不同的內存。Android應用程序是運行在Dalvik虛擬機里面的,并且每一個應用程序對應有一個單獨的Dalvik虛擬機實例。但是Android應用程序中的Dalvik虛擬機實例實際上是從Zygote進程的地址空間拷貝而來的,這樣就可以加快Android應用程序的啟動速度,Zygote讓Dalvik虛擬機共享代碼,低內存占用,最小啟動時間成為可能。

Zygote其實是一個虛擬機進程,它會完成虛擬機的初始化、庫的加載、預制類庫、核心類庫的初始化等,當系統需要一個新的虛擬機時,它會迅速復制自己,提供給系統。每當我們打開一個新的APP時都會fork之前的Zygote進程。下面介紹下Zygote啟動過程:

App_main.main()

此函數位于(frameworks/base/cmds/app_process/App_main.cpp)中,主要添加了Android運行環境,即創建一個AppRuntime變量runtime,而AppRuntime繼承自AndroidRuntime,在App_main.main()方法中執行了runtime.start()方法,也就是執行了AndroidRuntime類的start方法,由于在init.rc文件中設置了啟動app_process的參數為:--zygote--start-system-server,因此實際上在main函數里最終會執行下面語句:

runtime.start("com.android.internal.os.ZygoteInit", args, zygote);

AndroidRuntime.start()

此函數位于(frameworks/base/core/jni/AndroidRuntime.cpp)中,主要做了以下三件事情:

  • startVm():創建虛擬機,主要是關于虛擬機參數的設置;
  • startReg(): 注冊JNI方法;
  • env->GetStaticMethodID(): 通過JNI調用Java函數,進入Java代碼中,實際上最終調用了com.android.internal.os.ZygoteInit的main函數。

ZygoteInit.main()

此函數位于(frameworks/base/core/java/com/android/internal/os/ZygoteInit.java)中:

public static void main(String argv[]) {
    try {
        
        ......
        
        String socketName = "zygote";

        ......

        registerZygoteSocket(socketName);
        preload(); 
        
        ......

        if (startSystemServer) {
            startSystemServer(abiList, socketName);
        } 
        runSelectLoop(abiList); 
        closeServerSocket();
    } catch (MethodAndArgsCaller caller) {
        ......
        
    } catch (RuntimeException ex) {
        ......
        
    }
}

主要做了以下幾件事情:

  • registerZygoteSocket():創建一個名為Zygote的socket接口,用來和ActivityManagerService通訊
  • preload():預加載通用類等資源,如res(drawable,xml信息,strings)等
  • startSystemServer(): 啟動SystemServer等服務,在此函數內部Zygote進程通過Zygote.forkSystemServer函數來創建一個新的進程來啟動SystemServer組件,如下所示:
private static boolean startSystemServer(){
    
    ....
    // SystemServer是由Zygote通過Zygote.forkSystemServer函數fork出來的
    pid = Zygote.forkSystemServer(
                parsedArgs.uid, parsedArgs.gid,
                parsedArgs.gids, debugFlags, null,
                parsedArgs.permittedCapabilities,
                parsedArgs.effectiveCapabilities);

    ....
    
    // 子進程返回0,即SystemServer
    if (pid == 0) {
        ....
        handleSystemServerProcess(parsedArgs);
    }
}

private static void handleSystemServerProcess(
            ZygoteConnection.Arguments parsedArgs)
            throws ZygoteInit.MethodAndArgsCaller {
    // 關閉這里繼承來的socket,因為這里的子進程并不會使用到socket      
    closeServerSocket();
    
    // 繼續啟動SystemServer的操作
    RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
    /* should never reach here */
}

public static final void zygoteInit(String[] argv)  
            throws ZygoteInit.MethodAndArgsCaller { 
            
    ....
    // 初始化Binder進程間通信機制
    zygoteInitNative();  

    ....
}  
  • runSelectLoopMode(): 輪詢監聽socket,不斷處理來自客戶端的AMS請求,然后交給runOnce()處理

至此,Zygote進程就啟動完成了,總結如下:

  • init進程創建Zygote進程,而Zygote負責整個后續Android 應用程序的創建
  • Zygote進程啟動時會創建SystemServer進程,SystemServer進程負責啟動系統的關鍵服務,如ActivityManagerService,PowerManagerService,PackageManagerService等
  • 當我們準備啟動一個APP時,AMS通過Socket和Zygote進程間通信,通知Zygote fork子進程,加載需要的類。

整體過程如下圖所示:

Zygote啟動過程

SystemServer啟動過程

上述啟動Zygote進程的過程中,我們提到了啟動SystemServer這個Android系統核心進程,為了更清楚理解Android系統工作,這里我們再將啟動SystemServer進程的過程單獨列為一小節說明。

SystemServer由Zygote fork而成,進程名為system_server,承載著framework服務。

具體的SystemServer啟動過程較為復雜,通過一系列方法最終轉移到SystemServer類的main()方法中,如下圖所示:


SystemServer啟動過程

圖片來自 作者:Gityuan博客 來源:Android系統啟動-SystemServer上篇
鏈接:http://gityuan.com/2016/02/14/android-system-server/

SystemServer.main()方法主要的流程如下:

SystemServer.main()           // 初始化SystemServer對象,再調用run()方法
SystemServer.run()            // 調用以下方法
    createSystemContext()     // 創建system_server進程的上下文信息
    startBootstrapServices(); // 創建AMS,PMS,LightService,DisplayManagerService等服務
    startCoreServices();      // 啟動BatteryService,UsageStatsService,WebViewUpdateService服務
    startOtherServices();     // 顯示啟動界面,調用AMS.systemReady()方法
    Looper.loop();            // 開啟消息循環

在上面的startOtherServices()方法中有一段代碼如下:

mActivityManagerService.systemReady(new Runnable() {
    public void run() {
      ...
    }
});

public final class ActivityManagerService{

    public void systemReady(final Runnable goingCallback) {
        
        ....
        
        startHomeActivityLocked(mCurrentUserId, "systemReady"); //啟動桌面
        mStackSupervisor.resumeTopActivitiesLocked(); //恢復棧頂的Activity
    }
}

AMS的systemReady()方法中啟動了WebView,SystemUI,開啟WatchDog,啟動桌面Launcher 程序。
啟動Launcher的過程如下:首先通過Zygote進程fork一個子進程作為APP進程,然后創建Application,再啟動Activity,最后顯示出實際畫面。
完整的啟動過程如下:


Android完整開機過程

本圖片摘自 作者: Jeanboydev 來源:CSDN
鏈接 https://blog.csdn.net/freekiteyu/article/details/79318031

Launcher

從上面分析我們知道,AMS在完成服務注冊等初始化工作后,在main()方法中調用systemReady()方法,進而調用startHomeActivityLocked()來啟動Launcher。

boolean startHomeActivityLocked(int userId, String reason) {
    if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
            && mTopAction == null) {
        return false;
    }
    // 獲取啟動Launcher的intent信息
    Intent intent = getHomeIntent();
    // 通過PackageManagerService獲得Launcher的Activity描述信息Info
    ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
    if (aInfo != null) {
        intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
        aInfo = new ActivityInfo(aInfo);
        aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
        ProcessRecord app = getProcessRecordLocked(aInfo.processName,
                aInfo.applicationInfo.uid, true);
        if (app == null || app.instrumentationClass == null) {
            intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
            mActivityStarter.startHomeActivityLocked(intent, aInfo, reason);
        }
    } else {
        Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
    }
    return true;
}

啟動Launcher的Intent對象中添加了Intent.CATEGORY_HOME常量,代表將要啟動Home界面。
獲得intent和ActivityInfo后,后續方法調用順序為:

ActivityStarter.startHomeActivityLocked()
ActivityStackSupervisor.moveHomeStackTaskToTop() // 把Launcher的堆棧移到頂部
ActivityStarter.startActivityLocked()
......

進入ActivityStarter.startActivityLocked()方法后,后續過程就和啟動一個普通的Activity沒什么區別,啟動一個普通Activity的過程可以參考我的上一篇文章Android Activity生命周期,啟動模式,啟動過程詳解
第一次啟動Launcher時,執行到ActivityStackSupervisor的startSpecificActivityLocked()方法時,會判斷Launcher Activity所在進程是否已經存在,如果不存在則會創建一個進程容納Activity,創建進程的流程就是通過AMS向Zygote發起請求,Zygote收到請求后fork一個子進程,然后再繼續啟動Launcher。創建子進程的過程,我們會在下文中點擊Icon啟動一個APP的過程中進行分析。

總而言之,Launcher的啟動過程就是類似一個啟動Activity的過程。但是,我們知道Launcher上放著各種應用程序圖標,有著不同的狀態,因此其Activity實現具體原理還可以進一步探究。可以參考Android Launcher加載流程源碼分析Android M Launcher3主流程源碼淺析Android系統啟動流程(四)Launcher啟動過程與系統啟動流程

應用安裝的時候,通過PackageManagerService解析apk的AndroidManifest.xml文件,提取出這個apk的信息寫入packages.xml中,包括權限、包名、icon、apk安裝位置、版本、userID等信息,Launcher為已安裝的程序在桌面上生成圖標,點擊圖標便可啟動應用,下面分析啟動應用的過程。

本段來自 Jeanboydev CSDN博客 一篇文章看明白 Android 從點擊應用圖標到界面顯示的過程

點擊Icon啟動APP

其實點擊Icon啟動APP的過程就是啟動APP主Activity的過程,即啟動MainActivity的過程,從而把APP啟動起來,但是當我們新啟動一個APP過程時,需要判斷此APP所在進程是否已經存在,如果不存在則需要fork一個子進程。下面詳細介紹這個具體過程。

Launcher.startActivitySafely

點擊圖標時,桌面程序Launcher.java做出響應,調用Launcher.startActivitySafely來啟動一個Activity

public class Launcher extends Activity
        implements LauncherExterns, View.OnClickListener, OnLongClickListener,
                   LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
                   AccessibilityManager.AccessibilityStateChangeListener {
 
    ......

    public void onClick(View v) {
        Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {
            onClickAppShortcut(v);
        } 
        
        ......
    }
    
    protected void onClickAppShortcut(final View v) {
        
        ......
        
        startAppShortcutOrInfoActivity(v);
    }
    
    private void startAppShortcutOrInfoActivity(View v) {
        ItemInfo item = (ItemInfo) v.getTag();
        Intent intent = item.getIntent();
        
        ......
        
        boolean success = startActivitySafely(v, intent, item);

        ......
    }
    
    public boolean startActivitySafely(Intent intent, Object tag) {
        ......
        
        // 表示在新的Task中啟動此Activity
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        ......
        try {
        
            ......
            
            startActivity(intent);
        } catch (ActivityNotFoundException e) {
            ......
        } catch (SecurityException e) {
            ......
        }
    }
 
    ......
 
}

Activity.startActivity

Launcher繼承自Activity,因此上段代碼中startActivity(intent)便調用了Activity類的該方法:

public void startActivity(Intent intent) {
    this.startActivity(intent, null);
}

public void startActivity(Intent intent, @Nullable Bundle options) {
    if (options != null) {
        startActivityForResult(intent, -1, options);
    } else {
        startActivityForResult(intent, -1);
    }
}

Activity的StartActivity()方法最終調用了startActivityForResult()方法,這和我之前一篇文章Android Activity生命周期,啟動模式,啟動過程詳解相同,后續過程基本沒有區別。和前文類似,當執行到ActivityStackSupervisor的startSpecificActivityLocked()方法時,會判斷APP所在進程是否已經存在,如果不存在則會fork一個進程。下面介紹fork新進程的過程:

fork新進程

Android創建進程的流程圖大概如下:


Android創建進程

圖片來自 作者:Gityuan博客 來源:理解Android進程創建流程
鏈接:http://gityuan.com/2016/03/26/app-process-create/

ActivityManagerService.startProcessLocked()

ActivityStackSupervisor的startSpecificActivityLocked()的方法中有下面一段代碼

ProcessRecord app = mService.getProcessRecordLocked(r.processName, r.info.applicationInfo.uid, true);

第一次啟動APP時,很明顯上面獲得的變量app=null,在配置文件AndroidManifest.xml中我們如果沒有指定process屬性,系統會默認使用package的名稱當做process名。而且每一個程序都有自己的uid,uid+process的組合就可以為每一個應用程序創建一個ProcessRecord,每次新建進程前都會判斷此ProcessRecord是否存在,如果已經存在則不會新建進程。
如果不存在,則新建進程:

mService.startProcessLocked(r.processName, r.info.applicationInfotrue, 0,
        "activity", r.intent.getComponent(), false, false, true);

因為startProcessLocked有多個重載形式,上述方法最終調用的方法為:

private final void startProcessLocked(ProcessRecord app, String hostingType,
        String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
    
    ......
        
    Process.ProcessStartResult startResult = Process.start(entryPoint,
            app.processName, uid, uid, gids, debugFlags, mountExternal,
            app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
            app.info.dataDir, entryPointArgs);
    ......
    
}

最終轉到Process的start()方法中。

Process.start()

public static final ProcessStartResult start(final String processClass,
                              final String niceName,
                              int uid, int gid, int[] gids,
                              int debugFlags, int mountExternal,
                              int targetSdkVersion,
                              String seInfo,
                              String abi,
                              String instructionSet,
                              String appDataDir,
                              String[] zygoteArgs) {
    try {
        return startViaZygote(processClass, niceName, uid, gid, gids,
                debugFlags, mountExternal, targetSdkVersion, seInfo,
                abi, instructionSet, appDataDir, zygoteArgs);
    } catch (ZygoteStartFailedEx ex) {
        ......
    }
}

又繼續調用Process.startViaZygote()方法:

private static ProcessStartResult startViaZygote(final String processClass,
                              final String niceName,
                              final int uid, final int gid,
                              final int[] gids,
                              int debugFlags, int mountExternal,
                              int targetSdkVersion,
                              String seInfo,
                              String abi,
                              String instructionSet,
                              String appDataDir,
                              String[] extraArgs)
                              throws ZygoteStartFailedEx {
    synchronized(Process.class) {
        
        ......
        
        // openZygoteSocketIfNeeded(abi)方法是根據當前的abi來選擇與zygote還是zygote64來進行通信
        return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
    }
}

繼續調用zygoteSendArgsAndGetResult()方法:

private static ProcessStartResult zygoteSendArgsAndGetResult(
        ZygoteState zygoteState, ArrayList<String> args)
        throws ZygoteStartFailedEx {
    try {
        
        ......

        final BufferedWriter writer = zygoteState.writer;
        final DataInputStream inputStream = zygoteState.inputStream;

        writer.write(Integer.toString(args.size()));
        writer.newLine();

        for (int i = 0; i < sz; i++) {
            String arg = args.get(i);
            writer.write(arg);
            writer.newLine();
        }

        writer.flush();

        ProcessStartResult result = new ProcessStartResult();
        
        // 等待Zygote的socket返回新進程的pid
        result.pid = inputStream.readInt();
        result.usingWrapper = inputStream.readBoolean();

        if (result.pid < 0) {
            throw new ZygoteStartFailedEx("fork() failed");
        }
        return result;
    } catch (IOException ex) {
        zygoteState.close();
        throw new ZygoteStartFailedEx(ex);
    }
}

通過socket向Zygote發送一系列參數,然后進入阻塞等待狀態,直到遠端的socket服務端發送回來新創建的進程pid才返回。Zygote收到請求后,開始工作,我們又回到了ZygoteInit.main()方法中。

ZygoteInit.main()

ZygoteInit.main()方法中主要停留在runSelectLoop函數中,等待AMS的請求,收到請求時會調用ZygoteConnection的runOnce函數來處理請求,后序函數調用邏輯為:

ZygoteConnection.runOnce()
    Zygote.forkAndSpecialize()              // fork當前進程來創建一個子進程
    ZygoteConnection.handleChildProc()      // 啟動上面fork的子進程,并切換到子進程中執行后續代碼 
        RuntimeInit.zygoteInit()            // 創建Binder線程池,一些初始化工作
            RuntimeInit.invokeStaticMain()  // 利用反射ActivityThread.main()方法
ActivityThread.main()                       // 創建消息循環,進入消息循環狀態

ActivityThread.main()

public static void main(String[] args) {

    ......
    // 創建消息循環
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // 進入消息循環
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

此時只創建了應用程序的 ActivityThread 和 ApplicationThread,和開啟了 Handler 消息循環機制,其他的都還未創建, ActivityThread.attach(false) 又會最終到 ActivityMangerService 的 attachApplication,這個方法其實是將本地的 ApplicationThread 傳遞到 ActivityMangerService。然后 ActivityMangerService 就可以通過 ApplicationThread 的代理 ApplicationThreadProxy 來調用應用程序 ApplicationThread.bindApplication,通知應用程序的 ApplicationThread 已和 ActivityMangerService 綁定,可以不借助其他進程幫助直接通信了。此時 Launcher 的任務也算是完成了。

本段來源
作者:Jeanboydev
來源:CSDN
原文:https://blog.csdn.net/freekiteyu/article/details/79318031

下面看下ActivityMangerService的attachApplication方法:

public final void attachApplication(IApplicationThread thread) {
    synchronized (this) {
        ......
        attachApplicationLocked(thread, callingPid);
        ......
    }
}

private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid) {

    ......

    // 檢查頂層Activity是否等待被運行
    if (normalMode) {
        try {
            if (mStackSupervisor.attachApplicationLocked(app)) {
                didSomething = true;
            }
        } catch (Exception e) {
            Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
            badApp = true;
        }
    }

    // 尋找所有需要在該進程中運行的服務
    if (!badApp) {
        try {
            didSomething |= mServices.attachApplicationLocked(app, processName);
        } catch (Exception e) {
            Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
            badApp = true;
        }
    }

    // 檢查是否在這個進程中有下一個廣播接收者
    if (!badApp && isPendingBroadcastProcessLocked(pid)) {
        try {
            didSomething |= sendPendingBroadcastsLocked(app);
        } catch (Exception e) {
            // If the app died trying to launch the receiver we declare it 'bad'
            Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
            badApp = true;
        }
    }
    
    ......
    
    return true;
}

當發現有Activity需要被啟動時,調用ActivityStackSupervisor.attachApplicationLocked()方法:

boolean attachApplicationLocked(ProcessRecord app) throws RemoteException {
    ......
    
    realStartActivityLocked(hr, app, true, true);
    
    ......
}

后續具體的啟動步驟又回到了啟動一個普通Activity的過程中,同樣可以參考上篇文章Android Activity生命周期,啟動模式,啟動過程詳解
最后,再引用一張圖片結束本文:

Launcher啟動Activity流程

圖片來自 Jeanboydev 來源:CSDN
鏈接:https://blog.csdn.net/freekiteyu/article/details/79318031

至此,我們基本把Android從開機到點擊Icon的過程理清楚了,但是中間每一部分都有很多細節值得深入,因此本文只給了一個大概流程,具體的實現細節可以分模塊去查找探究,本文結束!

參考信息

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。