欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

AIDL能扶起的阿斗

程序员文章站 2022-06-21 20:30:46
Binder是Android系统提供的一种IPC( 进程间通信) 机制,在Java层中如果想要利用Binder进行跨进程的通信, 也得定义一个类似ITest的接口,不过这是一个aidl文件。阿斗(aidl的谐音) 本来是扶不起的, 可是我们有了AIDL工具,就有可能将他扶起!即AIDL是Binder系统面向Java层的一种实现机制。参考:Android中AIDL的使用详解一、AIDL简单演示AIDL是Android中IPC(Inter-Process Communication)方式中的一......

 

一、AIDL基本使用

1、服务端进程

2、客户端应用

3、跨进程服务

二、AIDL基本原理

三、AIDL与Binder native库


 

Binder是Android系统提供的一种IPC( 进程间通信) 机制,在Java层中如果想要利用Binder进行跨进程的通信, 也得定义一个类似ITest的接口,不过这是一个aidl文件。阿斗(aidl的谐音) 本来是扶不起的, 可是我们有了AIDL工具,就有可能将他扶起!即AIDL是Binder系统面向Java层的一种实现机制

参考:Android中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能扶起的阿斗

这个AIDL文件被存储的路径与java同层:项目名\app\src\main\aidl\包路径\xxx.aidl

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来一键生成这些服务。

AIDL能扶起的阿斗

 

如上图,当同步成功后就会在对应的generated目录生成对应的interface,如果该目录下有漏掉的interface那么应该去排查对应的aidl是否有语法错误导致Android studio无法解析,如下我这种情况,ICommonService没有生成,但我并没有检查出什么语法错误,去掉一些注释就好了,怀疑Android studio的bug:

AIDL能扶起的阿斗

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文件拷贝过来

AIDL能扶起的阿斗

3、跨进程服务

目前服务端应用程序AIDLServer和客户端应用程序AIDLClient都已经准备好了,找一台手机来运行一下,看应用程序AIDLClient能够调用AIDLServer里面的服务吗?运行日志结果如下:

AIDL能扶起的阿斗

上面的报错日志显示SPlayerService不允许aidlclient程序访问?为什么呢?原来我们还差个步骤,需要将SPlayerService配置成远程访问允许,因此需要在SPlayerService配置的地方加上属性exported。配置如下:

AIDL能扶起的阿斗

二、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接口代码如下:

AIDL能扶起的阿斗

如上该文件中定义了接口ISPlayerService,该接口除了定义aidl文件中描述的接口之外,还有定义了一个静态内部类Default和一个抽象内部类Stub。

除此之外接口ISPlayerService继承于android.os.IInterface,其实该接口内部就定义了asBinder方法,用来将IXXX转换成接口IBinder,如下:

AIDL能扶起的阿斗

1、AIDL接口的默认实现

静态内部类Default其实是对aidl接口的一个默认实现,其实什么都没有,也没有什么研究的必要。如下代码:

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。如下代码:

AIDL能扶起的阿斗

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里面,该方法如下:

AIDL能扶起的阿斗

我们把绑定远程服务成功后返回的IBinder通过远程服务描述来判断是否在同一个进程,如果在同一进程就直接返回服务端的Stub对象本身,否则返回Stub.Proxy代理对象。因此前面访问本地服务的例子中为什么能够拿到Service的实例对象,因为这里直接返回了Stub本身。

三、AIDL与Binder native库

 

 

本文地址:https://blog.csdn.net/qq_27672101/article/details/107428594

相关标签: AIDL Binder