我们公司是做一些定制系统的开发,开发的各种应用。
经常是由一个 App 运行另一个 App ,然后由那个 App 处理完数据再回传。
这样能很好地把一些核心算法逻辑单独封装成一个 Service,由一个固定的 App 运行 。
这样跨进程的调用实际上就是 AIDL ,不过关于有许多关于 AIDL 的文章,高质量的也很多,就不打算描述了。
主要是想说说实际操作,以及如何在客户端与服务端搭建一个回调(观察者模式)。
先统一下说法,负责处理数据的部分称为服务端,负责将数据传入服务端,再将处理结果接收的部分称为客户端。
现在我们先尝试在一个 App 中将处理数据的部分做成一个 Service ,在 Activity 中进行绑定,然后调用 Service 的接口处理数据。
此时这个例子,Acitivty 就是客户端, Service 就是服务端。
假设我们需要实现两个简单的功能:
打算新建一个 AIDL 文件,因为 AIDL 既然能跨进程调用,那么肯定也能在同一个进程中使用。
AIDL 的定义,Application Interface Defination Language (应用程序接口定义语言) 从本质上还是一个接口吧,服务端实现了该接口的方法,由客户端调用该方法并传入需要使用的参数。
那既然是个接口,说明是服务端,客户端双方都可以共用的,那么我们是不是可以使用一个公共的 Library ,让客户端和服务端都来依赖这个 Library 呢 ?
那我们来导入一个 Library ,比如就叫 commonlib ,然后在 commonlib 中新建 AIDL 文件。
(本来这里是可以直接用普通接口类写在 App 里,不用 AIDL 文件;不过后面会逐渐改成跨进程调用,所以这里就直接使用 AIDL 文件)
然后在 main 目录上右键,添加 AIDL 文件。
添加后,发现 main 目录下多了一个 AIDL 目录,并且里面生成了我们刚输入名字的 AIDL 文件,里面已经有一个方法,和我们需要的不太一样,我们把它删除,然后写入需要的方法。
interface IMyAidl {
//取两个整数和返回
int onNumberPlus(int number1,int number2);
//取字符串大写返回
String onUpperString(String strins);
}
并且点击 Sync Project With Gradle Files。Android Studio 会为我们自动生成对应的文件。
不过现在好像没法知道文件是不是生成好了,先不着急。
让我们先让 app 依赖 commonlib,在 app/build.gradle 文件中添加代码。
implementation project (':commonlib')
然后在 app 中的 Acitivity 中写一个 IMyAidl;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//测试 aidl 文件是否自动生成好。
IMyAidl iMyAidl;
}
}
点击 IMyAidl 发现是自动生成好的文件,并且上面的提示也不建议我们进行编辑,咱们先暂时不看这个自动生成的。
能看见这个文件,说明前面的操作没有问题,咱们就开始动手编写服务端的代码。(如果想认真看看的话,建议使用代码对齐后查看)
新建一个 Service 类来完成服务端的逻辑。
我们的目的是让客户端能调用服务端的代码,现在客户端需要拿到服务端的一个 Service 实例,那么肯定是需要在 Service 的 onBind() 中返回一个 IBinder 对象。
并且添加之前我们要实现的功能,对两个数字求和返回与求一个字符串取大写字符返回。
在 Service 中新建一个类,并且继承 IMyAidl.Stub 。
public class WorkService extends Service {
public class WorkBinder extends IMyAidl.Stub{
@Override
public int onNumberPlus(int number1, int number2) throws RemoteException {
//调用前面写的实现
return addNumber(number1,number2);
}
@Override
public String onUpperString(String strins) throws RemoteException {
//调用前面写的实现
return toUpperString(strins);
}
}
private int addNumber(int a, int b){
return a + b;
}
private String toUpperString(String s){
if(s == null || s.equals("")){
return null;
}else{
return s.toUpperCase();
}
}
}
这里也需要实现两个代码的逻辑,不过我们在前面已经写了,那么直接把前面的代码加进来。
WorkBinder 只负责调用,真正的逻辑实现是在外部,这样就进行了解耦。
当然还需要在 WorkService 中增加一个 WorkBinder 的对象,不然我们返回哪个对象是吧。
所以在 Service 的 onCreate() 中,对 WorkBinder 进行初始化,然后在连接成功时返回该 WorkBinder 实例。
然后整理好 WorkService 就是这样的。
public class WorkService extends Service {
private WorkBinder mWorkBinder;
@Override
public void onCreate() {
super.onCreate();
//初始化实例
if(mWorkBinder == null){
mWorkBinder = new WorkBinder();
}
}
//加法实现
private int addNumber(int a, int b){
return a + b;
}
//大写实现
private String toUpperString(String s){
if(s == null || s.equals("")){
return null;
}else{
return s.toUpperCase();
}
}
public class WorkBinder extends IMyAidl.Stub{
@Override
public int onNumberPlus(int number1, int number2) throws RemoteException {
return addNumber(number1,number2);
}
@Override
public String onUpperString(String strins) throws RemoteException {
return toUpperString(strins);
}
}
//返回该 Service 实例时重要的方法。
//如果是通过 startService 方法时,可以忽略此方法。
@Override
public IBinder onBind(Intent intent) {
return mWorkBinder == null ? null : mWorkBinder;
}
}
好了,我们在 Activity 当中,直接开启 Service ,并且在连接建立后,尝试调用 AIDL 定义的方法。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Server.MainActivity";
//定义为全局引用,方便后面调用
IMyAidl myAidl;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
myAidl = IMyAidl.Stub.asInterface(service);
int sum;
String string = null;
try {
//测试调用服务端的接口
sum = myAidl.onNumberPlus(10,20);
string = myAidl.onUpperString("abcde");
} catch (RemoteException e) {
e.printStackTrace();
sum = -1;
string = "Error";
}
//试着打印下结果
Log.i(TAG, "onServiceConnected: sum " + sum );
Log.i(TAG, "onServiceConnected: String " + string );
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: " );
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService();
}
//建立连接
private boolean bindService(){
Intent intent = new Intent(this,WorkService.class);
return bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
//结束时解除连接
if(mServiceConnection != null){
unbindService(mServiceConnection);
}
}
}
最后不要忘了,在 Manifest 文件中添加 Service。
<application
……
<activity android:name=".MainActivity">
……
</activity>
<service android:name=".WorkService"/>
</application>
好了,我们启动来,看看 Log.
com.rambopan.aidlcallback I/MainActivity: Server.onServiceConnected:
com.rambopan.aidlcallback I/MainActivity: Server.onServiceConnected: sum 30
com.rambopan.aidlcallback I/MainActivity: Server.onServiceConnected: String ABCDE
看来没什么问题,当然这个 AIDL 此时的作用只是在同一个进程当中。
接下来改成跨进程的通讯,搭建客户端,新建一个 Module 。比如叫 client,同样让 client 依赖 commonlib。
现在要把 client 作为客户端, app 作为服务端,因为之前处理逻辑的 Service 在 app 当中。
我们先写 client 的代码。再修改 app 中的代码。
client 也类似 app,唯一不同的就是开启 Service 时需要隐式打开
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Client.MainActivity";
IMyAidl myAidl;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
myAidl = IMyAidl.Stub.asInterface(service);
int sum;
String string = null;
try {
sum = myAidl.onNumberPlus(40,47);
string = myAidl.onUpperString("client");
} catch (RemoteException e) {
e.printStackTrace();
sum = -1;
string = "Error";
}
Log.i(TAG, "onServiceConnected: sum " + sum );
Log.i(TAG, "onServiceConnected: String " + string );
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: " );
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService();
}
@Override
protected void onDestroy() {
super.onDestroy();
//结束时解除连接
if(mServiceConnection != null){
unbindService(mServiceConnection);
}
}
//隐式调用
private void bindService() {
Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
}
}
然后我们去服务端 app 那边 ,把隐式 Intent 所需的 Action 添加进 Manifest 文件。
<application
……
<activity android:name=".MainActivity">
……
</activity>
<service android:name=".WorkService" >
<intent-filter>
<action android:name="com.rambopan.commonlib.IMyAidl"/>
</intent-filter>
</service>
</application>
这次我们先把之前服务端的 app 重新装一次,因为添加了隐式的 Action ,然后运行客户端 client。
嗯,然后一打开发现崩溃了,咱们来瞅瞅日志。
12-14 23:52:41.530 12552-12552/com.rambopan.client E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.rambopan.client, PID: 12552
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.rambopan.client/com.rambopan.client.MainActivity}: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.rambopan.commonlib.IMyAidl }
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2315)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2375)
at android.app.ActivityThread.access$900(ActivityThread.java:147)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1283)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5254)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:910)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:705)
Caused by: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=com.rambopan.commonlib.IMyAidl }
at android.app.ContextImpl.validateServiceIntent(ContextImpl.java:1686)
at android.app.ContextImpl.bindServiceCommon(ContextImpl.java:1785)
at android.app.ContextImpl.bindService(ContextImpl.java:1763)
at android.content.ContextWrapper.bindService(ContextWrapper.java:548)
at com.rambopan.client.MainActivity.bindService(MainActivity.java:52)
at com.rambopan.client.MainActivity.onCreate(MainActivity.java:47)
at android.app.Activity.performCreate(Activity.java:5993)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1105)
说需要指定为显示的,我们明明要通过其他应用来开启 Service ,肯定是需要隐式的。google 一下,发现是缺少一些信息.
在 5.0 之后会抛出异常,找到一个解决方案,增加自定义类的包名。
具体分析可查看这篇文章:https://www.jianshu.com/p/08902fab84d4
然后修改 bindService() 代码,添加包名。
private void bindService() {
Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
//添加目标Service的包名
intent.setPackage("com.rambopan.aidlcallback");
bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
}
修改之后我们先把之前服务端应用 app 从后台清除,再运行客户端 client ,观察下日志。
com.rambopan.client I/Client.MainActivity: onServiceConnected:
com.rambopan.client I/Client.MainActivity: onServiceConnected: sum 87
com.rambopan.client I/Client.MainActivity: onServiceConnected: String CLIENT
没有启动服务端,只启动客户端,也成功调起了服务端的服务。
现在已经实现了基本的调用操作,那么 …… 如果我们想实现这样一个功能:
开启服务端,让服务端不断的执行数据处理,然后回调给客户端,假设从 1 开始 ,每两秒发送 +1 的结果给客户端。
按照类似 setOnClickListener() 的方式,客户端给服务端设置一个监听,由服务端把数据回调给客户端,我们先来试一下。
然后新加入一个 AIDL 文件 CountListener 作为回调的接口。
interface CountListener {
//计数的接口
void onGetNumber(int number);
}
先在 commonlib 中的 IMyAidl 文件中加入四个新的方法:开始计数、是停止计数、注册回调、反注册回调。
interface IMyAidl {
int onNumberPlus(int number1,int number2);
String onUpperString(String strins);
//开始计数
boolean onStartCount();
//停止计数
boolean onStopCount();
//注册回调
void registerListener(CountListener listener);
//反注册回调
void unregisterListener(CountListener listener);
}
因为对 CountListener 的引用,在 IMyAidl 文件中,对 CountListener 进行导入。注意必须手动导入。
既然修改了 AIDL 文件,那么服务端 app 的实现逻辑肯定会变。
主要是增加了自动生成要传递的数字,以及注册接口。
public class WorkService extends Service {
private static final String TAG = "Server.WorkService";
private WorkBinder mWorkBinder;
//开启线程来递增数字
private Thread mThread;
//回调接口
private CountListener mCountListener;
//递增数字
private int mNumber = 0;
//开始与结束线程
private boolean isStart = false;
@Override
public void onCreate() {
super.onCreate();
if(mWorkBinder == null){
mWorkBinder = new WorkBinder();
}
}
private int addNumber(int a, int b){
return a + b;
}
private String toUpperString(String s){
if(s == null || s.equals("")){
return null;
}else{
return s.toUpperCase();
}
}
//开始计数的实现 开启线程,2秒+1
private boolean startCount(){
if(!isStart && mThread == null){
mThread = new Thread(new Runnable() {
@Override
public void run() {
while(isStart){
mNumber++;
if(mCountListener != null){
try {
mCountListener.onGetNumber(mNumber);
} catch (RemoteException e) {
Log.w(TAG, "deliver number Error " );
e.printStackTrace();
}
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
isStart = true;
mThread.start();
return true;
}
return false;
}
//停止计数的实现
private boolean stopCount(){
if(isStart && mThread != null){
isStart = false;
return true;
}
return false;
}
//注册回调实现
private void registerListenerImp(CountListener listener){
Log.i(TAG, "registerListener: listener : " + listener);
if(listener != null){
mCountListener = listener;
}
}
//反注册回调实现 因为这里是一对一注册,所以传入参数没有使用到。
//如果是有多个客户端注册,那么需要根据传入的 listener 来确定需要移除哪一个。
private void unregisterListenerImp(CountListener listener){
Log.i(TAG, "unregisterListenerImp: listener : " + listener);
mCountListener = null;
}
//包装一层,调用 Service 中的实现。
public class WorkBinder extends IMyAidl.Stub{
@Override
public int onNumberPlus(int number1, int number2) throws RemoteException {
return addNumber(number1,number2);
}
@Override
public String onUpperString(String strins) throws RemoteException {
return toUpperString(strins);
}
@Override
public boolean onStartCount() throws RemoteException {
return startCount();
}
@Override
public boolean onStopCount() throws RemoteException {
return stopCount();
}
@Override
public void registerListener(CountListener listener) throws RemoteException {
registerListenerImp(listener);
}
@Override
public void unregisterListener(CountListener listener) throws RemoteException {
unregisterListenerImp(listener);
}
}
//返回该 Service 实例时重要的方法。
//如果是通过 startService 方法时,可以忽略此方法。
@Override
public IBinder onBind(Intent intent) {
if(mWorkBinder == null){
return null;
} else{
return mWorkBinder;
}
}
}
服务端 app 实现修改好了,我们继续回到客户端 client。
先增加两个按钮,方便我们点击注册与反注册。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<Button
android:id="@+id/btn_start_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start_Count"
/>
<Button
android:id="@+id/btn_stop_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Stop_Count"/>
</LinearLayout>
MainActivity 当中主要是增加 CountListener.Stub 类,关于回调的传递、注册、实现。
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Client.MainActivity";
IMyAidl myAidl;
private CountClientStub mCountClientStub;
//新建一个继承 CountListener.Stub的类作为传入的对象。
private class CountClientStub extends CountListener.Stub{
@Override
public void onGetNumber(int number) throws RemoteException {
Log.i(TAG, "onGetNumber: ---> " + number);
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
myAidl = IMyAidl.Stub.asInterface(service);
int sum;
String string = null;
try {
sum = myAidl.onNumberPlus(40,47);
string = myAidl.onUpperString("client");
} catch (RemoteException e) {
e.printStackTrace();
sum = -1;
string = "Error";
}
Log.i(TAG, "onServiceConnected: sum " + sum );
Log.i(TAG, "onServiceConnected: String " + string );
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: " );
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bindService();
//传入一个 Listener
if(mCountClientStub == null) {
mCountClientStub = new CountClientStub();
}
Button btnStartCount = findViewById(R.id.btn_start_count);
Button btnStopCount = findViewById(R.id.btn_stop_count);
btnStartCount.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(myAidl != null){
try {
myAidl.registerListener(mCountClientStub);
myAidl.onStartCount();
} catch (RemoteException e) {
e.printStackTrace();
Log.w(TAG, "onClick: registerListener RemoteException" );
}
}
}
});
btnStopCount.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(myAidl != null){
try {
myAidl.onStopCount();
myAidl.unregisterListener(mCountClientStub);
} catch (RemoteException e) {
e.printStackTrace();
Log.w(TAG, "onClick: unregisterListener RemoteException" );
}
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
//结束时解除连接
if(mServiceConnection != null){
unbindService(mServiceConnection);
}
}
private void bindService() {
Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
intent.setPackage("com.rambopan.aidlcallback");
bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
}
}
现在把服务端客户端重新装一次,再试一次。
12-15 16:09:57.543 25584-25584/com.rambopan.client I/Client.MainActivity: onServiceConnected:
12-15 16:09:57.543 25584-25584/com.rambopan.client I/Client.MainActivity: onServiceConnected: sum 87
12-15 16:09:57.543 25584-25584/com.rambopan.client I/Client.MainActivity: onServiceConnected: String CLIENT
12-15 16:10:11.276 25606-25623/com.rambopan.aidlcallback I/Server.WorkService: registerListener: listener : com.rambopan.commonlib.CountListener S t u b Stub StubProxy@cd84546
12-15 16:10:11.306 25584-25600/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 1
12-15 16:10:13.308 25584-25601/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 2
12-15 16:10:15.310 25584-25600/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 3
12-15 16:10:17.312 25584-25601/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 4
12-15 16:10:19.314 25584-25600/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 5
12-15 16:10:21.316 25584-25601/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 6
根据日志可以看出,只要成功注册回调,然后调用开始计数,服务端就不会断发送数据传输。
这样就完成了我们平常的开发要求:跨进程传递数据进行处理,处理完后再跨进程将数据传回。
如果我们如果回调,不只是传基本类型,要是传一些类的对象呢 ?
那肯定是需要对该类实现序列化的接口。才能在一个进程中序列化写入,另一个进程序列化读出。
在 aidl 目录下,新建一个新的文件,Person。只需要写一句话。
parcelable Person;
然后在相同的包下,java 目录下,新建一个类,也叫 Person。并且实现 Parcelable。
我们增加一个名字和一个年龄属性,然后自动生成代码。
实现 Parcelable 的目的,就是能将对象序列化为序列化对象,也能将序列化对象转化为对象。
可以从 Person(Parcel in) 与 writeToParcel(Parcel dest, int flags) 看出。
public class Person implements Parcelable {
private String mName;
private int mAge;
protected Person(Parcel in) {
mName = in.readString();
mAge = in.readInt();
}
//新增构造函数。
public Person(String name,int age){
mName = name;
mAge = age;
}
public static final Creator<Person> CREATOR = new Creator<Person>() {
@Override
public Person createFromParcel(Parcel in) {
return new Person(in);
}
@Override
public Person[] newArray(int size) {
return new Person[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
dest.writeInt(mAge);
}
@Override
public String toString() {
return " mName : " + mName +
" mAge : " + mAge;
}
}
我们增加了一个两个参数的构造函数。并且复写了 toString() 方法,方便一会打印参数。
Person 类准备好了,我们去 CountListener 当中新增加一个方法,并且加入需要导入的 Person类。
注意:Person 前面需要增加 in 关键字。
import com.rambopan.commonlib.Person;
interface CountListener {
//计数的接口
void onGetNumber(int number);
//获取每个人的信息
void onGetPersonInfo(in Person person);
}
我们分别对服务端、客户端代码进行修改。
服务端 app : WorkService 中修改 startCount() ,增加构造 Person 类的方法。
Random mRandom = new Random();
//开始计数的实现 开启线程,2秒+1
private boolean startCount(){
if(!isStart && mThread == null){
mThread = new Thread(new Runnable() {
@Override
public void run() {
while(isStart){
mNumber++;
if(mCountListener != null){
try {
mCountListener.onGetNumber(mNumber);
//随机生成 A - Z 的字母作为名字
String name = Character.toString((char)(mRandom.nextInt(26) + 65));
//随机生成 0 - 20 的数字作为年龄
int age = mRandom.nextInt(20);
Person person = new Person(name,age);
mCountListener.onGetPersonInfo(person);
} catch (RemoteException e) {
Log.w(TAG, "deliver number Error " );
e.printStackTrace();
}
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
isStart = true;
mThread.start();
return true;
}
return false;
}
客户端 client :MainAcitivity 中修改 CountClientStub 类,增加打印的消息。
private class CountClientStub extends CountListener.Stub{
@Override
public void onGetNumber(int number) throws RemoteException {
Log.i(TAG, "onGetNumber: ---> " + number);
}
@Override
public void onGetPersonInfo(Person person) throws RemoteException {
Log.i(TAG, "onGetPersonInfo: ---> Person : " + person);
}
}
12-15 16:49:46.764 26999-27017/com.rambopan.aidlcallback I/Server.WorkService: registerListener: listener : com.rambopan.commonlib.CountListener S t u b Stub StubProxy@cd84546
12-15 16:49:46.764 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 1
12-15 16:49:46.774 26963-26988/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : I mAge : 15
12-15 16:49:48.776 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 2
12-15 16:49:48.786 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : F mAge : 8
12-15 16:49:50.788 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 3
12-15 16:49:50.788 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : P mAge : 7
12-15 16:49:52.789 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 4
12-15 16:49:52.789 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : M mAge : 11
12-15 16:49:54.801 26963-26988/com.rambopan.client I/Client.MainActivity: onGetNumber: —> 5
12-15 16:49:54.801 26963-26990/com.rambopan.client I/Client.MainActivity: onGetPersonInfo: —> Person : mName : K mAge : 8
从运行结果来说成功了,个人信息在不断变化,并且不断在回调给客户端。
现在可以把 commonlib 共享出去,给需要使用的其他工程依赖。
那么依赖工程很麻烦,有没有更简单的方法,比如打个 jar 包,然后让其他应用直接丢进 lib 文件夹依赖。
当然是可以的,我们可以先 clean 下工程,再 build ,在这个目录下可以看见生成的 jar 文件。(不同 Android Studio 版本可能有差异)
/commonlib/build/intermediates/packaged-classes/debug/
然后把 jar 文件分别放进客户端 client 与 服务端 app,再分别修改 build.gradle 。
注释掉之前依赖的 commonlib。并确保最后一行代码存在 :对 libs 目录所有 jar 文件依赖。
(注意此处的 libs 目录是和 src 目录同级的)
//implementation project (':commonlib')
//依赖 libs 目录下所有 jar 类型文件。
implementation fileTree(dir: 'libs', include: ['*.jar'])
替换了文件,然后重新同步下工程,再点击 IMyAidl ,已经提示是通过 .class 文件反编译的,无法修改了,说明是 jar 包生效了。
这样保证了公共的工程在共享过程中不会被随意改动。
那还有没有什么可以优化的地方呢,要是每个客户端用的时候,都要写一次绑定服务的代码,是不是也可以把这重复的部分抽出来呢 ?
当然是可以的啦。来创建一个辅助单例类 ConnectAssist,把绑定,解绑定 等做成公共的部分(当然确保客户端不会添加新的功能)。
public class ConnectAssist {
private static final String TAG = "ConnectAssist";
private static ConnectAssist connectAssist;
private IMyAidl myAidl;
private Context mContext;
private ConnectAssist(Context context){
mContext = context;
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
myAidl = IMyAidl.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: " );
}
};
public static synchronized ConnectAssist getInstance(Context context){
if(connectAssist == null){
synchronized (ConnectAssist.class){
if(connectAssist == null){
connectAssist = new ConnectAssist(context.getApplicationContext());
}
}
}
return connectAssist;
}
public boolean bindService() {
Intent intent = new Intent("com.rambopan.commonlib.IMyAidl");
intent.setPackage("com.rambopan.aidlcallback");
return mContext.bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
}
public boolean unbindService(){
if(mServiceConnection != null){
mContext.unbindService(mServiceConnection);
return true;
}else{
return false;
}
}
//获取 AIDL 接口
public IMyAidl getMyAidl(){
return myAidl;
}
}
重新生成 jar 文件,然后替换客户端 jar 包,替换 jar 包后同步下工程,简单调整客户 client 绑定代码。
调整后如图,其实如果不是两个 OnClickListener 和 CountClientStub 类 ,还是挺简洁的 ……
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Client.MainActivity";
IMyAidl myAidl;
private CountClientStub mCountClientStub;
//新建一个继承 CountListener.Stub的类作为传入的对象。
private class CountClientStub extends CountListener.Stub{
@Override
public void onGetNumber(int number) throws RemoteException {
Log.i(TAG, "onGetNumber: ---> " + number);
}
@Override
public void onGetPersonInfo(Person person) throws RemoteException {
Log.i(TAG, "onGetPersonInfo: ---> Person : " + person);
}
}
ConnectAssist connectAssist;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//bindService();
connectAssist = ConnectAssist.getInstance(this);
connectAssist.bindService();
//传入一个 Listener
if(mCountClientStub == null) {
mCountClientStub = new CountClientStub();
}
Button btnStartCount = findViewById(R.id.btn_start_count);
Button btnStopCount = findViewById(R.id.btn_stop_count);
btnStartCount.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//尝试在使用之前判断一次,如果为 null 就取一次
if(myAidl == null){
myAidl = connectAssist.getMyAidl();
}
if(myAidl != null){
try {
myAidl.registerListener(mCountClientStub);
myAidl.onStartCount();
} catch (RemoteException e) {
e.printStackTrace();
Log.w(TAG, "onClick: registerListener RemoteException" );
}
}
}
});
btnStopCount.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//尝试在使用之前判断一次,如果为 null 就取一次
if(myAidl == null){
myAidl = connectAssist.getMyAidl();
}
if(myAidl != null){
try {
myAidl.onStopCount();
myAidl.unregisterListener(mCountClientStub);
} catch (RemoteException e) {
e.printStackTrace();
Log.w(TAG, "onClick: unregisterListener RemoteException" );
}
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
//结束时解除连接
connectAssist.unbindService();
}
}
终于圆满了,当然在操作过程中也碰到一些需要思考的地方,比如。
WorkBinder 为什么要继承 IMyAidl.Stub ?
那我们先把 commonlib 中的 IMyAidl 点开,看看是什么结构。
public interface IMyAidl extends android.os.IInterface {
……
public static abstract class Stub extends android.os.Binder implements com.rambopan.commonlib.IMyAidl {
……
private static class Proxy implements com.rambopan.commonlib.IMyAidl {
……
}
}
}
public class Binder implements IBinder {
……
}
可以看到 IMyAidl.Stub 继承了 Binder 类,Binder 类对 IBinder 进行了实现。所以 Stub 可以作为 IBinder 类型返回。
看看 服务端 ServiceConnection 代码。
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
myAidl = IMyAidl.Stub.asInterface(service);
……
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: " );
}
};
从名字上可以看出,是把 IBinder 类型作为定义的接口类型返回。
而 Stub 类,就是不光继承了 IBinder 类,同时也实现了定义的 AIDL 接口类 IMyAidl 。
public static com.rambopan.commonlib.IMyAidl asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.rambopan.commonlib.IMyAidl))) {
return ((com.rambopan.commonlib.IMyAidl) iin);
}
return new com.rambopan.commonlib.IMyAidl.Stub.Proxy(obj);
}
点开 asInterface() 方法,可以看出,是对 IBinder 类进行判断。
点击 Proxy 类,能看到持有一个 IBInder 类型引用,几个方法都基本类似,我们就只分析一个 onNumberPlus(int ,int )。
private static class Proxy implements com.rambopan.commonlib.IMyAidl {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public int onNumberPlus(int number1, int number2) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(number1);
_data.writeInt(number2);
mRemote.transact(Stub.TRANSACTION_onNumberPlus, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
……
}
Proxy 类当中的各种方法,就是将数据变成序列化之后,对每个方法加上唯一的描述符。
然后调用 mRemote 引用中的 transact() 方法,把需要调用该方法的数据传入。
等待 mRemote 调用结束之后,再从回复当中读取数据,就完成了一次跨进程的调用。
此处的 mRemote ,就是外层的 Stub 类,来看看 Stub 类中对应的方法,发现并没有找到 transact() 方法,那去它的父类 IBinder 类中看看。
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
观察 transact() 执行的逻辑,主要还是在 onTransact() 方法中,虽然 IBinder 当中是存在 onTransact() 的。
不过 Stub 类中已经重写了方法。在我们定义的这几种操作的情况下,会执行我们写的逻辑,其他情况下调用 super() 。
public static abstract class Stub extends android.os.Binder implements com.rambopan.commonlib.IMyAidl {
private static final java.lang.String DESCRIPTOR = "com.rambopan.commonlib.IMyAidl";
……
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_onNumberPlus: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.onNumberPlus(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
……
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
……
}
可以看到执行的逻辑,先判断对应的 transact() 当时写入的每个操作对应的唯一描述符。
再从序列化中读出数据,最后调用 this.onNumberPlus() ,再把结果写入。
而 this 此时就是指每个 Stub 对象,而我们在 WorkBinder 定义当中已经复写这几个操作的逻辑。
所以现在绕了一圈,所有线索都拼接上了。也算是把 AIDL 流程简单熟悉了下。
为什么要在 myAidl 使用前进行一次 (myAidl == null) 的判断?在 onCreate 当中加入获取不行么?
当然是可以的,不过如果直接在 bindService() 之后去获取的话,可能是 null 。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
connectAssist = ConnectAssist.getInstance(this);
connectAssist.bindService();
//测试
myAidl = connectAssist.getMyAidl();
Log.i(TAG, "ConnectAssist: ---> myAidl : " + myAidl);
……
}
我们加入日志测试,查看一下。
11:32:13.418 12674-12674/com.rambopan.client I/Client.MainActivity: ConnectAssist: —> myAidl : null
11:32:13.448 12674-12674/com.rambopan.client I/ConnectAssist: onServiceConnected:
从结果来看,Service 连接成功还需要一点时间,所以 bindService() 是异步执行的,所以等没有等待连接成功或失败之后再执行剩下的代码。
Demo 地址:https://github.com/RamboPan/demo_aidlcallback
技术浅薄,如存在问题或错误,欢迎指出