首页 > 技术文章 > Android AIDL客户端与服务端双向通信

tony-yang-flutter 2022-06-15 11:39 原文

一、概述

  在App开发过程中,有时候为了得到更多的可用内存、App性能优化、或者App保活的时候可能会用到多进程之间通讯。最常用的方式是使用AIDL进行通讯,这也是Android推荐的一种IPC通讯方式。

  下面就记录一下用实际的代码来实现IPC通讯双方客户端和服务器端进行通讯的详细步骤。

  案例:实现一个IPC通讯,客户端可以向服务端添加书籍,服务端写一个定时器来定时向客户端推送指定的书籍。

  步骤如下:

    1.新建IBookManager.aidl,用于管理AIDL通讯

      

// IBookManager.aidl
package com.yw.custommutilimageadapter.aidl;

// Declare any non-default types here with import statements
import com.yw.custommutilimageadapter.aidl.Book;
import com.yw.custommutilimageadapter.aidl.IOnServerCallback;

interface IBookManager {
    ///从远程服务端获取图书列表
    List<Book> getBookList();
    //往图书列表中添加一本书
    void addBook(in Book book);
    void registerListener(IOnServerCallback callback);
    void unRegisterListener(IOnServerCallback callback);
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

    2.新建Book.aidl用于IPC通讯的实体

// Book.aidl
package com.yw.custommutilimageadapter.aidl;

// Declare any non-default types here with import statements
parcelable Book;
//interface Book {
//    /**
//     * Demonstrates some basic types that you can use as parameters
//     * and return values in AIDL.
//     */
//    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
//            double aDouble, String aString);
//}

    3.新建IOnServerCallback.aidl用于在IBookManager.aidl中设置回调函数,从而实现服务端向客户端传递数据。次类在客户端实现

// IOnServerCallback.aidl
package com.yw.custommutilimageadapter.aidl;

// Declare any non-default types here with import statements
import com.yw.custommutilimageadapter.aidl.Book;

interface IOnServerCallback {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    void onBookReceived(in Book book);
}

    4.新建一个Book.java类,此类必须实现parcelable接口,此类用户跨进程通讯传输,Book.java类的包名必须和aidl的包名保持一致

package com.yw.custommutilimageadapter.aidl;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {
    public int bookId;
    public String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

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

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

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

    @Override
    public int describeContents() {
        return 0;
    }

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

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

    5.新建后的目录结构

    

 

 

     6.新建一个BookService类,其中BookManagerImpl类为Binder服务端实现类

package com.yw.custommutilimageadapter.service

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.os.RemoteCallbackList
import com.yw.custommutilimageadapter.aidl.Book
import com.yw.custommutilimageadapter.aidl.BookManagerImpl
import com.yw.custommutilimageadapter.aidl.IBookManager
import com.yw.custommutilimageadapter.aidl.IOnServerCallback
import java.lang.Exception
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.atomic.AtomicBoolean

/**
 * AIDL图书馆测试类
 */
class BookService : Service() {
    //RemoteCallbackList是专门用于删除跨进程listener接口,它是一个泛型,支持管理任意的AIDL接口
    private var mListener = RemoteCallbackList<IOnServerCallback>()
    private var isServiceDestroy = AtomicBoolean(false)//服务是否销毁
    private var books = CopyOnWriteArrayList<Book>()

    override fun onCreate() {
        super.onCreate()
        books.add(Book(1,"《深入Java虚拟机》1"))
        Thread(serverWork).start()
    }

    override fun onBind(intent: Intent?): IBinder? {
        return BookManagerImpl(mListener)
    }

    override fun onDestroy() {
        isServiceDestroy.set(true)
        super.onDestroy()
    }

    /**
     * 发送图书到客户端
     */
    private fun sendBookToClient(book: Book){
        books.add(book)
        var n = mListener.beginBroadcast()
        for(index in 0 until n){
            var callback = mListener.getBroadcastItem(index)
            callback?.onBookReceived(book)
        }
        mListener.finishBroadcast()

    }

    /**
     * 服务端开始工作
     */
    private var serverWork = Runnable {
        while(!isServiceDestroy.get()){//如果服务没有销毁就一直运行
            try{
                Thread.sleep(5000)
                var i = books.size+1
                sendBookToClient(Book(i,"《深入Java虚拟机》${i}"))
            }catch (e:Exception){
                e.printStackTrace()
            }
        }
    }
}

  

**
 * AIDL图书馆代理实现类
 */
class BookManagerImpl(var mListener: RemoteCallbackList<IOnServerCallback>) : IBookManager.Stub() {
    var list = ArrayList<Book>()
    override fun getBookList(): MutableList<Book> {
        LogUtils.e("服务端书籍数量:${list.size}")
        return list
    }

    override fun addBook(book: Book?) {
        book?.let { list.add(it) }
        LogUtils.e("向服务端添加书籍")
    }

    override fun registerListener(callback: IOnServerCallback?) {
        mListener?.register(callback)
    }

    override fun unRegisterListener(callback: IOnServerCallback?) {
        mListener.unregister(callback)
    }

    override fun basicTypes(
        anInt: Int,
        aLong: Long,
        aBoolean: Boolean,
        aFloat: Float,
        aDouble: Double,
        aString: String?
    ) {
    }
}

 

    7.注册BookService类到AndroidManifest.xml中

    <!--运行在不同进程的Service-->
        <service android:name=".service.BookService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote"
            />

  8.BookManagerActivity为客户端调用类

package com.yw.custommutilimageadapter.ui

import android.app.Activity
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.os.RemoteException
import android.util.Log
import com.yw.custommutilimageadapter.R
import com.yw.custommutilimageadapter.aidl.Book
import com.yw.custommutilimageadapter.aidl.BookManagerImpl
import com.yw.custommutilimageadapter.aidl.IBookManager
import com.yw.custommutilimageadapter.aidl.IOnServerCallback
import com.yw.custommutilimageadapter.service.BookService
import kotlinx.android.synthetic.main.activity_book_manager.*


/**
 * 测试AIDL,IPC通信
 */
class BookManagerActivity : Activity() {
    /**
     * 图书管理类
     */
    private var iBookmanager: IBookManager? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_book_manager)
        btnBind.setOnClickListener {
            bindServiceClick()
        }

        btnUnBind.setOnClickListener {
            unBindServiceClick()
        }

        btnAddBook.setOnClickListener {
            iBookmanager?.addBook(Book(1, "《Android性能优化》"))
        }

        btnGetBooks.setOnClickListener {
            var sb = StringBuffer()
            var list = iBookmanager?.bookList
            list?.forEachIndexed { index, book ->
                sb.append(book.toString())
            }
            tvBookContent.text = sb.toString()
        }
    }

    /**
     * 绑定Service
     */
    private fun bindServiceClick() {
        var intent = Intent(this@BookManagerActivity, BookService::class.java)
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    /**
     * 接触绑定Service
     */
    private fun unBindServiceClick() {
        unbindService(conn)
    }

    private var conn = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            iBookmanager = IBookManager.Stub.asInterface(service)
            try {
                //设置死亡代理
                service?.linkToDeath(mDeathRecipient, 0)
                iBookmanager?.registerListener(serverListener)
            } catch (e: RemoteException) {
                e.printStackTrace()
            }

        }

        override fun onServiceDisconnected(name: ComponentName?) {
            //Service意外中断时调用
            iBookmanager = null
        }

    }

    /**
     * 监听Binder是否死亡
     */
    private var mDeathRecipient = object : IBinder.DeathRecipient {
        override fun binderDied() {
            if (iBookmanager == null) {
                return
            }
            iBookmanager?.asBinder()?.unlinkToDeath(this, 0)
            iBookmanager = null
            bindServiceClick()//重新绑定
        }
    }

    /**
     * 监听服务端消息的回调函数
     */
    private var serverListener = object : IOnServerCallback.Stub() {
        override fun basicTypes(
            anInt: Int,
            aLong: Long,
            aBoolean: Boolean,
            aFloat: Float,
            aDouble: Double,
            aString: String?
        ) {
        }

        override fun onBookReceived(book: Book?) {
            Log.e("收到服务端数据:", book.toString())
        }


    }

    /**
     * 销毁监听器
     */
    private fun destroyService() {
        if (conn != null && iBookmanager?.asBinder()?.isBinderAlive!!) {
            try {
                iBookmanager?.unRegisterListener(serverListener);
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
        }
    }

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

    

     ps:把所有的代码按照目录结构贴在项目中就可以运行测试   

二、重要代码说明

  1.类介绍

    a.IBookManager.aidl里面定义了四个方法 :

   //从远程服务端获取图书列表
    List<Book> getBookList();
    //往图书列表中添加一本书
    void addBook(in Book book);
  //注册服务端向客户端发送消息的回调函数
void registerListener(IOnServerCallback callback);
  //解除注册客户端向服务端发送消息的回调函数
void unRegisterListener(IOnServerCallback callback);

    b.Book.aidl,由于Android的IPC通讯只允许指定的数据类型进行传输,所以此处定义的类用户跨进程通讯,因为其继承了parcelabel

    c.IOnServerCallback.aidl,其实一个回调接口,用于向客户端回传消息,由于AIDL通讯不支持一般的interface所以,此接口继承了IInterface接口

//向客户端回传消息的方法
void
onBookReceived(in Book book);

    d.AIDL文件中支持的数据类型

1.基本数据类型(intlongchar、boolean、double等);
2.String和CharSequence;
3.List:只支持ArrayList,里面每个元素都必须能够被AIDL支持;
4.Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
5.Percelable:所有实现了Parecelable接口的对象
6.AIDL:所有的AIDL接口本身也可以在AIDL文件中使用

ps:如果AIDL文件中用到了自定义的parcelable对象,必须新建一个和他同名的AIDL文件,并在其中声明他为Parcelable类型,同时在引用的AIDL文件中必须导入这个AIDL,如上述的Book.aidl及IBookManager.aidl

  e.RemoteCallbackList

1:RemoteCallbackList是专门用于删除跨进程listener的接口,它是一个泛型,支持管理任意的AIDL接口。

为什么要使用它呢?因为在服务端进行注册客户端发送过来的listener时,Binder会把客户端传递过来的对象重新转化并生成一个新的对象,因为对象是不能进行跨进程直接传输的,对象的跨进程传世都是反序列化的过程,这就是为什么AIDL中自定义对象都必须实现Parcelable的原因

 2:使用RemoteCallbackList,我们无法像操作list一样去操作它,它并不是一个list,遍历的时候

      int n=mListener.beginBroadcast();
        for(int i=0;i<n;i++){
            IOnNewPersonArrivedListener l=mListener.getBroadcastItem(i);
            if (l!=null){
                try {
                    l.onServerCallback(book);//服务端通过这个向客户端发送消息
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        mListener.finishBroadcast();

      这样去进行,其中beginBroadcast和finishBroadcast必须要配对出现,哪怕是要获取RemoteCallbackList中的元素个数

    f.

 

推荐阅读