正常情況下,一個apk啟動后只會運行在一個進程中,其進程名為AndroidManifest.xml文件中指定的應(yīng)用包名,所有的基本組件都會在這個進程中運行。但是如果需要將某些組件(如Service、Activity等)運行在單獨的進程中,就需要用到android:process屬性了。我們可以為android的基礎(chǔ)組件指定process屬性來指定它們運行在指定進程中。
有什么好處
一般來說,Android應(yīng)用多進程有三個好處。
1)我們知道Android系統(tǒng)對每個應(yīng)用進程的內(nèi)存占用是有限制的,而且占用內(nèi)存越大的進程,通常被系統(tǒng)殺死的可能性越大。讓一個組件運行在單獨的進程中,可以減少主進程所占用的內(nèi)存,降低被系統(tǒng)殺死的概率.
2)如果子進程因為某種原因崩潰了,不會直接導(dǎo)致主程序的崩潰,可以降低我們程序的崩潰率。
3)即使主進程退出了,我們的子進程仍然可以繼續(xù)工作,假設(shè)子進程是推送服務(wù),在主進程退出的情況下,仍然能夠保證用戶可以收到推送消息
怎么來實現(xiàn)
對process屬性的設(shè)置有兩種形式:
第一種形式如 android:process=":remote",以冒號開頭,冒號后面的字符串原則上是可以隨意指定的。如果我們的包名為“com.example.processtest”,則實際的進程名為“com.example.processtest:remote”。這種設(shè)置形式表示該進程為當(dāng)前應(yīng)用的私有進程,其他應(yīng)用的組件不可以和它跑在同一個進程中。
第二種情況如 android:process="com.example.processtest.remote",以小寫字母開頭,表示運行在一個以這個名字命名的全局進程中,其他應(yīng)用通過設(shè)置相同的ShareUID可以和它跑在同一個進程。
下面通過一個例子來進行一下驗證。我們定義兩個類:ProcessTestActivity和ProcessTestService,然后在AndroidManifest.xml文件中增加這兩個類,并為我們的Service指定一個process屬性,代碼如下:
<application>
...
<service
android:name=".ProcessTestService"
android:process=":remote">
</service>
...
</application>
運行代碼后即可在androidStudio的android Monitor中看到有2個進程。
有哪些坑
我們已經(jīng)開啟了應(yīng)用內(nèi)多進程,那么,開啟多進程是不是只是我們看到的這么簡單呢?其實這里面會有一些陷阱,稍微不注意就會陷入其中。我們首先要明確的一點是進程間的內(nèi)存空間時不可見的。從而,開啟多進程后,我們需要面臨這樣幾個問題:
1)Application的多次重建。
2)靜態(tài)成員的失效。
3)文件共享問題。
4)斷點調(diào)試問題。
我們先通過一個簡單的例子來看一下第一種情況。
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;
}
}
我們發(fā)現(xiàn)MyApplication的onCreate方法調(diào)用了兩次,分別是在啟動ProcessTestActivity和ProcessTestService的時候,而且我們發(fā)現(xiàn)打印出來的pid也不相同。由于通常會在Application的onCreate方法中做一些全局的初始化操作,它被初始化多次是完全沒有必要的。出現(xiàn)這種情況,是由于即使是通過指定process屬性啟動新進程的情況下,系統(tǒng)也會新建一個獨立的虛擬機,自然需要重新初始化一遍Application。那么怎么來解決這個問題呢?
我們可以通過在自定義的Application中通過進程名來區(qū)分當(dāng)前是哪個進程,然后單獨進行相應(yīng)的邏輯處理。
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);
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
if (runningApps != null && !runningApps.isEmpty()) {
for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
if (procInfo.pid == pid) {
if (procInfo.processName.equals("com.example.processtest")) {
Log.d(TAG, "process name is " + procInfo.processName);
} else if (procInfo.processName.equals("com.example.processtest:remote")) {
Log.d(TAG, "process name is " + procInfo.processName);
}
}
}
}
}
}
下面我們來看第二個問題,將之前定義的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;
}
}

從上面的代碼和執(zhí)行結(jié)果看,我們在Activity中定義了一個標(biāo)志processFlag并在onCreate中修改了它的值為true,然后啟動Service,但是在Service中讀到這個值卻為false。按照正常的邏輯,靜態(tài)變量是可以在應(yīng)用的所有地方共享的,但是設(shè)置了process屬性后,產(chǎn)生了兩個隔離的內(nèi)存空間,一個內(nèi)存空間里值的修改并不會影響到另外一個內(nèi)存空間。
第三個問題是文件共享問題。多進程情況下會出現(xiàn)兩個進程在同一時刻訪問同一個數(shù)據(jù)庫文件的情況。這就可能造成資源的競爭訪問,導(dǎo)致諸如數(shù)據(jù)庫損壞、數(shù)據(jù)丟失等。在多線程的情況下我們有鎖機制控制資源的共享,但是在多進程中比較難,雖然有文件鎖、排隊等機制,但是在Android里很難實現(xiàn)。解決辦法就是多進程的時候不并發(fā)訪問同一個文件,比如子進程涉及到操作數(shù)據(jù)庫,就可以考慮調(diào)用主進程進行數(shù)據(jù)庫的操作。
最后是斷點調(diào)試的問題。調(diào)試就是跟蹤程序運行過程中的堆棧信息,由于每個進程都有自己獨立的內(nèi)存空間和各自的堆棧,無法實現(xiàn)在不同的進程間調(diào)試。不過可以通過下面的方式實現(xiàn):調(diào)試時去掉AndroidManifest.xml中android:process標(biāo)簽,這樣保證調(diào)試狀態(tài)下是在同一進程中,堆棧信息是連貫的。待調(diào)試完成后,再將標(biāo)簽復(fù)原。
總結(jié)
從上面的例子中我們可以看到,android實現(xiàn)應(yīng)用內(nèi)多進程并不是簡單的設(shè)置屬性process就可以了,而是會產(chǎn)生很多特殊的問題。像前面提到的,android啟動多進程模式后,不僅靜態(tài)變量會失效,而且類似的如同步鎖機制、單例模式也會存在同樣的問題。這就需要我們在使用的時候多加注意。而且設(shè)置多進程之后,各個進程間就無法直接相互訪問數(shù)據(jù),只能通過AIDL等進程間通信方式來交換數(shù)據(jù)