1. 程式人生 > >Android程序系統:程序的建立、啟動與排程流程

Android程序系統:程序的建立、啟動與排程流程

Android程序框架:程序的建立、啟動與排程流程

文章目錄

  • 一 程序的建立與啟動流程
  • 二 程序的優先順序
  • 三 程序的排程流程

Android系統的啟動流程如下圖(點選檢視大圖)所示:

Loader層

  1. 當手機處於關機狀態時,長按電源鍵開機,引導晶片開始從固化在Boot ROM裡的預設程式碼開始執行,然後載入載入程式Boot Loader到RAM。
  2. Boot Loader被載入到RAM之後開始執行,該程式主要完成檢查RAM,初始化硬體引數等功能。

Kernel層

  1. 載入程式之後進入Android核心層,先啟動swapper程序(idle程序),該程序用來初始化程序管理、記憶體管理、載入Display、Camera Driver、Binder Driver等相關工作。
  2. swapper程序程序之後再啟動kthreadd程序,該程序會建立核心工作執行緒kworkder、軟中斷執行緒ksoftirqd、thernal等核心守護程序,kthreadd程序是所有核心程序的鼻祖。

Native層

  1. 接著會啟動init程序,init程序是所有使用者程序的鼻祖,它會接著孵化出ueventd、logd、healthd、installd、adbd、lmkd等使用者守護程序,啟動ServiceManager來管理系統
    服務,啟動Bootnaim開機動畫。
  2. init程序通過解析init.rc檔案fork生成Zygote程序,該程序是Android系統第一個Java程序,它是所有Java程序父程序,該程序主要完成了載入ZygoteInit類,註冊Zygote Socket
    服務套接字;載入虛擬機器;預載入Class;預載入Resources。

Framework層

  1. init程序接著fork生成Media Server程序,該程序負責啟動和管理整個C++ Framwork(包含AudioFlinger、Camera Service等服務)。

  2. Zygote程序接著會fork生成System Server程序,該程序負責啟動和管理整個Java Framwork(包含ActivityManagerService、WindowManagerService等服務)。

App層

Zygote程序孵化出的第一個應用程序是Launcher程序(桌面),它還會孵化出Browser程序(瀏覽器)、Phone程序(電話)等。我們每個建立的應用都是一個單獨的程序。

通過上述流程的分析,想必讀者已經對Android的整個程序模型有了大致的理解。作為一個應用開發者我們往往更為關注Framework層和App層裡程序的建立與管理相關原理,我們來
一一分析。

一 程序的建立與啟動流程

在正式介紹程序之前,我們來思考一個問題,何為程序,程序的本質是什麼?��

我們知道,程式碼是靜態的,有程式碼和資源組成的系統要想執行起來就需要一種動態的存在,程序就是程式的動態執行過程。何為程序?
程序就是處理執行狀態的程式碼以及相關資源的集合,包括程式碼端段、檔案、訊號、CPU狀態、記憶體地址空間等。

程序使用task_struct結構體來描述,如下所示:

  • 程式碼段:編譯後形成的一些指令
  • 資料段:程式執行時需要的資料
    • 只讀資料段:常量
    • 已初始化資料段:全域性變數,靜態變數
    • 未初始化資料段(bss):未初始化的全域性變數和靜態變數
  • 堆疊段:程式執行時動態分配的一些記憶體
  • PCB:程序資訊,狀態標識等

關於程序的更多詳細資訊,讀者可以去翻閱Linux相關書籍,這裡只是給讀者帶來一種整體上的理解,我們的重心還是放在程序再Android平臺上的應用。

在文章開篇的時候,我們提到了系統中執行的各種程序,那麼這些程序如何被建立呢?��

我們先來看看我們最熟悉的應用程序是如何被建立的,前面我們已經說來每一個應用都執行在一個單獨的程序裡,當ActivityManagerService去啟動四大元件時,
如果發現這個元件所在的程序沒有啟動,就會去建立一個新的程序,啟動程序的時機我們在分析四大元件的啟動流程的時候也有講過,這裡再總結一下:

  • Activity ActivityStackSupervisor.startSpecificActivityLocked()
  • Service ActiveServices.bringUpServiceLocked()
  • ContentProvider ActivityManagerService.getContentProviderImpl()
    = Broadcast BroadcastQueue.processNextBroadcast()

這個新程序就是zygote程序通過複製自身來建立的,新程序在啟動的過程中還會建立一個Binder執行緒池(用來做程序通訊)和一個訊息迴圈(用來做執行緒通訊)
整個流程如下圖所示:

  1. 當我們點選應用圖示啟動應用時或者在應用內啟動一個帶有process標籤的Activity時,都會觸發建立新程序的請求,這種請求會先通過Binder
    傳送給system_server程序,也即是傳送給ActivityManagerService進行處理。
  2. system_server程序會呼叫Process.start()方法,會先收集uid、gid等引數,然後通過Socket方式傳送給Zygote程序,請求建立新程序。
  3. Zygote程序接收到建立新程序的請求後,呼叫ZygoteInit.main()方法進行runSelectLoop()迴圈體內,當有客戶端連線時執行ZygoteConnection.runOnce()
    方法,最後fork生成新的應用程序。
  4. 新建立的程序會呼叫handleChildProc()方法,最後呼叫我們非常熟悉的ActivityThread.main()方法。

注:整個流程會涉及Binder和Socket兩種程序通訊方式,這個我們後續會有專門的文章單獨分析,這個就不再展開。

整個流程大致就是這樣,我們接著來看看具體的程式碼實現,先來看一張程序啟動序列圖:

從第一步到第三步主要是收集整理uid、gid、groups、target-sdk、nice-name等一系列的引數,為後續啟動新程序做準備。然後呼叫openZygoteSocketIfNeeded()方法
開啟Socket通訊,向zygote程序發出建立新程序的請求。

注:第二步中的Process.start()方法是個阻塞操作,它會一直等待程序建立完畢,並返回pid才會完成該方法。

我們來重點關注幾個關鍵的函式。

1.1 Process.openZygoteSocketIfNeeded(String abi)

關於Process類與Zygote程序的通訊是如何進行的呢?��

Process的靜態內部類ZygoteState有個成員變數LocalSocket物件,它會與ZygoteInit類的成員變數LocalServerSocket物件建立連線,如下所示:

客戶端

public static class ZygoteState {
    final LocalSocket socket;
}

服務端

public class ZygoteInit {
    //該Socket與/dev/socket/zygote檔案繫結在一起
    private static LocalServerSocket sServerSocket;
}

我們來具體看看程式碼裡的實現。

 public static class ZygoteState {

    public static ZygoteState connect(String socketAddress) throws IOException {
        DataInputStream zygoteInputStream = null;
        BufferedWriter zygoteWriter = null;
        //建立LocalSocket物件
        final LocalSocket zygoteSocket = new LocalSocket();

        try {
            //將LocalSocket與LocalServerSocket建立連線,建立連線的過程就是
            //LocalSocket物件在/dev/socket目錄下查詢一個名稱為"zygote"的檔案
            //然後將自己與其繫結起來,這樣就建立了連線。
            zygoteSocket.connect(new LocalSocketAddress(socketAddress,
                    LocalSocketAddress.Namespace.RESERVED));

            //建立LocalSocket的輸入流,以便可以接收Zygote程序傳送過來的資料
            zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());

            //建立LocalSocket的輸出流,以便可以向Zygote程序傳送資料。
            zygoteWriter = new BufferedWriter(new OutputStreamWriter(
                    zygoteSocket.getOutputStream()), 256);
        } catch (IOException ex) {
            try {
                zygoteSocket.close();
            } catch (IOException ignore) {
            }

            throw ex;
        }

        String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
        Log.i("Zygote", "Process: zygote socket opened, supported ABIS: " + abiListString);

        return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
                Arrays.asList(abiListString.split(",")));
    }
}

建立Socket連線的流程很明朗了,如下所示:

  1. 建立LocalSocket物件。
  2. 將LocalSocket與LocalServerSocket建立連線,建立連線的過程就是LocalSocket物件在/dev/socket目錄下查詢一個名稱為”zygote”的檔案,然後將自己與其繫結起來,這樣就建立了連線。
  3. 建立LocalSocket的輸入流,以便可以接收Zygote程序傳送過來的資料。
  4. 建立LocalSocket的輸出流,以便可以向Zygote程序傳送資料。

1.2 ZygoteInit.main(String argv[])

ZygoteInit是Zygote程序的啟動類,該類會預載入一些類,然後便開啟一個迴圈,等待通過Socket發過來的建立新程序的命令,fork出新的
子程序。

ZygoteInit的入口函式就是main()方法,如下所示:

public class ZygoteInit {

    public static void main(String argv[]) {
            // Mark zygote start. This ensures that thread creation will throw
            // an error.
            ZygoteHooks.startZygoteNoThreadCreation();

            try {
                //...
                registerZygoteSocket(socketName);
                //...
                //開啟迴圈                
                runSelectLoop(abiList);

                closeServerSocket();
            } catch (MethodAndArgsCaller caller) {
                caller.run();
            } catch (Throwable ex) {
                Log.e(TAG, "Zygote died with exception", ex);
                closeServerSocket();
                throw ex;
            }
        }

    // 開啟一個選擇迴圈,接收通過Socket發過來的命令,建立新執行緒
    private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {

        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

        //sServerSocket指的是Socket通訊的服務端,在fds中的索引為0
        fds.add(sServerSocket.getFileDescriptor());
        peers.add(null);

        //開啟迴圈
        while (true) {
            StructPollfd[] pollFds = new StructPollfd[fds.size()];
            for (int i = 0; i < pollFds.length; ++i) {
                pollFds[i] = new StructPollfd();
                pollFds[i].fd = fds.get(i);
                pollFds[i].events = (short) POLLIN;
            }
            try {
                //處理輪詢狀態,當pollFds有時間到來時則往下執行,否則阻塞在這裡。
                Os.poll(pollFds, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
            for (int i = pollFds.length - 1; i >= 0; --i) {

                //採用IO多路複用機制,當接收到客戶端發出的連線請求時或者資料處理請求到來時則
                //往下執行,否則進入continue跳出本次迴圈。
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }
                //索引為0,即為sServerSocket,表示接收到客戶端發來的連線請求。
                if (i == 0) {
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } 
                //索引不為0,表示通過Socket接收來自對端的資料,並執行相應的操作。
                else {
                    boolean done = peers.get(i).runOnce();
                    //處理完成後移除相應的檔案描述符。
                    if (done) {
                        peers.remove(i);
                        fds.remove(i);
                    }
                }
            }
        }
    }
}

可以發現ZygoteInit在其入口函式main()方法裡呼叫runSelectLoop()開啟了迴圈,接收Socket發來的請求。請求分為兩種:

  1. 連線請求
  2. 資料請求

沒有連線請求時Zygote程序會進入休眠狀態,當有連線請求到來時,Zygote程序會被喚醒,呼叫acceptCommadPeer()方法建立Socket通道ZygoteConnection

private static ZygoteConnection acceptCommandPeer(String abiList) {
    try {
        return new ZygoteConnection(sServerSocket.accept(), abiList);
    } catch (IOException ex) {
        throw new RuntimeException(
                "IOException during accept()", ex);
    }
}

然後呼叫runOnce()方法讀取連線請求裡的資料,然後建立新程序。

此外,連線的過程中服務端接受的到客戶端的connect()操作會執行accpet()操作,建立連線手,客戶端通過write()寫資料,服務端通過read()讀資料。

1.3 ZygoteConnection.runOnce()

class ZygoteConnection {

    boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {

            String args[];
            Arguments parsedArgs = null;
            FileDescriptor[] descriptors;

            try {
                //讀取客戶端發過來的引數列表
                args = readArgumentList();
                descriptors = mSocket.getAncillaryFileDescriptors();
            } catch (IOException ex) {
                Log.w(TAG, "IOException on command socket " + ex.getMessage());
                closeSocket();
                return true;
            }

            //... 引數處理

            try {

                //... 引數處理


                //呼叫Zygote.forkAndSpecialize(來fork出新程序
                pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
                        parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
                        parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,
                        parsedArgs.appDataDir);
            } catch (ErrnoException ex) {
                logAndPrintError(newStderr, "Exception creating pipe", ex);
            } catch (IllegalArgumentException ex) {
                logAndPrintError(newStderr, "Invalid zygote arguments", ex);
            } catch (ZygoteSecurityException ex) {
                logAndPrintError(newStderr,
                        "Zygote security policy prevents request: ", ex);
            }

            try {
                //pid == 0時表示當前是在新建立的子程序重磅執行
                if (pid == 0) {
                    // in child
                    IoUtils.closeQuietly(serverPipeFd);
                    serverPipeFd = null;
                    handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);

                    // should never get here, the child is expected to either
                    // throw ZygoteInit.MethodAndArgsCaller or exec().
                    return true;
                } 
                // pid < 0表示建立新程序失敗,pid > 0 表示當前是在父程序中執行
                else {
                    // in parent...pid of < 0 means failure
                    IoUtils.closeQuietly(childPipeFd);
                    childPipeFd = null;
                    return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
                }
            } finally {
                IoUtils.closeQuietly(childPipeFd);
                IoUtils.closeQuietly(serverPipeFd);
            }
     }
}

該方法主要用來讀取程序啟動引數,然後呼叫Zygote.forkAndSpecialize()方法fork出新程序,該方法是建立新程序的核心方法,它主要會陸續呼叫三個
方法來完成工作:

  1. preFork():先停止Zygote程序的四個Daemon子執行緒的執行以及初始化GC堆。這四個Daemon子執行緒分別為:Java堆記憶體管理現場、堆線下引用佇列執行緒、析構執行緒與監控執行緒。
  2. nativeForkAndSpecialize():呼叫Linux系統函式fork()建立新程序,建立Java堆處理的執行緒池,重置GC效能資料,設定程序的訊號處理函式,啟動JDWP執行緒。
  3. postForkCommon():啟動之前停止的Zygote程序的四個Daemon子執行緒。

上面的方法都完成會後,新程序會建立完成,並返回pid,接著就呼叫handleChildProc()來啟動新程序。handleChildProc()方法會接著呼叫RuntimeInit.zygoteInit()來
完成新程序的啟動。

1.4 RuntimeInit.zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)

這個就是個關鍵的方法了,它主要用來建立一些執行時環境,我們來看一看。

public class RuntimeInit {

    public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
            throws ZygoteInit.MethodAndArgsCaller {
        if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "RuntimeInit");
        redirectLogStreams();
        //建立應用程序的時區和鍵盤等通用資訊
        commonInit();
        //在應用程序中建立一個Binder執行緒池
        nativeZygoteInit();
        //建立應用資訊
        applicationInit(targetSdkVersion, argv, classLoader);
    }
}

該方法主要完成三件事:

  1. 呼叫commonInit()方法建立應用程序的時區和鍵盤等通用資訊。
  2. 呼叫nativeZygoteInit()方法在應用程序中建立一個Binder執行緒池。
  3. 呼叫applicationInit(targetSdkVersion, argv, classLoader)方法建立應用資訊。

Binder執行緒池我們後續的文章會分析,我們重點來看看applicationInit(targetSdkVersion, argv, classLoader)方法的實現,它主要用來完成應用的建立。

該方法裡的argv引數指的就是ActivityThread,該方法會呼叫invokeStaticMain()通過反射的方式呼叫ActivityThread類的main()方法。如下所示:

public class RuntimeInit {

      private static void applicationInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
              throws ZygoteInit.MethodAndArgsCaller { 
          //...

          // Remaining arguments are passed to the start class's static main
          invokeStaticMain(args.startClass, args.startArgs, classLoader);
      }

      private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader)
              throws ZygoteInit.MethodAndArgsCaller {
          Class<?> cl;

          //通過反射呼叫ActivityThread類的main()方法
          try {
              cl = Class.forName(className, true, classLoader);
          } catch (ClassNotFoundException ex) {
              throw new RuntimeException(
                      "Missing class when invoking static main " + className,
                      ex);
          }

          Method m;
          try {
              m = cl.getMethod("main", new Class[] { String[].class });
          } catch (NoSuchMethodException ex) {
              throw new RuntimeException(
                      "Missing static main on " + className, ex);
          } catch (SecurityException ex) {
              throw new RuntimeException(
                      "Problem getting static main on " + className, ex);
          }
          //...
      }  
}

走到ActivityThread類的main()方法,我們就很熟悉了,我們知道在main()方法裡,會建立主執行緒Looper,並開啟訊息迴圈,如下所示:

public final class ActivityThread {

   public static void main(String[] args) {
       //...
       Environment.initForCurrentUser();
       //...
       Process.setArgV0("<pre-initialized>");
       //建立主執行緒looper
       Looper.prepareMainLooper();

       ActivityThread thread = new ActivityThread();
       //attach到系統程序
       thread.attach(false);

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

       //主執行緒進入迴圈狀態
       Looper.loop();

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

前面我們從Process.start()開始講起,分析了應用程序的建立及啟動流程,既然有啟動就會有結束,接下來我們從
Process.killProcess()開始講起,繼續分析程序的結束流程。

二 程序的優先順序

程序按照優先順序大小不同又可以分為實時程序與普通程序。

prio值越小表示程序優先順序越高,

  • 靜態優先順序:優先順序不會隨時間改變,核心也不會修改,只能通過系統呼叫改變nice值,優先順序對映公式為:static_prio = MAX_RT_PRIO + nice + 20,其中MAX_RT_PRIO = 100,那麼取值區間為[100, 139];對應普通程序;
  • 實時優先順序:取值區間為[0, MAX_RT_PRIO -1],其中MAX_RT_PRIO = 100,那麼取值區間為[0, 99];對應實時程序;
  • 懂愛優先順序:排程程式通過增加或者減少程序優先順序,來達到獎勵IO消耗型或按照懲罰CPU消耗型的程序的效果。區間範圍[0, MX_PRIO-1],其中MX_PRIO = 140,那麼取值區間為[0,139];

三 程序排程流程

程序的排程在Process類裡完成。

3.1 優先順序排程

優先順序排程方法

setThreadPriority(int tid, int priority)

程序的優先順序以及對應的nice值如下所示:

  • THREAD_PRIORITY_LOWEST 19 最低優先順序
  • THREAD_PRIORITY_BACKGROUND 10 後臺
  • THREAD_PRIORITY_LESS_FAVORABLE 1 比預設略低
  • THREAD_PRIORITY_DEFAULT 0 預設
  • THREAD_PRIORITY_MORE_FAVORABLE -1 比預設略高
  • THREAD_PRIORITY_FOREGROUND -2 前臺
  • THREAD_PRIORITY_DISPLAY -4 顯示相關
  • THREAD_PRIORITY_URGENT_DISPLAY -8 顯示(更為重要),input事件
  • THREAD_PRIORITY_AUDIO -16 音訊相關
  • THREAD_PRIORITY_URGENT_AUDIO -19 音訊(更為重要)

3.2 組優先順序排程

程序組優先順序排程方法

setProcessGroup(int pid, int group)
setThreadGroup(int tid, int group)

組優先順序及對應取值

  • THREAD_GROUP_DEFAULT -1 僅用於setProcessGroup,將優先順序<=10的程序提升到-2
  • THREAD_GROUP_BG_NONINTERACTIVE 0 CPU分時的時長縮短
  • THREAD_GROUP_FOREGROUND 1 CPU分時的時長正常
  • THREAD_GROUP_SYSTEM 2 系統執行緒組
  • THREAD_GROUP_AUDIO_APP 3 應用程式音訊
  • THREAD_GROUP_AUDIO_SYS 4 系統程式音訊

3.3 排程策略

排程策略設定方法

setThreadScheduler(int tid, int policy, int priority)
  • SCHED_OTHER 預設 標準round-robin分時共享策略
  • SCHED_BATCH 批處理排程 針對具有batch風格(批處理)程序的排程策略
  • SCHED_IDLE 空閒排程 針對優先順序非常低的適合在後臺執行的程序
  • SCHED_FIFO 先進先出 實時排程策略,android暫未實現
  • SCHED_RR 迴圈排程 實時排程策略,android暫未實現

3.4 程序adj排程

另外除了這些基本的排程策略,Android系統還定義了兩個和程序相關的狀態值,一個就是定義在ProcessList.java裡的adj值,另一個
是定義在ActivityManager.java裡的procState值。

定義在ProcessList.java檔案,oom_adj劃分為16級,從-17到16之間取值。

  • UNKNOWN_ADJ 16 一般指將要會快取程序,無法獲取確定值
  • CACHED_APP_MAX_ADJ 15 不可見程序的adj最大值 1
  • CACHED_APP_MIN_ADJ 9 不可見程序的adj最小值 2
  • SERVICE_B_AD 8 B List中的Service(較老的、使用可能性更小)
  • PREVIOUS_APP_ADJ 7 上一個App的程序(往往通過按返回鍵)
  • HOME_APP_ADJ 6 Home程序
  • SERVICE_ADJ 5 服務程序(Service process)
  • HEAVY_WEIGHT_APP_ADJ 4 後臺的重量級程序,system/rootdir/init.rc檔案中設定
  • BACKUP_APP_ADJ 3 備份程序 3
  • PERCEPTIBLE_APP_ADJ 2 可感知程序,比如後臺音樂播放 4
  • VISIBLE_APP_ADJ 1 可見程序(Visible process) 5
  • FOREGROUND_APP_ADJ 0 前臺程序(Foreground process) 6
  • PERSISTENT_SERVICE_ADJ -11 關聯著系統或persistent程序
  • PERSISTENT_PROC_ADJ -12 系統persistent程序,比如telephony
  • SYSTEM_ADJ -16 系統程序
  • NATIVE_ADJ -17 native程序(不被系統管理)

更新程序adj值的方法定義在ActivityManagerService中,分別為:

  • updateOomAdjLocked:更新adj,當目標程序為空,或者被殺則返回false;否則返回true;
  • computeOomAdjLocked:計算adj,返回計算後RawAdj值;
  • applyOomAdjLocked:應用adj,當需要殺掉目標程序則返回false;否則返回true。

那麼程序的adj值什麼時候會被更新呢?��

Activity

  • ActivityManagerService.realStartActivityLocked: 啟動Activity
  • ActivityStack.resumeTopActivityInnerLocked: 恢復棧頂Activity
  • ActivityStack.finishCurrentActivityLocked: 結束當前Activity
  • ActivityStack.destroyActivityLocked: 摧毀當前Activity

Service

  • ActiveServices.realStartServiceLocked: 啟動服務
  • ActiveServices.bindServiceLocked: 繫結服務(只更新當前app)
  • ActiveServices.unbindServiceLocked: 解綁服務 (只更新當前app)
  • ActiveServices.bringDownServiceLocked: 結束服務 (只更新當前app)
  • ActiveServices.sendServiceArgsLocked: 在bringup或則cleanup服務過程呼叫 (只更新當前app)

BroadcastReceiver

  • BroadcastQueue.processNextBroadcast: 處理下一個廣播
  • BroadcastQueue.processCurBroadcastLocked: 處理當前廣播
  • BroadcastQueue.deliverToRegisteredReceiverLocked: 分發已註冊的廣播 (只更新當前app)

ContentProvider

  • ActivityManagerService.removeContentProvider: 移除provider
  • ActivityManagerService.publishContentProviders: 釋出provider (只更新當前app)
  • ActivityManagerService.getContentProviderImpl: 獲取provider (只更新當前app)

另外,Lowmemorykiller也會根據當前的記憶體情況逐級進行程序釋放,一共有六個級別(上面加粗的部分):

  • CACHED_APP_MAX_ADJ
  • CACHED_APP_MIN_ADJ
  • BACKUP_APP_ADJ
  • PERCEPTIBLE_APP_ADJ
  • VISIBLE_APP_ADJ
  • FOREGROUND_APP_ADJ

定義在ActivityManager.java檔案,process_state劃分18類,從-1到16之間取值

  • PROCESS_STATE_CACHED_EMPTY 16 程序處於cached狀態,且為空程序
  • PROCESS_STATE_CACHED_ACTIVITY_CLIENT 15 程序處於cached狀態,且為另一個cached程序(內含Activity)的client程序
  • PROCESS_STATE_CACHED_ACTIVITY 14 程序處於cached狀態,且內含Activity
  • PROCESS_STATE_LAST_ACTIVITY 13 後臺程序,且擁有上一次顯示的Activity
  • PROCESS_STATE_HOME 12 後臺程序,且擁有home Activity
  • PROCESS_STATE_RECEIVER 11 後臺程序,且正在執行receiver
  • PROCESS_STATE_SERVICE 10 後臺程序,且正在執行service
  • PROCESS_STATE_HEAVY_WEIGHT 9 後臺程序,但無法執行restore,因此儘量避免kill該程序
  • PROCESS_STATE_BACKUP 8 後臺程序,正在執行backup/restore操作
  • PROCESS_STATE_IMPORTANT_BACKGROUND 7 對使用者很重要的程序,使用者不可感知其存在
  • PROCESS_STATE_IMPORTANT_FOREGROUND 6 對使用者很重要的程序,使用者可感知其存在
  • PROCESS_STATE_TOP_SLEEPING 5 與PROCESS_STATE_TOP一樣,但此時裝置正處於休眠狀態
  • PROCESS_STATE_FOREGROUND_SERVICE 4 擁有給一個前臺Service
  • PROCESS_STATE_BOUND_FOREGROUND_SERVICE 3 擁有給一個前臺Service,且由系統繫結
  • PROCESS_STATE_TOP 2 擁有當前使用者可見的top Activity
  • PROCESS_STATE_PERSISTENT_UI 1 persistent系統程序,並正在執行UI操作
  • PROCESS_STATE_PERSISTENT 0 persistent系統程序
  • PROCESS_STATE_NONEXISTENT -1 不存在的程序

根據上面說描述的adj值和state值,我們又可以按照重要性程度的不同,將程序劃分為五級:

前臺程序

使用者當前操作所必需的程序。如果一個程序滿足以下任一條件,即視為前臺程序:

  • 託管使用者正在互動的 Activity(已呼叫 Activity 的 onResume() 方法)
  • 託管某個 Service,後者繫結到使用者正在互動的 Activity
  • 託管正在“前臺”執行的 Service(服務已呼叫 startForeground())
  • 託管正執行一個生命週期回撥的 Service(onCreate()、onStart() 或 onDestroy())
  • 託管正執行其 onReceive() 方法的 BroadcastReceiver

通常,在任意給定時間前臺程序都為數不多。只有在記憶體不足以支援它們同時繼續執行這一萬不得已的情況下,系統才會終止它們。 此時,裝置往往已達到記憶體分頁狀態,因此需要終止一些前臺程序來確保使用者介面正常響應。

可見程序

沒有任何前臺元件、但仍會影響使用者在螢幕上所見內容的程序。 如果一個程序滿足以下任一條件,即視為可見程序:

  • 託管不在前臺、但仍對使用者可見的 Activity(已呼叫其 onPause() 方法)。例如,如果前臺 Activity 啟動了一個對話方塊,允許在其後顯示上一 Activity,則有可能會發生這種情況。
  • 託管繫結到可見(或前臺)Activity 的 Service。

可見程序被視為是極其重要的程序,除非為了維持所有前臺程序同時執行而必須終止,否則系統不會終止這些程序。

服務程序

正在執行已使用 startService() 方法啟動的服務且不屬於上述兩個更高類別程序的程序。儘管服務程序與使用者所見內容沒有直接關聯,但是它們通常在執行一些使用者關
心的操作(例如,在後臺播放音樂或從網路下載資料)。因此,除非記憶體不足以維持所有前臺程序和可見程序同時執行,否則系統會讓服務程序保持執行狀態。

後臺程序

包含目前對使用者不可見的 Activity 的程序(已呼叫 Activity 的 onStop() 方法)。這些程序對使用者體驗沒有直接影響,系統可能隨時終止它們,以回收記憶體供前臺程序、可見程序或服務程序使用。 通常會有很多後臺程序在執行,因此它們會儲存在 LRU (最近最少使用)列表中,以確保包含使用者最近檢視的 Activity 的程序最後一個被終止。如果某個 Activity 正確實現了生命週期方法,並儲存了其當前狀態,則終止其程序不會對使用者體驗產生明顯影響,因為當用戶導航回該 Activity 時,Activity 會恢復其所有可見狀態。

空程序

不含任何活動應用元件的程序。保留這種程序的的唯一目的是用作快取,以縮短下次在其中執行元件所需的啟動時間。 為使總體系統資源在程序快取和底層核心快取之間保持平衡,系統往往會終止這些程序。