1. 程式人生 > >Android多程序總結一:生成多程序(android:process屬性)

Android多程序總結一:生成多程序(android:process屬性)

前言

正常情況下,一個apk啟動後只會執行在一個程序中,其程序名為apk的包名,所有的元件都會在這個程序中執行,以下為DDMS的程序截圖:

這裡寫圖片描述

com.biyou.multiprocess為程序名,也是apk的包名, 


但是如果需要將某些元件(如Service,Activity等)執行在單獨的程序中,就需要用到android:process屬性了。我們可以給android的元件設定android:process屬性來使其執行在指定的程序中。

  • AndroidMantifest.xml中的activity、service、receiver和provider均支援android:process
    屬性
  • 設定該屬性可以使每個元件均在各自的程序中執行,或者使一些元件共享一個程序
  • AndroidMantifest.xml中的application元素也支援android:process屬性,可以修改應用程式的預設程序名(預設值為包名)

為何要使用多程序

 

1.分散記憶體的佔用



我們知道Android系統對每個應用程序的記憶體佔用是有限制的,而且佔用記憶體越大的程序,通常被系統殺死的可能性越大。讓一個元件執行在單獨的程序中,可以減少主程序所佔用的記憶體,避免OOM問題,降低被系統殺死的概率,

2.實現多模組



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

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

3.子程序奔潰,主程序可以繼續工作

如果子程序因為某種原因崩潰了,不會直接導致主程式的崩潰,可以降低我們程式的崩潰率。 
 

4.主程序退出,子程序可以繼續工作

即使主程序退出了,我們的子程序仍然可以繼續工作,假設子程序是推送服務,在主程序退出的情況下,仍然能夠保證使用者可以收到推送訊息。 
 

5.實現守護程序



如果主執行緒中的服務要從開機起持續執行,若由於記憶體等原因被系統kill掉,守護程序可以重新啟動主執行緒的服務。

通過JNI利用C/C++,呼叫fork()方法來生成子程序,一般開發者會利用這種方法來做一些daemon(守護程序)程序,來實現防殺保活等效果。

另外:

還能通過監控程序,將這個錯誤上報給系統,告知他在什麼機型、環境下、產生了什麼樣的Bug,提升使用者體驗。

實現

1 . 如果android:process的值以冒號開頭的話,那麼該程序就是私有程序,如下:

配置:

<application
   ……
   <service android:name=".ProcessTestService" android:process=":secondProcess"/>
   ……
</application>

程序:

這裡寫圖片描述

2 . 以小寫字母開頭(如com.secondProcess),那麼就是公有程序,android:process值一定要有個點號:

不能以數字開頭,並且要符合命名規範,必須要有.否則將會出現這種錯誤: Invalid process name simon in package com.wind.check: must have at least one ‘.’ 
配置:

<application
   ……
   <service android:name=".LocalService" android:process="com.secondProcess"/>
   ……
</application>

程序:

這裡寫圖片描述

3 . 私有程序和公有程序的區別: 
android:process=":remote",以冒號開頭,冒號後面的字串原則上是可以隨意指定的。如果我們的包名為“com.biyou.multiprocess”,則實際的程序名 
為“com.biyou.multiprocess:remote”。這種設定形式表示該程序為當前應用的私有程序,其他應用的元件不可以和它跑在同一個程序中。 
全域性程序 
程序名稱不以“:”開頭的程序都可以叫全域性程序,如android:process="com.secondProcess",以小寫字母開頭,表示執行在一個以這個名字命名的全域性程序中,其他應用通過設定相同的ShareUID可以和它跑在同一個程序

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 發生什麼情況,該操作至少具備“服務程序”優先順序。 同理,廣播接收器也應使用服務,而不是簡單地將耗時冗長的操作放入執行緒中。

注意的地方

有一點一定要記住:程序間的記憶體空間是不可見的。從而,開啟多程序後,我們需要面臨這樣幾個問題:

1. Application的多次重建。

Manifest檔案如上面提到的,定義了兩個類:ProcessTestActivity和ProcessTestService,我們只是在Activity的onCreate方法中直接啟動了該Service,同時,我們自定義了自己的Application類。程式碼如下:

public class MyApplication extends Application {
    public static final String TAG = "viclee";
    @Override
    public void onCreate() {
        super.onCreate();
        int pid = android.os.Process.myPid();
        Log.d(TAG, "MyApplication onCreate");
        Log.d(TAG, "MyApplication pid is " + pid);
    }
}
public class ProcessTestActivity extends Activity {
    public final static String TAG = "viclee";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_process_test);

        Log.i(TAG, "ProcessTestActivity onCreate");
        this.startService(new Intent(this, ProcessTestService.class));
    }
}
public class ProcessTestService extends Service {
    public static final String TAG = "viclee";

    @Override
    public void onCreate() {
        Log.i(TAG, "ProcessTestService onCreate");
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

}


 

這裡寫圖片描述 
 

 

  我們發現MyApplication的onCreate方法呼叫了兩次,分別是在啟動ProcessTestActivity和ProcessTestService的時候,而且我們發現打印出來的pid也不相同。由於通常會在Application的onCreate方法中做一些全域性的初始化操作,它被初始化多次是完全沒有必要的。出現這種情況,是由於即使是通過指定process屬性啟動新程序的情況下,系統也會新建一個獨立的虛擬機器,自然需要重新初始化一遍Application。那麼怎麼來解決這個問題呢? 
下面給出解決方案:

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

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;
        }
    }

2. 靜態成員的失效。

將之前定義的Activity和Service的程式碼進行簡單的修改,程式碼如下:

public class ProcessTestActivity extends Activity {
    public final static String TAG = "viclee";
    public static boolean processFlag = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_process_test);

        processFlag = true;
        Log.i(TAG, "ProcessTestActivity onCreate");
        this.startService(new Intent(this, ProcessTestService.class));
    }
}
public class ProcessTestService extends Service {
    public static final String TAG = "viclee";

    @Override
    public void onCreate() {
        Log.i(TAG, "ProcessTestService onCreate");
        Log.i(TAG, "ProcessTestActivity.processFlag is " + ProcessTestActivity.processFlag);
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }

}

重新執行程式碼,列印Log :
 

這裡寫圖片描述

 

  從上面的程式碼和執行結果看,我們在Activity中定義了一個標誌processFlag並在onCreate中修改了它的值為true,然後啟動Service,但是在Service中讀到這個值卻為false。按照正常的邏輯,靜態變數是可以在應用的所有地方共享的,但是設定了process屬性後,產生了兩個隔離的記憶體空間,一個記憶體空間裡值的修改並不會影響到另外一個記憶體空間。

3. 檔案共享問題。

  多程序情況下會出現兩個程序在同一時刻訪問同一個資料庫檔案的情況。這就可能造成資源的競爭訪問,導致諸如資料庫損壞、資料丟失等。在多執行緒的情況下我們有鎖機制控制資源的共享,但是在多程序中比較難,雖然有檔案鎖、排隊等機制,但是在Android裡很難實現。解決辦法就是多程序的時候不併發訪問同一個檔案,比如子程序涉及到操作資料庫,就可以考慮呼叫主程序進行資料庫的操作。

參考: 
1. 這可能是最全的Android:Process (程序)講解了 
2. Android應用內多程序分析和研究