当前位置: 移动技术网 > IT编程>开发语言>Java > AIDL能扶起的阿斗

AIDL能扶起的阿斗

2020年07月23日  | 移动技术网IT编程  | 我要评论

 

一、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文件被存储的路径与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

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网