Android多进程的实现

前言

一般,一个app只有一个进程,但会有多个线程,比如UI主线程,各种网络请求的子线程。
但是,一些大型的app,比如QQ,会有多个进程!刚通过top命令,看了一下QQ运行时的进程:

10644 u0_a130  20   0  12% S    57 1425816K  86924K  bg com.tencent.mobileqq:tool
10371 u0_a130  16  -4   5% S   107 1765092K 176420K  bg com.tencent.mobileqq:mini
 3195 u0_a130  20   0   1% S    49 1401268K  53152K  fg com.tencent.mobileqq:MSF
 3271 u0_a130  10 -10   0% S   130 1744152K 161772K  ta com.tencent.mobileqq
10451 u0_a130  20   0   0% S    38 1382680K  66268K  bg com.tencent.mobileqq:mini3
29008 u0_a130  20   0   0% S    31 1367512K  38376K  fg com.tencent.mobileqq:TMAssistantDownloadSDKService

艾玛,太过分了,竟然有6个进程!我们知道,在Android中,系统会为每个应用或进程分配独立的虚拟机和内存空间,看来,QQ内存欲很强呀,通过增加进程这种方式,满足内存需要呀^^

使用多进程的场景

一般有2种情况,需要使用多进程。

  1. 内存不够,扩大内存。
  2. 一些业务,希望在单独进程运行。
    比如,QQ的一些插件功能(微视),希望在一个独立进程运行,当它遇到崩溃时,不会影响QQ退出(毕竟进程不同)

开启多进程

很简单,在AndroidManifest.xml中注册Service、Activity、Receiver、ContentProvider时指定android:process属性。

<service
    android:name=".RemoteService"
    android:process=":remote">
service>

<activity
    android:name=".RemoteActivity"
    android:process="com.chenxf.ipc.remote">
activity>

有两种声明方式,一个加冒号,一个完整的名字,区别如下:
:remote: 以冒号开头是一种简写,系统会在当前进程名前附件当前包名,完整的进程名为:com.chenxf.ipc:remote,同时以冒号开头的进程属于当前应用的私有进程,其它应用的组件不能和它跑在同一进程。

com.chenxf.ipc.remote:这是完整的命名方式,不会附加包名,其它应用如果和该进程的ShareUID、签名相同,则可以和它跑在同一个进程,实现数据共享。(一般极少这样用,除非是同一公司开发的app,且2个app关联很大,才会签名也一样)

多进程的优缺点

优点

其实就是上面的场景,算是它的优点。

  1. 增加内存。
  2. 业务隔离。一些子业务,放子进程,如果崩溃了,不会影响主app退出。

缺点

  1. 静态成员和单例模式失效
  2. 线程同步机制失效
  3. SharedPreferences 可靠性降低
  4. Application 被多次创建

1, 2 很容易理解,每个应用或进程分配独立的虚拟机,不同的虚拟机自然占有不同的内存地址空间。可以认为,每个进程,都有独立的静态成员和单例模式的对象,所以进程之间,千万不能通过这些通信,因为它们属于不同的时空喔。
3嘛,如果一个读,一个写,还好,要是同时去写,就可能出问题了,A进程刚写1,B进程又写2,A一脸懵逼,为啥变成2了,你说可靠不可靠。
4很重要,指的是,Application会被重复创建。比如,如果有3个进程,Application会初始化3次。如果希望不同进程做不同的初始化,则可以参考如下的实现:

package com.chenxf.processtest;

import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;

public class MyApplication extends Application {
    private static final String TAG = "MyApplication";
    private static final String DOWNLOADER_PROCESS = ":downloader";
    private static final String PLUGIN_PROCESS = ":plugin";

    private BaseApplication mProxy;

    @Override
    public void onCreate() {
        super.onCreate();
        String processName = getMyProcessName();
        Log.i(TAG, "onCreate " + processName);
        initProxyApplication(processName);
    }

    private void initProxyApplication(String processName) {
        String mPackageName = getPackageName();

        if (TextUtils.equals(mPackageName, processName)) {
            //主进程
            Log.i(TAG, "init process " + mPackageName);
            mProxy = new MainApplication(processName);
        } else if (TextUtils.equals(processName, mPackageName + PLUGIN_PROCESS)) {
            //插件安装进程
            Log.i(TAG, "init process " + PLUGIN_PROCESS);
            mProxy = new PluginApplication(processName);
        } else if (TextUtils.equals(processName, mPackageName + DOWNLOADER_PROCESS)) {
            //下载进程
            Log.i(TAG, "init process " + DOWNLOADER_PROCESS);
            mProxy = new DownloaderApplication(processName);
        } else {
            mProxy = new BaseApplication(processName);
        }
    }

    /**
     * 获取进程的名称
     *
     * @return
     */
    public String getMyProcessName() {
        if (mProxy != null) {
            return mProxy.getProcessName();
        } else {
            return initCurrentProcessName(this);
        }
    }

    private String initCurrentProcessName(Context context) {
        int pid = android.os.Process.myPid();
        ActivityManager manager =
                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        for (ActivityManager.RunningAppProcessInfo process : manager.getRunningAppProcesses()) {
            if (process.pid == pid) {
                return process.processName;
            }
        }
        return null;
    }
}

多进程的通信

有多种通信方式,包括:
AIDL:功能强大,支持进程间一对多的实时并发通信,并可实现 RPC (远程过程调用)。
Messenger:支持一对多的串行实时通信, AIDL 的简化版本,不需要写AIDL文件,只能支持一次处理一个调用。
ContentProvider:强大的数据源访问支持,主要支持 CRUD 操作,一对多的进程间数据共享,例如我们的应用访问系统的通讯录数据。
BroadcastReceiver:即广播,但只能单向通信,接收者只能被动的接收消息。
文件共享:在非高并发情况下共享简单的数据。
Socket:通过网络传输数据。

AIDL最为复杂,网上也有很多文章介绍,这里根据多进程的情况下,来写一个AIDL实现多进程通信吧。

AIDL实现多进程通信

服务端实现

服务端,咱单独建一个模块,如downloader。模块的Manifest声明一个service在独立进程。
service加一个action,方便其他模块启动service。


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.chenxf.downloader">

    <application>
        <service
            android:name=".DownloadService"
            android:enabled="true"
            android:exported="true"
            android:process=":downloader">
            <intent-filter>
                <action android:name="com.chenxf.downloader.action.START_SERVICE" />
            intent-filter>
        service>
    application>

manifest>

选中模块,右键,New -> AIDL -> AIDL File。
创建一个文件,IDownloadAidl。

// IDownloadAidl.aidl
package com.chenxf.downloader;
import com.chenxf.downloader.DownloadBean;
// Declare any non-default types here with import statements

interface IDownloadAidl {
    void sendMessage(in DownloadBean url);//注意加in,不然编不过
    DownloadBean getMessage(in DownloadBean param);
}

以上代码的DownloadBean是传输数据用的,需要也写一个AIDL文件才能编译过:

// DownloadBean.aidl
package com.chenxf.downloader;

// Declare any non-default types here with import statements

parcelable DownloadBean;

这个文件,注意写法和上面不一样,不是interface,是parcelable,待会我们还得实现一个包名一致的类,DownloadBean,继承Parcelable。

接着,执行Build-> Make Project,编译downloader模块,将会生成一个文件。
Android多进程的实现_第1张图片
这个文件声明了一些类,看起来乱七八糟,其实跟我们有关系的,只有一个内部类:IDownloadAidl.Stub。服务端,需要继承IDownloadAidl.Stub,来实现对应的函数。

package com.chenxf.downloader;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

public class DownloadService extends Service {
    private DownloadServiceStub downloadServiceStub;
    public DownloadService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        downloadServiceStub = new DownloadServiceStub();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return downloadServiceStub;
    }

    public class DownloadServiceStub extends IDownloadAidl.Stub {


        @Override
        public void sendMessage(DownloadBean url) throws RemoteException {

        }

        @Override
        public DownloadBean getMessage(DownloadBean param) throws RemoteException {
            DownloadBean result = new DownloadBean();
            result.setDownloadResult("/sdcard/xx.mp4");
            return result;
        }
    }
}

很简单吧,对了,DownloadBean 还要实现一下:

package com.chenxf.downloader;

import android.os.Parcel;
import android.os.Parcelable;

public class DownloadBean implements Parcelable {
    String url;

    String downloadResult;

    public DownloadBean() {
    }

    public DownloadBean(String url) {
        this.url = url;
    }


    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getDownloadResult() {
        return downloadResult;
    }

    public void setDownloadResult(String downloadResult) {
        this.downloadResult = downloadResult;
    }


    protected DownloadBean(Parcel in) {
        url = in.readString();
        downloadResult = in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(url);
        dest.writeString(downloadResult);
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<DownloadBean> CREATOR = new Creator<DownloadBean>() {
        @Override
        public DownloadBean createFromParcel(Parcel in) {
            return new DownloadBean(in);
        }

        @Override
        public DownloadBean[] newArray(int size) {
            return new DownloadBean[size];
        }
    };
}

好了,服务端写好啦。

接着是客户端调用。也很简单。

客户端实现

客户端就写在app模块吧,gradle得依赖downloader模块,这样,DownloadBean才可以用。我们让一个activity绑定远程Service,然后通信。
ps:记得activity退出时,unbindService。

package com.chenxf.processtest;

import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;

import com.chenxf.downloader.DownloadBean;
import com.chenxf.downloader.DownloadService;
import com.chenxf.downloader.IDownloadAidl;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private IDownloadAidl downloadAidl;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.start_service).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                handleClick();
            }
        });
    }

    private void handleClick() {
        if(downloadAidl == null) {
            //点击时,才开启进程,没有start的话,系统不会有downloder进程
            startService();
        } else {
            try {
                DownloadBean result = downloadAidl.getMessage(new DownloadBean("http://xx.mp4"));
                Toast.makeText(MainActivity.this, "result " + result.getDownloadResult(), Toast.LENGTH_LONG).show();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if(downloadAidl != null) {
            unbindService(conn);
        }
    }

    private void startService() {
    // 创建所需要绑定的Service的Intent
//        Intent intent = new Intent();
//        intent.setAction("com.chenxf.downloader.action.START_SERVICE");
//        intent.setPackage("com.chenxf.downloader");
//        // 绑定远程的服务
//        bindService(intent, conn, Service.BIND_AUTO_CREATE);
        Intent intent = new Intent(this, DownloadService.class);
        bindService(intent, conn, Service.BIND_AUTO_CREATE);
    }

    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.i(TAG, "onServiceDisconnected " +name);

            downloadAidl = null;
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 获取远程Service的onBinder方法返回的对象代理
            downloadAidl = IDownloadAidl.Stub.asInterface(service);
            Log.i(TAG, "onServiceConnected " +name +" downloadAidl" +downloadAidl );
        }
    };


}

需要强调的是,虽然我们声明了DownloadService是在独立进程,但只有这个service启动了,进程才会被创建,否则不会喔!

完整源码

https://github.com/newchenxf/MultiProcess

参考文献

Android 多进程通信

你可能感兴趣的:(android应用)