标签搜索

AIDL和Messager

Pop.Kite
2021-09-04 / 0 评论 / 289 阅读 / 正在检测是否收录...

AIDL是Android中IPC(Inter-Process Communication,进程通信)方式中的一种,AIDL是Android Interface definition language的缩写。对于小白来说,AIDL的作用是让你可以在自己的APP里绑定一个其他APP的service,这样你的APP可以和其他APP交互。

实践

AIDL用于进程之间通信,我们可以在Android Studio中建立两个APP实现两个进程。当然我们也可以在AndroidManifest.xml中声明任一四大组件运行在独立进程中,形如下文,声明了一个运行在名为AKMessagerService进程中的服务:

<service
    android:name="com.akotiner.service.MyService"
    android:enabled="true"
    android:exported="true"
    android:process=":AKMessagerService"/>

普适度的原因,本文仅使用第一种方式进行实践,我们使用AIDL经典案例中的图书馆里作为本文的DEMO。

服务端

首先我们新建一个项目作为服务端,然后新建aidl文件夹用于存储aidl文件,项目的目录形如下图:
[图片]
需要注意的是aidl和java内的文件的包名需要一致。在aidl目录下,我们可以看到有两个aidl文,其中Book是自定义的类,IBookManger是自定义接口,个人认为aidl可以根据上述分两类。

类的传递

AIDL可以传递默认类型有Int、Float等基本数据类型以及String、含有基本数据类型的Map和List。如果我们想要传递自定义类型,如上图中的Book类,我们需要在java目录下定义一个实现Parcelable接口的Book类,并在aidl目录对应的包下声明该类,Code如下:

  • 在java文件夹实现自定义类型:

    package com.akotiner.aidlserver

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

    class Book(val bookId: Int, val bookName: String?) : Parcelable {

       constructor(parcel: Parcel) : this(
           parcel.readInt(),
           parcel.readString()
       )

    override fun toString(): String {

       return "Book(bookId=$bookId, bookName='$bookName')"

    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {

       parcel.writeInt(bookId)
       parcel.writeString(bookName)

    }

    override fun describeContents(): Int {

       return 0

    }

    companion object CREATOR : Parcelable.Creator {

       override fun createFromParcel(parcel: Parcel): Book {
           return Book(parcel)
       }

    override fun newArray(size: Int): Array<Book?> {

       return arrayOfNulls(size)
       }
       }

    }

  • 在aidl中声明自定义类型:

    // Book.aidl
    package com.akotiner.aidlserver;

    // Declare any non-default types here with import statements

    parcelable Book;

接口传递

aidl通信的本质是Binder的传递,通过获取远程服务提供的代理Binder对象,我们可以进行跨进程通信。

  • 在IBookManager.aidl定义方法:

    // IBookManager.aidl
    package com.akotiner.aidlserver;

    // Declare any non-default types here with import statements
    import com.akotiner.aidlserver.Book;
    interface IBookManager {

       //in表示数据的流向是流入,还有out表示流出。
       String addBook(in Book book);
       String deleteBook(in Book book);
    
       //还有关键字oneway,表示异步调用
       //如下述方法,表示删除一本书,但是不必等待返回值,而是仅负责将book这个参数传递下去
       //oneway void deleteBook(in Book book);
    

    }

  • build项目后会生成IBookManaer.stub文件,该文件继承了系统的Binder类并实现了我们自定义的接口。 在Java中实现IBookManaer.aidl的方法:
  package com.akotiner.aidlserver
 //构造方法中传入一个HashMap用于存储书籍
 class BookManager(private val bookList: HashMap<Int, String>) : IBookManager.Stub() {
     override fun addBook(book: Book?): String {
         if (book != null) {
             return if (bookList.containsKey(book.bookId)) {
                 "Has already added"
             } else {
                 bookList[book.bookId] = book.bookName.toString()
                 "Success"
             }
         }
         return "Invalid Book"
     }

     override fun deleteBook(book: Book?): String {
         if (book != null) {
             if (bookList.containsKey(book.bookId) && bookList.containsValue(book.bookName)) {
                 bookList.remove(book.bookId, book.bookName)
                 return "Success"
             }
         }
         return "Invalid Book"
     }

 }

远程服务

Service是可以通过Intent进行跨进程绑定,然后返回Binder 对象,通过客户端对Binder对象的操作实现跨进程操作。在这里我们建立一个服务用于被绑定:

package com.akotiner.aidlserver

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log

class BookService : Service() {
    private val bookList = HashMap<Int, String>()
    private val bookManagerBinder = BookManager(bookList)

    override fun onCreate() {
        Log.e(TAG, "onCreate")
        super.onCreate()
    }

    override fun onBind(intent: Intent): IBinder {
        Log.e(TAG, "onBind")
        return bookManagerBinder
    }

    companion object {
        private val TAG = "BookService"
    }
}

需要注意的是,Android新版本不允许通过绑定启动服务,我们需要在Acticity中手动启动服务后才能被绑定。同时我们需要设置Service的action用于隐式的绑定,在AndroidManifest,xml中声明服务的部分内容如下即可(服务的名字和aciton可以不同):

<service
    android:name=".BookService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.AIDL_SERVER"/>
    </intent-filter>
</service>

以上,服务端的准备就完成了。我们可以将该app安装至测试机器可以看到log打印以下内容,说明服务启动成功了:

2022-02-09 15:46:13.599 14879-14879/com.akotiner.aidlserver E/BookService: onCreate

客户端

为了添加书籍,客户端APP的界面加了两个按钮和两个文本输入框。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout     xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<EditText
    android:layout_marginTop="200dp"
    android:id="@+id/book_id"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hint="@string/book_id"
    android:inputType="number"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"/>

<EditText
    android:id="@+id/book_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:hint="@string/book_name"
    android:inputType="text"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    android:layout_marginTop="50dp"
    app:layout_constraintTop_toBottomOf="@id/book_id"/>

<Button
    android:id="@+id/add"
    android:layout_marginTop="50dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/add_book"
    app:layout_constraintTop_toBottomOf="@id/book_name"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent" />

<Button
    android:id="@+id/del"
    android:layout_marginTop="50dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/delete_book"
    app:layout_constraintTop_toBottomOf="@id/add"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
引入远程aidl文件
为了调用远程的aidl文件,我们可以将服务端的代码打成jar包,也可以将服务端的aidl文件夹复制到客户端(请注意包名)。
然后我们Build一下客户端的代码,接下来进行服务绑定并获取binder对象就可以进行跨进程通信了:

package com.akotiner.aidlclient
class MainActivity : AppCompatActivity(), View.OnClickListener {

    private lateinit var bookService: IBookManager
    private var isBoundService = false
    private lateinit var bookIdText: EditText
    private lateinit var bookNameText: EditText
    private lateinit var addBook: Button
    private lateinit var delBook: Button

    private val serviceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            bookService = IBookManager.Stub.asInterface(service)
            isBoundService = true
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            isBoundService = false
        }

    }

    private val mIntent = Intent()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        bookIdText = findViewById(R.id.book_id)
        bookNameText = findViewById(R.id.book_name)
        addBook = findViewById(R.id.add)
        delBook = findViewById(R.id.del)
        addBook.setOnClickListener(this)
    }

    override fun onResume() {
        super.onResume()
        mIntent.action = "android.intent.action.AIDL_SERVER"
        mIntent.`package` = "com.akotiner.aidlserver"
        //隐式绑定远程服务
        bindService(mIntent, serviceConnection, BIND_AUTO_CREATE)
    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.add -> {
                val res = bookService.addBook(
                    Book(
                        bookIdText.text.toString().toInt(),
                        bookNameText.text.toString()
                    )
                )
                Toast.makeText(this, res, Toast.LENGTH_SHORT).show()
            }
            R.id.del -> {
                Toast.makeText(this, "Waiting", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

上述只实现了一个添加书籍的方法,添加有效内容后会弹出Success的Toast。

源代码

AIDL Demo

理论

也可以参考这篇博客Android中AIDL的使用详解

Messenger

Messenger是对于AIDL的封装,相对于AIDL更轻量、简洁。但是不支持RPC和同步执行任务,所以使用AIDL还是Messenger要因地制宜。

实践

相对于AIDL来说Messenger的使用就简单很多了。

建立跨进程服务

这里我们使用下面的方式定义一个跨进程的服务

<service
    android:name="com.akotiner.service.MyService"
    android:enabled="true"
    android:exported="true"
    android:process=":AKMessagerService"/>

服务的代码如下:

package com.akotiner.service
import android.app.Service
import android.content.Intent
import android.os.*
import android.util.Log

class MyService : Service() {

    //定义一个Handler用于构建Messenger对象
    internal class ReceiveHandler : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            Log.e(TAG, "ReceiveHandler: ${msg.what}")
            val message = Message.obtain()
            message.what = 1
            //从Client传递来的Messenger对象,其中包含了一个Handler,发送消息实现异步
            msg.replyTo.send(msg)
        }
   }

    //建立Messenger对象
    private val myMessenger: Messenger = Messenger(ReceiveHandler())

    //返回Binder
    override fun onBind(intent: Intent): IBinder {
        return myMessenger.binder
    }

    companion object {
        private const val TAG = "MyService"
    }
}

绑定服务

老生常谈,没什么好说的绑就完了:

package com.akotiner.akmessager

import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.*
import android.util.Log
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.akotiner.service.MyService

class MainActivity : AppCompatActivity() {
    private lateinit var mButton: Button
    private lateinit var mSend: Button
    private lateinit var mMessenger: Messenger
    private var isBound = false

    private val mServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            mMessenger = Messenger(service)
            isBound = true
        }

        override fun onServiceDisconnected(name: ComponentName?) {
            isBound = false
        }
    }

    //定义Handler用于处理远程服务的回调
    internal class CallBackHandler : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            Log.e(TAG, "CallBackHandler: ${msg.what}")
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mButton = findViewById(R.id.button)
        mSend = findViewById(R.id.send)
        //这个按钮用于绑定服务
        mButton.setOnClickListener {
            if (!isBound) {
                bindMyService()
            } else {
                Toast.makeText(this, "Connection has already established", Toast.LENGTH_SHORT)
                    .show()
            }
        }
        //这个按钮用于发送消息实现跨进程通信
        mSend.setOnClickListener {
            //创建一个Messenger对象,用于传递Handler
            val messenger = Messenger(CallBackHandler())
            val message = Message.obtain()
            //将Messenger对象作为参数传递给Server,相当于我们把Handler也传递了过去
            message.replyTo = messenger
            message.what = 1
            //发送消息
            mMessenger.send(message)
        }
    }    

    private fun bindMyService() {
        bindService(
            Intent(applicationContext, MyService::class.java),
            mServiceConnection,
            BIND_AUTO_CREATE
        )
    }

    companion object {
        private const val TAG = "MainActivity"
    }
}

运行程序点击按钮,通过log我们可以发现实现可跨进程的消息传递和异步的回传

源代码

Messager Demo

Binder

此部分暂略

参考博客
Android中的IPC方式

0

评论 (0)

取消