背景
我們知道,每個進程所分配的堆內存空間是寶貴且有限的。而我們經(jīng)常所面對的異常就是內存溢出(OOM)。
拋開由于使用不當而造成的內存泄漏,在一個復雜的 UI 上進行頻繁的頁面更新,容易造成 OOM ,即使沒有發(fā)生,那么也會帶來界面卡頓的現(xiàn)象,非常影響用戶體驗。
還有一點,就是在編程時候的封裝思想。我們希望也努力嘗試將不同的東西分別封裝起來然后組合在一起使用,這樣盡量達到解耦的效果,以更加靈活的方式面對未來需求的變更。
ok,讓我們將在邏輯上獨立的東西封裝起來,單獨運行在一個進程中,不影響 UI 進程的使用,從而使它們各司其職,協(xié)作起來。
讓組件運行在新的進程中
組件可以運行在指定的進程中,這時只需要在 manifest.xml 配置文件中,指定組件的 process 屬性。
<service
android:name=".DataProcessService"
android:label="@string/title_activity_login"
android:process=":DataSerivce"/>
如上,我們將 DataProcessService 運行在指定的 login 進程中。前面的冒號,是指它將運行在一個私有進程中,進程名的前綴為包名。假設包名為 "com.yuegs.process",則該組件將運行在 "com.yuegs.process:DataService" 進程中。
這是一個私有進程,不能和其他應用共享。相對的就有全局進程的概念:
<service
android:name=".DataProcessService"
android:label="@string/title_activity_login"
android:process="com.yuegs.newProcess"/>
以上 activity 將運行在進程名為 "com.yuegs.newProcess" 的進程中。其他應用組件可以通過 ShareUserId 共享這個進程。
一個小插曲,讓我們向系統(tǒng)盡量請求更多的內存空間,在 manifest.xml 中配置:
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
//注意這里
android:largeHeap="true">
這時,將向系統(tǒng)申請更多的堆內存,給不給取決于設備和系統(tǒng)。
多進程使用的坑
我們使用多進程的目的,主要是為了減輕主用戶交互進程的堆內存使用壓力。但是,如果使用不當,效果可能不明顯,甚至起發(fā)作用。
每啟動一個新的進程,Application 就會被實例化創(chuàng)建一次。這也就意味著,如果我們在 Application 中進行了一些全局初始化工作,那么,必須根據(jù)實際情況,處理好初始化工作。
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
String processName = Utils.getProcessName();
if(processName.equals("com.yuegs.process:DataService")){
PushUtils.init();
}else if(processName.equals("com.yuegs.process")){
PicassoUtils.init();
}
}
}
即,根據(jù)需要分別在不同進程中初始化不同的資源。注意,這里容易出現(xiàn) bug。比如,在初始化長連接的時候,原本只是想在 push 進程中使用,但是不小心在 com.yuegs.process 進程中也初始化了,那么將出現(xiàn)兩條長連接。然而,如果一臺設備只允許一條連接,那么這就悲劇了,我就是遇到過這樣的情況:使用了定位服務,它運行在新的進程中,我沒有進行資源分別初始化,稍不注意,這條連接被實例化了兩條。
跨進程的通信問題
現(xiàn)在我們做到了對不同模塊的多進程應用,但是,問題是協(xié)作是需要通信的。
最容易想到的,可能是 aidl。但是它的使用相對繁瑣,也更加強大,現(xiàn)在我們需要一個更加輕量級、更加方便的跨進程方式。
我們可以使用 Messenger。
如需讓接口跨不同的進程工作,則可使用 Messenger 為服務創(chuàng)建接口。服務可以這種方式定義對應于不同類型對象的 Handler。此 Handler 是 Messenger 的基礎,后者隨后可與客戶端分享一個 IBinder,從而讓客戶端能利用 Message 對象向服務發(fā)送命令。此外,客戶端還可定義自有Messenger,以便服務回傳消息。
引用來源
簡單來說,Messenger 封裝了一個 Handler,從而具備了發(fā)送 Message 的能力,同時,它也可以返回一個 IBinder,從而具備了跨進程的能力。
public class DataProcessService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
Handler handler = new DataIncomingHandler() ;
return new Messenger(new DataIncomingHandler()).getBinder();
}
}
客戶端通過 bindService 綁定服務,服務端通過 Messenger 返回給客戶端它的代理類。
此時,我們注意到在構建 Messenger 時帶入了 Handler 參數(shù)。
注意,Handler 和創(chuàng)建它的線程以及該線程中的 MessageQueue 是息息相關、綁定在一起的,所以,Handler的創(chuàng)建場合很重要。如果我們希望 DataInComingHandler 運行在 DataService 進程中,那么,它必須在 DataService 進程中帶有 MessageQueue 的線程中創(chuàng)建。
下面,我們來啟動這個服務:
public class DataServiceProcessStart {
private Messenger mService = null;
private boolean mIsBound;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
mService = new Messenger(service);
DMHelper.inMsger = mService ;
try {
Message msg = Message.obtain(null,
DataIncomingHandler.DATA_SERVICE_PROCESS_CLIENT);
//注意,這里用于接受服務端消息的 Messenger 的創(chuàng)建場合
DMHelper.outMsger = new Messenger(new DataOutgoingHandler()) ;
msg.replyTo = DMHelper.outMsger;
mService.send(msg);
} catch (RemoteException e) {
}
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
}
};
public void start(Context context){
doBindService(context) ;
}
private void doBindService(Context context) {
context.bindService(new Intent(context,
DataProcessService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
public void doUnbindService(Context context) {
if (mIsBound) {
// Detach our existing connection.
context.unbindService(mConnection);
mIsBound = false;
}
}
}
調用 new DataServiceProcessStart().start() ,這樣就完成了服務綁定工作。
綁定完成之后,我們獲得的 DMHelper.inMsger 就擁有了向服務端進程發(fā)送消息的能力。同時,我們拿上利用這種能力,將主進程的 DMHelper.outMsger 發(fā)送給服務端,使得服務端也拿有客戶端的 Messenger ,可以向客戶端發(fā)送消息。
// handleMessage 將運行在 DataService 進程中
public class DataIncomingHandler extends Handler {
public static final int DATA_SERVICE_PROCESS_CLIENT = 1 ;
public DataIncomingHandler(){
}
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case DATA_SERVICE_PROCESS_CLIENT:
//拿到客戶端的 messenger,在需要的時候發(fā)消息給客戶端
DMHelper.outMsger = msg.replyTo ;
break;
}
}
DMHelper 是 DataMessage 的 Helper 類,用于封裝客戶端和服務端需要的 Messenger。
public class DMHelper {
public static Messenger outMsger ;
public static Messenger inMsger ;
public static final String TAG = "DMHelper" ;
//從 DataService 進程向 主進程中發(fā)送消息,消息處理在 主進程中
public static void outMsgerSend(Message msg){
try{
outMsger.send(msg);
}catch (Exception e){
Logger.e(e.getMessage());
}
}
/**
* 從 主進程 往 DataService 進程中發(fā)消息,在 DataService 做消息處理
* @param msg
*/
public static void inMsgerSend(Message msg){
try{
inMsger.send(msg);
}catch (Exception e){
Logger.e(e.toString());
}
}
}
小結
現(xiàn)在,我們可以愉快的使用多進程工作了。
跨進程的通信,是個相對麻煩且容易出錯的地方,但是從邏輯的清晰性和節(jié)省內存的角度來看,還是值得的。
我們都知道,非前臺進程,都有可能因為系統(tǒng)資源緊張的問題,而被系統(tǒng)殺死,一個可以優(yōu)化的方向就是進程管理。當進程由于資源緊張或者異常崩潰之后,重啟進程;或者在服務進程中檢測內存使用,當內存資源緊張的時候,也可以做一些工作。