android之AIDL实例详解

前面我们将了本地service的基本用法,今天来介绍的就是远程服务,就是Service端和Client端分别在不同的进程。

这里就不得不提到AIDL了。

什么是AIDL:

大家可以看一下官方文档的定义,简单来说AIDL就是Android系统提供的一套帮助开发者快速实现binder的工具。而什么是binder呢?binder是Android系统实现进程间通讯的机制。这个再再再后面讲……

使用AIDL:

既然是C/S模型那么为了方便演示,这里我们就直接创建2个mode,一个aidlService(服务端),一个aidlClient(客户端)。

android之AIDL实例详解_第1张图片

aidlService端:

创建aidl文件:直接在项目main目录右键-new-aidl,然后输入文件名称即可。

android之AIDL实例详解_第2张图片

系统会默认新建一个和Java目录平级的aidl目录,下面是和我们报名相同的路径,然后是我们创建的aidl文件。

android之AIDL实例详解_第3张图片

先来编写接口:

package com.example.aidlService;
import com.example.aidlService.bean.BookBean;

interface IMyAidlInterface {

    String getString();

    BookBean getBook();
}

可以看到创建的aidl文件是一个接口,我们定义了2个方法,一个getString返回一个String,getBook返回我们自定义的实体类。这里讲一下aidl支持的数据类型:

除了基本数据类型外还有String、CharSequence、List、map、实现Parcelable的类。其中Parcelable接口类需要自己写import导入,就像上面的import com.example.aidlService.bean.BookBean;类,就是为了导入BookBean。

Parcelable接口是Android提供的可序列化的接口,简单看一下BookBean的实现:

class BookBean() : Parcelable {

    var name = ""

    var price = 0.0f

    constructor(name: String, price: Float) : this() {
        this.name = name
        this.price = price
    }

    constructor(parcel: Parcel) : this() {
        name = parcel.readString().toString()
        price = parcel.readFloat()
    }

    override fun writeToParcel(dest: Parcel?, flags: Int) {
        dest?.let {
            it.writeString(name)
            it.writeFloat(price)
        }
    }

    override fun describeContents(): Int = 0

    override fun toString(): String {
        return "BookBean(name='$name', price=$price)"
    }

    companion object CREATOR : Parcelable.Creator {
        override fun createFromParcel(parcel: Parcel): BookBean {
            return BookBean(parcel)
        }

        override fun newArray(size: Int): Array {
            return arrayOf()
        }
    }
}

Parcelable接口主要需要我们实现一个带Parcel参数的构造方法和writeToParcel方法,以及Creator接口,Creator接口主要是用来创建我们的BookBean实例的,在writeToParcel方法中我们需要将需要保存的属性通过Parcel的writexxx方法写入,然后在带有Parcel参数的构造方法中农使用readxxx读取出来。需要注意的是两边写入和读取的顺序要相同。

编写完aidl接口后我们需要让android studio同步一下代码,让它自动生成aidl文件对应的java文件。可以点击Build菜单的make mode来重新编译整个项目。

编译成功之后会在build目录下生成一个aidl同名的Java接口文件

android之AIDL实例详解_第4张图片

里面的内容暂时先不用管,我们后面再说。先看怎么使用这个接口。

接下来我们就需要创建Service了

android之AIDL实例详解_第5张图片

创建 一个AIDLService文件,这就是我们的Service,还记得上一章四大组件之Service(1)讲到的Service的启动方式和对应的生命周期函数吗?因为是要给客户端提供接口的,所以客户端必须使用bindService方法来启动我们的service,所以我们这里的Service只需要实现onBind并且返回Ibinder的接口实现类。

看看代码:

class AIDLService : Service() {

    override fun onBind(intent: Intent): IBinder {
        return myAidlInterfaceBinder
    }

    private val myAidlInterfaceBinder = object : IMyAidlInterface.Stub() {

        override fun getBook(): BookBean {
            Log.d("ZLog AIDLService", "getBook: ")
            return BookBean("android 从入门到放弃", 0.0f)
        }

        override fun getString(): String {
            Log.d("ZLog AIDLService", "getString: ")
            return "这是服务端返回的String"
        }
    }
}

重点是我们在onBind中返回的是什么?我们这里的myAidlInterfaceBinde是一个直接继承ImyAidlInterface.Stub的类。IMyAIDLInterface是我们自己创建的aidl接口,那Stub是哪来的呢?并且还包含了我们定义的2个接口方法getString和getBook。回到我们刚刚创建好ImyAidlInterface.aidl后重新编译了项目生成的ImyAidlInterface.java接口,这个时候再来看看它里面的内容:

public interface IMyAidlInterface extends android.os.IInterface {
    /**
     * Default implementation for IMyAidlInterface.
     */
    public static class Default implements com.example.aidlService.IMyAidlInterface {
        @Override
        public java.lang.String getString() throws android.os.RemoteException {
            return null;
        }

        @Override
        public com.example.aidlService.bean.BookBean getBook() throws android.os.RemoteException {
            return null;
        }

        @Override
        public android.os.IBinder asBinder() {
            return null;
        }
    }

    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.example.aidlService.IMyAidlInterface {
        private static final java.lang.String DESCRIPTOR = "com.example.aidlService.IMyAidlInterface";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.example.aidlService.IMyAidlInterface interface,
         * generating a proxy if needed.
         */
        public static com.example.aidlService.IMyAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.example.aidlService.IMyAidlInterface))) {
                return ((com.example.aidlService.IMyAidlInterface) iin);
            }
            return new com.example.aidlService.IMyAidlInterface.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @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_getString: {
                    data.enforceInterface(descriptor);
                    java.lang.String _result = this.getString();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
                case TRANSACTION_getBook: {
                    data.enforceInterface(descriptor);
                    com.example.aidlService.bean.BookBean _result = this.getBook();
                    reply.writeNoException();
                    if ((_result != null)) {
                        reply.writeInt(1);
                        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        private static class Proxy implements com.example.aidlService.IMyAidlInterface {
            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 java.lang.String getString() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    boolean _status = mRemote.transact(Stub.TRANSACTION_getString, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        return getDefaultImpl().getString();
                    }
                    _reply.readException();
                    _result = _reply.readString();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public com.example.aidlService.bean.BookBean getBook() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                com.example.aidlService.bean.BookBean _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    boolean _status = mRemote.transact(Stub.TRANSACTION_getBook, _data, _reply, 0);
                    if (!_status && getDefaultImpl() != null) {
                        return getDefaultImpl().getBook();
                    }
                    _reply.readException();
                    if ((0 != _reply.readInt())) {
                        _result = com.example.aidlService.bean.BookBean.CREATOR.createFromParcel(_reply);
                    } else {
                        _result = null;
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            public static com.example.aidlService.IMyAidlInterface sDefaultImpl;
        }

        static final int TRANSACTION_getString = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

        public static boolean setDefaultImpl(com.example.aidlService.IMyAidlInterface impl) {
            if (Stub.Proxy.sDefaultImpl == null && impl != null) {
                Stub.Proxy.sDefaultImpl = impl;
                return true;
            }
            return false;
        }

        public static com.example.aidlService.IMyAidlInterface getDefaultImpl() {
            return Stub.Proxy.sDefaultImpl;
        }
    }

    public java.lang.String getString() throws android.os.RemoteException;

    public com.example.aidlService.bean.BookBean getBook() throws android.os.RemoteException;
}

内容很多,我们直接看里面的Stub类,它继承了我们上一章说的Binder类,并且实现了我们定义的ImyAidlInterface接口,所以我们可以在onBind中返回它。

其中有一个特殊的方法 asInterface()它接收一个Ibinder参数,返回的是我们定义的接口。看到这里大概有的朋友也知道了,这个方法就是客户端拿到我们定义的接口的方法。

好了,我们接着往下,接着我们需要配置一下service,在manifest.xml中我们给service增加一个action


            
                
            
        

我们添加了一个com.example.aidlService.Service的action,并且配置了service 的process属性,表示我们这个service是以一个独立的进程运行的。当然,这个配置不是必须的。

至此Service端的工作就做完了,接下来开始Client端。

首先要做的第一件事就是我们需要把service端所有的aidl都拷贝到client来,并且如果有传递了自定义的数据类也要拷贝过来,保证包名相同!保证包名相同!保证包名相同!

android之AIDL实例详解_第6张图片

 

这样client端才能正常使用相关内容

然后就是在客户端启动service:

private lateinit var aidlServiceIntent: Intent

    private lateinit var aidlInterface: IMyAidlInterface

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        aidlServiceIntent = Intent()
        aidlServiceIntent.action = "com.example.aidlService.Service"
        aidlServiceIntent.component =
            ComponentName("com.example.aidlService", "com.example.aidlService.AIDLService")

        bindService(aidlServiceIntent, conn, Context.BIND_AUTO_CREATE)

        acMainBtGetData.setOnClickListener {
            if (this::aidlInterface.isInitialized) {
                acMainTvMsg.text = aidlInterface.book.toString()
Log.d("ZLog MainActivity", "调用服务端getBook: ${aidlInterface.book}")
                Log.d("ZLog MainActivity", "调用服务端getString: ${aidlInterface.book}")
            } else {
                acMainTvMsg.text = "绑定失败"
            }
        }
    }

    private val conn = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName?) {
        }

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.d("ZLog MainActivity", "onServiceConnected: $service")
            aidlInterface = IMyAidlInterface.Stub.asInterface(service)
        }
}

这里bindService的intent跟上一章将的不太一样,因为这里我们是没有Service的,只能通过action和包名来告诉系统我们要注册的service在哪。Android 5.0之后不允许隐式启动service,所以我们这里首先设置了intent的action,就是我们在服务端的manifest中添加的action。下面设置了ComponentName,第一个参数是服务端的包名,第二参数是服务端service的完整类名。这里有一个问题,因为这里的C、S都是我们自己写的,所以是知道包名这些参数的,如果是第三方或者其他人写的,而且只提供了action怎么办呢?推荐一个下面的方法来获取:

private fun createExplicitFromImplicitIntent(implicitIntent: Intent): Intent? {
        val resolveInfo = packageManager.queryIntentServices(implicitIntent, 0)
        if (resolveInfo.size != 1) {
            return null
        }
        val serviceInfo = resolveInfo[0]
        val packageName = serviceInfo.serviceInfo.packageName
        val className = serviceInfo.serviceInfo.name
        val component = ComponentName(packageName, className)

        val explicitIntent = Intent(implicitIntent)
        explicitIntent.component = component
        return explicitIntent
    }

这个方法的参数Intent只需要设置action即可,然后通过packageManager获取一个ResolveInfo列表,正常情况这个列表里面符合条件的就一项,然后通过ResolveInfo就可以拿到包名和类名了。

然后我们看绑定的ServiceConnection方法,在onServiceConnected方法中我们使用之前讲的IMyAidlInterface.Stub.asInterface方法就能获取IMyAIDLInterface接口了。

在button的点击事件中我们来调用接口的相关方法。

这里有一点要注意的是我们需要先启动service端的apk,在启动client端的apk。

好了,到这里aidl的基本用法就讲完了。

你可能感兴趣的:(Android从入门到放弃,android,java,aidl,android,studio,kotlin)