多進程的好處與坑

正常情況下,一個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;  
    }  
  
}  
12.png

從上面的代碼和執(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ù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容