Android 架構啟動流程

來源:互聯網
上載者:User

Android 架構啟動流程

As we all know,Android手機系統本質上是一個基於Linux的應用程式,它以Linux系統為核心。因此系統的啟動過程包括Linux核心啟動和Android架構啟動兩個階段。

Linux核心啟動

1、裝載引導程式bootloader
Linux核心啟動時首先裝載執行bootloader引導程式,裝載完成後進入核心程式。
2、載入Linux核心
Linux核心載入主要包括初始化kernel核心(記憶體初始化,開啟中斷,初始化進程表等)、初始化驅動、啟動核心後台(daemons)線程、安裝根(root)檔案系統等。
Linux載入的最後階段啟動執行第一個使用者級進程init(核心引導參數上一般都會設定“init=/init”,由kernel自動執行,PID為1,是所有進程的父進程)。由此進入Android架構的啟動階段。

android架構的啟動Android架構的啟動始於init進程,這個階段也是本文要重點講解的。概括起來啟動過程可以分為以下幾個主要的階段:
1、init進程啟動
2、init.rc指令碼啟動
3、zygote服務啟動
4、System Server進程啟動
5、Home應用啟動  下面將從Android5.0源碼中,和網路達人對此的總結中,對此過程加以學習瞭解和總結,

以下學習過程中程式碼片段中均有省略不完整,請參照源碼。Init進程的啟動 

init是一個進程,確切地說,它是Linux系統中使用者空間的第一個進程。由於Android是基於Linux核心的,所以init也是Android系統中使用者空間的第一個進程,它的進程號是1。

init進程的入口函數是main,system\core\init\init.c

init進程可以在/system/core/init找到。
init.rc檔案可以在/system/core/rootdir/init.rc找到。
readme.txt可以在/system/core/init/readme.txt找到。

 

int main(int argc, char **argv){intdevice_fd = -1;intproperty_set_fd = -1;intsignal_recv_fd = -1;intkeychord_fd = -1;int fd_count;ints[2];intfd;structsigaction act;chartmp[PROP_VALUE_MAX];structpollfd ufds[4];char*tmpdev;char*debuggable;//設定子進程退出的訊號處理函數,該函數為sigchld_handler。act.sa_handler = sigchld_handler;act.sa_flags= SA_NOCLDSTOP;act.sa_mask = 0;act.sa_restorer = NULL;sigaction(SIGCHLD, &act, 0);......//建立一些檔案夾,並掛載裝置,這些是和Linux相關的,不擬做過多討論。mkdir("/dev/socket", 0755);mount("devpts", "/dev/pts", "devpts", 0,NULL);mount("proc", "/proc", "proc", 0, NULL);mount("sysfs", "/sys", "sysfs", 0, NULL);//重新導向標準輸入/輸出/錯誤輸出到/dev/_null_。open_devnull_stdio();/*設定init的日誌輸出裝置為/dev/__kmsg__,不過該檔案開啟後,會立即被unlink了,這樣,其他進程就無法開啟這個檔案讀取日誌資訊了。*/log_init();//上面涉及很多和Linux系統相關的知識,不熟悉的讀者可自行研究,它們不影響我們的分析//解析init.rc設定檔parse_config_file("/init.rc");......//下面這個函數通過讀取/proc/cpuinfo得到機器的Hardware名,我的HTCG7手機為bravo。get_hardware_name();snprintf(tmp,sizeof(tmp), "/init.%s.rc", hardware);//解析這個和機器相關的設定檔,我的G7手機對應檔案為init.bravo.rc。parse_config_file(tmp);/*解析完上述兩個設定檔後,會得到一系列的Action(動作),下面兩句代碼將執行那些處於early-init階段的Action。init將動作執行的時間劃分為四個階段:early-init、init、early-boot、boot。由於有些動作必須在其他動作完成後才能執行,所以就有了先後之分。哪些動作屬於哪個階段由設定檔決定。後面會介紹設定檔的相關知識。*/action_for_each_trigger("early-init", action_add_queue_tail);drain_action_queue();/*建立利用Uevent和Linux核心互動的socket。關於Uevent的知識,第9章中對Vold進行分析時會做介紹。*/device_fd = device_init();//初始化和屬性相關的資源property_init();//初始化/dev/keychord裝置,這和調試有關,本書不討論它的用法。讀者可以自行研究,//內容比較簡單。keychord_fd = open_keychord();....../*INIT_IMAGE_FILE定義為”/initlogo.rle”,下面這個函數將載入這個檔案作為系統的開機畫面,注意,它不是開機動畫控製程序bootanimation載入的開機動畫檔案。*/if(load_565rle_image(INIT_IMAGE_FILE) ) {/*如果載入initlogo.rle檔案失敗(可能是沒有這個檔案),則會開啟/dev/ty0裝置,並輸出”ANDROID”的字樣作為開機畫面。在模擬器上看到的開機畫面就是它。*/......}}if(qemu[0])import_kernel_cmdline(1);......//調用property_set函數設定屬性項,一個屬性項包括屬性名稱和屬性值。property_set("ro.bootloader", bootloader[0] ? bootloader :"unknown");......//執行位於init階段的動作action_for_each_trigger("init", action_add_queue_tail);drain_action_queue();//啟動屬性服務property_set_fd = start_property_service();/*調用socketpair函數建立兩個已經connect好的socket。socketpair是Linux的系統調用,不熟悉的讀者可以利用man socketpair查詢相關資訊。後面就會知道它們的用處了。*/if(socketpair(AF_UNIX, SOCK_STREAM, 0, s) == 0) {signal_fd = s[0];signal_recv_fd = s[1];......}......//執行設定檔中early-boot和boot階段的動作。action_for_each_trigger("early-boot", action_add_queue_tail);action_for_each_trigger("boot", action_add_queue_tail);drain_action_queue();......//init關注來自四個方面的事情。ufds[0].fd= device_fd;//device_fd用於監聽來自核心的Uevent事件ufds[0].events = POLLIN;ufds[1].fd = property_set_fd;//property_set_fd用於監聽來自屬性伺服器的事件ufds[1].events= POLLIN;//signal_recv_fd由socketpair建立,它的事件來自另外一個socket。ufds[2].fd = signal_recv_fd;ufds[2].events = POLLIN;fd_count = 3;if(keychord_fd > 0) {//如果keychord裝置初始化成功,則init也會關注來自這個裝置的事件。ufds[3].fd = keychord_fd;ufds[3].events = POLLIN;fd_count++;}......#if BOOTCHART......//與Boot char相關,不做討論了。/*Boot chart是一個小工具,它能對系統的效能進行分析,並產生系統啟動過程的圖表,以提供一些有價值的資訊,而這些資訊最大的用處就是協助提升系統的啟動速度。*/#endiffor(;;) {//從此init將進入一個無限迴圈。int nr, i, timeout = -1;for (i = 0; i < fd_count; i++)ufds[i].revents = 0;//在迴圈中執行動作drain_action_queue();restart_processes(); //重啟那些已經死去的進程......#if BOOTCHART...... // Boot Chart相關#endif//調用poll等待一些事情的發生nr= poll(ufds, fd_count, timeout);......//ufds[2]儲存的是signal_recv_fd,用於接收來自socket的訊息。if(ufds[2].revents == POLLIN) {//有一個子進程去世,init要處理這個事情read(signal_recv_fd, tmp, sizeof(tmp));while (!wait_for_one_process(0));continue;}if(ufds[0].revents == POLLIN)handle_device_fd(device_fd);//處理Uevent事件if(ufds[1].revents == POLLIN)handle_property_set_fd(property_set_fd);//處理屬性服務的事件。if(ufds[3].revents == POLLIN)handle_keychord(keychord_fd);//處理keychord事件。}return0;}

這個函數摘抄過來已經精簡了不少。總的來說,在函數中執行了:檔案夾建立,掛載,rc檔案解析,屬性設定,啟動服務,執行動作,socket監聽……

總的來說init的工作流程精簡為以下幾點:

建立一些檔案夾並掛載裝置

解析兩個設定檔init.rc和init.hardware.rc,其中,將分析對init.rc檔案的解析。
執行各個階段的動作,建立Zygote的工作就是在其中的某個階段完成的。
調用property_init初始化屬性相關的資源,並且通過property_start_service啟動屬性服務。

init進入一個無限迴圈,並且等待一些事情的發生。重點關注init如何處理來自socket和來自屬性伺服器相關的事情。

 

1、解析 Init.rc

對於init.rc檔案,Android中有特定的格式以及規則。在Android中,我們叫做Android初始化語言。
Android初始化語言由四大類型的聲明組成,即Actions(動作)、Commands(命令)、Services(服務)、以及Options(選項)。
Action(動作):動作是以命令流程命名的,有一個觸發器決定動作是否發生。
文法
on
 
  Service(服務):服務是init進程啟動的程式、當服務退出時init進程會視情況重啟服務。
文法


service []*


...
Options(選項)
選項是對服務的描述。它們影響init進程如何以及何時啟動服務。
咱們來看看預設的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”logo了。

 

需要重點說明的是init.rc指令檔配置了一些重要的服務,init進程通過建立子進程啟動這些服務,這裡建立的service都屬於native服務,運行在Linux空間,通過socket向上層提供特定的服務,並以守護進程的方式運行在後台。指令碼中服務的定義樣本如下:
service servicemanager /system/bin/servicemanager      class core      user system      group system      critical      onrestart restart zygote  08    onrestart restart media    service ril-daemon /system/bin/rild      class main      socket rild stream 660 root radio      socket rild-debug stream 660 radio system      user root      group radio cache inet misc audio sdcard_rw log    service surfaceflinger /system/bin/surfaceflinger      class main      user system      group graphics      onrestart restart zygote    service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server      class main      socket zygote stream 666      onrestart write /sys/android_power/request_state wake      onrestart write /sys/power/state on      onrestart restart media      onrestart restart netd    service media /system/bin/mediaserver      class main      user media      group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc      ioprio rt 4


通過init.rc指令碼系統啟動了以下幾個重要的服務:
1)servicemanager:啟動binder IPC,管理所有的Android系統服務
2)mountd:裝置安裝Daemon,負責裝置安裝及狀態通知
3)debuggerd:啟動debug system,處理調試進程的請求
4)rild:啟動radio interface layer daemon服務,處理電話相關的事件和請求
5)mediaserver:啟動AudioFlinger,MediaPlayerService and CameraService,負責多媒體播放相關的功能,包括音視頻解碼、顯示輸出
6)zygote:進程孵化器,啟動Android Java VMRuntime和啟動systemserver,負責Android應用進程的孵化工作  在解析rc指令檔時,將相應的類型放入各自的List中:

  \system\core\init\Init_parser.c :init_parse_config_file( )存入到action_queue、 action_list、 service_list中,解析過程可以看一下parse_config函數,類似狀態機器形式

  這其中包含了服務:adbd、servicemanager、vold、ril-daemon、debuggerd、surfaceflinger、zygote、media…… 2、Nativie服務啟動(包括ServiceManager)檔案解析完成之後將service放入到service_list中。

  \system\core\init\builtins.c

Service的啟動是在do_class_start函數中完成:


int do_class_start(int nargs, char **args){service_for_each_class(args[1], service_start_if_not_disabled);return 0;}遍曆所有名稱為classname,狀態不為SVC_DISABLED的Service啟動void service_for_each_class(const char *classname,                            void (*func)(struct service *svc)){       ……}
static void service_start_if_not_disabled(struct service *svc){    if (!(svc->flags & SVC_DISABLED)) {        service_start(svc, NULL);    }}

do_class_start對應的命令:

  KEYWORD(class_start, COMMAND, 1, do_class_start)


init.rc檔案中搜尋class_start:class_start main 、class_start core、……

  main、core即為do_class_start參數classname

init.rc檔案中Service class名稱都是main:

service drm /system/bin/drmserver

    class main

  service surfaceflinger /system/bin/surfaceflinger

   class main

於是就能夠通過main名稱遍曆到所有的Service,將其啟動。

do_class_start調用:

init.rc中

    on boot  //action
      class_start core    //執行command 對應 do_class_start
     class_start main

Init進程main函數中:
system/core/init/init.c中:
int main(){    //掛載檔案       //解析設定檔:init.rc……       //初始化化action queue     ……       for(;;){              execute_one_command();              restart_processes();              for (i = 0; i < fd_count; i++) {            if (ufds[i].revents == POLLIN) {                if (ufds[i].fd == get_property_set_fd())                    handle_property_set_fd();                else if (ufds[i].fd == get_keychord_fd())                    handle_keychord();                else if (ufds[i].fd == get_signal_fd())                    handle_signal();            }        }       }}

  迴圈調用service_start,將狀態SVC_RESTARTING啟動, 將啟動後的service狀態設定為SVC_RUNNING。
  pid=fork();
  execve();

  在訊息迴圈中:Init進程執行了Android的Command,啟動了Android的NativeService,監聽Service的變化需求,Signal處理。

Init進程是作為屬性服務(Property service),維護這些NativeService。需要重點說明的是init.rc指令檔配置了一些重要的服務,init進程通過建立子進程啟動這些服務,這裡建立的service都屬於native服務,運行在Linux空間,通過socket向上層提供特定的服務,並以守護進程的方式運行在後台 
重點看下servicemanager ,在.rc指令檔中servicemanager的描述:

service servicemanager /system/bin/servicemanager
  class core
  user system
  group system
  critical
  onrestart restart zygote
  onrestart restart media
  onrestart restart surfaceflinger
  onrestart restart drm


ServiceManager用來管理系統中所有的binder service,不管是本地的c++實現的還是java語言實現的都需要
這個進程來統一管理,最主要的管理就是,註冊添加服務,擷取服務。所有的Service使用前都必須先在servicemanager中進行註冊。

  do_find_service( )
  do_add_service( )
  svcmgr_handler( )
  代碼位置:frameworks\base\cmds\servicemanager\Service_manager.c3、zygote服務啟動在Java中,我們知道不同的虛擬機器執行個體會為不同的應用程式指派不同的記憶體。假如Android應用應該儘可能快地啟動,但如果Android系統為每一個應用啟動不同的Dalvik虛擬機器執行個體,就會消耗大量的記憶體以及時間。因此,為了克服這個問題,Android系統創造了”Zygote”。Zygote讓Dalvik虛擬機器共用代碼、低記憶體佔用以及最小的啟動時間成為可能。Zygote是一個虛擬器進程,正如我們在前一個步驟所說的在系統引導的時候啟動。Zygote預先載入以及初始化核心庫類。通常,這些核心類一般是唯讀,也是Android SDK或者核心架構的一部分。在Java虛擬機器中,每一個執行個體都有它自己的核心庫類檔案和堆對象的拷貝。


zygote進程孵化了所有的Android應用進程,是Android Framework的基礎,該進程的啟動也標誌著Framework架構初始化啟動的開始。zygote服務進程的主要功能:


1)註冊底層功能的JNI函數到虛擬機器
2)預先載入java類和資源
3)fork並啟動System Server核心進程
4)作為守護進程監聽處理“孵化新進程”的請求 載入ZygoteInit類,原始碼:/frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
registerZygoteSocket()為zygote命令串連註冊一個伺服器通訊端。
preloadClassed “preloaded-classes”是一個簡單的包含一系列需要預先載入類的文字檔,你可以在/frameworks/base找到“preloaded-classes”檔案。
preloadResources() preloadResources也意味著本地主題、布局以及android.R檔案中包含的所有東西都會用這個方法載入。
在這個階段,你可以看到啟動動畫。


如上面所述,zygote是通過init指令碼啟動的:


service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
上面指令碼的含義為作為孵化進程(-Xzygote參數),通過system/bin/app_process啟動zygote服務,同時啟動SystemServer(--start-system-server參數)進程。該服務對應的可執行檔app_process的源碼位於frameworks/base/cmds/app_process。


zygote服務在函數app_main.cpp:main()中啟動。該函數的關鍵代碼有兩處:
1)建立AndroidRuntime對象:


AppRuntime runtime; // androidRunspace
2)啟動zygote和systemserver:


runtime.start("com.android.internal.os.ZygoteInit",
startSystemServer ? "start-system-server" : "");
方法start()代碼位於frameworks/base/core/jni/AndroidRuntime.cpp中,主要邏輯如下:
   ---調用startVM建立虛擬機器:內部使用以下代碼建立vm對象:


if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
LOGE("JNI_CreateJavaVM failed\n");
goto bail;
}
---註冊底層功能的JNI函數到JNIEnv: startReg()
   ---調用env->GetStaticMethodID和env->CallStaticVoidMethod()函數運行Java類ZygoteInit的main()
由此可見虛擬機器啟動後執行的第一個Java類是ZygoteInit.java,並進入ZygoteInit.java:main()函數中。在main函數中實現了以下邏輯:
1)啟動服務端Socket連接埠:
調用registerZygoteSocket()實現,主要用於接受處理建立新進程的請求。
  2)預先載入指定的java類和資源:
調用preloadClasses()預先載入指定的java類,調用preloadResources()預先載入指定的Resources。特別說明的是孵化器進程會把這些積極式載入的類和資源共用給所有APK應用進程,這樣有效解決了Framework類和資源共用的問題。
  3)啟動System Server進程:
調用startSystemServer()建立(fork)SystemServer進程。該函數的關鍵代碼有三處:
---設定啟動進程的相關資訊:比如進程名稱、啟動後裝載的第一個java類
---調用forkSystemServer()從當前的zygote進程孵化出新的進程
---調用函數hanldeSystemServerProcess()關閉從Zygote進程繼承過來的Socket,調用RuntimeInit.zygoteInit()啟動SystemServer.java:main()函數
  4)迴圈監聽孵化新Dalvik進程的請求:
調用runSelectLoopMode()進入無限迴圈:監聽用戶端socket串連,根據請求孵化新的應用進程。Process類中儲存了用戶端socket,並由ActivityManagerService管理該用戶端。每當需要啟動新的Dalvik應用進程時,ActivityManagerService都會通過該socket用戶端與Zygote進程的socket服務端進行通訊,請求Zygote孵化出新的進程。
  至此,啟動zygote服務工作完成,需要說明的是zygote進程即為app_process可執行程式所在進程。
4、System Server進程啟動SystemServer進程在Android的運行環境中扮演了“神經中樞”的作用,Android應用能夠直接互動的大部分系統服務都在該進程中運行,如WindowManagerServer、ActivityManagerSystemService、PackageManagerServer等,這些系統服務都是以獨立線程的方式存在於SystemServer進程中。System Server進程的主要功能:

1)載入android servers底層函數庫
2)啟動android系統中的native服務
3)建立、註冊並啟動Android的系統服務,在獨立線程中運行
4)建立Looper訊息迴圈,處理System Server進程中的事件訊息


在zygote進程中調用函數startSystemServer()建立和啟動Server進程,進程首先執行的函數是SystemServer.java:main()。該函數函數實現的主要邏輯為:
1)載入android_servers函數庫
2)啟動native服務:
調用本地函數init1()實現,該函數的源碼位於檔案frameworks/base/services/jni/com_android_server_systemService.cpp中,涉及的函數system_init()實現在檔案frameworks/base/cmds/system_server/library/system_init.cpp中。
3)啟動Android系統的各種系統服務:
調用函數init2()實現,該函數首先建立了一個ServerThread對象,該對象是一個線程,然後直接運行該線程,如以下代碼所示:


public static final void init2() {
Slog.i(TAG, "Entered the Android system server!");
Thread thr = new ServerThread();
thr.setName("android.server.ServerThread");
thr.start();
}
從ServerThread的run()方法內部開始真正啟動各種服務線程。
---建立Android系統服務物件,並註冊到ServiceManager
---在SystemServer進程中建立Looper訊息迴圈:通過Looper.prepare和Looper.loop來實現
   ---系統就緒通知:調用systemReady()通知各個服務


System Server進程啟動過程中最核心的一步是“啟動Android系統的各種系統服務”,這些系統服務構成了整個Android架構的基礎(),通過Binder IPC為上層應用提供各種功能。Zygote建立新的進程去啟動系統服務。你可以在ZygoteInit類的”startSystemServer”方法中找到原始碼。介紹下幾個重要系統服務的功能。


1)ActivityManagerService
Activity管理服務,主要功能包括:
---統一管理和調度各應用程式的Activity,維護系統中啟動並執行所有應用Task和Activity
---記憶體管理:應用程式關閉時對應進程還在運行,當系統記憶體不足時根據策略kill掉優先順序較低進程
---進程管理:維護和管理系統中啟動並執行所有進程,並提供了查詢進程資訊的API
---Provider、Service和Broadcast管理和調度


2)WindowManagerService
視窗管理服務,主要功能包括為應用程式分配視窗,並管理這些視窗。包括分配視窗的大小、調節各視窗的疊放次序、隱藏或者顯示視窗,程式退出時刪除視窗。


3)PackageManagerService
程式包管理服務,主要功能為:
---根據intent尋找匹配的Activity、Provider以及Service
---進行許可權檢查,即當應用程式調用某個需要一定許可權的函數時,系統能夠判斷調用者是否具備該許可權
---提供安裝、刪除應用程式的API


4)NotificationManagerService
通知管理服務,負責管理和通知後台事件的發生等,這個和statusbar服務結合在一起,一般會在statusbar上添加響應表徵圖。使用者可以通過這知道系統後台發生了什麼事情。


5)AudioService
音頻管理服務,AudioFlinger的上層管理封裝,主要是音量、音效、聲道及鈴聲等的管理。


6)TelephonyRegistry
電話語音管理,用於監聽和上報電話狀態,包括來電、通話、訊號變化等。

到這裡,Android Framework的啟動已經完成,架構中提供的各種服務也已經就緒,可以正常運行並響應處理應用的各種操作請求。一旦系統服務在記憶體中跑起來了,Android就完成了引導過程。在這個時候“ACTION_BOOT_COMPLETED”開機啟動廣播就會發出去。

核心服務:

啟動電源管理器;
建立Activity管理器;
啟動電話註冊;
啟動包管理器;
設定Activity管理服務為系統進程;
啟動上下文管理器;
啟動系統Context Providers;
啟動電池服務;
啟動定時管理器;
啟動感測服務;
啟動視窗管理器;
啟動藍芽服務;
啟動掛載服務。
其他服務:

啟動狀態列服務;
啟動硬體服務;
啟動網路狀態服務;
啟動網路連接服務;
啟動通知管理器;
啟動裝置儲存監視服務;
啟動定位管理器;
啟動搜尋服務;
啟動剪下板服務;
啟動登記服務;
啟動壁紙服務;
啟動音頻服務;
啟動耳機監聽;
啟動AdbSettingsObserver(處理adb命令)。


啟動完成後需要啟動home應用進入手機主介面,這是下面第5節講解的。


5、Home應用啟動在ServerThread:run()函數的最後調用了ActivityManagerService.self().systemReady方法,該方法實現了如下代碼用於啟動第一個Activity:

mMainStack.resumeTopActivityLocked(null);
public void systemReady(final Runnable goingCallback) {    ……    //ready callback       if (goingCallback != null)              goingCallback.run();       synchronized (this) {              // Start up initial activity.              // ActivityStack mMainStack;              mMainStack.resumeTopActivityLocked(null);       }……}final boolean resumeTopActivityLocked(ActivityRecord prev) {  // Find the first activity that is not finishing.  ActivityRecord next = topRunningActivityLocked(null);  if (next == null) {    // There are no more activities!  Let's just start up the    // Launcher...    if (mMainStack) {      //ActivityManagerService mService;      return mService.startHomeActivityLocked();    }  }  ……}


由於系統剛啟動時沒有任何Activity對象,代碼會調用ActivityManagerService:startHomeActivityLocked函數啟動Home應用:


Intent intent = new Intent(
mTopAction,
mTopData != null ? Uri.parse(mTopData) : null);
intent.setComponent(mTopComponent);
if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
intent.addCategory(Intent.CATEGORY_HOME);
}  最後用一張圖片總結下圖片出處:http://blog.csdn.net/zirconsdu/article/details/8574049    

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.