AIDL 跨进程通讯简单应用
关于多进程
在Android中,每一个应用都是一个独立的进程。有时候部分模块处于特殊需求,也需要在独立的进程中运行。同时多进程可以增加应用的可用内存空间(分配给单独进程的空间不变)
Android中常用的进程间通讯(IPC)有以下几种
- AIDL
- Messenger
- Bundle
- ContentProvider
- BroadcastReceiver
- 文件共享
- Socket
本篇笔记的重点在AIDL上,对其它方法不做记录
简单步骤
接下来的步骤将记录如何创建一个记录注释和时间的服务,并通过另一个app进行操作。
创建服务
自定义类型
新建一个项目AIDLTEST0
作为服务端接下来右键项目新建aidl
。
在AS中新建模板aidl时,会创建一个aidl包。aidl仅支持基本数据类型,以及“String”、“CharSequence”、“ArrayList”、“HashMap”(内部元素也需支持aidl)、实现了Parcelable
接口的对象,以及其它AIDL类型的接口(非常规java接口)。
首先新建一个Comment.java
作为需要保存的数据的类型,并实现Parcelable
接口。该类只有两个属性 内容content
和 时间time
。都是String类型。
time的格式为“HH:mm:ss”,为了方便我直接用
new SimpleDateFormater("HH:mm:ss", Locale.China).formate(new Date());
进行初始化。在此说明一下
为了能在aidl中使用这个类型, 还需创建一个Comment.aidl
进行声明
// Comment.aidl
package com.cloud_hermits.aidltest0;
// Declare any non-default types here with import statements
parcelable Comment;
在Android Studio中创建aidl时,AS会自动创建一个aidl包。为了方便移植,我们会将写好的
Comment.java
也放在这个包里,这会导致AS编译aidl文件时找不到这个自定义类型而发生错误。为了避免该情况,应在项目的build.gradle
中android{}
代码块内添加以下内容sourceSets { main { manifest.srcFile 'src/main/AndroidManifest.xml' java.srcDirs = ['src/main/java', 'src/main/aidl'] resources.srcDirs = ['src/main/java', 'src/main/aidl'] aidl.srcDirs = ['src/main/aidl'] res.srcDirs = ['src/main/res'] assets.srcDirs = ['src/main/assets'] } }
创建aidl接口
定义好类型后,就该着手创建接口了
新建ICommentManager.aidl
// ICommentManager.aidl
package com.cloud_hermits.aidltest0;
import com.cloud_hermits.aidltest0.Comment;
// Declare any non-default types here with import statements
interface ICommentManager {
//获取全部Comment
List<Comment> getAllComment();
//添加一条Comment
void addComment(in Comment comment);
}
有几条需要注意
- 使用自定义类型需要导入该类,而即使这个数据类在同一个包内,依旧需要填写完整包名
- 定向tag 所有非基本参数都需要一个定向tag指出数据流通方式,而基本参数的定向tag默认且只能是in。在这里我们用
in
作为Comment
参数的定向tag。关于定向tag的区别如下- in:服务端会收到完整的数据,而服务端对收到的数据进行的修改不会导致客户端的数据变化
- out:服务端会收到参数的数据类型的空数据,而服务端对该数据进行的修改会导致客户端的参数变化
- inout:上述两条的合一,既会收到来自客户端的完整数据,也会将修改同步到客户端
- 接口内不能有中文注释,否则会导致报错“
错误: 解析时已到达文件结尾
”而无法生成相应类
编写完接口,记得进行一次编译,以便让IDE自动生成所需的类。
编写服务类
接下来需要创建实现这个接口的服务。新建服务CommentService.java
//CommentService.java 完整内容
package com.cloud_hermits.aidltest0;
import android.app.ActivityManager;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.util.Log;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class CommentService extends Service {
private static final String TAG = "CommentService";
private final CopyOnWriteArrayList<Comment> mList = new CopyOnWriteArrayList<>(); //线程读写安全列表
private IBinder mBinder = new ICommentManager.Stub() {
@Override
public List<Comment> getAllComment() throws RemoteException {
Log.d(TAG, "getAllComment: comments = " + mList.toString());
return mList;
}
@Override
public void addComment(Comment comment) throws RemoteException {
mList.add(comment);
Log.d(TAG, "addComment: comments = " + mList.toString());
}
};
public CommentService() {
}
@Override
public void onCreate() {
super.onCreate();
//打印当前进程
int processId = Process.myPid();
ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
assert manager != null;
for (ActivityManager.RunningAppProcessInfo info : manager.getRunningAppProcesses())
if (info.pid == processId) Log.d(TAG, "onCreate: current process = " + info.processName);
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
当服务启动、新增Comment或者获取Comment的时候,都会触发打印。记得在Manifest.xml
中注册该Sercice,建议为其添加<intent-filter>
以便隐式调用。
同应用客户端
完成了服务端的编写,现在就可用试试使用同应用客户端进行修改了
在该AIDLTEST0的主页面上添加一个按钮,并在onCreate(Bundle savedInstanceState)
中绑定该服务
// AIDLTEST0 MainActivity.java
public class MainActivity extends AppCompatActivity{
private ICommentManager manager;
private static final String TAG = "MainActivity";
private int index = new Random().nextInt();
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
manager = ICommentManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//...
bindCommentService();
findViewById(R.id.button).setOnClickListener(v -> addComment());
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
/**
* 绑定服务
* @author binze 2019/12/31 11:30
*/
private void bindCommentService() {
Intent intent = new Intent("com.cloud_hermits.comment.SERVICE");
//android 5.0后,隐式绑定服务必须设置包名
intent.setPackage("com.cloud_hermits.aidltest0");
bindService(intent, mConnection, BIND_AUTO_CREATE);
}
/**
* 添加Comment
* @author binze 2019/12/31 11:31
*/
private void addComment() {
if (manager == null) {
Log.e(TAG, "addComment: 获取服务失败");
return;
}
Comment comment = new Comment(String.format("条目%s", index++));
try {
manager.addComment(comment);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
运行app,点击按钮后会在logcat中打印日志。
跨应用调用
新建工程AIDLTEST1,并将AIDLTEST0中的aidl
包复制到新工程的main
目录下。在主页面和AIDLTEST0一样即可,隐式绑定服务,并操作,观察两个工程的打印情况,你会发现在工程AIDLTEST1中的操作成功的修改了AIDLTEST0中的数据。
通知数据更新
既然已经能跨应用调用服务方法,自然可用在该方法中发送全局广播。接下来各个app处理该广播即可