IPC简介
进程间通信(InterProcess Communication缩写IPC)是指在不同进程之间传播或交换信息。进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元。
IPC不是Android中所独有的,任何一个操作系统都需要有相应的IPC机制。在Android系统中一个进程会对应一个虚拟机实例,不同的虚拟机在内存分配上有不同的地址空间,所以:只有在多进程的环境下才需要考虑使用IPC进行通讯。Android中的应用程序可以为一个进程,也可以配置成多进程,每个进程都在自己独立的空间中运行
在Android中有两个典型的场景:第一种是应用自身需要开启多个进程,比如多模块应用,由于系统设置了应用获取的最大内存限制,为了获得更多的内存空间将不同的模块放在不同的线程中。另外一种情况就是获取其他应用里的数据,典型的案例就是获取通讯录和短信。
Android中的多进程模式
Android默认是运行在默认名为包名的进程中,除非特别指定,所有的组件都运行在默认进程中。可以通过修改AndroidManifest文件,在 application 标签下添加 android:process 属性可以修改Android默认的进程名字:
<application
android:name=".MainApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:process="com.aa.bbb"
android:theme="@style/Theme.Sunflower">
</application>
在Android中只有给四大组件(Activity Service Broadcast ContentProvider)设置android:process属性这一种使用多进程的方法。
<service
android:name=".messenger.MessengerService"
android:process=":services" />
上面的代码为MessengerService
指定了process属性,为应用增加了一个新的进程。当MessengerService
启动时,系统会为它创建单独的进程。使用:adb shell ps | grep [包名]查看进程信息:
注意上面中的XML代码,process
属性有两种设置方式,一种是如上文中":"开头后面接进程名,一种则是完整的进程名,例如android:process="com.packages.name.services"
。前者会在进程名前加上包名("com.packages.name:services"),且规定了进程为当前应用的私有进程,其他应用的组件不可以和它使用统一进程。后者则你写了什么,进程名就是什么,且为全局进程,其他应用也可以使用该进程。
虽然开启多进程的方法很简单,但是这种看似简单的操作稍有不深就会带来巨大的问题:
首先,多进程会造成Application的多次创建:当一个组件需要运行在新的进程中时,实际的创建过程就相当于又重新启动了一次应用,应用启动,必然会创建Application。而运行在不同进程中的组件,不仅属于不同的虚拟机,而且其Application也是不同的。
其次,多进程会导致静态成员和单例完全无效:由于不同进程都被分配了独立且不同的虚拟机,其在内存分配上有这不同的地址空间。这就会导致在着一个类的多个副本,各自修改互不影响。
再次,多进程模式下,线程的同步机制也会失效:因为不同的进程,其线程不所属同一内存,那么无论是对象锁还是类锁,锁本身都不是同一个了。
最后,多进程模式下SharedPreferences风险会增大:SharedPreferences底层是通过文件读写实现的,并发操作可能会造成问题。
总之:在不同进程中的组件,如果使用内存来通讯,都会存在隐患或者直接失败。这就是多进程带来的最主要的影响。
Android中的跨进程通讯(IPC)
上文对Android中的多进程做了一些介绍,接下来讲解一下多进程间通讯的方式:
利用Bundle
在Android开发中,我们通过Intent启动Activity、Service和Receiver都是可以通过Bundle传递参数的。它实现了Parcelable接口,并以键值对的方式保存数据。可以将其视为一个容器,其支持基本数据类型(string、int、boolean、byte、float、long、double)以及它们对应的数据。当需要传递对象或对象数组时,被传递的对象必须实现Serialiable或Parcelable接口。
接下来我们通过一个Activity(MainMessengerActivity)和一个Service(MessengerService)来看一下如何利用Bundle实现跨进程通讯。
首先,让二者运行在不同的进程中:
<activity
android:name=".messenger.MainMessengerActivity"/>
<service
android:name=".messenger.MessengerService"
android:process=":services" />
在配置文件指定Service的运行线程即可。
在通过 bindService(Intent, ServiceConnection,int)
为MainMessengerActivity
绑定服务时,为intent添加bundle:
val intent = Intent(this, MessengerService::class.java)
val bundle = Bundle()
bundle.putString("name", "Butler")
bundle.putInt("age", 28)
intent.putExtra("message", bundle)
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
在service中的onBind方法接受数据:
override fun onBind(intent: Intent?): IBinder? {
intent?.getBundleExtra("message")?.apply {
Log.e("Name is:", "${this.getString("name")}")
Log.e("age is:", "${this.getInt("age") }")
}
return mMessenger.binder
}
运行结果如下:
使用方法很简单,跟我们平时使用Bundle没有什么区别。
使用文件共享
通过共享文件,不同的进程可以使用读写文件的方式交换数据。在Android中,对文件的并发读写并没有什么特殊的要求。虽然这可能造成问题,但是却依然可以帮我们在不同进程间传递数据。我们创建一个新的Activty(FileActivity),并指定其进程android:process=":file"
在MainMessengerActivity中写文件并实现跳转:
findViewById<Button>(R.id.file).setOnClickListener {
val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
coroutineScope.launch {
val message = "name:Butler,age:28"
try {
val fileOutputStream = openFileOutput(FileName, MODE_PRIVATE)
fileOutputStream.write(message.toByteArray())
fileOutputStream.close()
} catch (e: Exception) {
print(e.message)
} finally {
startActivity(Intent(this@MainMessengerActivity, FileActivity::class.java))
}
}
}
在FileActivity读取文件内容:
coroutineScope.launch {
try {
val fileInputStream = openFileInput(FileName)
var n = 0
val sBuffer = StringBuffer()
while (n != -1) {
n = fileInputStream.read()
val by = n.toChar()
sBuffer.append(by)
}
Log.e("message:","$sBuffer")
} catch (e:Exception){
print(e.message)
} finally {
}
}
运行结果为:E/message:: name:Butler,age:28
注意,不要使用SharedPreferences去做跨进程通讯,原则上它不支持多进程。虽然它本质上也是一个文件,但是由于它在应用运行时会再内存中加载缓存,然而进程间是不能内存共享的,每个进程操作的SharedPreferences都会是一个单独的实例,这就会导致安全性问题,这个问题只能通过多进程间其它的通信方式或者是在确保不会同时操作SharedPreferences数据的前提下使用SharedPreferences来解决。
使用ContentProvider
ContentProvider提供一种应用管理其自身和其他应用所存储数据的能力,并提供与其他应用共享这些数据的方法。它会封装数据,并提供用于定义数据安全性的机制。无论你是否需要和其他应用分享数据,你都可以使用ContentProvider去访问这些数据,虽然当无需和其他应用共享数据时没必要使用它。系统预置了许多ContentProvider,比如通话记录,通讯录,信息等,只需要通过ContentProvider就可以拿到这些信息。ContentProvider以一个或多个表的形式将数据呈现给外部应用,这些表与关系型数据库中的表类似。行表示提供程序收集的某种类型数据的实例,行中的每一列表示为一个实例所收集的单个数据。
通常有两个典型的使用场景:一种是通过实现代码访问其他应用中的现有的ContentProvider用来获得数据;另一种是创建新的ContentProvider,与其他应用共享数据。
下面我们用一个最简单的例子,演示一下使用它进行跨进程通讯。为了方便演示,我们在同一个应用内进行:
首先创建一个ContentProvider(UserProvider):
class UserProvider : ContentProvider() {
private lateinit var appDatabase: AppDatabase
private var userDao: UserDao? = null
override fun onCreate(): Boolean {
appDatabase =
Room.databaseBuilder(context!!, AppDatabase::class.java, "database-provider").build()
userDao = appDatabase.userDao()
return true
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?
): Cursor? {
return userDao?.getAll()
}
override fun getType(uri: Uri): String? {
return ""
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val user = User(
System.currentTimeMillis().toInt(),
values?.getAsString("name"),
values?.getAsInteger("age")
)
Log.e("-------${user.firstName}", "------------${user.age}")
userDao?.insertAll(user)
return uri
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
//不展示
return 0
}
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?
): Int {
//不展示
return 0
}
}
代码很简单,就是一个使用Room的数据持久化,只做了insert和查询的逻辑处理,详细的Room代码可查看这里。接下来在配置文件里配置它,并知名其所在的进程。
<provider
android:name=".provider.UserProvider"
android:authorities="com.karl.butler.provider"
android:permission="com.karl.PROVIDER"
android:process=":provider" />
然后创建Activity(ProviderActivity),并让它运行在应用的默认进程里面
class ProviderActivity : AppCompatActivity() {
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_provider)
val textName = findViewById<EditText>(R.id.et_name)
val textAge = findViewById<EditText>(R.id.et_age)
val textView = findViewById<TextView>(R.id.tv)
findViewById<Button>(R.id.btn_save).setOnClickListener(){
if (textName.text != null && textAge.text != null) {
val uri = Uri.parse("content://com.karl.butler.provider")
val contentValue = ContentValues()
contentValue.put("name", "${textName.text}")
contentValue.put("age", textAge.text.toString().toInt())
contentResolver.insert(uri, contentValue)
}
}
findViewById<Button>(R.id.btn_find).setOnClickListener(){
textView.text = ""
val uri = Uri.parse("content://com.karl.butler.provider")
val query = contentResolver.query(uri, null, null, null)
var text = ""
while (query?.moveToNext()!!){
text += "姓名:${query.getString(1)} 年龄:${query.getInt(2)}\n"
}
textView.text = text
}
}
}
代码很简单:两个输入框分别输入姓名和年龄,一个保存按钮一个查询按钮以及一个展示所有User的Text。运行效果如下:
可以看到,处于两个不同进程中的Activity和Provider成功的实现了数据通讯。但是,由于设计原因,ContentProvider只支持增删改查,更像是一个跨进程的数据库。
使用Messenger
Messenger是跨进程通信的类,通过它可以再不同进程中传递Message。底层通过AIDL实现,可以理解为是官方为了怕我们使用AIDL太过于繁琐而提供的一种简单的方案。
它是使用也很简单,首先创建服务端Service(MessengerService),并设置它所在的进程android:process=":services"
:
class MessengerService : Service() {
private val mHandlerThread: HandlerThread = HandlerThread("服务端")
private lateinit var mMessenger: Messenger
private lateinit var mHandler: Handler
override fun onCreate() {
super.onCreate()
mHandlerThread.start()
mHandler = object : Handler(mHandlerThread.looper) {
override fun handleMessage(msg: Message) {
if (msg.what == 0) {
val obtain = Message.obtain(msg)
obtain.what = 1
obtain.arg2 = 2*obtain.arg1
Thread.sleep(2000L)
obtain.replyTo.send(obtain)
return
}
super.handleMessage(msg)
}
}
mMessenger = Messenger(mHandler)
}
}
代码很简单,接受客户端传来的Int值,延迟两秒后并返回它的倍数。
接下来在应用默认进程中实现一下客户端Activity (MainMessengerActivity):
class MainMessengerActivity : AppCompatActivity() {
private lateinit var mServiceMessenger: Messenger
private val mServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
mServiceMessenger = Messenger(service)
}
override fun onServiceDisconnected(name: ComponentName?) {
}
}
private val handler: Handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
if (msg.what == 1) {
Log.e("${currentTime()}--客户端收到的消息:", "${msg.arg2}")
}
super.handleMessage(msg)
}
}
private val mClientMessenger: Messenger = Messenger(handler)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = Intent(this, MessengerService::class.java)
val bundle = Bundle()
bundle.putString("name", "Butler")
bundle.putInt("age", 28)
intent.putExtra("message", bundle)
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
setContentView(R.layout.activity_main_messenger)
findViewById<Button>(R.id.Messenger).setOnClickListener {
val nextInt = Random().nextInt(100)
Log.e("${currentTime()}--客户端发送的消息:", "$nextInt")
val message = Message.obtain(handler, 0, nextInt, 0)
message.replyTo = mClientMessenger
mServiceMessenger.send(message)
}
findViewById<Button>(R.id.file).setOnClickListener {
val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
coroutineScope.launch {
val message = "name:Butler,age:28"
try {
val fileOutputStream = openFileOutput(FileName, MODE_PRIVATE)
fileOutputStream.write(message.toByteArray())
fileOutputStream.close()
} catch (e: Exception) {
print(e.message)
} finally {
startActivity(Intent(this@MainMessengerActivity, FileActivity::class.java))
}
}
}
}
override fun onDestroy() {
unbindService(mServiceConnection)
super.onDestroy()
}
fun currentTime(): String {
val formatter = SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss")
return formatter.format(Date(System.currentTimeMillis()))
}
}
代码很简单,Activity启动时和MessengerService绑定,点击按钮向不同进程的服务发送数字并接受新的返回值,运行效果如下:
Messenger通过Handler将Message发送到另一个进程,实现了进程间通信,底层依然是使用了Binder机制,其本质上也是基于AIDL实现的。
Messenger的AIDL文件(IMessenger)内容如下:
package android.os;
import android.os.Message;
/** @hide */
oneway interface IMessenger {
void send(in Message msg);
}
不难发现,它的作用就是跨进程发送Message。它的大致流程如下:
但是,Messenger也有明显的缺点:首先,服务端是以串行的方式在处理消息,不太适合用来出来大量的并发请求。其次,它的主要作用是传递消息,不太适合用来跨进程调用方法。
使用AIDL(Messenger原理)
上一小节我们介绍了Messenger实现跨进程通讯的方法,其本质上也是基于AIDL实现的,这一节我们使用AIDL自己实现一个Messenger,用来演示AIDL的使用以及穿插者学习Messenger的工作原理,为了简化代码。我们并不直接使用Message作为我们传递的数据,而是使用一个Bean作为传递的数据。其核心代码如下:
package com.karl.butler.aidl
import android.os.Parcel
import android.os.Parcelable
class Bean: Parcelable {
var name: String? = null
var age = 0
constructor(parcel: Parcel) {
name = parcel.readString()
age = parcel.readInt()
}
}
注意:在AIDL中,并不是所有的数据类型都是可以使用,它支持一下数据类型:
- 基本数据类型;
- ArrayList,且里面的元素必须是被AIDL支持的数据类型;
- HashMap,且里面的元素必须是被AIDL支持的数据类型;
- 实现了Parcelable接口的对象;
- AIDL接口本身。
所以我们的Bean类要实现Parcelable接口。同理,上节中Messenger中传递的Message也是实现了Parcelable接口:
接下创建AIDL文件BeanAidl.aidl:
package com.karl.butler.aidl;
parcelable Bean;
interface BeanAidl {
Bean send(in Bean bean);
}
注意:自定义的对象必须import进来。除此之外,AIDL中除了基本数据类型,其他类型的参数必须标上方向:in、out或者inout:
- in: 对象中的变量可以从客户端传到服务端, 而服务端对该对象的修改不会影响到客户端.
- out: 对象中的变量无法从客户端传到服务端, 服务端收到的是一个内部变量全为初始值的变量, 但是服务端对该对象的的修改可以影响到客户端
- inout: 对象中的变量既可以传到服务端, 服务队对其的修改也可以影响到客户端
接下来创建服务端代码AIdlService:
class AIdlService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return iService.asBinder()
}
private val iService = object : BeanAidl.Stub() {
override fun send(bean: Bean?): Bean {
bean!!.age = 2 * bean!!.age
Thread.sleep(5000L)
return bean
}
}
}
我们需要做的是创建一个BeanAidl.Stub
(自动生成的代码,稍后讲解)的实例,并将其作为IBinder返回即可。而在BeanAidl.Stub
的具体实现中,我们实现了BeanAidl.aidl中的send方法。当客户端通过AIDL调用send方法时,最终服务端的send方法会被调用。这里我们将age扩大了一倍,并返回一个bean对象。
接下来我们看一下IMessenger中对应的实现。首先,aidl文件中定义的send方法在Handler有具体实现:
private final class MessengerImpl extends IMessenger.Stub {
public void send(Message msg) {
msg.sendingUid = Binder.getCallingUid();
Handler.this.sendMessage(msg);
}
}
可见,它将客户端的消息,使用Handler又发了一遍。这就是上文中,我们需要创建Handler,并使用handleMessage
来处理客户端发送的消息。那么Handler哪里来的呢,又是怎么和Messenger关联了呢?我们接着看MessengerService.kt的代码:
private val mHandlerThread: HandlerThread = HandlerThread("服务端")
private lateinit var mMessenger: Messenger
private lateinit var mHandler: Handler
override fun onCreate() {
super.onCreate()
mHandlerThread.start()
mHandler = object : Handler(mHandlerThread.looper) {
override fun handleMessage(msg: Message) {
if (msg.what == 0) {
val obtain = Message.obtain(msg)
obtain.what = 1
obtain.arg2 = 2*obtain.arg1
Thread.sleep(2000L)
obtain.replyTo.send(obtain)
return
}
super.handleMessage(msg)
}
}
mMessenger = Messenger(mHandler)
}
不难看到,在创建Messenger
实例时,必须传入一个Handler对象。因为它只有一个构造方法:
private final IMessenger mTarget;
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}
而Handler的getIMessenger方法实现如下:
@UnsupportedAppUsage
final IMessenger getIMessenger() {
synchronized (mQueue) {
if (mMessenger != null) {
return mMessenger;
}
mMessenger = new MessengerImpl();
return mMessenger;
}
}
注意里面的MessengerImpl
,正是上文中AIDL的send方法的实现处。就是那个Handler里的内部类。
而在services的onBinder方法中:
public IBinder getBinder() {
return mTarget.asBinder();
}
可以明显看到调用了asBinder方法。
注意上文中,我们的服务端代码会延时五秒返回给客户端信息。此时,我们会发现,应用的页面被阻塞了,无法响应任何事件。而且,有时还会触发ANR异常。这是因为:客户端调用的方法是运行在服务端的Binder线程池中,这时客户端线程会挂起,此时如果服务端方法执行比较耗时,会导致客户端线程的阻,如果是UI线程的话,就触发ANR异常。因此,如果明确知道服务端要进行耗时操作,一定要在子线程里去访问服务端的代码。然而,服务端则不同,它本身就是运行在Binder线程池中的,所以无需开启新的子线程,唯一需要注意的就是,当多个客户端和服务端通信时需要保证线程安全。
AIDL同样支持接口回调的监听方式。首先创建一个新的AILD(ChangeListener)
package com.karl.butler.aidl;
parcelable Bean;
interface ChangeListener {
void onChangeListener(in Bean bean);
}
然后在BeanAidl里增加方法:
void sendTwo(in Bean bean);
void setListener(in ChangeListener listener);
void removeListener(in ChangeListener listener);
修改AIdlService:
private var listener: ChangeListener? = null
private val iService = object : BeanAidl.Stub() {
override fun send(bean: Bean?): Bean {
bean!!.age = 2 * bean!!.age
Thread.sleep(5000L)
listener?.onChangeListener(bean)
return bean
}
override fun sendTwo(bean: Bean?) {
bean!!.age = 2 * bean!!.age
listener?.onChangeListener(bean)
}
override fun setListener(on: ChangeListener?) {
listener = on
}
override fun removeListener(on: ChangeListener?) {
listener = null
}
}
客户端AidlActivity添加如下代码即可实现监听:
private val listener = object : ChangeListener.Stub() {
override fun onChangeListener(bean: Bean) {
textView.text = bean.toString()
}
}
AIDL的工作原理
Android 接口定义语言 (Android Interface Definition Language) 是一款可供用户用来抽象化IPC的工具,具体通信还是得用Binder来进行。以在 .aidl 文件中指定的接口为例,各种构建系统都会使用 aidl 二进制文件构造 C++ 或 Java 绑定,以便跨进程使用该接口。
AIDL 可以在 Android 中的任何进程之间使用:在平台组件之间使用或在应用之间使用均可。但是,AIDL 绝不能用作应用的API。
AIDL 使用 Binder 内核驱动程序进行调用。当发出调用时,系统会将方法标识符和所有对象打包到某个缓冲区中,然后将其复制到远程进程,该进程中有一个Binder线程正在等待读取数据。Binder 线程收到某个事务的数据后,该线程会在本地进程中查找原生桩对象,然后此类会解压缩数据并调用本地接口对象。此本地接口对象正是服务器进程所创建和注册的对象。当在同一进程和同一后端中进行调用时,不存在代理对象,因此直接调用即可,无需执行任何打包或解压缩操作。
Binder就是一个很深的话题,由于它本身很复杂。这里我们就不过多的探讨它的底层原理了。在Android开发中,Binder主要用在Service中,其中普通Service中的Binder不涉及进程间通信。这里主要讲跨进程的只是,在AIDL和Messenger中Messenger的底层其实是AIDL,所以用AIDL来探究一下Binder的工作机制。
我们新建一个最简单的AIDL(DemoAidl)文件:
package com.karl.butler;
interface DemoAidl {
void fun(int val);
void func(int val);
}
此时,SDK会为我们自动生成AIDL所生成的Binder类,它处于build/generated目录下:
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.karl.butler;
public interface DemoAidl extends android.os.IInterface
{
/** Default implementation for DemoAidl. */
public static class Default implements com.karl.butler.DemoAidl
{
@Override public void fun(int val) throws android.os.RemoteException
{
}
@Override public void func(int val) throws android.os.RemoteException
{
}
@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.karl.butler.DemoAidl
{
private static final java.lang.String DESCRIPTOR = "com.karl.butler.DemoAidl";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.karl.butler.DemoAidl interface,
* generating a proxy if needed.
*/
public static com.karl.butler.DemoAidl asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.karl.butler.DemoAidl))) {
return ((com.karl.butler.DemoAidl)iin);
}
return new com.karl.butler.DemoAidl.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_fun:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
this.fun(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_func:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
this.func(_arg0);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.karl.butler.DemoAidl
{
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 void fun(int val) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(val);
boolean _status = mRemote.transact(Stub.TRANSACTION_fun, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().fun(val);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void func(int val) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(val);
boolean _status = mRemote.transact(Stub.TRANSACTION_func, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().func(val);
return;
}
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
public static com.karl.butler.DemoAidl sDefaultImpl;
}
static final int TRANSACTION_fun = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_func = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
public static boolean setDefaultImpl(com.karl.butler.DemoAidl impl) {
if (Stub.Proxy.sDefaultImpl == null && impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.karl.butler.DemoAidl getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public void fun(int val) throws android.os.RemoteException;
public void func(int val) throws android.os.RemoteException;
}
上面的代码是系统根据DemoAidl.aidl生成的。它是一个实现了IInterface这个接口的接口,要想使用Binder机制实现跨进程通讯就必须实现该接口。这个类主要有结构如下:
首先,声明两个方法fun(int val)
和func(int val)
,也就是我们在AIDL文件中定义的两个方法。同时,声明了两个静态常量:
static final int TRANSACTION_fun = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_func = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
标识这两个方法。目的是为了在onTransact
中标识到底是哪个方法被调用。
接着,定义了一个内部类Stub,它继承了Binder并实现了DemoAidl
这个接口,很明显它就是Binder。同时,这个内部类里还有一个代理类Proxy
。当客户端和服务端在同一个进程内时,方法调用不会走跨进程的Transact过程。当不在一个进程内时,就会通过内部的代理类来完成。那么它是怎么判断呢?
我们看一下其asInterface的代码:
public static com.karl.butler.aidl.BeanAidl asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.karl.butler.aidl.BeanAidl))) {
return ((com.karl.butler.aidl.BeanAidl)iin);
}
return new com.karl.butler.aidl.BeanAidl.Stub.Proxy(obj);
}
可以看到,它是通过queryLocalInterface首先判断本地是否有对应的Binder对象。也就是当前线程内是否有,如果有,则说明客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身。否则,就是在不同的进程内,返回的是Stub.proxy对象。
接着看一下onTransact方法,它是运行在服务端的Binder线程池中的,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
最后看一下Proxy中的fun和func方法:方法运行在客户端,当方法被调用时,首先准备数据,然后发起远程请求并挂起当前线程,经过底层驱动调用度无端的onTransact方法会。返回结果后,当前线程继续执行。
流程大致如下:
其实,AIDL可以理解为Binder规定的一种规范。是为了便于我们开发而用来自动生成代码的工具。我们完全可以自己来写代码,而无需借助AIDL文件完成Binder。
Binder
Binder 是一种进程间通信机制,基于开源的 OpenBinder 实现。
- Binder是一种CS架构的进程间通讯机制(机制);
- 一个虚拟的物理设备(驱动;
- 在应用层是一个能发起通讯的Java类。
当应用中使用多进程通讯时就需要Binder。而由于虚拟机分配给各个进程的运行内存是有限制的,同时LMK也会优先回收占用资源较多的进程。所以我们可以运用多进程:
- 规避内存限制,如大量占用内存的图库
- 提高稳定性,使用独立的进程保持长连接
- 规避内存泄露,独立的webView进程
- 规避风险,使用独立进程运行高风险的功能,避免影响主业务
同时,Android应用是有四大组件(Activity、Service、Broadcast Receiver 和 Content Provide)组成的。有时这些组件运行在同一进程,有时运行在不同的进程。这些进程间的通信就依赖于 Binder IPC 机制。同时,android中的AMS、PMS都是基于Binder机制来实现的。所以,组件的启动流程、通信机制以及AIDL都离不开Binder。
先了解一下系统中的内存划分。操作系统会将内存划分为两块:
- 用户空间:用户程序代码运行的地方,应用独享;
- 内核空间:内核代码运行的空间,所有进程共享。
为了确保安全性,它们是隔离开的。而且不同进程之间也是隔离开的,内存是不共享的,无法直接访问不同进程间的数据。
不同进程之间依赖内核空间作为桥梁
进行通讯:通过将A进程中的数据copy到内核空间(copy from user),再由内核空间copy到B进程中(copy to user),实现A进程和B进程之间的通讯。本质上是:通过内核内存空间,拷贝赋值来完成进程间的数据传输
Linux提供了管道(Handler使用了管道机制+epoll)、信号量、 消息队列、共享内存(Fresco)和 Socket 等 IPC 机制。而Binder相比它们具有以下优势:
- Binder 只需要一次数据拷贝,性能上仅次于共享内存,而其他的则需拷贝两次。
- Binder 基于 C/S 架构,内存共享机制需要自行管理和控制并发和同步等,稳定性和便捷性不如Binder,且所有进程都可以访问,安全性低。
- Binder通过在内核中获取PID添加身份标识,而非程序自己生成,安全性更高。
- Binder是一对多的关系,一个服务端可以有多个客户端与之通讯,而管道只支持一对一的通讯方式。
在Linux中,使用的虚拟内存寻址方式:
- 用户空间的虚拟内存地址是映射到物理内存中的;
- 对虚拟内存的读写实际上是对物理内存的读写,通过系统调用mmap()来实现内存映射。
Binder借助了内存映射的方法,在内核空间和接收方用户空间的数据缓存区之间做了一层内存映射,就相当于直接拷贝到了接收方用户空间的数据缓存区,从而减少了一次数据拷贝。
由于个人对Binder的了解也仅限于皮毛,这里只对其基本概念做一个简单的描述。更深入的学习推荐去看:Android Binder设计与实现 - 设计篇