我们经常开发过程中经常会听到线程和进程,在讲述Android进程多进程前我打算先简单梳理一下这俩者。
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内的不同进程的实例是互相独立,互不影响。
在 Linux 系统中,虚拟内存空间(我理解的就是运行软件程序的空间,是对物理空间的映射)只有 4G;最高的 1GB(对应虚拟地址0xC0000000到0xFFFFFFFF)被成为内核空间,而较低的 3GB(对应虚拟地址0x00000000到0xBFFFFFFF)被成为用户空间。内核空间是Linux内核运行空间,用户空间是应用程序的运行空间。如图所示:
不同进程之间,用户空间是不共享,但是内核空间是可以共享。
默认情况下,启动一个APP,仅仅启动了一个进程,该进程名为包名,那如何定义多进程呢? Android 提供了一种方式,就是在 AndroidManifest 文件中可以通过 “android:process” 来指定进程:
为什么一个 Android 应用要引入多进程?多进程有哪些应用场景呢?
通常在下面两种情况下需要引入多进程:
既然在 Android 中引入了多进程,而对于进程的用户空间不共享,那么多进程之间怎么通信呢?
这种多进程通信又称为IPC(Inter Process Communication)
对于IPC,并不是Android系统特有的,在Linux系统中就存在的跨进程通信,在Linux系统中常见的IPC方式有:
除去 Socket,其他的都是基于 Binder 机制实现的
在进程结构中也提到了用户空间是不共享的,并且每个进程都是对应单独的系统堆栈区、静态区等,那么多进程也引入了一些问题:
好了,到了这里我们对进程和线程应该有了基本认识,同时也知道了常用的跨进程通信方式,包括Android常用的几种IPC方式,接下来我们就对Android常用的几种IPC方式做一个深入的了解。
进程间通信(InterProcess Communication缩写IPC)是指在不同进程之间传播或交换信息。进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元。
IPC不是Android中所独有的,任何一个操作系统都需要有相应的IPC机制。在Android系统中一个进程会对应一个虚拟机实例,不同的虚拟机在内存分配上有不同的地址空间,所以:只有在多进程的环境下才需要考虑使用IPC进行通讯。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.test.process"
android:theme="@style/Theme.Sunflower">
application>
在Android中只有给四大组件(Activity Service Broadcast ContentProvider)设置android:process属性这一种使用多进程的方法。
<service
android: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”),且规定了进程为当前应用的私有进程,其他应用的组件不可以和它使用统一进程。后者则你写了什么,进程名就是什么,且为全局进程,其他应用也可以使用该进程。
虽然开启多进程的方法很简单,但是这种看似简单的操作稍有不深就会带来巨大的问题:
问题一:多进程会造成Application的多次创建:当一个组件需要运行在新的进程中时,实际的创建过程就相当于又重新启动了一次应用,应用启动,必然会创建Application。而运行在不同进程中的组件,不仅属于不同的虚拟机,而且其Application也是不同的。
问题二:多进程会导致静态成员和单例完全无效:由于不同进程都被分配了独立且不同的虚拟机,其在内存分配上有这不同的地址空间。这就会导致在着一个类的多个副本,各自修改互不影响。
问题三:多进程模式下,线程的同步机制也会失效:因为不同的进程,其线程不所属同一内存,那么无论是对象锁还是类锁,锁本身都不是同一个了。
问题四:多进程模式下SharedPreferences风险会增大:SharedPreferences底层是通过文件读写实现的,并发操作可能会造成问题。
总之:在不同进程中的组件,如果使用内存来通讯,都会存在隐患或者直接失败。这就是多进程带来的最主要的影响。
接下来讲解一下多进程间通讯的方式。
在Android开发中,我们通过Intent启动Activity、Service和Receiver都是可以通过Bundle传递参数的。它实现了Parcelable接口,并以键值对的方式保存数据。可以将其视为一个容器,其支持基本数据类型(string、int、boolean、byte、float、long、double)以及它们对应的数据。当需要传递对象或对象数组时,被传递的对象必须实现Serialiable或Parcelable接口。
接下来我们通过一个Activity和一个Service来看一下如何利用Bundle实现跨进程通讯。
首先,让二者运行在不同的进程中:
<activity
android:name=".process.TestActivity"/>
<service
android: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没有什么区别。
通过共享文件,不同的进程可以使用读写文件的方式交换数据。在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 = 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 {
}
}
注意⚠️:不要使用SharedPreferences去做跨进程通讯,原则上它不支持多进程。虽然它本质上也是一个文件,但是由于它在应用运行时会再内存中加载缓存,然而进程间是不能内存共享的,每个进程操作的SharedPreferences都会是一个单独的实例,这就会导致安全性问题,这个问题只能通过多进程间其它的通信方式或者是在确保不会同时操作SharedPreferences数据的前提下使用SharedPreferences来解决。
ContentProvider提供一种应用管理其自身和其他应用所存储数据的能力,并提供与其他应用共享这些数据的方法。它会封装数据,并提供用于定义数据安全性的机制。无论你是否需要和其他应用分享数据,你都可以使用ContentProvider去访问这些数据,虽然当无需和其他应用共享数据时没必要使用它。系统预置了许多ContentProvider,比如通话记录,通讯录,信息等,只需要通过ContentProvider就可以拿到这些信息。ContentProvider以一个或多个表的形式将数据呈现给外部应用,这些表与关系型数据库中的表类似。行表示提供程序收集的某种类型数据的实例,行中的每一列表示为一个实例所收集的单个数据。
通常有两个典型的使用场景:一种是通过实现代码访问其他应用中的现有的ContentProvider用来获得数据;另一种是创建新的ContentProvider,与其他应用共享数据。
下面我们用一个最简单的例子,演示一下使用它进行跨进程通讯。为了方便演示,我们在同一个应用内进行:
首先创建一个ContentProvider。代码很简单,就是一个使用Room的数据持久化,只做了insert和查询的逻辑处理。
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
}
}
接下来在配置文件里配置它,并指明其所在的进程process。
<provider
android: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只支持增删改查,更像是一个跨进程的数据库。
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 (MessengerActivity):
class MessengerActivity : 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)内容如下:
/* //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也有明显的缺点:首先,服务端是以串行的方式在处理消息,不太适合用来出来大量的并发请求。其次,它的主要作用是传递消息,不太适合用来跨进程调用方法。
通过上面,我们知道Messenger实现跨进程通讯,其本质上也是基于AIDL实现的,这一节我们使用AIDL自己实现一个Messenger,用来演示AIDL的使用以及穿插者学习Messenger的工作原理,为了简化代码。我们并不直接使用Message作为我们传递的数据,而是使用一个Bean作为传递的数据。其核心代码如下:
package com.hirzy.test.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中,并不是所有的数据类型都是可以使用,它支持一下数据类型:
所以我们的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.
*
* 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.
*/
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}.
*
* 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 flag
sPoolSize--;
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 target member on the Message returned.
* @param h Handler to assign to the returned Message object's target 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 target 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 target and
* what members on the Message.
* @param h Value to assign to the target member.
* @param what Value to assign to the what 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 target, what, and obj
* members.
* @param h The target value to set.
* @param what The what value to set.
* @param obj The object 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 target, what,
* arg1, and arg2 members.
*
* @param h The target value to set.
* @param what The what value to set.
* @param arg1 The arg1 value to set.
* @param arg2 The arg2 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 target, what,
* arg1, arg2, and obj members.
*
* @param h The target value to set.
* @param what The what value to set.
* @param arg1 The arg1 value to set.
* @param arg2 The arg2 value to set.
* @param obj The obj 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.
*
* 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.
*
*/
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.
*/
@UnsupportedAppUsage
void 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 target {@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 */
@UnsupportedAppUsage
public 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.
*
* 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.
*
* 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.
*
* 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.
*
*
* @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() {
}
@Override
public String toString() {
return toString(SystemClock.uptimeMillis());
}
@UnsupportedAppUsage
String 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:
接下来创建服务端代码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: 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.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!!.age
Thread.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()}")
}
}
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
{
}
@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.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 是一种进程间通信机制,基于开源的 OpenBinder 实现。
当应用中使用多进程通讯时就需要Binder。而由于虚拟机分配给各个进程的运行内存是有限制的,同时LMK也会优先回收占用资源较多的进程。所以我们可以运用多进程:
同时,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相比它们具有以下优势:
在Linux中,使用的虚拟内存寻址方式:
Binder借助了内存映射的方法,在内核空间和接收方用户空间的数据缓存区之间做了一层内存映射,就相当于直接拷贝到了接收方用户空间的数据缓存区,从而减少了一次数据拷贝。
由于个人对Binder的了解也不多,这里只对其基本概念做一个简单的描述。想更深入的学习Binder机制,推荐去看:Android Binder设计与实现 - 设计篇,介绍的非常详细。
我认识了进程和线程的区别
也知道了常用了Android IPC通信方式
注意⚠️:除去 Socket,其他的都是基于 Binder 机制实现的
常见多进程通信IPC(Inter Process Communication)有如下几种
Android引入多进程的目的