1. 程式人生 > >這可能是最全的Android:Process (程序)講解了

這可能是最全的Android:Process (程序)講解了

官方是這樣描述的:

Tools for managing OS processes.

管理作業系統程序的工具類。

下面就來詳細介紹下關於Process的點滴:

概述

預設情況下,同一應用的所有元件均在相同的程序中執行,且大多數應用都不會改變這一點。 但是,如果您發現需要控制某個元件所屬的程序,則可在清單檔案中執行此操作。

各類元件元素的清單檔案條目activity、service、receiver 和 provider均支援 android:process 屬性,此屬性可以指定該元件應在哪個程序執行。您可以設定此屬性,使每個元件均在各自的程序中執行,或者使一些元件共享一個程序,而其他元件則不共享。 此外,您還可以設定 android:process,使不同應用的元件在相同的程序中執行,但前提是這些應用共享相同的 Linux 使用者 ID 並使用相同的證書進行簽署。

此外,application元素還支援 android:process 屬性,以設定適用於所有元件的預設值。

如果記憶體不足,而其他為使用者提供更緊急服務的程序又需要記憶體時,Android 可能會決定在某一時刻關閉某一程序。在被終止程序中執行的應用元件也會隨之銷燬。 當這些元件需要再次執行時,系統將為它們重啟程序。

決定終止哪個程序時,Android 系統將權衡它們對使用者的相對重要程度。例如,相對於託管可見 Activity 的程序而言,它更有可能關閉託管螢幕上不再可見的 Activity 的程序。 因此,是否終止某個程序的決定取決於該程序中所執行元件的狀態。(引自官方文件)

多程序的應用場景

1.在新程序中開啟服務;
2.多模組應用。
多模組應用:
比如我做的應用大而全,裡面肯定會有很多模組,假如有地圖模組、大圖瀏覽、自定義WebView等等(這些都是吃記憶體大戶),還會有一些諸如下載服務,監控服務等等,一個成熟的應用一定是多模組化的。

首先多程序開發能為應用解決了OOM問題,Android對記憶體的限制是針對於程序的,這個閾值可以是48M、24M、16M等,視機型而定,所以,當我們需要載入大圖之類的操作,可以在新的程序中去執行,避免主程序OOM。

多程序不光解決OOM問題,還能更有效、合理的利用記憶體。我們可以在適當的時候生成新的程序,在不需要的時候及時殺掉,合理分配,提升使用者體驗。減少系統被殺掉的風險。

多程序還能帶來一個好處就是,單一程序崩潰並不影響整體應用的使用。例如我在圖片瀏覽程序打開了一個過大的圖片,java heap 申請記憶體失敗,但是不影響我主程序的使用,而且,還能通過監控程序,將這個錯誤上報給系統,告知他在什麼機型、環境下、產生了什麼樣的Bug,提升使用者體驗。

再一個好處就是,當我們的應用開發越來越大,模組越來越多,團隊規模也越來越大,協作開發也是個很麻煩的事情。專案解耦,模組化,是這階段的目標。通過模組解耦,開闢新的程序,獨立的JVM,來達到資料解耦目的。模組之間互不干預,團隊並行開發,責任分工也明確。

開啟程序的方法

1.我們通常會使用修改清單檔案的android:process來達到多程序的目的。如果android:process的value值以冒號開頭的話,那麼該程序就是私有程序,如果是以其他字元開頭,那麼就是公有程序,這樣擁有相同 ShareUID 的不同應用可以跑在同一程序裡。
2.通過JNI利用C/C++,呼叫fork()方法來生成子程序,一般開發者會利用這種方法來做一些daemon(守護程序)程序,來實現防殺保活等效果。

ps:ShareUID :
ShareUserId,在Android裡面每個app都有一個唯一的linux user ID,則這樣許可權就被設定成該應用程式的檔案只對該使用者可見,只對該應用程式自身可見,而我們可以使他們對其他的應用程式可見,這會使我們用到SharedUserId,也就是讓兩個apk使用相同的userID,這樣它們就可以看到對方的檔案。為了節省資源,具有相同ID的apk也可以在相同的linux程序中進行(注意,並不是一定要在一個程序裡面執行),共享一個虛擬機器。
ShareUserId的作用,資料共享、呼叫其他程式資源。

程序生命週期與優先順序

Android 系統將盡量長時間地保持應用程序,但為了新建程序或執行更重要的程序,最終需要移除舊程序來回收記憶體。 為了確定保留或終止哪些程序,系統會根據程序中正在執行的元件以及這些元件的狀態,將每個程序放入“重要性層次結構”中。 必要時,系統會首先消除重要性最低的程序,然後是重要性略遜的程序,依此類推,以回收系統資源。

重要性層次結構一共有 5 級。以下列表按照重要程度列出了各類程序(第一個程序最重要,將是最後一個被終止的程序):

1.前臺程序:(foreground process)
使用者當前操作所必需的程序。如果一個程序滿足以下任一條件,即視為前臺程序:
託管使用者正在互動的 Activity(已呼叫 Activity 的 onResume() 方法)
託管某個 Service,後者繫結到使用者正在互動的 Activity
託管正在“前臺”執行的 Service(服務已呼叫 startForeground())
託管正執行一個生命週期回撥的 Service(onCreate()、onStart() 或 onDestroy())
託管正執行其 onReceive() 方法的 BroadcastReceiver
通常,在任意給定時間前臺程序都為數不多。只有在記憶體不足以支援它們同時繼續執行這一萬不得已的情況下,系統才會終止它們。 此時,裝置往往已達到記憶體分頁狀態,因此需要終止一些前臺程序來確保使用者介面正常響應。

2.可見程序:
沒有任何前臺元件、但仍會影響使用者在螢幕上所見內容的程序。 如果一個程序滿足以下任一條件,即視為可見程序:
託管不在前臺、但仍對使用者可見的 Activity(已呼叫其 onPause() 方法)。例如,如果前臺 Activity 啟動了一個對話方塊,允許在其後顯示上一 Activity,則有可能會發生這種情況。
託管繫結到可見(或前臺)Activity 的 Service。
可見程序被視為是極其重要的程序,除非為了維持所有前臺程序同時執行而必須終止,否則系統不會終止這些程序。

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

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

5.空程序
不含任何活動應用元件的程序。保留這種程序的的唯一目的是用作快取,以縮短下次在其中執行元件所需的啟動時間。 為使總體系統資源在程序快取和底層核心快取之間保持平衡,系統往往會終止這些程序。
根據程序中當前活動元件的重要程度,Android 會將程序評定為它可能達到的最高級別。例如,如果某程序託管著服務和可見 Activity,則會將此程序評定為可見程序,而不是服務程序。

此外,一個程序的級別可能會因其他程序對它的依賴而有所提高,即服務於另一程序的程序其級別永遠不會低於其所服務的程序。 例如,如果程序 A 中的內容提供程式為程序 B 中的客戶端提供服務,或者如果程序 A 中的服務繫結到程序 B 中的元件,則程序 A 始終被視為至少與程序 B 同樣重要。

由於執行服務的程序其級別高於託管後臺 Activity 的程序,因此啟動長時間執行操作的 Activity 最好為該操作啟動服務,而不是簡單地建立工作執行緒,當操作有可能比 Activity 更加持久時尤要如此。例如,正在將圖片上傳到網站的 Activity 應該啟動服務來執行上傳,這樣一來,即使使用者退出 Activity,仍可在後臺繼續執行上傳操作。使用服務可以保證,無論 Activity 發生什麼情況,該操作至少具備“服務程序”優先順序。 同理,廣播接收器也應使用服務,而不是簡單地將耗時冗長的操作放入執行緒中。

程序間通訊

Android 利用遠端過程呼叫 (RPC) 提供了一種程序間通訊 (IPC) 機制,通過這種機制,由 Activity 或其他應用元件呼叫的方法將(在其他程序中)遠端執行,而所有結果將返回給呼叫方。 這就要求把方法呼叫及其資料分解至作業系統可以識別的程度,並將其從本地程序和地址空間傳輸至遠端程序和地址空間,然後在遠端程序中重新組裝並執行該呼叫。 然後,返回值將沿相反方向傳輸回來。 Android 提供了執行這些 IPC 事務所需的全部程式碼,因此您只需集中精力定義和實現 RPC 程式設計介面即可。

要執行 IPC,必須使用 bindService() 將應用繫結到服務上。

元件中的Process

前面已經提到了,我們可以在任意元件和application中定義process屬性,我們先看看官方文件怎麼說:

android:process
The name of the process where the service is to run. Normally, all components of an application run in the default process created for the application. It has the same name as the application package. The element’s process attribute can set a different default for all components. But component can override the default with its own process attribute, allowing you to spread your application across multiple processes.
If the name assigned to this attribute begins with a colon (‘:’), a new process, private to the application, is created when it’s needed and the service runs in that process. If the process name begins with a lowercase character, the service will run in a global process of that name, provided that it has permission to do so. This allows components in different applications to share a process, reducing resource usage.

我英文才過四級,翻譯的不好請多指教:
中文:這個程序的名字就是正在執行的服務所在的程序,通常來說,所有元件和應用在預設的程序中執行,也就是應用包名,在application中應用此屬性,將會為所有的元件開啟一個不同的程序,但是元件能夠覆蓋application中的程序,允許你應用在跨程序通訊。
如果process屬性以:開頭(:simon),那麼將在需要的時候和服務需要執行在另外一個程序的時候開啟一個屬於此應用的私有程序,如果以小寫字母開頭(com.simon),(不能以數字開頭,並且要符合命名規範,必須要有.否則將會出現這種錯誤: Invalid process name simon in package com.wind.check: must have at least one ‘.’)服務將在以這個名字命名的全域性程序中,如果這是被允許的話。這個將允許元件在不同的應用中共享同一個程序,減少資源佔用。

最後再講一個process的效能優化

由多程序引起的application例項化多次

設定了 android:process 屬性將元件執行到另一個程序,相當於另一個應用程式,所以在另一個程序中也將新建一個 Application 的例項。因此,每新建一個程序 Application 的 onCreate 都將被呼叫一次。 如果在 Application 的 onCreate 中有許多初始化工作並且需要根據程序來區分的,那就需要特別注意了。
我們去看老羅的blog:Android系統在新程序中啟動自定義服務過程(startService)的原理分析

我們可以看到step17:

這個函式定義在frameworks/base/core/java/android/app/ActivityThread.java檔案中:

public final class ActivityThread {  

    ......  

    private final void handleCreateService(CreateServiceData data) {  
        // If we are getting ready to gc after going to the background, well  
        // we are back active so skip it.  
        unscheduleGcIdler();  

        LoadedApk packageInfo = getPackageInfoNoCheck(  
            data.info.applicationInfo);  
        Service service = null;  
        try {  
            java.lang.ClassLoader cl = packageInfo.getClassLoader();  
            service = (Service) cl.loadClass(data.info.name).newInstance();  
        } catch (Exception e) {  
            if (!mInstrumentation.onException(service, e)) {  
                throw new RuntimeException(  
                    "Unable to instantiate service " + data.info.name  
                    + ": " + e.toString(), e);  
            }  
        }  

        try {  
            if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);  

            ContextImpl context = new ContextImpl();  
            context.init(packageInfo, null, this);  

            Application app = packageInfo.makeApplication(false, mInstrumentation);  
            context.setOuterContext(service);  
            service.attach(context, this, data.info.name, data.token, app,  
                ActivityManagerNative.getDefault());  
            service.onCreate();  
            mServices.put(data.token, service);  
            try {  
                ActivityManagerNative.getDefault().serviceDoneExecuting(  
                    data.token, 0, 0, 0);  
            } catch (RemoteException e) {  
                // nothing to do.  
            }  

        } catch (Exception e) {  
            if (!mInstrumentation.onException(service, e)) {  
                throw new RuntimeException(  
                    "Unable to create service " + data.info.name  
                    + ": " + e.toString(), e);  
            }  
        }  
    }  

    ......  

}  

我們可以看到這段程式碼:
Application app = packageInfo.makeApplication(false, mInstrumentation);
可以知道在這裡建立了Application。

需要注意的是:
1.不管是以:開頭的還是以字母開頭的程序,也就是無論是全域性的程序還是私有的程序,只要是新建了一個程序,都會呼叫onCreate()方法,另外只要程序不被殺死,就不會再呼叫onCreate()方法了,親測。

下面讓我們來看看啟動多個程序呼叫application的onCreate()方法:

這裡寫圖片描述

這種多少會給應用帶來影響的,下面給出解決方案:

思路:判斷是否為主程序,只有主程序的時候才執行下面的操作

 String processName = this.getProcessName();

        if (!TextUtils.isEmpty(processName) && processName.equals(this.getPackageName())) {//判斷程序名,保證只有主程序執行
            //在這裡進行主程序初始化邏輯操作                          
            Log.i(">>>>>>","oncreate");
             }

        }

獲取程序名的方法,這個方法是效率最好的;

  public static String getProcessName() {
        try {
            File file = new File("/proc/" + android.os.Process.myPid() + "/" + "cmdline");
            BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));
            String processName = mBufferedReader.readLine().trim();
            mBufferedReader.close();
            return processName;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

下面貼出處理後的結果:

這裡寫圖片描述

只調用了一次application的onCreate方法。

Process方法和屬性

最後如果有什麼不足的地方還請大家指正。