背景
我们知道,每个进程所分配的堆内存空间是宝贵且有限的。而我们经常所面对的异常就是内存溢出(OOM)。
抛开由于使用不当而造成的内存泄漏,在一个复杂的 UI 上进行频繁的页面更新,容易造成 OOM ,即使没有发生,那么也会带来界面卡顿的现象,非常影响用户体验。
还有一点,就是在编程时候的封装思想。我们希望也努力尝试将不同的东西分别封装起来然后组合在一起使用,这样尽量达到解耦的效果,以更加灵活的方式面对未来需求的变更。
ok,让我们将在逻辑上独立的东西封装起来,单独运行在一个进程中,不影响 UI 进程的使用,从而使它们各司其职,协作起来。
让组件运行在新的进程中
组件可以运行在指定的进程中,这时只需要在 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 共享这个进程。
一个小插曲,让我们向系统尽量请求更多的内存空间,在 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">
这时,将向系统申请更多的堆内存,给不给取决于设备和系统。
多进程使用的坑
我们使用多进程的目的,主要是为了减轻主用户交互进程的堆内存使用压力。但是,如果使用不当,效果可能不明显,甚至起发作用。
每启动一个新的进程,Application 就会被实例化创建一次。这也就意味着,如果我们在 Application 中进行了一些全局初始化工作,那么,必须根据实际情况,处理好初始化工作。
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();
}
}
}
即,根据需要分别在不同进程中初始化不同的资源。注意,这里容易出现 bug。比如,在初始化长连接的时候,原本只是想在 push 进程中使用,但是不小心在 com.yuegs.process 进程中也初始化了,那么将出现两条长连接。然而,如果一台设备只允许一条连接,那么这就悲剧了,我就是遇到过这样的情况:使用了定位服务,它运行在新的进程中,我没有进行资源分别初始化,稍不注意,这条连接被实例化了两条。
跨进程的通信问题
现在我们做到了对不同模块的多进程应用,但是,问题是协作是需要通信的。
最容易想到的,可能是 aidl。但是它的使用相对繁琐,也更加强大,现在我们需要一个更加轻量级、更加方便的跨进程方式。
我们可以使用 Messenger。
如需让接口跨不同的进程工作,则可使用 Messenger 为服务创建接口。服务可以这种方式定义对应于不同类型对象的 Handler。此 Handler 是 Messenger 的基础,后者随后可与客户端分享一个 IBinder,从而让客户端能利用 Message 对象向服务发送命令。此外,客户端还可定义自有Messenger,以便服务回传消息。
引用来源
简单来说,Messenger 封装了一个 Handler,从而具备了发送 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 参数。
注意,Handler 和创建它的线程以及该线程中的 MessageQueue 是息息相关、绑定在一起的,所以,Handler的创建场合很重要。如果我们希望 DataInComingHandler 运行在 DataService 进程中,那么,它必须在 DataService 进程中带有 MessageQueue 的线程中创建。
下面,我们来启动这个服务:
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 的创建场合
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 就拥有了向服务端进程发送消息的能力。同时,我们拿上利用这种能力,将主进程的 DMHelper.outMsger 发送给服务端,使得服务端也拿有客户端的 Messenger ,可以向客户端发送消息。
// 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,在需要的时候发消息给客户端
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 进程向 主进程中发送消息,消息处理在 主进程中
public static void outMsgerSend(Message msg){
try{
outMsger.send(msg);
}catch (Exception e){
Logger.e(e.getMessage());
}
}
/**
* 从 主进程 往 DataService 进程中发消息,在 DataService 做消息处理
* @param msg
*/
public static void inMsgerSend(Message msg){
try{
inMsger.send(msg);
}catch (Exception e){
Logger.e(e.toString());
}
}
}
小结
现在,我们可以愉快的使用多进程工作了。
跨进程的通信,是个相对麻烦且容易出错的地方,但是从逻辑的清晰性和节省内存的角度来看,还是值得的。
我们都知道,非前台进程,都有可能因为系统资源紧张的问题,而被系统杀死,一个可以优化的方向就是进程管理。当进程由于资源紧张或者异常崩溃之后,重启进程;或者在服务进程中检测内存使用,当内存资源紧张的时候,也可以做一些工作。