书到用时方恨少,事非经过不知难。这篇文章主要讲述Android 进程间通信相关的知识,希望能为你提供帮助。
什么鬼!单例居然失效了,一个地方设置值,另个地方居然取不到,这怎么可能?没道理啊!排查半天,发现这两就不在一个进程里,才恍然大悟……什么是进程按照操作系统中的描述:进程一般指一个执行单元,在 PC 和移动设备上指一个程序或者一个应用。
为什么要使用多进程我们都知道,系统为 APP 每个进程分配的内存是有限的,如果想获取更多内存分配,可以使用多进程,将一些看不见的服务、比较独立而又相当占用内存的功能运行在另外一个进程当中。
目录结构预览先放出最终实践后的目录结构,有个大概印象,后面一一介绍。
文章图片
文章图片
如何使用多进程androidManifest.xml 清单文件中注册 Activity、Service 等四大组件时,指定 android:process 属性即可开启多进程,如:
<
activity
android:name="
.Process1Activity"
android:process="
:process1"
/>
<
activity
android:name="
.Process2Activity"
android:process="
com.wuxiaolong.androidprocesssample.process2"
/>
文章图片
说明:
1、
com.wuxiaolong.androidprocesssample
,主进程,默认的是应用包名;2、
android:process="
:process1"
,“:”开头,是简写,完整进程名包名 + :process1
;3、
android:process="
com.wuxiaolong.androidprocesssample.process2"
,以小写字母开头的,属于全局进程,其他应用可以通过 ShareUID 进行数据共享;4、进程命名跟包名的命名规范一样。
进程弊端 Application 多次创建我们自定义一个 Application 类,
onCreate
方法进行打印 Log.d("
wxl"
, "
AndroidApplication onCreate"
);
,然后启动 Process1Activity:com.wuxiaolong.androidprocesssample D/wxl: AndroidApplication onCreate
com.wuxiaolong.androidprocesssample:process1 D/wxl: AndroidApplication onCreate
看到确实被创建两次,原因见:android:process 的坑,你懂吗?多数情况下,我们都会在工程中自定义一个 Application 类,做一些全局性的初始化工作,因为我们要区分出来,让其在主进程进行初始化,网上解决方案:
@Override
public void onCreate() {
super.onCreate();
String processName = AndroidUtil.getProcessName();
if (getPackageName().equals(processName)) {
//初始化操作
Log.d("
wxl"
, "
AndroidApplication onCreate="
+ processName);
}
}
AndroidUtil:
public static String getProcessName() {
try {
File file = new File("
/proc/"
+ android.os.Process.myPid() + "
/"
+ "
cmdline"
);
BufferedReader mBufferedReader = new BufferedReader(new FileReader(file));
String processName = mBufferedReader.readLine().trim();
mBufferedReader.close();
return processName;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
静态成员和单例模式失效创建一个类 SingletonUtil:
public class SingletonUtil {
private static SingletonUtil singletonUtil;
private String userId = "
0"
;
public static SingletonUtil getInstance() {
if (singletonUtil == null) {
singletonUtil = new SingletonUtil();
}
return singletonUtil;
}public String getUserId() {
return userId;
}public void setUserId(String userId) {
this.userId = userId;
}
}
在 MainActivity 进行设置:
SingletonUtil.getInstance().setUserId("
007"
);
Process1Activity 取值,打印:
Log.d("
wxl"
, "
userId="
+ SingletonUtil.getInstance().getUserId());
发现打印
userId=0
,单例模式失效了,因为这两个进程不在同一内存了,自然无法共享。进程间通信 文件共享既然内存不能共享,是不是可以找个共同地方,是的,可以把要共享的数据保存 SD 卡,实现共享。首先将 SingletonUtil 实现 Serializable 序列化,将对象存入 SD 卡,然后需要用的地方,反序列化,从 SD 卡取出对象,完整代码如下:
SingletonUtil
public class SingletonUtil implements Serializable{
public static String ROOT_FILE_DIR = Environment.getExternalStorageDirectory() + File.separator + "
User"
+ File.separator;
public static String USER_STATE_FILE_NAME_DIR = "
UserState"
;
private static SingletonUtil singletonUtil;
private String userId = "
0"
;
public static SingletonUtil getInstance() {
if (singletonUtil == null) {
singletonUtil = new SingletonUtil();
}
return singletonUtil;
}public String getUserId() {
return userId;
}public void setUserId(String userId) {
this.userId = userId;
}
}
序列化和反序列化
public class AndroidUtil {
public static boolean createOrExistsDir(final File file) {
// 如果存在,是目录则返回true,是文件则返回false,不存在则返回是否创建成功
return file != null &
&
(file.exists() ? file.isDirectory() : file.mkdirs());
}/**
* 删除目录
*
* @param dir 目录
* @return {@code true}: 删除成功<
br>
{@code false}: 删除失败
*/
public static boolean deleteDir(final File dir) {
if (dir == null) return false;
// 目录不存在返回true
if (!dir.exists()) return true;
// 不是目录返回false
if (!dir.isDirectory()) return false;
// 现在文件存在且是文件夹
File[] files = dir.listFiles();
if (files != null &
&
files.length != 0) {
for (File file : files) {
if (file.isFile()) {
if (!file.delete()) return false;
} else if (file.isDirectory()) {
if (!deleteDir(file)) return false;
}
}
}
return dir.delete();
}/**
* 序列化,对象存入SD卡
*
* @param obj存储对象
* @param destFileDirSD卡目标路径
* @param destFileName SD卡文件名
*/
public static void writeObjectToSDCard(Object obj, String destFileDir, String destFileName) {createOrExistsDir(new File(destFileDir));
deleteDir(new File(destFileDir + destFileName));
FileOutputStream fileOutputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
fileOutputStream = new FileOutputStream(new File(destFileDir, destFileName));
objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(obj);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (objectOutputStream != null) {
objectOutputStream.close();
objectOutputStream = null;
}
if (fileOutputStream != null) {
fileOutputStream.close();
fileOutputStream = null;
}
} catch (IOException e) {
e.printStackTrace();
}}}/**
* 反序列化,从SD卡取出对象
*
* @param destFileDirSD卡目标路径
* @param destFileName SD卡文件名
*/
public static Object readObjectFromSDCard(String destFileDir, String destFileName) {
FileInputStream fileInputStream = null;
Object object = null;
ObjectInputStream objectInputStream = null;
try {
fileInputStream = new FileInputStream(new File(destFileDir, destFileName));
objectInputStream = new ObjectInputStream(fileInputStream);
object = objectInputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (objectInputStream != null) {
objectInputStream.close();
objectInputStream = null;
}
if (fileInputStream != null) {
fileInputStream.close();
fileInputStream = null;
}
} catch (IOException e) {
e.printStackTrace();
}}
return object;
}
}
需要权限:
<
uses-permission android:name="
android.permission.WRITE_EXTERNAL_STORAGE"
/>
MainActivity 序列写入
SingletonUtil singletonUtil = SingletonUtil.getInstance();
singletonUtil.setUserId("
007"
);
AndroidUtil.writeObjectToSDCard(singletonUtil, SingletonUtil.ROOT_FILE_DIR, SingletonUtil.USER_STATE_FILE_NAME_DIR);
Process1Activity 反序列化取值
Object object = AndroidUtil.readObjectFromSDCard(SingletonUtil.ROOT_FILE_DIR, SingletonUtil.USER_STATE_FILE_NAME_DIR);
if (object != null) {
SingletonUtil singletonUtil = (SingletonUtil) object;
Log.d("
wxl"
, "
userId="
+ singletonUtil.getUserId());
//打印:userId=007
}
AIDLAIDL,Android 接口定义语言,定义客户端与服务端进程间通信,服务端有处理多线程时,才有必要使用 AIDL,不然可以使用 Messenger ,后文介绍。
单个应用,多个进程
服务端AIDL 传递数据有基本类型 int,long,boolean,float,double,也支持 String,CharSequence,List,Map,传递对象需要实现 Parcelable 接口,这时需要指定 in(客户端数据对象流向服务端)、out (数据对象由服务端流向客户端)。
1、Userbean.java
public class UserBean implements Parcelable {
private int userId;
private String userName;
public int getUserId() {
return userId;
}public void setUserId(int userId) {
this.userId = userId;
}public String getUserName() {
return userName;
}public void setUserName(String userName) {
this.userName = userName;
}public UserBean() {}private UserBean(Parcel in) {
userId = in.readInt();
userName = in.readString();
}/**
* @return 0 或 1 ,1 含有文件描述符
*/
@Override
public int describeContents() {
return 0;
}/**
* 系列化
*
* @param dest当前对象
* @param flags 0 或 1,1 代表当前对象需要作为返回值,不能立即释放资源
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
}/**
* 反序列化
*/
public static final Creator<
UserBean>
CREATOR = new Creator<
UserBean>
() {
@Override
public UserBean createFromParcel(Parcel in) {
return new UserBean(in);
}@Override
public UserBean[] newArray(int size) {
return new UserBean[size];
}
};
}
2、UserBean.aidl
Userbean.java 同包下创建对应的 UserBean.aidl 文件,与 aidl 调用和交互。
// UserBean.aidl
package com.wuxiaolong.androidprocesssample;
parcelable UserBean;
3、IUserManager.aidl
// IUserManager.aidl
package com.wuxiaolong.androidprocesssample;
// Declare any non-default types here with import statements
//手动导入
import com.wuxiaolong.androidprocesssample.UserBean;
interface IUserManager {//基本数据类型:int,long,boolean,float,double,String
void hello(String aString);
//非基本数据类型,传递对象
void getUser(in UserBean userBean);
//in 客户端->
服务端}
4、服务类
新建 AIDLService 继承 Service,并且实现 onBind() 方法返回一个你实现生成的 Stub 类,把它暴露给客户端。Stub 定义了一些辅助的方法,最显著的就是 asInterface(),它是用来接收一个 IBinder,并且返回一个 Stub 接口的实例 。
public class AIDLService extends Service {private Binder binder = new IUserManager.Stub() {@Override
public void getUser(UserBean userBean) throws RemoteException {
Log.d("
wxl"
, userBean.getUserId() + "
,"
+ userBean.getUserName() + "
from AIDL Service"
);
}@Override
public void hello(String aString) throws RemoteException {
Log.d("
wxl"
, aString + "
from AIDL Service"
);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return binder;
}@Override
public void onCreate() {
super.onCreate();
}
}
AndroidManifest 注册:
<
service
android:name="
.AIDLService"
android:process="
:aidlRemote"
/>
以上创建完毕,build clean 下,会自动生成 aidl 对应的 java 类供客户端调用。
客户端1、app/build.gradle
需要指定 aidl 路径:
android {
//……
sourceSets {
main {
java.srcDirs = ['
src/main/java'
, '
src/main/aidl'
]
}
}
}
2、启动服务,建立联系
public class MainActivity extends AppCompatActivity {private ServiceConnection aidlServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IUserManager remoteService = IUserManager.Stub.asInterface(service);
UserBean userBean = new UserBean();
userBean.setUserId(1);
userBean.setUserName("
WuXiaolong"
);
try {
remoteService.getUser(userBean);
remoteService.hello("
Hello"
);
} catch (RemoteException e) {
e.printStackTrace();
}
}@Override
public void onServiceDisconnected(ComponentName name) {}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, AIDLService.class);
bindService(intent, aidlServiceConnection, Context.BIND_AUTO_CREATE);
}@Override
protected void onDestroy() {
unbindService(aidlServiceConnection);
super.onDestroy();
}
}
打印:
com.wuxiaolong.androidprocesssample:aidlRemote D/wxl: 1,WuXiaolong from AIDL Service
com.wuxiaolong.androidprocesssample:aidlRemote D/wxl: Hello from AIDL Service
多个应用,多进程
和上面基本差不多,把服务端和客户端分别创建的两个项目,可以互相通信,注意点:
1、服务端创建好的 aidl 文件,带包拷贝到客户端项目中;
2、客户端启动服务是隐式启动,Android 5.0 中对 service 隐式启动有限制,必须通过设置 action 和 package,代码如下:
AndroidManifest 注册:
<
service android:name="
.AIDLService"
>
<
intent-filter>
<
action android:name="
android.intent.action.AIDLService"
/>
<
/intent-filter>
启动服务:
Intent intent = new Intent();
intent.setAction("
android.intent.action.AIDLService"
);
intent.setPackage("
com.wuxiaolong.aidlservice"
);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
使用 MessengerMessenger 可以在不同的进程传递 Message 对象,而我们可以在 Message 对象中放入我们所需要的数据,这样就能实现进程间通信了。Messenger 底层实现是 AIDL,对 AIDL 做了封装, 不需要处理多线程,实现步骤也分为服务端和客户端,代码如下:
服务端
MessengerService:
public class MessengerService extends Service {private final Messenger messenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MainActivity.MSG_FROM_CLIENT:
//2、服务端接送消息
Log.d("
wxl"
, "
msg="
+ msg.getData().getString("
msg"
));
//4、服务端回复消息给客户端
Messenger serviceMessenger = msg.replyTo;
Message replyMessage = Message.obtain(null, MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putString("
msg"
, "
Hello from service."
);
replyMessage.setData(bundle);
try {
serviceMessenger.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
super.handleMessage(msg);
}
}@Nullable
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
}
AndroidManafest.xml 注册:
<
service
android:name="
.MessengerService"
android:process="
:messengerRemote"
/>
客户端
MainActivity
public class MainActivity extends AppCompatActivity {
public static final int MSG_FROM_CLIENT = 1000;
public static final int MSG_FROM_SERVICE = 1001;
private Messenger clientMessenger;
private ServiceConnection messengerServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//1、发送消息给服务端
clientMessenger = new Messenger(service);
Message message = Message.obtain(null, MSG_FROM_CLIENT);
Bundle bundle = new Bundle();
bundle.putString("
msg"
, "
Hello from client."
);
message.setData(bundle);
//3、这句是服务端回复客户端使用
message.replyTo = getReplyMessenger;
try {
clientMessenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}@Override
public void onServiceDisconnected(ComponentName name) {}
};
private final Messenger getReplyMessenger = new Messenger(new MessengerHandler());
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MainActivity.MSG_FROM_SERVICE:
//5、服务端回复消息给客户端,客户端接送消息
Log.d("
wxl"
, "
msg="
+ msg.getData().getString("
msg"
));
break;
}
super.handleMessage(msg);
}
}@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Messenger 进行通信
Intent intent = new Intent(this, MessengerService.class);
bindService(intent, messengerServiceConnection, Context.BIND_AUTO_CREATE);
}@Override
protected void onDestroy() {
unbindService(messengerServiceConnection);
super.onDestroy();
}}
打印信息:
com.wuxiaolong.androidprocesssample:remote D/wxl: msg=Hello from client.
com.wuxiaolong.androidprocesssample D/wxl: msg=Hello from service.
最后《Android开发艺术探索》一书关于 Android 进程间通信这块,还有 ContentProvider、Socket 方式,由于篇幅所限,这里不一一介绍了,有兴趣可以自行查看。如果需要这次 Sample 的源码,可在我的公众号「吴小龙同学」回复:「AndroidProcessSample」获取。
参考《Android开发艺术探索》
Android 中的多进程,你值得了解的一些知识
Android使用AIDL实现跨进程通讯(IPC)
【Android 进程间通信】
文章图片
推荐阅读
- 如何将WPF browser app 转换成WPF windows application
- 基于QProbe创建基本Android图像处理框架
- Android 撸起袖子,自己封装 DialogFragment
- Android面试收集录17 Android进程优先级
- HTML DOM appendChild() 方法
- 天猫魔盒价格多少钱?天猫魔盒价格设置介绍
- 迅雷随身wifi是啥?迅雷随身wifi技巧
- 附0元公测活动网址 迅雷随身wifi0元公测活动规则
- 迅雷随身wifi与360随身wifi哪一个好?迅雷vs360随身wifi比较评测