IPC又称进程间通信,面试老生常谈的问题,Linux常用方式有:管道、信号、消息队列、共享内存、内存映射、信号量、套接字。
Android进程间通信方式:
Intent、Broastcast,AIDL、Messenger、ContentProvider、Binder、Socket。
一、Intent
这个平时用的最多的,就不多说了
二、Broastcast
广播也比较简单,简单列举一下知识点:
四大组建之一
分类
标准广播:也叫无需广播,所有广播接收器都能接收到广播,无法拦截,效率高。
有序广播:优先级高的先接收到,逻辑执行完往下传递,前面的广播接收器可以拦截广播。
注册方式
静态注册:在AndroidManifest.xml中,应用存活着就会一直处于接收状态。
动态注册:在代码中注册,可以手动解除注册。
其中有序广播需要设置优先级priority。
注意:不要在 onReceive()方法中添加过多的逻辑或者进行任何的耗时操作,因为在广播接收器中是不允许开启线程的,当onReceive()方法运行了较长时间而没有结束时,程序就会报错。因此广播接收器更多的是扮演一种打开程序其它组件的角色,比如创建一条状态栏通知,或者启动一个服务等。
三、AIDL
aidl是IPC机制的一种解决方案,使用起来相对Messenger等稍微复杂,简单例子如下:
简单使用
1、服务端
首先创建服务端的应用,app/src/main下创建aidl文件,这里只定义了一个方法,传进来一个字符串,经过服务端处理在返回给客户端:
package com.lianzhuoxinxi.baoduoduo;
import com.lianzhuoxinxi.baoduoduo.User;
interface IMyAidlInterface {
String getName(String str);
}
然后rebuild一下之后会根据此文件生成对应接口类
接下来就是服务端的处理代码,创建一个Service,它里面有一个aidl接口的实现,主要在这里面处理数据,然后返回给客户端:
public class MyRemoteService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
private class MyBinder extends IMyAidlInterface.Stub {
@Override
public String getName(String str) throws RemoteException {
if (!TextUtils.isEmpty(str)) {
// 把客户端传过来的字符串转大写返回给客户端
return str.toUpperCase();
}
return "empty";
}
}
}
最后在AndroidManifest.xml中注册服务,这里有三个属性要注意,enabled必须为true,false表示被禁用;exported为true表示能与其他应用交互,false表示此服务只能在应用内使用;action是客户端绑定使用的。
<service android:name=".service.MyRemoteService"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="lzxx.bd.aidl.service"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
服务端就这么简单,运行起来就行了。
2、客户端
首先创建一个客户端应用,然后把服务端的aidl文件(包括包名)拷贝过来,这里注意保证文件的包名一定要和服务端一样,所以最保险的方式就是拷贝。
在MainActivity中绑定服务,当绑定成功就可以调用服务端aidl中定义的方法了。
private void aidlTest() {
// 此处action和服务端配置的一致,必须设置服务端应用的包名,否则找不到。
Intent intent = new Intent("lzxx.bd.aidl.service");
intent.setPackage("com.lianzhuoxinxi.baoduoduo");
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
aidlInterface = IMyAidlInterface.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, BIND_AUTO_CREATE);
}
private void getNameFromServer() {
try {
Log.e("AIDL", ">>>>" + aidlInterface.getName("hello aidl"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
打印结果如下,可以看到服务端的处理逻辑起作用了。
2020-11-27 09:58:40.633 25189-25189/? E/AIDL: >>>>HELLO AIDL
自定义数据类型
1、服务端
在刚才的aidl文件中增加两个方法,一个是添加用户,一个是获取用户列表
interface IMyAidlInterface {
String getName(String str);
List<User> getUserList();
void addUser(inout User user);
}
创建一个用户实体类,需要实现Parcelable接口,并且需要添加一个readFromParcel方法
public class User implements Parcelable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.name);
dest.writeInt(this.age);
}
public void readFromParcel(Parcel parcel) {
name = parcel.readString();
age = parcel.readInt();
}
public User() {
}
protected User(Parcel in) {
this.name = in.readString();
this.age = in.readInt();
}
public static final Parcelable.Creator<User> CREATOR = new Parcelable.Creator<User>() {
@Override
public User createFromParcel(Parcel source) {
return new User(source);
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
}
同时在aidl文件夹中再创建一个User.aidl文件,声明自定义的数据类型
package com.lianzhuoxinxi.baoduoduo;
parcelable User;
Service中也实现新增加的方法:
public class MyRemoteService extends Service {
private List<User> users;
@Override
public void onCreate() {
super.onCreate();
users = new ArrayList<>();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
private class MyBinder extends IMyAidlInterface.Stub {
@Override
public String getName(String str) throws RemoteException {
if (!TextUtils.isEmpty(str)) {
return str.toUpperCase();
}
return "empty";
}
@Override
public List<User> getUserList() throws RemoteException {
return users;
}
@Override
public void addUser(User user) throws RemoteException {
users.add(user);
}
}
}
编译之后运行起来。
目录结构:
2、客户端
同样把aidl文件夹拷贝过来,把User类连同包名拷贝过来,目录结构:
其他不用修改,编译之后就可以调用新加的addUser()和getUserList()方法了。
四、Messenger
基于Binder的一种进程间通信方式
1、服务端
创建一个service用来接收请求并返回结果:
public class MessengerService extends Service {
private Messenger serverMessenger = new Messenger(new MessengerHandler());
@Override
public void onCreate() {
super.onCreate();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return serverMessenger.getBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
}
private static class MessengerHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Bundle data = msg.getData();
String type = data.getString(Cons.client_key);
Message message = Message.obtain();
Bundle bundle = new Bundle();
if ("1".equals(type)) {
bundle.putString(Cons.server_key, "request type 1 response");
} else if ("2".equals(type)) {
bundle.putString(Cons.server_key, "request type 2 response");
} else {
bundle.putString(Cons.server_key, "request unknown type response");
}
message.setData(bundle);
try {
msg.replyTo.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
2、客户端
- 实现ServiceConnection接口,实例化发送请求的messenger对象:
private class MyServiceConnection implements ServiceConnection {
private String type;
public MyServiceConnection(String type) {
this.type = type;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 1、get connection from server
if (“1”.equals(type)) {
messenger1 = new Messenger(service);
} else if (“2”.equals(type)) {
messenger2 = new Messenger(service);
} else {
messengerX = new Messenger(service);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
- 绑定服务:
这里创建了3个连接相当于3个客户端请求,共用同一个服务。
connection1 = new MyServiceConnection(type_1);
connection2 = new MyServiceConnection(type_2);
connectionX = new MyServiceConnection("");
bindService(new Intent(this, MessengerService.class), connection1, BIND_AUTO_CREATE);
bindService(new Intent(this, MessengerService.class), connection2, BIND_AUTO_CREATE);
bindService(new Intent(this, MessengerService.class), connectionX, BIND_AUTO_CREATE);
- 创建一个Handler来接收服务器返回结果:
private static class ClientHandler extends Handler {
private WeakReference<MessengerActivity> reference;
public ClientHandler(MessengerActivity activity) {
this.reference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
MessengerActivity activity = reference.get();
Bundle data = msg.getData();
String serverData = data.getString(Cons.server_key);
activity.updateResult(serverData);
}
}
private void updateResult(String str) {
result.append(str).append("\n");
tvResult.setText(result.toString());
}
- 实例化接收请求结果的Messenger对象:
private Messenger clientMessenger = new Messenger(new ClientHandler(this));
- 然后是发送请求的方法:
这里创建Message对象千万别用new,因为牵扯到内存抖动问题,这里不过多介绍。
private void sendData(String type) {
// 2、create Message
Message message = Message.obtain();
Bundle bundle = new Bundle();
bundle.putString(Cons.client_key, type);
message.setData(bundle);
// 3、data receive object
message.replyTo = clientMessenger;
try {
// 4、send request
if (type_1.equals(type)) {
messenger1.send(message);
} else if (type_2.equals(type)) {
messenger2.send(message);
} else {
messengerX.send(message);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
发送请求:
// 发送类型1请求
sendData("1");
// 发送类型2请求
sendData("2");
// 发送其他类型请求
sendData("");
最后在activity的onDestroy()方法中解绑服务:
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection1);
unbindService(connection2);
unbindService(connectionX);
}
随便点击调用sendData方法之后结果:
五、ContentProvider
- ContentProvider是Android中提供的专门用于不同应用间数据共享的方式,从这一点来看,他肯定是支持进程间通信的,和Messenger一样,他的底层实现也是Binder,由此可见,Binder在Android系统中的重要性
- ContentProvider比Messenger使用起来要简单的多了,因为系统已经为我们做了封装,我们无需关心底层即可轻松实现IPC
- 系统预置了好多ContentProvider,比如通讯录信息,日程表信息,等,要跨进程访问这些信息,只需要通过ContentProvider的query,updata,insert,delete方法即可
接下来简单使用它,创建一个ContentProvider:
public class MContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
Log.e(getClass().getSimpleName(), "onCreate Thread: " + Thread.currentThread());
return false;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
Log.e(getClass().getSimpleName(), "query() Thread : " + Thread.currentThread());
MatrixCursor cursor = new MatrixCursor(new String[]{"name", "age", "city"}, 5);
cursor.addRow(new String[]{"张伟", "39", "北京"});
cursor.addRow(new String[]{"王刚", "21", "上海"});
cursor.addRow(new String[]{"刘敏", "9", "重庆"});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Bundle bundle = new Bundle();
bundle.putString("data", "Cursor data from " + Thread.currentThread());
cursor.setExtras(bundle);
}
return cursor;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
在AndroidManifest.xml中注册:
authorities:定义连接时的uri
process:指定新的进程名
<provider
android:name=".ipc.MContentProvider"
android:authorities="com.test.provider.MContentProvider"
android:process=":test.provider" />
ContentProvider里面有系统封装好的一个方法,另外还有一个类似Activity生命周期的onCreate方法,这个方法在第一次连接时调用,即getContentResolver().query()时调用,而且只调用一次,后面再连接不会调用。
从onCreate中日志可以看出,初始化在主线程中。
由于Cursor是一个接口,所以在query方法中,我们创建了一个MatrixCursor来模拟查询数据,我往Cursor里面插入了3条数据,并且设置了一条Bundle数据,我们在主线程中去查询一下:
private void providerTest() {
// "content://${authorities}"
Uri uri = Uri.parse("content://com.test.provider.MContentProvider");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Cursor query = getContentResolver().query(uri, null, null, null);
if (query != null) {
Bundle extras = query.getExtras();
String data = extras.getString("data");
Log.e(getClass().getSimpleName(), Thread.currentThread() + " " + data);
while (query.moveToNext()) {
int nameIndex = query.getColumnIndex("name");
int ageIndex = query.getColumnIndex("age");
int cityIndex = query.getColumnIndex("city");
String name = query.getString(nameIndex);
String age = query.getString(ageIndex);
String city = query.getString(cityIndex);
Log.e(getClass().getSimpleName(), nameIndex + ":" + name + ", " + ageIndex + ":" + age + ", " + cityIndex + ":" + city);
}
}
}
}
打印结果:
从第一个Log执行结果看出来,当前在主线程,并且获取到了Cursor中Bundle携带的数据。
从第二条Log执行结果看出来,我们在ContentProvider所在的子线程插入的3条数据拿到了。
六、Binder
Binder是Android中一个非常重要的东西,上面三四五都是基于Binder实现的,我在某乎上看到一篇介绍Binder的文章,讲的很详细,传送门。
七、Socket
这个也不多说了,网上很多。