一般,一个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种情况,需要使用多进程。
很简单,在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 很容易理解,每个应用或进程分配独立的虚拟机,不同的虚拟机自然占有不同的内存地址空间。可以认为,每个进程,都有独立的静态成员和单例模式的对象,所以进程之间,千万不能通过这些通信,因为它们属于不同的时空喔。
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实现多进程通信吧。
服务端,咱单独建一个模块,如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模块,将会生成一个文件。
这个文件声明了一些类,看起来乱七八糟,其实跟我们有关系的,只有一个内部类: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 多进程通信