Android多进程和跨进程通讯方式

前言

我们经常开发过程中经常会听到线程和进程,在讲述Android进程多进程前我打算先简单梳理一下这俩者。

了解什么是进程与线程

  1. 进程: 系统中正在运行的一个应用程序,某个程序一旦运行就是一个进程,是资源分配的最小单位;
  2. 线程: 程序执行的最小单位,包含在进程中,一个进程可以包含多个线程。

Android 应用中的多进程

1.dalivk虚拟机

Android 系统的底层任务管理以及驱动都是基于 Linux 系统;一个 Android 系统其实就是一个 Linux 系统,通过 adb shell 进入连接的手机,就可以看到 Linux 系统的文件系统
在这里插入图片描述

像在运行一个 Java 程序,我们知道 Linux 系统会启动一个Java虚拟机来运行该 Java 程序,而 Android 系统是一个特殊的 Linux 系统,当启动一个 APP,系统会为该 APP 分配一个 Linux 进程,而该进程中就会有一个 dalivk 虚拟机(又称为 DVM )实例来运行该 APP,所以 dalivk 虚拟机就是用来运行 APP 程序

不同的APP运行在不同的进程中,对应着不同的dalivk虚拟机,就对应着不同的地址空间。反过来在一个应用内,如果新开一个进程,那么由于系统在新开进程的时候会分配独立的dalivk虚拟机,那么对于该APP内的不同进程的实例是互相独立,互不影响。

  1. 每个进程有独立的dalivk虚拟机,对应着单独的内存空间
  2. 一个应用可以有多个进程,就有多个dalivk虚拟机,对应多个内存空间
  3. 一个进程可以被多个应用访问,多个应用可以共享该进程

2.Linux 系统组成

在 Linux 系统中,虚拟内存空间(我理解的就是运行软件程序的空间,是对物理空间的映射)只有 4G;最高的 1GB(对应虚拟地址0xC0000000到0xFFFFFFFF)被成为内核空间,而较低的 3GB(对应虚拟地址0x00000000到0xBFFFFFFF)被成为用户空间。内核空间是Linux内核运行空间,用户空间是应用程序的运行空间。如图所示:
在这里插入图片描述

  1. 内核空间:可以访问受保护的内存空间,有访问底层硬件设备的所有权限
  2. 用户空间:上层应用程序和Native层的运行空间,用户空间没法直接访
  3. 内核空间,需要系统调用才可以访问内核空间。

不同进程之间,用户空间是不共享,但是内核空间是可以共享。

3.Android定义多进程的三种方式

默认情况下,启动一个APP,仅仅启动了一个进程,该进程名为包名,那如何定义多进程呢? Android 提供了一种方式,就是在 AndroidManifest 文件中可以通过 “android:process” 来指定进程:

  1. 不指定 process: 默认的进程,进程名为包名
  2. 指定 process,但以":"开头: 该进程为当前APP的私有进程,不允许其他APP访问
  3. 指定process,但以小写字母开头的字符串: 该进程为全局进程 ,其他应用可设置相同的shareUID来共享该进程

4.为什么要引入多进程?

为什么一个 Android 应用要引入多进程?多进程有哪些应用场景呢?

通常在下面两种情况下需要引入多进程:

  1. 由于 Android 系统会限制每个应用的最大内存,所以如果一个应用需要更多可用的内存时,就需要引入多进程,让某些模块运行在另外的进程中,获取更多的内存
  2. 由于不同的应用运行在不同的进程中,但是如果两个不同的应用之间需要进行数据通信

5.跨进程通信

既然在 Android 中引入了多进程,而对于进程的用户空间不共享,那么多进程之间怎么通信呢?

这种多进程通信又称为IPC(Inter Process Communication)

对于IPC,并不是Android系统特有的,在Linux系统中就存在的跨进程通信,在Linux系统中常见的IPC方式有:

  1. 管道Pipe: 在内存中创建一个共享文件,利用共享文件传递信息。该共享文件并不是文件系统,只存在于内存中;只能在一个方向上流动
  2. 信号Signal: 异步通信。信号在用户空间和内核空间之间交互,内核可利用信号来通知用户空间的进程发生哪些系统事件。不适用于信号交换,适用于过程中断控制;
  3. 信号量Semaphore: 控制多个进程对共享资源的访问。主要是进程间以及同一进程不同线程之间的同步手段;
  4. 消息队列 Message Queue: 存放在内存中并由消息对了标识符标识,允许一个或多个进程对它进行读写消息。信息会复制两次,不适用于频繁或信息量大的通信
  5. 共享内存Shared Memory: 直接读写内核的一块内存空间。不需要进行数据拷贝
  6. 套接字Socket: 不同机器之间进程间通信。

除去 Socket,其他的都是基于 Binder 机制实现的

6.多进程带来的问题

在进程结构中也提到了用户空间是不共享的,并且每个进程都是对应单独的系统堆栈区、静态区等,那么多进程也引入了一些问题:

  • 每个进程都是保持自己单独的静态成员变量和单例
  • 每个进程都是单独的进程锁
  • SharedPreferences可靠性下降,不支持并发写
  • 对于单个APP的多进程,就会创建多个Application,每个进程都会拥有己的Application对象。

好了,到了这里我们对进程和线程应该有了基本认识,同时也知道了常用的跨进程通信方式,包括Android常用的几种IPC方式,接下来我们就对Android常用的几种IPC方式做一个深入的了解。

认识IPC

进程间通信(InterProcess Communication缩写IPC)是指在不同进程之间传播或交换信息。进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元。

IPC不是Android中所独有的,任何一个操作系统都需要有相应的IPC机制。在Android系统中一个进程会对应一个虚拟机实例,不同的虚拟机在内存分配上有不同的地址空间,所以:只有在多进程的环境下才需要考虑使用IPC进行通讯。Android中的应用程序可以为一个进程,也可以配置成多进程,每个进程都在自己独立的空间中运行。

在Android中有两个典型的场景:第一种是应用自身需要开启多个进程,比如多模块应用,由于系统设置了应用获取的最大内存限制,为了获得更多的内存空间将不同的模块放在不同的线程中。另外一种情况就是获取其他应用里的数据,典型的案例就是获取通讯录和短信。

Android中的多进程模式

Android默认进程是运行在默认名为包名的进程中,除非特别指定,所有的组件都运行在默认进程中。可以通过修改AndroidManifest文件,在 application 标签下添加 android:process 属性可以修改Android默认的进程名字:

<applicationandroid: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.test.process"android:theme="@style/Theme.Sunflower"></application>

在Android中只有给四大组件(Activity Service Broadcast ContentProvider)设置android:process属性这一种使用多进程的方法。

<serviceandroid:name=".service.TestService"android:process=":services" />

上面的代码为TestService指定了process属性,为应用增加了一个新的进程。当TestService 启动时,系统会为它创建单独的进程。使用

adb shell ps | grep [包名] 

查看进程信息.

注意上面中的XML代码,process 属性有两种设置方式,一种是如上文中":"开头后面接进程名,一种则是完整的进程名,例如android:process=“com.packages.name.services”。前者会在进程名前加上包名(“com.packages.name:services”),且规定了进程为当前应用的私有进程,其他应用的组件不可以和它使用统一进程。后者则你写了什么,进程名就是什么,且为全局进程,其他应用也可以使用该进程。

虽然开启多进程的方法很简单,但是这种看似简单的操作稍有不深就会带来巨大的问题:

  1. 问题一:多进程会造成Application的多次创建:当一个组件需要运行在新的进程中时,实际的创建过程就相当于又重新启动了一次应用,应用启动,必然会创建Application。而运行在不同进程中的组件,不仅属于不同的虚拟机,而且其Application也是不同的。

  2. 问题二:多进程会导致静态成员和单例完全无效:由于不同进程都被分配了独立且不同的虚拟机,其在内存分配上有这不同的地址空间。这就会导致在着一个类的多个副本,各自修改互不影响。

  3. 问题三:多进程模式下,线程的同步机制也会失效:因为不同的进程,其线程不所属同一内存,那么无论是对象锁还是类锁,锁本身都不是同一个了。

  4. 问题四:多进程模式下SharedPreferences风险会增大:SharedPreferences底层是通过文件读写实现的,并发操作可能会造成问题。

总之:在不同进程中的组件,如果使用内存来通讯,都会存在隐患或者直接失败。这就是多进程带来的最主要的影响。

Android中的跨进程通讯(IPC)

接下来讲解一下多进程间通讯的方式。

1:使用Bundle

在Android开发中,我们通过Intent启动Activity、Service和Receiver都是可以通过Bundle传递参数的。它实现了Parcelable接口,并以键值对的方式保存数据。可以将其视为一个容器,其支持基本数据类型(string、int、boolean、byte、float、long、double)以及它们对应的数据。当需要传递对象或对象数组时,被传递的对象必须实现Serialiable或Parcelable接口。

接下来我们通过一个Activity和一个Service来看一下如何利用Bundle实现跨进程通讯。

首先,让二者运行在不同的进程中:

<activity android:name=".process.TestActivity"/>
<serviceandroid:name=".process.MessengerService"android:process=":services" />

在配置文件指定Service的运行线程即可。

在通过 bindService(Intent, ServiceConnection,int)为TestActivity绑定服务时,为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没有什么区别。

2:使用文件共享

通过共享文件,不同的进程可以使用读写文件的方式交换数据。在Android中,对文件的并发读写并没有什么特殊的要求。虽然这可能造成问题,但是却依然可以帮我们在不同进程间传递数据。我们创建一个新的Activty(FileActivity),并指定其进程android:process=“:file”

在TestActivity中写入文件并实现跳转:

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@TestActivity, FileActivity::class.java))}}
}

在FileActivity读取文件内容:

coroutineScope.launch {try {val fileInputStream = openFileInput(FileName)var n = 0val 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 {}
}

运行结果如下:
在这里插入图片描述

注意⚠️:不要使用SharedPreferences去做跨进程通讯,原则上它不支持多进程。虽然它本质上也是一个文件,但是由于它在应用运行时会再内存中加载缓存,然而进程间是不能内存共享的,每个进程操作的SharedPreferences都会是一个单独的实例,这就会导致安全性问题,这个问题只能通过多进程间其它的通信方式或者是在确保不会同时操作SharedPreferences数据的前提下使用SharedPreferences来解决。

3:使用ContentProvider

ContentProvider提供一种应用管理其自身和其他应用所存储数据的能力,并提供与其他应用共享这些数据的方法。它会封装数据,并提供用于定义数据安全性的机制。无论你是否需要和其他应用分享数据,你都可以使用ContentProvider去访问这些数据,虽然当无需和其他应用共享数据时没必要使用它。系统预置了许多ContentProvider,比如通话记录,通讯录,信息等,只需要通过ContentProvider就可以拿到这些信息。ContentProvider以一个或多个表的形式将数据呈现给外部应用,这些表与关系型数据库中的表类似。行表示提供程序收集的某种类型数据的实例,行中的每一列表示为一个实例所收集的单个数据。

通常有两个典型的使用场景:一种是通过实现代码访问其他应用中的现有的ContentProvider用来获得数据;另一种是创建新的ContentProvider,与其他应用共享数据。

下面我们用一个最简单的例子,演示一下使用它进行跨进程通讯。为了方便演示,我们在同一个应用内进行:

首先创建一个ContentProvider。代码很简单,就是一个使用Room的数据持久化,只做了insert和查询的逻辑处理。

class UserProvider : ContentProvider() {private lateinit var appDatabase: AppDatabaseprivate var userDao: UserDao? = nulloverride 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}
}

接下来在配置文件里配置它,并指明其所在的进程process。

<providerandroid:name=".provider.UserProvider"android:authorities="com.hirzy.test.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.hirzy.test.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.hirzy.test.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}}
}

运行效果如下:
在这里插入图片描述

可以看到,处于两个不同进程中的Activity和Provider成功的实现了数据通讯。但是,由于设计原因,ContentProvider只支持增删改查,更像是一个跨进程的数据库。

4:使用Messenger

Messenger是跨进程通信的类,通过它可以再不同进程中传递Message。底层通过AIDL实现,可以理解为是官方为了怕我们使用AIDL太过于繁琐而提供的一种简单的方案。

它是使用也很简单,首先创建服务端Service(MessengerService),并设置它所在的进程

android:process=":services":
class MessengerService : Service() {private val mHandlerThread: HandlerThread = HandlerThread("服务端")private lateinit var mMessenger: Messengerprivate lateinit var mHandler: Handleroverride 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  = 1obtain.arg2  = 2*obtain.arg1Thread.sleep(2000L)obtain.replyTo.send(obtain)return}super.handleMessage(msg)}}mMessenger = Messenger(mHandler)}
}

接受客户端传来的Int值,延迟两秒后并返回它的倍数。

接下来在应用默认进程中实现一下客户端Activity (MessengerActivity):

class MessengerActivity : AppCompatActivity() {private lateinit var mServiceMessenger: Messengerprivate 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 = mClientMessengermServiceMessenger.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)内容如下:

/* //device/java/android/android/app/IActivityPendingResult.aidl
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/package android.os;import android.os.Message;/** @hide */
oneway interface IMessenger {void send(in Message msg);
}

不难发现,它的作用就是跨进程发送Message。它的大致流程如下:
在这里插入图片描述

但是,Messenger也有明显的缺点:首先,服务端是以串行的方式在处理消息,不太适合用来出来大量的并发请求。其次,它的主要作用是传递消息,不太适合用来跨进程调用方法。

5:使用AIDL

通过上面,我们知道Messenger实现跨进程通讯,其本质上也是基于AIDL实现的,这一节我们使用AIDL自己实现一个Messenger,用来演示AIDL的使用以及穿插者学习Messenger的工作原理,为了简化代码。我们并不直接使用Message作为我们传递的数据,而是使用一个Bean作为传递的数据。其核心代码如下:

package com.hirzy.test.aidlimport android.os.Parcel
import android.os.Parcelableclass Bean: Parcelable {var name: String? = nullvar age = 0constructor(parcel: Parcel)  {name = parcel.readString()age = parcel.readInt()}}

注意⚠️:在AIDL中,并不是所有的数据类型都是可以使用,它支持一下数据类型:

  • 基本数据类型
  • ArrayList,且里面的元素必须是被AIDL支持的数据类型
  • HashMap,且里面的元素必须是被AIDL支持的数据类型
  • 实现了Parcelable接口的对象
  • AIDL接口本身

所以我们的Bean类要实现Parcelable接口。同理,上节中Messenger中传递的Message也是实现了Parcelable接口,源码如下:

/** Copyright (C) 2006 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at**      http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package android.os;import android.compat.annotation.UnsupportedAppUsage;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;import com.android.internal.annotations.VisibleForTesting;/**** Defines a message containing a description and arbitrary data object that can be* sent to a {@link Handler}.  This object contains two extra int fields and an* extra object field that allow you to not do allocations in many cases.** <p class="note">While the constructor of Message is public, the best way to get* one of these is to call {@link #obtain Message.obtain()} or one of the* {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull* them from a pool of recycled objects.</p>*/
public final class Message implements Parcelable {/*** User-defined message code so that the recipient can identify* what this message is about. Each {@link Handler} has its own name-space* for message codes, so you do not need to worry about yours conflicting* with other handlers.*/public int what;/*** arg1 and arg2 are lower-cost alternatives to using* {@link #setData(Bundle) setData()} if you only need to store a* few integer values.*/public int arg1;/*** arg1 and arg2 are lower-cost alternatives to using* {@link #setData(Bundle) setData()} if you only need to store a* few integer values.*/public int arg2;/*** An arbitrary object to send to the recipient.  When using* {@link Messenger} to send the message across processes this can only* be non-null if it contains a Parcelable of a framework class (not one* implemented by the application).   For other data transfer use* {@link #setData}.** <p>Note that Parcelable objects here are not supported prior to* the {@link android.os.Build.VERSION_CODES#FROYO} release.*/public Object obj;/*** Optional Messenger where replies to this message can be sent.  The* semantics of exactly how this is used are up to the sender and* receiver.*/public Messenger replyTo;/*** Indicates that the uid is not set;** @hide Only for use within the system server.*/public static final int UID_NONE = -1;/*** Optional field indicating the uid that sent the message.  This is* only valid for messages posted by a {@link Messenger}; otherwise,* it will be -1.*/public int sendingUid = UID_NONE;/*** Optional field indicating the uid that caused this message to be enqueued.** @hide Only for use within the system server.*/public int workSourceUid = UID_NONE;/** If set message is in use.* This flag is set when the message is enqueued and remains set while it* is delivered and afterwards when it is recycled.  The flag is only cleared* when a new message is created or obtained since that is the only time that* applications are allowed to modify the contents of the message.** It is an error to attempt to enqueue or recycle a message that is already in use.*//*package*/ static final int FLAG_IN_USE = 1 << 0;/** If set message is asynchronous *//*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;/** Flags to clear in the copyFrom method *//*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;@UnsupportedAppUsage/*package*/ int flags;/*** The targeted delivery time of this message. The time-base is* {@link SystemClock#uptimeMillis}.* @hide Only for use within the tests.*/@UnsupportedAppUsage@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)public long when;/*package*/ Bundle data;@UnsupportedAppUsage/*package*/ Handler target;@UnsupportedAppUsage/*package*/ Runnable callback;// sometimes we store linked lists of these things@UnsupportedAppUsage/*package*/ Message next;/** @hide */public static final Object sPoolSync = new Object();private static Message sPool;private static int sPoolSize = 0;private static final int MAX_POOL_SIZE = 50;private static boolean gCheckRecycle = true;/*** Return a new Message instance from the global pool. Allows us to* avoid allocating new objects in many cases.*/public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();}/*** Same as {@link #obtain()}, but copies the values of an existing* message (including its target) into the new one.* @param orig Original message to copy.* @return A Message object from the global pool.*/public static Message obtain(Message orig) {Message m = obtain();m.what = orig.what;m.arg1 = orig.arg1;m.arg2 = orig.arg2;m.obj = orig.obj;m.replyTo = orig.replyTo;m.sendingUid = orig.sendingUid;m.workSourceUid = orig.workSourceUid;if (orig.data != null) {m.data = new Bundle(orig.data);}m.target = orig.target;m.callback = orig.callback;return m;}/*** Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.* @param h  Handler to assign to the returned Message object's <em>target</em> member.* @return A Message object from the global pool.*/public static Message obtain(Handler h) {Message m = obtain();m.target = h;return m;}/*** Same as {@link #obtain(Handler)}, but assigns a callback Runnable on* the Message that is returned.* @param h  Handler to assign to the returned Message object's <em>target</em> member.* @param callback Runnable that will execute when the message is handled.* @return A Message object from the global pool.*/public static Message obtain(Handler h, Runnable callback) {Message m = obtain();m.target = h;m.callback = callback;return m;}/*** Same as {@link #obtain()}, but sets the values for both <em>target</em> and* <em>what</em> members on the Message.* @param h  Value to assign to the <em>target</em> member.* @param what  Value to assign to the <em>what</em> member.* @return A Message object from the global pool.*/public static Message obtain(Handler h, int what) {Message m = obtain();m.target = h;m.what = what;return m;}/*** Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, and <em>obj</em>* members.* @param h  The <em>target</em> value to set.* @param what  The <em>what</em> value to set.* @param obj  The <em>object</em> method to set.* @return  A Message object from the global pool.*/public static Message obtain(Handler h, int what, Object obj) {Message m = obtain();m.target = h;m.what = what;m.obj = obj;return m;}/*** Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,* <em>arg1</em>, and <em>arg2</em> members.** @param h  The <em>target</em> value to set.* @param what  The <em>what</em> value to set.* @param arg1  The <em>arg1</em> value to set.* @param arg2  The <em>arg2</em> value to set.* @return  A Message object from the global pool.*/public static Message obtain(Handler h, int what, int arg1, int arg2) {Message m = obtain();m.target = h;m.what = what;m.arg1 = arg1;m.arg2 = arg2;return m;}/*** Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,* <em>arg1</em>, <em>arg2</em>, and <em>obj</em> members.** @param h  The <em>target</em> value to set.* @param what  The <em>what</em> value to set.* @param arg1  The <em>arg1</em> value to set.* @param arg2  The <em>arg2</em> value to set.* @param obj  The <em>obj</em> value to set.* @return  A Message object from the global pool.*/public static Message obtain(Handler h, int what,int arg1, int arg2, Object obj) {Message m = obtain();m.target = h;m.what = what;m.arg1 = arg1;m.arg2 = arg2;m.obj = obj;return m;}/** @hide */public static void updateCheckRecycle(int targetSdkVersion) {if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {gCheckRecycle = false;}}/*** Return a Message instance to the global pool.* <p>* You MUST NOT touch the Message after calling this function because it has* effectively been freed.  It is an error to recycle a message that is currently* enqueued or that is in the process of being delivered to a Handler.* </p>*/public void recycle() {if (isInUse()) {if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");}return;}recycleUnchecked();}/*** Recycles a Message that may be in-use.* Used internally by the MessageQueue and Looper when disposing of queued Messages.*/@UnsupportedAppUsagevoid recycleUnchecked() {// Mark the message as in use while it remains in the recycled object pool.// Clear out all other details.flags = FLAG_IN_USE;what = 0;arg1 = 0;arg2 = 0;obj = null;replyTo = null;sendingUid = UID_NONE;workSourceUid = UID_NONE;when = 0;target = null;callback = null;data = null;synchronized (sPoolSync) {if (sPoolSize < MAX_POOL_SIZE) {next = sPool;sPool = this;sPoolSize++;}}}/*** Make this message like o.  Performs a shallow copy of the data field.* Does not copy the linked list fields, nor the timestamp or* target/callback of the original message.*/public void copyFrom(Message o) {this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM;this.what = o.what;this.arg1 = o.arg1;this.arg2 = o.arg2;this.obj = o.obj;this.replyTo = o.replyTo;this.sendingUid = o.sendingUid;this.workSourceUid = o.workSourceUid;if (o.data != null) {this.data = (Bundle) o.data.clone();} else {this.data = null;}}/*** Return the targeted delivery time of this message, in milliseconds.*/public long getWhen() {return when;}public void setTarget(Handler target) {this.target = target;}/*** Retrieve the {@link android.os.Handler Handler} implementation that* will receive this message. The object must implement* {@link android.os.Handler#handleMessage(android.os.Message)* Handler.handleMessage()}. Each Handler has its own name-space for* message codes, so you do not need to* worry about yours conflicting with other handlers.*/public Handler getTarget() {return target;}/*** Retrieve callback object that will execute when this message is handled.* This object must implement Runnable. This is called by* the <em>target</em> {@link Handler} that is receiving this Message to* dispatch it.  If* not set, the message will be dispatched to the receiving Handler's* {@link Handler#handleMessage(Message)}.*/public Runnable getCallback() {return callback;}/** @hide */@UnsupportedAppUsagepublic Message setCallback(Runnable r) {callback = r;return this;}/*** Obtains a Bundle of arbitrary data associated with this* event, lazily creating it if necessary. Set this value by calling* {@link #setData(Bundle)}.  Note that when transferring data across* processes via {@link Messenger}, you will need to set your ClassLoader* on the Bundle via {@link Bundle#setClassLoader(ClassLoader)* Bundle.setClassLoader()} so that it can instantiate your objects when* you retrieve them.* @see #peekData()* @see #setData(Bundle)*/public Bundle getData() {if (data == null) {data = new Bundle();}return data;}/*** Like getData(), but does not lazily create the Bundle.  A null* is returned if the Bundle does not already exist.  See* {@link #getData} for further information on this.* @see #getData()* @see #setData(Bundle)*/public Bundle peekData() {return data;}/*** Sets a Bundle of arbitrary data values. Use arg1 and arg2 members* as a lower cost way to send a few simple integer values, if you can.* @see #getData()* @see #peekData()*/public void setData(Bundle data) {this.data = data;}/*** Chainable setter for {@link #what}** @hide*/public Message setWhat(int what) {this.what = what;return this;}/*** Sends this Message to the Handler specified by {@link #getTarget}.* Throws a null pointer exception if this field has not been set.*/public void sendToTarget() {target.sendMessage(this);}/*** Returns true if the message is asynchronous, meaning that it is not* subject to {@link Looper} synchronization barriers.** @return True if the message is asynchronous.** @see #setAsynchronous(boolean)*/public boolean isAsynchronous() {return (flags & FLAG_ASYNCHRONOUS) != 0;}/*** Sets whether the message is asynchronous, meaning that it is not* subject to {@link Looper} synchronization barriers.* <p>* Certain operations, such as view invalidation, may introduce synchronization* barriers into the {@link Looper}'s message queue to prevent subsequent messages* from being delivered until some condition is met.  In the case of view invalidation,* messages which are posted after a call to {@link android.view.View#invalidate}* are suspended by means of a synchronization barrier until the next frame is* ready to be drawn.  The synchronization barrier ensures that the invalidation* request is completely handled before resuming.* </p><p>* Asynchronous messages are exempt from synchronization barriers.  They typically* represent interrupts, input events, and other signals that must be handled independently* even while other work has been suspended.* </p><p>* Note that asynchronous messages may be delivered out of order with respect to* synchronous messages although they are always delivered in order among themselves.* If the relative order of these messages matters then they probably should not be* asynchronous in the first place.  Use with caution.* </p>** @param async True if the message is asynchronous.** @see #isAsynchronous()*/public void setAsynchronous(boolean async) {if (async) {flags |= FLAG_ASYNCHRONOUS;} else {flags &= ~FLAG_ASYNCHRONOUS;}}/*package*/ boolean isInUse() {return ((flags & FLAG_IN_USE) == FLAG_IN_USE);}@UnsupportedAppUsage/*package*/ void markInUse() {flags |= FLAG_IN_USE;}/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).*/public Message() {}@Overridepublic String toString() {return toString(SystemClock.uptimeMillis());}@UnsupportedAppUsageString toString(long now) {StringBuilder b = new StringBuilder();b.append("{ when=");TimeUtils.formatDuration(when - now, b);if (target != null) {if (callback != null) {b.append(" callback=");b.append(callback.getClass().getName());} else {b.append(" what=");b.append(what);}if (arg1 != 0) {b.append(" arg1=");b.append(arg1);}if (arg2 != 0) {b.append(" arg2=");b.append(arg2);}if (obj != null) {b.append(" obj=");b.append(obj);}b.append(" target=");b.append(target.getClass().getName());} else {b.append(" barrier=");b.append(arg1);}b.append(" }");return b.toString();}void dumpDebug(ProtoOutputStream proto, long fieldId) {final long messageToken = proto.start(fieldId);proto.write(MessageProto.WHEN, when);if (target != null) {if (callback != null) {proto.write(MessageProto.CALLBACK, callback.getClass().getName());} else {proto.write(MessageProto.WHAT, what);}if (arg1 != 0) {proto.write(MessageProto.ARG1, arg1);}if (arg2 != 0) {proto.write(MessageProto.ARG2, arg2);}if (obj != null) {proto.write(MessageProto.OBJ, obj.toString());}proto.write(MessageProto.TARGET, target.getClass().getName());} else {proto.write(MessageProto.BARRIER, arg1);}proto.end(messageToken);}public static final @android.annotation.NonNull Parcelable.Creator<Message> CREATOR= new Parcelable.Creator<Message>() {public Message createFromParcel(Parcel source) {Message msg = Message.obtain();msg.readFromParcel(source);return msg;}public Message[] newArray(int size) {return new Message[size];}};public int describeContents() {return 0;}public void writeToParcel(Parcel dest, int flags) {if (callback != null) {throw new RuntimeException("Can't marshal callbacks across processes.");}dest.writeInt(what);dest.writeInt(arg1);dest.writeInt(arg2);if (obj != null) {try {Parcelable p = (Parcelable)obj;dest.writeInt(1);dest.writeParcelable(p, flags);} catch (ClassCastException e) {throw new RuntimeException("Can't marshal non-Parcelable objects across processes.");}} else {dest.writeInt(0);}dest.writeLong(when);dest.writeBundle(data);Messenger.writeMessengerOrNullToParcel(replyTo, dest);dest.writeInt(sendingUid);dest.writeInt(workSourceUid);}private void readFromParcel(Parcel source) {what = source.readInt();arg1 = source.readInt();arg2 = source.readInt();if (source.readInt() != 0) {obj = source.readParcelable(getClass().getClassLoader(), java.lang.Object.class);}when = source.readLong();data = source.readBundle();replyTo = Messenger.readMessengerOrNullFromParcel(source);sendingUid = source.readInt();workSourceUid = source.readInt();}
}

接下创建AIDL文件BeanAidl.aidl:

package com.hirezy.test.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 {Thread.sleep(5000L)return bean}}
}

我们需要做的是创建一个BeanAidl.Stub(自动生成的代码,稍后讲解)的实例,并将其作为IBinder返回即可。而在BeanAidl.Stub的具体实现中,我们实现了BeanAidl.aidl中的send方法。当客户端通过AIDL调用send方法时,最终服务端的send方法会被调用。并返回一个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: Handleroverride 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  = 1obtain.arg2  = 2*obtain.arg1Thread.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.hirezy.test.aidl;parcelable Bean;interface ChangeListener {void onChangeListener(in Bean bean);
}

然后在BeanAidl里增加如下方法:

void sendBean(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!!.ageThread.sleep(5000L)listener?.onChangeListener(bean)return bean}override fun sendBean(bean: Bean?) {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()Log.i("bean","bean info is:${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(TestAidl)文件

package com.hirezy.aidl;interface TestAidl {void fun(int val);void func(int val);
}

此时,SDK会为我们自动生成AIDL所生成的Binder类,它处于build/generated目录下:

/** This file is auto-generated.  DO NOT MODIFY.*/
package com.hirezy.aidl;
public interface TestAidl extends android.os.IInterface
{/** Default implementation for TestAidl. */public static class Default implements com.hirezy.aidl.TestAidl{@Override public void fun(int val) throws android.os.RemoteException{}@Override public void func(int val) throws android.os.RemoteException{}@Overridepublic android.os.IBinder asBinder() {return null;}}/** Local-side IPC implementation stub class. */public static abstract class Stub extends android.os.Binder implements com.hirezy.aidl.TestAidl{private static final java.lang.String DESCRIPTOR = "com.hirezy.aidl.TestAidl";/** Construct the stub at attach it to the interface. */public Stub(){this.attachInterface(this, DESCRIPTOR);}/*** Cast an IBinder object into an com.hirezy.aidl.TestAidl interface,* generating a proxy if needed.*/public static com.hirezy.aidl.TestAidl asInterface(android.os.IBinder obj){if ((obj==null)) {return null;}android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);if (((iin!=null)&&(iin instanceof com.hirezy.aidl.TestAidl))) {return ((com.hirezy.aidl.TestAidl)iin);}return new com.hirezy.aidl.TestAidl.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.hirezy.aidl.TestAidl{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.hirezy.aidl.TestAidl 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.hirezy.aidl.TestAidl impl) {if (Stub.Proxy.sDefaultImpl == null && impl != null) {Stub.Proxy.sDefaultImpl = impl;return true;}return false;}public static com.hirezy.aidl.TestAidl 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.hirezy.test.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.hirezy.test.aidl.BeanAidl)iin);}return new com.hirezy.test.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中,使用的虚拟内存寻址方式:

  1. 用户空间的虚拟内存地址是映射到物理内存中的
  2. 对虚拟内存的读写实际上是对物理内存的读写,通过系统调用mmap()来实现内存映射。

Binder借助了内存映射的方法,在内核空间和接收方用户空间的数据缓存区之间做了一层内存映射,就相当于直接拷贝到了接收方用户空间的数据缓存区,从而减少了一次数据拷贝。

在这里插入图片描述
由于个人对Binder的了解也不多,这里只对其基本概念做一个简单的描述。想更深入的学习Binder机制,推荐去看:Android Binder设计与实现 - 设计篇,介绍的非常详细。

总结

我认识了进程和线程的区别

  1. 进程: 系统中正在运行的一个应用程序,某个程序一旦运行就是一个进程,是资源分配的最小单位;
  2. 线程: 程序执行的最小单位,包含在进程中,一个进程可以包含多个线程。

也知道了常用了Android IPC通信方式

  1. Bundle: 实现了Parcelable接口,常用于Activity、Service、
  2. BroadcastReceive之间的通信
  3. 文件共享: 常用于无并发,交换实时性不高的数据
  4. Messenger: 低并发的一对多即时通信。串行的方式处理Client发来的消息,只能传输数据,不能方法调用(RPC)
  5. ContentProvider: 存储和获取数据,不同程序之间共享数据。一对多的数据共享
  6. AIDL
  7. Socket: 网络数据交换

注意⚠️:除去 Socket,其他的都是基于 Binder 机制实现的

常见多进程通信IPC(Inter Process Communication)有如下几种

  1. 管道Pipe: 在内存中创建一个共享文件,利用共享文件传递信息。该共享文件并不是文件系统,只存在于内存中;只能在一个方向上流动
  2. 信号Signal: 异步通信。信号在用户空间和内核空间之间交互,内核可利用信号来通知用户空间的进程发生哪些系统事件。不适用于信号交换,适用于过程中断控制
  3. 信号量Semaphore: 控制多个进程对共享资源的访问。主要是进程间以及同一进程不同线程之间的同步手段
  4. 消息队列 Message Queue: 存放在内存中并由消息对了标识符标识,允许一个或多个进程对它进行读写消息。信息会复制两次,不适用于频繁或信息量大的通信
  5. 共享内存Shared Memory: 直接读写内核的一块内存空间。不需要进行数据拷贝
  6. 套接字Socket: 不同机器之间进程间通信。

Android引入多进程的目的

  1. 由于 Android 系统会限制每个应用的最大内存,所以如果一个应用需要更多可用的内存时,就需要引入多进程,让某些模块运行在另外的进程中,获取更多的内存
  2. 由于不同的应用运行在不同的进程中,但是如果两个不同的应用之间需要进行数据通信

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/220420.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SAP ABAP给销售订单添加抬头、行项目文本

SAP ABAP给销售订单添加抬头、行项目文本 第一步&#xff1a;创建文本ID 1&#xff0c;通过SE75&#xff0c;新键文本对象的文本ID 2&#xff0c;2 &#xff0c;找到对象VBBK 销售 标题文件 3&#xff0c;点击文本IDS 4&#xff0c;进去后新建文本ID 第二步&#xff1a;通过…

使用Visual Studio(VS)创建空项目的Win32桌面应用程序【main函数入口变WinMain】

前言 在Visual Studio中直接新建Windows桌面应用程序会有很多多余的代码生成&#xff0c;本文将提供从空项目创建Win32项目的方法&#xff0c;解决新建空项目直接使用WinMain代码编译报错的问题 例如&#xff1a;LNK2019 &#xff1a;无法解析的外部符号 参考博客&#xff1…

ChibiOS简介4/5

ChibiOS简介4/5 1. 源由2. ChibiOS基础知识4/52.13 Chapter 13 - RT Synchronous Messages2.13.1 Basic concepts2.13.2 APIs 2.14 Chapter 14 - RT Events2.14.1 Basic concepts2.14.1.1 Events2.14.1.2 Operations 2.14.2 APIs 2.15 Chapter 15 - RT Debug2.15.1 Compile Tim…

without explicit opt-in, is unsupported. Switch Maven repository ‘maven8

Using insecure protocols with repositories, without explicit opt-in, is unsupported. Switch Maven repository maven8 大概意思是 不支持对存储库使用不安全的协议.看下maven库&#xff0c;把http开头的改成https就好了。

B站武sir-django教程(1)

day15 初识Django Python知识点&#xff1a;函数、面向对象。前端开发&#xff1a;HTML、CSS、JavaScript、jQuery、BootStrap。MySQL数据库。Python的Web框架&#xff1a; Flask&#xff0c;自身短小精悍 第三方组件。Django&#xff0c;内部已集成了很多组件 第三方组件。…

Restormer技术点小结

1. 解决cnn的不足&#xff1a; 1&#xff09;感受野有限 2&#xff09;输入尺寸固定 2. 解决transform的不足&#xff1a; 1&#xff09;计算复杂度随着空间分辨率的增加而二次增长 3. 优势结构&#xff1a;MDTA(Multi-Dconv Head Transposed Attention)和GDFN( Gated-Dco…

Sentinel使用详解

组件简介 Sentinel是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点&#xff0c;从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。Sentinel承接了阿里巴巴近10年的双十一大促流量的核心场景&#xff0c;例如秒杀、消息削峰填谷、集群流量控…

C语言 内存操作函数 +内存分区

内存操作函数 memset() //memset 函数将指定内存区域 ptr 开始的 num 个字节设置为 value。 void *memset(void *ptr, int value, size_t num);参数&#xff1a;ptr&#xff1a;指向要填充的内存区域的指针。value&#xff1a;要填充的值&#xff0c;以整数形式传递。num&…

20-11版本AUTOSAR_PRS_LogAndTraceProtocol文档翻译

1简介和概述 本协议规范规定了AUTOSAR协议Dlt的格式、消息序列和语义。 该协议允许将诊断、日志和跟踪信息发送到通信总线上。 因此&#xff0c;Dlt模块从应用程序或其他软件模块收集调试信息&#xff0c;向调试信息添加元数据&#xff0c;并将其发送到通信总线。 此外&#x…

软件设计师——信息安全(一)

&#x1f4d1;前言 本文主要是【信息安全】——软件设计师——信息安全的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304…

华为OD试题五(数列描述、矩阵最大值、数据分类)

1. 数列描述 示例代码&#xff1a; # 核心 从第一项 推 第N项目 # 第一项 a0 1 # 推到 第N项 N 4 def fun(a0):# 计算每一项的具体值result left 0cursor 0while cursor < len(a0):if a0[cursor] ! a0[left]:count cursor -leftresult "{}{}".format(str(…

面相对象开发的原则

1、开闭原则 对修改关闭&#xff0c;对扩展打开。 2、里氏替换原则 子类继承父类的方法时&#xff0c;不可重写父类的方法。 如果重写了父类的方法会导致整个继承体系比较差&#xff0c;特别是运用多态比较平凡时&#xff0c;程序运行出错概率较大。 如果出现了违背“里氏替换…

计网Lesson9 - 链路协议和网络概述

文章目录 数据链路层协议Ethernet V2标准Ethernet V2帧格式Ethernet V2帧长度标准以太网帧 MAC 帧协议 PPP 协议PPP 概述PPP 帧 网络层网络层的设计选择 数据链路层协议 Ethernet V2标准 Ethernet V2帧格式 以太网帧格式说明&#xff1a; 6 6 6 字节目标地址 6 6 6 字节源地…

【LeetCode每日一题】1904. 你完成的完整对局数

给你两个字符串 startTime 和 finishTime &#xff0c;均符合 "HH:MM" 格式&#xff0c;分别表示你 进入 和 退出 游戏的确切时间&#xff0c;请计算在整个游戏会话期间&#xff0c;你完成的 完整对局的对局数 。 如果 finishTime 早于 startTime &#xff0c;这表示…

verilog基础,连续赋值之组合逻辑

连续赋值语句可以完成任意组合逻辑&#xff0c;本节对基本的逻辑电路进行测试分析&#xff0c;主要包含一下内容&#xff1a; 1. 反相器 2. 与门 3.与非门 4.或门 5.或非门 6.异或门 7.同或门 verilog实现逻辑操作的算符如下 // ~ .... Invert a single-bit signal…

「Leetcode」滑动窗口—长度最小的子数组

&#x1f4bb;文章目录 &#x1f4c4;题目✏️题目解析 & 思路&#x1f4d3;总结 &#x1f4c4;题目 209. 长度最小的子数组 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl1, …,…

【UE 材质】角色触碰空气墙效果

效果 步骤 1. 新建一个工程&#xff0c;创建一个Basic关卡&#xff0c;添加一个第三人称游戏资源到内容浏览器 2. 新建一个材质参数集&#xff0c;这里命名为“MPC_Vector” 打开“MPC_Vector”&#xff0c;添加一个向量参数 3. 新建一个材质&#xff0c;这里命名为“M_Wall”…

力扣每日一题:2132. 用邮票贴满网格图(2023-12-14)

力扣每日一题 题目&#xff1a;2132. 用邮票贴满网格图 日期&#xff1a;2023-12-14 用时&#xff1a;38 m 32 s 思路&#xff1a;使用前缀和&#xff0b;差分&#xff0c;只是往常是一维&#xff0c;现在变二维了&#xff0c;原理差不多 时间&#xff1a;22ms 内存&#xff1…

运行和部署若依分离版前端

一、运行 一、用vscode打开 二、安装依赖 # 建议不要直接使用 cnpm 安装依赖&#xff0c;会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 npm install --registryhttps://registry.npmmirror.com# 启动服务 npm run dev浏览器访问 http://localhost:80二、部…

数据库的优化

1、创建index索引&#xff1b; 2、减少select * 的使用&#xff0c;具体一点&#xff0c;全取会增加web服务器的负担&#xff1b; 3、explain select&#xff1a;显示了mysql如何使用索引来处理select语句以及连接表&#xff1b; 4、度多使用varchar/nvarchar&#xff0c;变长…