AIDL能扶起的阿斗
Binder是Android系统提供的一种IPC( 进程间通信) 机制,在Java层中如果想要利用Binder进行跨进程的通信, 也得定义一个类似ITest的接口,不过这是一个aidl文件。阿斗(aidl的谐音) 本来是扶不起的, 可是我们有了AIDL工具,就有可能将他扶起!即AIDL是Binder系统面向Java层的一种实现机制。
一、AIDL基本使用
AIDL是Android中IPC(Inter-Process Communication)方式中的一种,AIDL是Android Interface definition language的缩写(Android内部进程通信接口的描述语言)。对于小白来说,AIDL的作用是让你可以在自己的APP里绑定一个其他APP的service,这样你的APP可以和其他APP交互。下面将手写两个应用程序,其中一个应用AIDLServer作为服务端进程(为系统提供服务),另外一个应用AIDLClient作为客户端进程。
1、服务端进程
这里我们创建一个AIDLServer的应用程序作为服务端进程,为系统提供一些列服务,该进程可以提供多个服务,即可以创建多个AIDL服务。
1)、创建AIDL
AIDL其实是Android Interface definition language的缩写,在Android框架层中,可以用一个aidl文件来描述一个AIDL接口,实际开发中我们将创建一个或多个AIDL文件,其内容符合Android Interface definition language语法(很类似java的接口),每个AIDL文件都对应一个服务service。
可以通过Android studio的菜单创建一个AIDL文件:File->New->AIDL->AIDL File
这个AIDL文件被存储的路径与java同层:项目名\app\src\main\aidl\包路径\xxx.aidl
2)、创建接口
在步骤1中我们创建了两个aidl文件,意味着我们可以为系统提供两个不同的service(服务),这些service中我们可以提供一些列的方法集合。这里的ICommonService.aidl用来实现一些基础公共服务,ISPlayerService.aidl用来实现一些媒体播放服务。因此需要分别在aidl中加上一系列函数,如下:
// ICommonService.aidl
package com.shen.aidlserver;
interface ICommonService {
//提示AIDL文件中智能用int long boolean float double String几种基本数据类型,因为需要跨进程
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString);
//定义接口判断是否拥有root权限
boolean hasRootPerssion();
//定义接口重启关闭休眠唤醒系统
void resetSystem();
void shutSystem();
void sleepSystem();
void wakeSystem();
}
// ISPlayerService.aidl
package com.shen.aidlserver;
interface ISPlayerService {
//设置媒体播放的操作方法
int setDataSource(int fd, long offset, long length);
int prepareAsync();
int start();
int stop();
int pause();
int setVolume(float leftVolume, float rightVolume);
int setLooping(int loop);
}
如上代码,我们在ICommonService服务中增加了一些操作系统的方法,在ISPlayerService服务中增加了一些媒体播放的方法,但这个时候只是定义了service的接口而已,我们并不能使用这些服务。Android studio为我们提供sycn project来一键生成这些服务。
如上图,当同步成功后就会在对应的generated目录生成对应的interface,如果该目录下有漏掉的interface那么应该去排查对应的aidl是否有语法错误导致Android studio无法解析,如下我这种情况,ICommonService没有生成,但我并没有检查出什么语法错误,去掉一些注释就好了,怀疑Android studio的bug:
3)、创建服务
同步工程之后,Android studio会自动在generated生成一些临时文件,如ICommonService.java和ISPlayerService.java。这两个文件其实是通过ICommonService.aidl和ISPlayerService.aidl转换过去的,并实现了一些关于binder库相关的一些代码,具体详情参考第二章,这里我们需要知道除了定义对应的接口类(ICommonService),还定义了ICommonService.Stub内部类。
Stub其实对应Binder框架库里面的BnXXX,因此在具体的业务中,通常只需要我们实现该类的真正业务逻辑部分功能就完成了整个Binder通信机制。这里我在Java层来创建两个服务CommonService和SPlayerService来对应上面定义的两个aidl接口文件。如下代码:
package com.shen.aidlserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.Nullable;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
public class CommonService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return iBinder;
}
private IBinder iBinder = new ICommonService.Stub() {
@Override
public boolean hasRootPerssion() throws RemoteException {
PrintWriter PrintWriter = null;
Process process = null;
try {
process = Runtime.getRuntime().exec("su");
PrintWriter = new PrintWriter(process.getOutputStream());
PrintWriter.flush();
PrintWriter.close();
int value = process.waitFor();
return returnResult(value);
} catch (Exception e) {
e.printStackTrace();
}finally{
if(process!=null) process.destroy();
}
return false;
}
@Override
public void resetSystem() throws RemoteException {
try {
if (hasRootPerssion()) {
Process process = Runtime.getRuntime().exec("su");
DataOutputStream(process.getOutputStream());
out.writeBytes("reboot \n");
out.writeBytes("exit\n");
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void shutSystem() throws RemoteException {
try {
if (hasRootPerssion()) {
Process process = Runtime.getRuntime().exec("su");
DataOutputStream out = new DataOutputStream(process.getOutputStream());
out.writeBytes("reboot -p\n");
out.writeBytes("exit\n");
out.flush();
}
} catch(IOException e){
e.printStackTrace();
}
}
@Override
public void sleepSystem() throws RemoteException {
try {
if (hasRootPerssion()) {
Process process = Runtime.getRuntime().exec("su");
DataOutputStream out = new DataOutputStream(process.getOutputStream());
out.writeBytes("echo mem > /sys/power/state \n");
out.writeBytes("exit\n");
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void wakeSystem() throws RemoteException {
try {
if (hasRootPerssion()) {
Process process = Runtime.getRuntime().exec("su");
DataOutputStream out = new DataOutputStream(process.getOutputStream());
out.writeBytes("echo on > /sys/power/state \n");
out.writeBytes("exit\n");
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
};
}
如上类CommonService代码,继承于Service表示它是一个后台服务,重写了onBind方法返回了一个ICommonService.Stub类,在该类的接口中实现了重启/关机/睡眠/唤醒系统的功能。即其他应用程序可以通过四大组件之一Service的方式来访问我,我给他们提供重启/关机/睡眠/唤醒系统的服务。
其实上面的ICommonService的实现,我觉得还是有些需要优化的,以解耦功能性的方向来看,我们完全可以把具体业务逻辑实现放在Service里面,然后IBinder只需要当成中间桥梁的作用就行了。如下SPlayerService的代码:
package com.shen.aidlserver;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
public class SPlayerService extends Service {
private void prepareSPlayer(){
//...业务逻辑具体实现
}
private void startSPlayer(){
//...业务逻辑具体实现
}
private void stopSPlayer(){
//...业务逻辑具体实现
}
private void pauseSPlayer(){
//...业务逻辑具体实现
}
private void setSPlayerSource(int fd, long offset, long length){
//...业务逻辑具体实现
}
private void setSPlayerLooping(int loop){
//...业务逻辑具体实现
}
private void setSPlayerVolume(float leftVolume, float rightVolume){
//...业务逻辑具体实现
}
@Override
public IBinder onBind(Intent intent) {
return new ISPlayerService.Stub() {
@Override
public int prepareAsync() throws RemoteException {
prepareSPlayer();
return 0;
}
@Override
public int setDataSource(int fd, long offset, long length) throws RemoteException {
setSPlayerSource(fd,offset,length);
return 0;
}
@Override
public int start() throws RemoteException {
startSPlayer();
return 0;
}
@Override
public int stop() throws RemoteException {
stopSPlayer();
return 0;
}
@Override
public int pause() throws RemoteException {
pauseSPlayer();
return 0;
}
@Override
public int setVolume(float leftVolume, float rightVolume) throws RemoteException {
setSPlayerVolume(leftVolume, rightVolume);
return 0;
}
@Override
public int setLooping(int loop) throws RemoteException {
setSPlayerLooping(loop);
return 0;
}
};
}
}
2、客户端应用
第一节内容已经创建了服务端应用程序,该进程内部除了创建两个aidl文件还根据aidl接口创建了两个服务,没错这里的服务就是android四大组件之一Service。这里我们要创建一个客户端应用程序,该应用内部需要访问上面的这两个Service,在android架构中默认会为每个应用程序APP创建一个进程,因此这里就属于跨进程访问。
四大组件之Service的访问大家都应该很熟悉了,对于做应用来说,无论Service是否跨进程,访问的方式其实都是一样的。因此可以进行总结:同应用程序中的Service访问使用的代理模式;不同应用程序中需要进行Service访问,就需要使用AIDL来实现。
package com.shen.aidlclient;
import androidx.appcompat.app.AppCompatActivity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.widget.Button;
import com.shen.aidlserver.ISPlayerService;
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button bt = findViewById(R.id.play);
bt.setOnClickListener(this);
}
@Override
public void onClick(View view) {
Intent intent = new Intent();
//跨进程,无法使用Intent(this,xx.class)方法,可以使用下面的,通过包名和完整路径类名创建Intent
intent.setClassName("com.shen.aidlserver","com.shen.aidlserver.SPlayerService");
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
ISPlayerService iss = ISPlayerService.Stub.asInterface(iBinder);
try {
iss.prepareAsync();
iss.setVolume(30, 30);
iss.setLooping(1);
iss.start();
}catch (Exception e){
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
}, Context.BIND_AUTO_CREATE);
}
}
如上代码访问Service的通用方式:
- 通过bindService方法来绑定某个服务,该服务填充到Intent,因为这里访问的是其他应用程序的服务,本地中无法指定Service.class,因此必须使用包名的方式进行填充
- 当服务连接成功之后,使用方法ISPlayerService.Stub.asInterface(iBinder)将IBinder转换成IXXX接口,这里可能编译无法通过,因此需要将对应的aidl文件拷贝过来
3、跨进程服务
目前服务端应用程序AIDLServer和客户端应用程序AIDLClient都已经准备好了,找一台手机来运行一下,看应用程序AIDLClient能够调用AIDLServer里面的服务吗?运行日志结果如下:
上面的报错日志显示SPlayerService不允许aidlclient程序访问?为什么呢?原来我们还差个步骤,需要将SPlayerService配置成远程访问允许,因此需要在SPlayerService配置的地方加上属性exported。配置如下:
二、AIDL基本原理
从上面的示例中可以看出来,AIDL文件在中间只起了桥梁的作用,即AIDL为我们提供了IBinder的接口定义。在服务端应用程序中,四大组件Service的IBinder继承了类IXXX.Stub;在客户端应用程序中,在绑定服务连接成功的时候将前面的IXXX.Stub强转成ISPlayerService.Stub.asInterface(iBinder)。
其实IXXX.Stub和IXXX.Stub.asInterface都被定义在generated目录里面对应的接口里面,Android studio在同步或编译工程的时候会自动解析aidl文件,并将其转换成对应的接口,这个过程也可以使用android sdk下面工具命令转换。ISPlayerService接口代码如下:
如上该文件中定义了接口ISPlayerService,该接口除了定义aidl文件中描述的接口之外,还有定义了一个静态内部类Default和一个抽象内部类Stub。
除此之外接口ISPlayerService继承于android.os.IInterface,其实该接口内部就定义了asBinder方法,用来将IXXX转换成接口IBinder,如下:
1、AIDL接口的默认实现
静态内部类Default其实是对aidl接口的一个默认实现,其实什么都没有,也没有什么研究的必要。如下代码:
但值得注意的是除了实现aidl文件里面描述的接口方法之外,还实现了android.os.IInterface接口里面的asBinder方法,用来将ISPlayerService接口转换成IBinder接口。
2、Stub的实现
内部抽象类Stub继承于android.os.Binder且实现了IXXX接口,除此之外它还有一个静态内部类Proxy。其中Proxy/Stub的关系对应于Binder架构里面的C/S架构,即Stub在natvie层对应于一个BnXXX,Proxy在native层对应于一个BpXXX。如下代码:
Stub类内部还实现了几个重要的方法:
- asBinder:将自己转换成IBinder,因为Stub实现了IBinder接口,所以这里直接返回了自己this
- asInterface:将IBinder强转成IXXX,在使用bindService方法绑定服务成功之后,回调函数onServiceConnected的参数是最基本的IBinder类型(传递的父类),因此需要通过该方法将其强制转换成IXXX
- onTransact:进行任务分发,类似natvie层里面的BnXXX
1)、asInterface
前文有介绍Stub对应于C/S通信架构中的服务端,也可以将其理解成Service的本地对象;而Proxy对应于C/S通信架构中的客户端,也可以理解成Service的代理对象。
当客户端应用程序使用bindService绑定Service成功之后,就会得到一个IBinder接口,我们可以通过该接口来访问被绑定的Service,因为IBinder是一个通用基类(没有任何业务相关的实现),所有需要将其强制转换成业务子类。
- 访问本地Service:该方式通常在同一个APP中,Activity访问Service,他们同为四大组件,且都处于同一个进程,因此Activity能够获取到本地Service的实例对象,强转ServiceConnection返回的IBinder为自定义业务子类,通过该业务子类访问Service或者直接获取Service的实例对象。如下示例代码:
//Service实现
public class MyService extends Service {
//自定义Binder 该binder能返回Service实例对象
public class MyBinder extends Binder {
public MyService getService(){
return MyService.this; //返回的本实例
}
}
private MyBinder mBinder = new MyBinder();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public String getMyName(){
return "I am 诸神黄昏";
}
}
//acitivity建立连接
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder ibinder) {
//将ibinder强转MyBinder类型
MyBinder mBinder = (MyService.MyBinder)ibinder;
//通过自定义的MyBinder拿到MyService实例对象
MyService mService = mBinder.getService();
//通过实例对象来访问MyService的getMyName接口
textView.setText(mService.getMyName());
}
}
- 访问远程Service:该方式通常在不同APP或不同的进程之间,Service的访问方式,因为他们不再同一个进程中,因为进程隔离的原因,上面那种方式就行不通了,你根本无法拿到MyService的实例对象。这时候就需要通过IXXX.Stub.asInterface来将IBinder转换成aidl文件定义的接口才行。
//远程Service实现
public class MyService extends Service {
private MyBinder mBinder = new IXXX.Stub();
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public String getMyName(){
return "I am 诸神黄昏";
}
}
//acitivity建立连接
ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder ibinder) {
//将ibinder强转MyBinder类型
IXXX mBinder = IXXX.Stub.asInterface(ibinder);
//通过实例对象来访问MyService的getMyName接口
textView.setText(mService.getMyName());
}
}
是不是感觉一切奥秘都在Stub的asInterface里面,该方法如下:
我们把绑定远程服务成功后返回的IBinder通过远程服务描述来判断是否在同一个进程,如果在同一进程就直接返回服务端的Stub对象本身,否则返回Stub.Proxy代理对象。因此前面访问本地服务的例子中为什么能够拿到Service的实例对象,因为这里直接返回了Stub本身。
三、AIDL与Binder native库
本文地址:https://blog.csdn.net/qq_27672101/article/details/107428594
下一篇: C#动态生成按钮及定义按钮事件的方法
推荐阅读