AIDL使用实例(kotlin)

AIDL使用实例(kotlin)

对AIDL以及Binder机制还不太了解的建议先看这篇博客

https://blog.csdn.net/lj402159806/article/details/85038382

创建AIDL接口

首先创建一个数据类文件和四个aidl文件

public class Book implements Parcelable {

    public int bookId;

    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }
    
    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.bookId);
        dest.writeString(this.bookName);
    }

    protected Book(Parcel in) {
        this.bookId = in.readInt();
        this.bookName = in.readString();
    }

    public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public String toString() {
        return "Book{" +
                "bookId=" + bookId +
                ", bookName='" + bookName + '\'' +
                '}';
    }
}

该数据类文件表示图书信息类,实现了parcelable接口

// Book.aidl
package com.gavinandre.aidl;

parcelable Book;

由于Book类是自定义parcelable对象,所以必须新建一个和它同名的AIDL文件,并在其中声明为parcelable类型

// IOnNewBookArrivedListener.aidl
package com.gavinandre.aidl;

import com.gavinandre.aidl.Book;

interface IOnNewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}

AIDL中无法使用普通接口,因此创建一个AIDL接口,用于服务端主动回调客户端,接口中用到了自定义parcelable对象,必须显式import进来,不管是否在同一个包内

// IBookManager.aidl
package com.gavinandre.aidl;

import com.gavinandre.aidl.Book;
import com.gavinandre.aidl.IOnNewBookArrivedListener;

interface IBookManager {

    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);

}

图书管理接口,有四个方法,前两个分别是客户端从服务端获取图书列表和增加图书功能,后两个用于客户端向服务端注册和注销aidl接口,由于用到了自定义parcelable对象和AIDL对象,因此必须显式将两者import进来

然后rebuild一下,系统就会在build/generated/source/aidl/debug/${packageName}/目录下生成同名的java文件

创建远程服务端

创建一个Service,名为BookManagerService,并注册这个Service

<service
        android:name="com.gavinandre.aidldemo.BookManagerService"
        android:enabled="true"
        android:exported="true"
        android:process=":remote">
</service>
  • android:enabled
    是否可以被系统实例化,默认为 true因为父标签 也有 enable 属性,所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活
  • android:exported
    代表是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。为false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用
  • android:process
    是否需要在单独的进程中运行,当设置为android:process=”:remote”时,代表Service在单独的进程中运行。注意“:”很重要,它的意思是指要在当前进程名称前面附加上当前的包名,所以“remote”和”:remote”不是同一个意思,前者的进程名称为:remote,而后者的进程名称为:App-packageName:remote

然后声明自定义权限

<!--自定义权限-->
<permission
        android:name="com.gavinandre.aidldemo.permission.ACCESS_BOOK_SERVICE"
        android:protectionLevel="normal" />

<!--声明权限-->
<uses-permission android:name="com.gavinandre.aidldemo.permission.ACCESS_BOOK_SERVICE" />

BookManagerService完整代码如下:

class BookManagerService : Service() {

    private val TAG = BookManagerService::class.java.simpleName

    //使用CopyOnWriteArrayList来支持并发读写
    private val mBookList = CopyOnWriteArrayList<Book>()
    //使用RemoteCallbackList来支持aidl接口管理
    private val mListenerList = RemoteCallbackList<IOnNewBookArrivedListener>()

    override fun onCreate() {
        super.onCreate()
    }

    override fun onBind(intent: Intent): IBinder? {
        //验证权限方式1
        //在onBind函数中做验证,没有声明该权限则不返回binder
        checkCallingOrSelfPermission(ACCESS_BOOK_SERVICE_PERMISSION)
            .takeIf { it == PackageManager.PERMISSION_DENIED }
            ?.let {
                Log.e(TAG, "onBind: permission denied")
                return null
            }
            ?: return mBinder
    }

    override fun onDestroy() {
        super.onDestroy()
    }

    private val mBinder = object : IBookManager.Stub() {
        override fun addBook(book: Book) {
            //模拟耗时操作
            SystemClock.sleep(3000)
            mBookList.add(book)
            //通知所有注册监听接口的客户端有新书到达
            onNewBookArrived(book)
        }

        override fun getBookList(): List<Book> {
            //模拟耗时操作
            SystemClock.sleep(3000)
            return mBookList
        }

        override fun registerListener(listener: IOnNewBookArrivedListener?) {
            //添加aidl接口
            mListenerList.register(listener)
            //获取aidl接口数量
            val N = mListenerList.beginBroadcast()
            mListenerList.finishBroadcast()
            Log.i(TAG, "registerListener, current size:$N")
        }

        override fun unregisterListener(listener: IOnNewBookArrivedListener?) {
            //移除aidl接口
            val success = mListenerList.unregister(listener)
            if (success) {
                Log.i(TAG, "unregister success.")
            } else {
                Log.i(TAG, "not found, can not unregister.")
            }
            //获取aidl接口数量
            val N = mListenerList.beginBroadcast()
            mListenerList.finishBroadcast()
            Log.i(TAG, "unregisterListener, current size:$N")
        }

        override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
            //验证权限方式2
            //在onTransact函数中做验证,没有声明该权限则返回false
            checkCallingOrSelfPermission(ACCESS_BOOK_SERVICE_PERMISSION)
                .takeIf { it == PackageManager.PERMISSION_DENIED }
                ?.let {
                    Log.e(TAG, "onTransact: permission denied")
                    return false
                }

            //验证权限方式3
            //验证包名前缀,不相等则返回false
            packageManager.getPackagesForUid(getCallingUid())
                ?.takeIf { it.isNotEmpty() }
                ?.takeUnless { it[0].startsWith(PACKAGE_NAME_PREFIX) }
                ?.let {
                    Log.e(TAG, "onTransact: package name invalid")
                    return false
                }

            return super.onTransact(code, data, reply, flags)
        }
    }

    fun onNewBookArrived(book: Book) {
        val N = mListenerList.beginBroadcast()
        for (i in 0 until N) {
            try {
                //通知所有注册监听接口的客户端有新书到达
                mListenerList
                    .getBroadcastItem(i)
                    ?.onNewBookArrived(book)
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
        }
        mListenerList.finishBroadcast()
    }

    companion object {
        const val ACCESS_BOOK_SERVICE_PERMISSION = "com.gavinandre.aidldemo.permission.ACCESS_BOOK_SERVICE"
        const val PACKAGE_NAME_PREFIX = "com.gavinandre"
    }
}

上面的Service代码,首先创建了一个Binder对象,然后在onBind方法中返回,onBind方法中做了权限验证的功能,验证不通过直接返回null,这个Binder对象继承自IBookManager.Stub并实现了它内部的四个AIDL方法和onTransact方法,四个AIDL方法分别是获取图书列表,添加图书,注册监听和注销监听,onTransact方法用来做权限验证,这里实现了两种方式,分别是用权限和包名前缀来进行验证,不通过直接返回false

需要注意的点:

private val mBookList = CopyOnWriteArrayList<Book>()

由于AIDL方法是在服务端的Binder线程池中执行的,多个客户端同时连接就会存在并发的情况,因此这里要处理线程同步就可以使用CopyOnWriteArrayList来自动处理线程同步

override fun getBookList(): List<Book> {
    //模拟耗时操作
    SystemClock.sleep(3000)
    return mBookList
}

虽然AIDL中List只能使用ArrayList,但是这里却能用CopyOnWriteArrayList类型的mBookList作为返回值是因为AIDL中所支持的是抽象List,在Binder中会按照List的规范去访问数据并最终形成一个新的ArrayList传递给客户端,ConcurrentHashMap同理

服务端AIDL方法是运行在Binder线程池中执行,能做耗时操作,但不能做ui操作

private val mListenerList = RemoteCallbackList<IOnNewBookArrivedListener>()

AIDL对象在通过Binder传递的过程中会被重新转化并生成一个新对象,这也是为什么AIDL中的自定义对象都必须实现Parcelable接口的原因,因此AIDL接口的管理要使用RemoteCallbackList

创建客户端

创建Activity,在manifest中注册,完整代码如下:

class BookManagerActivity : AppCompatActivity() {

    private val TAG = BookManagerActivity::class.java.simpleName

    private var mRemoteBookManager: IBookManager? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_book_manager)
        bindService()
        initListener()
    }

    //绑定远程服务端
    private fun bindService() {
        val intent = Intent(this, BookManagerService::class.java)
        intent.setPackage(packageName)
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
    }

    //注册按钮监听,调用远程耗时方法需要开启线程
    private fun initListener() {
        btnGetBookList.setOnClickListener {
            Toast.makeText(this, "GetBookList", Toast.LENGTH_SHORT).show()
            Thread {
                try {
                    mRemoteBookManager?.let { mRemoteBookManager ->
                        val list = mRemoteBookManager.bookList
                        Log.i(TAG, "query book list: $list")
                    }
                } catch (e: RemoteException) {
                    e.printStackTrace()
                }
            }.start()
        }
        btnAddBook.setOnClickListener {
            Toast.makeText(this, "AddBook", Toast.LENGTH_SHORT).show()
            Thread {
                try {
                    mRemoteBookManager?.apply {
                        it.tag = it.tag?.run {
                            this as Int + 1
                        } ?: 1
                        val id = it.tag as Int
                        val newBook = Book(id, "Book$id")
                        Log.i(TAG, "add book: $newBook")
                        addBook(newBook)
                    }
                } catch (e: RemoteException) {
                    e.printStackTrace()
                }
            }.start()
        }
    }

    override fun onDestroy() {
        unBindService()
        //防止内存泄露
        mHandler.removeCallbacksAndMessages(null)
        super.onDestroy()
    }

    private fun unBindService() {
        try {
            Log.i(TAG, "onDestroy: $mOnNewBookArrivedListener")
            mRemoteBookManager
                ?.takeIf { it.asBinder().isBinderAlive }
                ?.unregisterListener(mOnNewBookArrivedListener)
        } catch (e: RemoteException) {
            e.printStackTrace()
        }
        unbindService(mConnection)
    }

    //服务端连接状态回调
    private var mConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.e(TAG, "onServiceConnected: $mRemoteBookManager")
            //将服务端返回的Binder对象转换为aidl对象
            mRemoteBookManager = IBookManager.Stub.asInterface(service)
            Log.e(TAG, "onServiceConnected: $mRemoteBookManager")
            try {
                //注册AIDL回调接口
                mRemoteBookManager?.registerListener(mOnNewBookArrivedListener)
                //设置死亡代理
                mRemoteBookManager?.asBinder()?.linkToDeath(mDeathRecipient, 0)
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            Log.i(TAG, "onServiceDisconnected ThreadName: ${Thread.currentThread().name}")
            mRemoteBookManager = null
        }
    }

    //aidl接口回调
    private val mOnNewBookArrivedListener = object : IOnNewBookArrivedListener.Stub() {
        override fun onNewBookArrived(newBook: Book?) {
            //obtainMessage可以复用Message,减少开销
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget()
        }
    }

    //使用lambda简化创建handler
    private val mHandler = Handler { msg ->
        when (msg.what) {
            MESSAGE_NEW_BOOK_ARRIVED -> Log.i(TAG, "receive new book: ${msg.obj}")
        }
        false
    }

    //死亡代理回调
    private val mDeathRecipient = object : IBinder.DeathRecipient {
        override fun binderDied() {
            Log.d(TAG, "binder died. ThreadName: ${Thread.currentThread().name}")
            //移除死亡代理
            mRemoteBookManager?.asBinder()?.unlinkToDeath(this, 0)
            mRemoteBookManager = null
            // TODO:这里重新绑定远程Service
            //bindService()
        }
    }

    companion object {
        const val MESSAGE_NEW_BOOK_ARRIVED = 1
    }
}

客户端首先绑定远程服务端,绑定成功后将服务端返回的Binder对象转换成AIDL客户端对象,接着向远程服务端注册IOnNewBookArrivedListener回调接口,设置DeathRecipient死亡代理,然后就可以通过aidl客户端对象调用服务端的远程方法了

需要注意的点:

btnGetBookList.setOnClickListener {
    Toast.makeText(this, "GetBookList", Toast.LENGTH_SHORT).show()
    Thread {
        try {
            mRemoteBookManager?.let { mRemoteBookManager ->
                val list = mRemoteBookManager.bookList
                Log.i(TAG, "query book list: $list")
            }
        } catch (e: RemoteException) {
            e.printStackTrace()
        }
    }.start()
}

客户端如果调用远程方法是耗时方法则需要在子线程内操作

完整demo:

https://github.com/GavinAndre/AIDLDemo

发布了174 篇原创文章 · 获赞 119 · 访问量 55万+

猜你喜欢

转载自blog.csdn.net/lj402159806/article/details/85095699