Android进程间通信

Android进程间通信

进程间通信

Android开发有时候会遇到多进程的场景,比如之前与Unity配合开发时,Unity非常占内存,并且Unity退出时会将整个进程杀死,为了避免退出Unity时整个应用程序退出,也为了避免Unity太吃内存导致OOM,就需要将其单独放在一个进程中。有时候需要调用别的APP提供的方法,这样也是多进程的场景。由于进程之前内存互相隔离,导致变量无法互相访问,因此必须妥善处理这类问题,这就需要跨进程通信技术。

进程间通信(IPC)主要有以下几种技术:Intent和Bundle、AIDL、Messenger、ContentProvider 、Socket。

Intent和Bundle支持跨进程传输数据,用法与同进程时使用完全一致,无需多讲。ContentProvider 提供了与其他应用共享数据的方法,本身就支持跨进程,用法与同进程场景下没有区别,具体用法可以参考我之前的文章。Socket采用本地网络通信的方式完成跨进程通信,需要声明网络权限,使用ServerSocket和Socket 两个类来实现,用法比较简单,具体可以参考这篇文章。下边着重讲一下AIDL和Messenger。

AIDL

简单使用

使用步骤如下:

  1. 新建ADIL文件,ADIL文件中会定义一个接口,并在接口中定义需要在服务端运行的方法。

  2. 新建一个Service,这个Service就是进程间通信的服务端。Service中需要有一个内部类实现AIDL文件中定义的接口的Stub类,Stub类是抽象类,同时也是Binder的子类,Stub类时由编译器在构建时生成的。这个内部类需要具体实现AIDL中接口里定义的方法。最后需要在Service的onBind方法中将这个内部类的对象返回。

  3. 客户端使用时,需要以bindService的方式启动服务端Service,并在ServiceConnectiononServiceConnected方法中获取到服务端返回的Binder,获取服务端返回的Binder时,需要用到Stub的asInterface方法。获取到Binder之后,就可以调用在AIDL中定义的方法了。

下边是具体的步骤示例:

第一步,先创建AIDL文件夹,推荐将AIDL文件夹放在main目录的根目录,与java文件夹同级。在文件夹中创建AIDL文件,Android Studio会自动创建一个默认方法basicTypes,这个方法是示例方法,介绍了AIDL中支持的基本数据类型,也就是int、long、boolean、float、double、char、String。AIDL中还支持一些其他类型,包括:CharSequence、ArrayList(每个元素都必须是AIDL支持的类型)、HashMap(key和value必须是AIDL支持的类型)、Parcelable、AIDL接口(也就是一个AIDL中可以使用其他的AIDL接口)。需要特别注意的是AIDL中用到的Parcelable和AIDL接口,必须手动import进来,无论他们是否和当前的AIDL文件在同一个包内。并且自定义的Parcelable类也必须新建一个同名的AIDL文件,并在AIDL文件中声明它为Parcelable类型

// IBookManager.aidl
package com.example.testaidl;

// Declare any non-default types here with import statements
// 这里用到了Book类,所以必须显式的import进来,即使在一个包内。
import com.example.testaidl.Book;
interface IBookManager {
	// 定义了一个getBookList的方法,虽然此处类型是List,但是实际返回的将是ArrayList。
    // List泛型类型必须满足AIDL对类型的要求。
    List<Book> getBookList();
   	// 定义了一个添加方法
    void addBook(in Book book);
}
复制代码

java包内的Book实体类,必须实现Parcelable

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

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }
    //省略Parcelable所需的方法,实际使用时可以用Android Parcelable Code Generator这个AS插件生成
}
复制代码

由于AIDL中用到了Book类,所以必须新建一个Book.aidl文件,并在AIDL文件中声明它为Parcelable类型,如下:

package com.example.testaidl;
parcelable Book;
复制代码

AIDL文件就创建完成了,在AS中构建一下项目,就会在app\build\generated\aidl_source_output_dir中生成一个java文件,如下图:

AIDL1.jpg

生成java文件源码如下:

package com.example.testaidl;
public interface IBookManager extends android.os.IInterface
{
  /** Default implementation for IBookManager. */
  public static class Default implements com.example.testaidl.IBookManager
  {
      //省略
  }
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.example.testaidl.IBookManager
  {
      //省略
  }
  public java.util.List<com.example.testaidl.Book> getBookList() throws android.os.RemoteException;
  public void addBook(com.example.testaidl.Book book) throws android.os.RemoteException;
}
复制代码

可以看到,这个java文件也是一个接口,接口中的方法与AIDL中的方法完全一致。同时还生成了一个抽象类Stub,在后续步骤中将会继承这个Stub。

第二步,创建代表服务端的Service,并在Service中创建一个匿名内部类,匿名内部内继承上一步中的Stub,并在Service的onBind方法中将这个内部类的对象返回。代码如下:

class AIDLServerService : Service() {
    private val mBookList = CopyOnWriteArrayList<Book>()
    override fun onBind(intent: Intent): IBinder {
        Log.i("zx", "onBind运行线程是" + Thread.currentThread().name)
        return binder
    }

    //实现Stub
    private val binder: Binder = object : IBookManager.Stub() {
        override fun getBookList(): List<Book> {
            Log.i("zx", "getBookList运行线程是" + Thread.currentThread().name)
            return mBookList
        }

        override fun addBook(book: Book?) {
            Log.i("zx", "addBook运行线程是" + Thread.currentThread().name)
            Log.i("zx", "收到了客户端发过来的book," + book?.bookName)
            mBookList.add(book)
        }
    }
}
复制代码

在这一步,我们要具体实现在AIDL中申明的方法,这些方法运行在服务端,会提供给客户端调用,从而事件跨进程调用方法。上述代码可以看出,创建了一个匿名类,这个匿名类继承了Stub,由于Stub是抽象类,所以这里必须实现Stub的抽象方法,而Stub的抽象方法也就是AIDL接口中声明的抽象方法。同时Stub也是Binder的子类,所以这个匿名类也是Binder的子类,可以在Service的onBind中将其返回,这样客户端就可以拿到这个Binder,从而跨进程调用服务端的方法。

上述代码中,addBook用于添加书籍,书籍保存在mBookList中,getBookList用于返回所有的书籍。如果服务端Service与客户端是同一个APP,需要将服务端Service指定为别的进程,这样才是跨进程方法调用,这种方式与跨APP之间调用,完全一致。

第三步,客户端调用,需要启动服务端Service,并在ServiceConnectiononServiceConnected方法中获取到服务端返回的Binder,然后用Stub的asInterface方法将Binder转为构建时生成的java文件中的接口(IBookManager),然后就可以调用接口的方法,就像调用本地方法一样。

override fun onCreate(savedInstanceState: Bundle?) {
	super.onCreate(savedInstanceState)
	setContentView(R.layout.activity_main)
    //启动服务端的Service
	val intent = Intent(this, AIDLServerService::class.java)
	bindService(intent, serviceConnection, BIND_AUTO_CREATE)
}
    
var serviceConnection: ServiceConnection = object : ServiceConnection {
    override fun onServiceConnected(name: ComponentName, service: IBinder) {
        Log.i("zx","onServiceConnected运行线程是" + Thread.currentThread().name)
        iBookManager = IBookManager.Stub.asInterface(service)
        try {
            iBookManager.addBook(Book(1, "PHP从入门到精通"))
            val list: List<Book> = iBookManager.getBookList()
            Log.i("zx", list[0].bookName)
        } catch (e: RemoteException) {
            e.printStackTrace()
        }
    }

    override fun onServiceDisconnected(name: ComponentName) {}
}
复制代码

运行一下,最终输出:

AIDL_log1.jpg

AIDL_log2.jpg

由日志可以看出,服务端的addBook和getBookList不是运行在主线程中,实际是运行在Binder线程池中,所以如果存在多个客户端同时调用服务端方法时,这两个方法会出现多线程同时访问时的线程安全问题,所以这里使用CopyOnWriteArrayList,CopyOnWriteArrayList是ArrayList的线程安全版本,支持并发读写,可以避免线程安全问题。

AIDL简单的使用到此就结束了,如果不考虑一些理论和原理,只考虑使用,AIDL还是挺简单的,只需要三步走就行了。

双向通信

目前仅仅实现了客户端远程调用了服务端的方法,并没有实现双向通信,也就是服务端调用客户端方法。如果服务端数据改变后要主动通知客户端,那就需要服务端与客户端通信,接下来就实现这个功能。

与客户端调用服务端一样,也需要先定义AIDL文件,在AIDL中声明服务端Service需要远程调用客户端Activity的方法,此时Activity更像是服务端(远程方法执行者),Service更像是客户端(远程方法调用者)。

// IOnNewBookArrivedListener.aidl
package com.example.testaidl;
import com.example.testaidl.Book;
// Declare any non-default types here with import statements

interface IOnNewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
复制代码

然后在Activity中继承IOnNewBookArrivedListener.stub,并实现方法。

val listener = object : IOnNewBookArrivedListener.Stub() {
    override fun onNewBookArrived(newBook: Book?) {
        Log.i("zx", "客户端收到了数据," + newBook?.bookId)
    }
}
复制代码

客户端Activity中创建了IOnNewBookArrivedListener.stub实现类的对象,这个对象也是Binder类型的,现在只需要把这个Binder对象传给服务端,服务端就可以在数据改变时远程调用Binder的方法,从而实现服务端主动通知客户端。那如何将这个Binder对象传给服务端呢?还是会用到AIDL。就像之前Activity用AIDL将Book对象传给服务端一样,再定义一个AIDL方法,然后将listener(Binder)传给服务端Service。

// IBookManager.aidl
package com.example.testaidl;

// Declare any non-default types here with import statements
import com.example.testaidl.Book;
import com.example.testaidl.IOnNewBookArrivedListener;
interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}
复制代码

在之前的AIDL中加了2个方法registerListener和unregisterListener,通过registerListener将listener传给服务端,通过unregisterListener告诉服务端解除监听,也就是服务端数据改变后不必再通知客户端。需要在Service中实现新增的这2个方法。

class AIDLServerService : Service() {
    private val mBookList = CopyOnWriteArrayList<Book>()

    // 这里使用RemoteCallbackList,如果使用ArrayList,会造成无法解除监听。
    // 因为客户端两次调用(registerListener和unregisterListener)传过来的同一个对象,经过Binder底层转换后,
    // 在服务端并不是同一个对象,所以如果使用ArrayList,就无法如果使用ArrayList.remove对应的监听器。
    private val mListenerList = RemoteCallbackList<IOnNewBookArrivedListener>()
    private val mIsServiceDestroyed = AtomicBoolean(false)

    override fun onCreate() {
        super.onCreate()
        //开一个线程,每2秒修改一次数据,并通知客户端
        thread {
            while (!mIsServiceDestroyed.get()) {
                try {
                    Thread.sleep(2000)
                } catch (e: InterruptedException) {
                    e.printStackTrace()
                }
                val bookId: Int = mBookList.size + 1
                val newBook = Book(bookId, "new book#$bookId")
                try {
                    notifyClientDataChanged(newBook)
                } catch (e: RemoteException) {
                    e.printStackTrace()
                }
            }
        }
    }

    override fun onBind(intent: Intent): IBinder {
        Log.i("zx", "onBind运行线程是" + Thread.currentThread().name)
        return binder
    }

    //实现Stub
    private val binder: Binder = object : IBookManager.Stub() {
        override fun getBookList(): List<Book> {
            Log.i("zx", "getBookList运行线程是" + Thread.currentThread().name)
            return mBookList
        }

        override fun addBook(book: Book?) {
            Log.i("zx", "addBook运行线程是" + Thread.currentThread().name)
            Log.i("zx", "收到了客户端发过来的book," + book?.bookName)
            mBookList.add(book)
        }

        override fun registerListener(listener: IOnNewBookArrivedListener?) {
            mListenerList.register(listener)
        }

        override fun unregisterListener(listener: IOnNewBookArrivedListener?) {
            val success = mListenerList.unregister(listener)
            Log.i("zx", "unregister是否成功$success")
            val size = mListenerList.beginBroadcast()
            mListenerList.finishBroadcast()
            Log.i("zx", "unregisterListener后,size = $size")
        }
    }

    private fun notifyClientDataChanged(book: Book) {
        mBookList.add(book)
        //beginBroadcast与finishBroadcast要配对使用
        val mListenerListSize = mListenerList.beginBroadcast()
        for (i in 0 until mListenerListSize) {
            val listener = mListenerList.getBroadcastItem(i)
            if (listener != null) {
                try {
                    //调用客户端方法,通知客户端。
                    listener.onNewBookArrived(book)
                } catch (e: RemoteException) {
                    e.printStackTrace()
                }
            }
        }
        mListenerList.finishBroadcast()
    }

    override fun onDestroy() {
        mIsServiceDestroyed.set(true)
        super.onDestroy()
    }
}
复制代码

在Service中新增了一个RemoteCallbackList,用于保存客户端传过来的listener。这里不能使用ArrayList,因为如果使用ArrayList会存在无法解除监听的问题。导致这个问题的原因是:**客户端registerListener和unregisterListener时传的是同一个listener,但是跨进程传递时存在序列化反序列化的过程,客户端传过来的同一个对象,服务端反序列化后创建的是2个不同的对象。也就无法使用ArrayList.remove()移除掉对应的监听了。**而虽然多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象有一个共同点,那就是它们底层的Binder对彖是同一个,利用这个特性,就可以实现上面我们无法实现的功能。RemoteCallbackList就是依据这个原理去实现的。RemoteCallbackList用ArrayMap存储数据,key是Binder对象,value就是服务端新生成的对象。当客户端unregisterListener的时候,我们只要遍历服务端所有的listener,找出那个和客户端传过来的listener具有相同Binder对象的服务端listener并把它删掉即可,这就是RemoteCallbackList的unregister()方法为我们做的事情。同时当客户端进程终止后,它能够自动移除客户端所注册的listener。并且RemoteCallbackList内部自动实现了线程同步的功能。所以这里使用RemoteCallbackList而不是用ArrayList。需要注意的是RemoteCallbackList使用时,beginBroadcast与finishBroadcast要配对使用,即使只是获取RemoteCallbackList中的元素数量。

上边的代码中,在Service的onCreate中创建了一个线程,每个2秒新增一条数据,并通知客户端。通知客户端时需要先获取listener,使用getBroadcastItem(index)获取,然后就可以调用了。

客户端Activity中需要调用上边的registerListener将listener传递给服务端(注册监听),同时在Activity销毁时调用unregisterListener告诉服务端数据改变时不必再通知(解除监听)。完整代码如下:

class MainActivity : AppCompatActivity() {
    private lateinit var iBookManager: IBookManager
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val intent = Intent(this, AIDLServerService::class.java)
        bindService(intent, serviceConnection, BIND_AUTO_CREATE)
    }

    var serviceConnection: ServiceConnection = object : ServiceConnection {
        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            Log.i("zx", "onServiceConnected运行线程是" + Thread.currentThread().name)
            iBookManager = IBookManager.Stub.asInterface(service)
            try {
                iBookManager.addBook(Book(1, "PHP从入门到精通"))
                // 将listener传递给服务端(注册监听)
                iBookManager.registerListener(listener)
                val list: List<Book> = iBookManager.getBookList()
                Log.i("zx", list[0].bookName)
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
        }

        override fun onServiceDisconnected(name: ComponentName) {}
    }

    val listener = object : IOnNewBookArrivedListener.Stub() {
        override fun onNewBookArrived(newBook: Book?) {
            Log.i("zx", "onNewBookArrived运行线程是" + Thread.currentThread().name)
            Log.i("zx", "客户端收到了数据," + newBook?.bookId)
        }
    }

    override fun onDestroy() {
        //告诉服务端数据改变时不必再通知(解除监听)
        if (iBookManager.asBinder().isBinderAlive) {
            try {
                iBookManager.unregisterListener(listener)
            } catch (e: RemoteException) {
                Log.e("zx", e.toString())
                e.printStackTrace()
            }
        }
        unbindService(serviceConnection)
        super.onDestroy()
    }
}
复制代码

最终输出

AIDL_log3.jpg

Activity返回之后,打印日志如下:

AIDL_log4.jpg

日志显示,确实可以解除监听。

由日志可以看出onNewBookArrived运行在Binder线程池中,结合之前的addBook和getBookList方法日志,可以总结出:远程方法(AIDL接口中抽象方法的实际实现)总是执行在Binder线程池中。就像网络请求一样,在获取到远程方法的返回结果之前,调用者会被一直阻塞,所以调用者最好是在非UI线程,避免阻塞界面

权限验证

目前的服务端Service,任意一个APP的Activity都能bind并且调用远程方法,有时候我们需要做出限制,只有符合我们要求的客户端才能调用服务端Service中的方法。一般在如下两个位置验证权限:

  1. Service的onBind()中,如果onBind返回null,那么客户端将无法拿到服务端的Binder,也就无法远程调用服务端方法,利用这个规则,就可以在onBind()中验证权限。

  2. Stub实现类的onTransact()中,AIDL文件生成的Stub类的onTransact()方法会根据客户端传过来的代表方法的code查找服务端的远程方法,如果没找到对应的远程方法,就会返回false,服务端将什么都不执行。所以我们如果在onTransact()中返回false,就等于拒绝了客户端的一切远程调用,一样可以达到验证权限的目的。

权限验证一般有如下两种方式:

  1. 验证客户端permission,通过checkCallingOrSelfPermission()可以确定 IPC的调用进程是否已被授予特定权限。这种方式需要我们在服务端的manifest文件中定义相关权限,然后校验客户端是否有这个权限(客户端manifest中是否有声明这个权限)。

  2. 验证客户端的包名,通过getCallingUid()可以获取客户端进程的 Linux uid,然后使用getPackageManager().getPackagesForUid()可以根据uid获取到客户端包名,得到包名之后就可以校验包名是否符合我们的规则,从而验证权限。

示例如下: 首先在服务端manifest中定义权限,并service中限定客户端必须有权限才能调用。这里protectionLevel是signature,客户端需要与服务端拥有相同签名才能通过权限验证,这种情况在一个公司的多个产品上很常见,如果不需要验证APP的签名,可以在定义permission时设置android:protectionLevel="normal"

<permission
    android:name="com.example.testaidl.permission.SERVICE"
    android:protectionLevel="signature" />

<service
         android:name=".AIDLServerService"
         android:enabled="true"
         android:exported="true"
         android:permission="com.example.testaidl.permission.SERVICE"
         android:process=":remote" />

复制代码

然后在Service的onBind()中和Stub实现类的onTransact()中采用上边的2种方式验证权限。

override fun onBind(intent: Intent): IBinder? {
    Log.i("zx", "onBind运行线程是" + Thread.currentThread().name)
    return if (checkPermission()) {
        binder
    } else null
}

//实现Stub
private val binder: Binder = object : IBookManager.Stub() {
	//省略代码
    
    // onTransact()方法会根据客户端传过来的代表方法的code查找服务端的远程方法,如果没找到对应的远程方法,
    // 就会返回false,服务端将什么都不执行。所以我们如果在onTransact()中返回false,
    // 就等于拒绝了客户端的一切远程调用,一样可以达到验证权限的目的
    override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
        return if (checkPackage()) {
            super.onTransact(code, data, reply, flags)
        } else false
    }
}

private fun checkPermission(): Boolean {
    //客户端APP的manifest如果没有声明com.example.testaidl.permission.SERVICE权限,权限验证不通过
    val check = checkCallingOrSelfPermission("com.example.testaidl.permission.SERVICE")
    return check != PackageManager.PERMISSION_DENIED
}

private fun checkPackage(): Boolean {
    val packageName: String
    val packages = packageManager.getPackagesForUid(
        Binder.getCallingUid()
    )
    return if (packages != null && packages.isNotEmpty()) {
        packageName = packages[0]
        Log.d("zx", "调用者包名: $packageName")
        //包名不符合规则,就拒绝
        packageName.startsWith("com.example")
    } else false

}
复制代码

最后,客户端使用时,需要在manifest中声明权限。

<uses-permission android:name="com.example.testaidl.permission.SERVICE" />
复制代码

同时包名也要符合设定的规则,并且客户端APP的签名也必须与服务端APP签名一致,全部符合上述条件时,才能通过权限验证。

理解Binder

Binder是一种Android进程间通信的机制,采用C/S架构。Binder框架定义了四个角色:Client,Server,ServiceManager以及Binder驱动。这四个角色的关系和互联网类似:Server是服务器,Client是客户端,ServiceManager是域名服务器(DNS),驱动是路由器。

Client(客户端)不用多讲,是跨进程调用的调用方,如上边例子中的Activity,Server(服务端)是跨进程调用过程的被调用方,如上边例子中的Service。

和DNS管理域名和IP的映射关系类似,ServiceManager的作用是管理Binder与Binder的引用,服务端将Binder注册到ServiceManger后,客户端通过名称请求ServiceManager,ServiceManager将字符形式的Binder名字转化成Client中对该Binder的引用,获取到Binder的引用后,Client就可以调用远程方法了。

Binder驱动运行在内核层面,类似于路由器,负责数据的转换和发送。前边讲的客户端获取到Binder的引用,这个引用与服务端Binder的引用并不是同一个,因为它们是两个进程,资源不共享,所以肯定不是同一个对象,不是同一个对象,为什么在客户端调用之后远程方法却能执行呢?解决这个问题的就是Binder驱动。Binder驱动将我们的引用做了一次映射,可以根据我们的引用对象找到对应的远程进程。客户端要调用远程对象函数时,只需把数据写入到Parcel,再调用所持有的Binder引用的transact()函数,transact函数执行过程中会把参数、标识符(标记远程对象及其函数)等数据放入到Client的共享内存,Binder驱动从Client的共享内存中读取数据,根据这些数据找到对应的远程进程的共享内存,把数据拷贝到远程进程的共享内存中,并通知远程进程执行onTransact()函数。远程进程Binder对象执行完成后,将结果写入自己的共享内存中,Binder驱动再将远程进程的共享内存数据拷贝到客户端的共享内存,并唤醒客户端线程。上述这些,由客户端Binder引用映射到服务端Binder、数据Parcel与解析、共享内存拷贝、唤醒线程等等,这些都是由Binder驱动在底层完成的。我们在应用层使用时,无需关注这些底层细节,只要定义好AIDL接口方法并在服务端实现,然后在客户端获取Binder引用并远程调用就行了。

Messenger

Messenger可以在不同进程中传递 Message对象,在 Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。 Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。

类似于AIDL,也需要先创建一个 Service当做服务端来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个 Messenger对象,然后在 Service的 on Bind中返回这个Messenger对象底层的 Binder。代码如下:

public class MessengerService extends Service {
    public MessengerService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        //返回Messenger对象底层的 Binder
        return messenger.getBinder();
    }


    @SuppressLint("HandlerLeak")
    Handler messengerHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.i("MessengerService", "服务端接收到的消息" + msg.getData().get("info").toString());
            //通过replyTo参数就可以回应客户端
            Messenger messenger = msg.replyTo;
            Message replyMessage = new Message();
            Bundle bundle = new Bundle();
            bundle.putString("response", "这是来自于服务端返回的消息");
            replyMessage.setData(bundle);
            try {
                //发送返回消息给客户端
                messenger.send(replyMessage);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    };

    //根据Handler创建Messenger
    Messenger messenger = new Messenger(messengerHandler);
}
复制代码

客户端要以bindService的方式启动服务端的 Service,并在ServiceConnection的onServiceConnected()中用服务端返回的 Binder对象创建一个 Messenger,通过这个 Messenger就可以向服务端发送消息了。如果需要服务端能够回应客户端,就和服务端一样,创建一个Handler并用它创建一个新的 Messenger,然后把这个 Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个 replyTo参数就可以回应客户端。代码如下:

public class MessengerActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        bindService(new Intent(this, MessengerService.class), serviceConnection, Service.BIND_AUTO_CREATE);
    }

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Messenger messenger = new Messenger(service);
            Message message = new Message();
            Bundle bundle = new Bundle();
            bundle.putString("info", "这是来自于客户端的消息");
            message.setData(bundle);
            // 把Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端
            message.replyTo = responseMessenger;
            try {
                messenger.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };


    @SuppressLint("HandlerLeak")
    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.i("MessengerActivity", "客户端收到了服务端的响应," + msg.getData().get("response").toString());
        }
    };
    //用来接收服务端返回的消息
    Messenger responseMessenger = new Messenger(handler);

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(serviceConnection);
    }
}
复制代码

运行后输出

Messenger_log2.jpg

Messenger_log1.jpg

由示例中可以看出,Messenger只能以Message的形式传递数据,不支持RPC(远程方法调用)。由于数据放在Bundle中进行传输,因此只能传输 Bundle支持的数据类型。相比AIDL,还是有局限性,但好在使用简单。

分类:
Android
标签: