目录
1)为什么App需要多进程
2)什么是多进程开发?
3)如何实现多进程开发?
4)跨进程间通讯(案例)
5)多进程需要注意什么问题?
6)多进程的底层原理是什么?【待写】
一、为什么App需要多进程?
某些应用场景下,单个进程可能无法满足需求,通过将任务分配到不同的进程中并行处理,可以提高系统整体的性能和响应速度。
在Android中,虚拟机分配给各个进程的运行内存是有限制值的,多进程app可以在系统中申请多份内存。
通过创建独立的后台进程,可以确保应用在主进程被杀死或处于台时仍然能够执行一些必要的任务,如推送、消息接收等。
很多app都已经开始使用多进程,比如:推送,保活,插件化,内存不够用,webview,闹钟,电话等等。
二、什么是多进程开发?
多进程是指一个应用程序可以同时运行在多个独立的进程中。每个进程都有自己独立的虚拟机实例和资源管理器,并且它们之间相互隔离。一个应用可以有多个进程,就有多个dalivk虚拟机,对应多个内存空间。
默认情况下,Android应用程序在同一个进程中运行,即单进程模式。这意味着应用程序的所有组件(Activity、Service、BroadcastReceiver等)都在同一个进程中执行。但是,通过配置AndroidManifest.xml文件中的android:process属性,开发者可以为特定的组件或整个应用程序指定不同的进程名称,从而实现多进程开发。
三、如何实现多进程开发以及通讯?
默认情况下,启动一个APP,仅仅启动了一个进程,该进程名为包名,那如何定义多进程呢? Android 提供了一种方式,就是在 AndroidManifest 文件中可以通过 “android:process” 来指定进程:
- 不指定 process: 默认的进程,进程名为包名
- 指定 process,但以":"开头: 该进程为当前APP的私有进程,不允许其他APP访问
- 指定 process,但以小写字母开头的字符串: 该进程为全局进程 ,其他应用可设置相同的shareUID来共享该进程
场景
当应用的一部分需要高安全性或高稳定性时,比如支付模块或敏感数据处理模块。这里我将给出一个简化的案例:一个包含普通功能和支付功能的应用,我们将支付功能放在单独的进程中以提高安全性。
步骤一:定义支付进程的Service
<service
android:name=".PaymentService"
android:process=":payment"
android:exported="false" />
步骤二:创建PaymentService
public class PaymentService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 接收来自其他组件的数据
String orderId = intent.getStringExtra("orderId");
// 模拟支付逻辑
boolean isSuccess = performPayment(orderId);
// 通知结果(实际开发中可能需要通过广播、AIDL等方式)
// 这里为了简化,我们假设有某种方式通知UI层
return START_NOT_STICKY;
}
private boolean performPayment(String orderId) {
// 这里实现具体的支付逻辑
// 例如调用第三方支付SDK等
return true; // 假设支付成功
}
@Override
public IBinder onBind(Intent intent) {
// 如果需要,可以返回IBinder实现IPC
return null;
}
}
步骤三:从主进程启动PaymentService
Intent intent = new Intent(this, PaymentService.class);
intent.putExtra("orderId", "123456789");
startService(intent);
如果支付服务需要返回结果给主进程,或者主进程需要向支付服务发送指令,下面我们需要学习一下跨进程通信(IPC)
四、跨进程间通讯
Android中支持的多进程通信方式主要有以下几种,它们之间各有优缺点,可根据使用场景选择选择:
1)BroadcastReceiver:即广播,但只能单向通信,接收者只能被动的接收消息。
2)Binder:是Android系统内部使用的一种高效IPC机制,基于C/S架构,通过内存映射实现高效的进程间通信。高效性,数据传输速度快。支持复杂的数据类型传递
3)ContentProvider:是Android系统中一种轻量级的跨进程通信方式,主要用于在不同应用程序之间共享数据。
4)AIDL:是Android系统中用于定义跨进程通信接口的一种语言,它允许定义可在不同进程间共享的服务接口。
这里我们主要介绍Binder。
4.1 Binder
优点
解决安全问题,内存不够的问题。
1)比如加载图片的时候,出现崩溃,导致安全问题出现。如果用子线程进行加载,那么即使崩溃了也不影响主线程。在比如微信小程序就是另外进程,不能影响微信。
2)为什么手机运行内存8G,16G,加载一张大图就会导致内存不够用呢?难道他有几G那么大?并不是,因为每个app运行,根据手机的不同,进程可以分到几十兆,或者几百兆的内存空间(如下图)。所以多个进程,那么内存就多了。
内存划分
进程之间的内存是相互隔离的。
那么如何才能实现进程之间相互通讯?
Android 为什么要增加Binder?
传统的方式,需要进行两次拷贝。
Binder拷贝:
内存映射到底是啥?虚拟地址映射到物理地址,而MMAP,就是将用户空间映射到内存空间。不需要拷贝了。通过快捷方式进行举例。
4.2 AIDL
是什么?简化调用Binder的流程。因为Binder的规则还是比较复杂的,而AIDL可以直接生成这套规则。这就是他的作用。
接下来我们就使用AIDL实现进程间通讯。比如说,两个进程,A进程通过发送一个字符串,B进程收到后进行对应的逻辑处理。
(1)打开aidl,不打开,生成不了aidl文件
先在build.gradle文件里面添加一个
android {
...
buildFeatures.aidl = true
...
}
(2)步骤 1: 定义AIDL接口
创建一个AIDL文件夹
创建AIDL文件
// IMessageService.aidl
package com.example.myapplicationa;
// Declare any non-default types here with import statements
interface IMessageService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
String sendMessage(String message);
}
(3)步骤 2: 实现AIDL接口
package com.example.myapplicationa
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log
import java.util.Locale
class MessageService : Service() {
private val mBinder: IMessageService.Stub = object : IMessageService.Stub() {
override fun sendMessage(message: String): String {
// 这里可以添加一些处理逻辑,比如转换大小写等
Log.d("AIDL A", "收到 sendMessage: ")
return "Processed: " + message.uppercase(Locale.getDefault())
}
}
override fun onBind(intent: Intent): IBinder? {
return mBinder
}
}
在AndroidManifest.xml中声明这个Service,并指定它运行在多进程中:
<service
android:name=".MessageService"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="com.example.myapplicationa.IMessageService" />
</intent-filter>
</service>
(4)步骤 3: 在App B中绑定Service
将IMessageService.aidl文件复制到App B的src/main/java/com/example/myapplicationa目录下。
在App B中创建一个ServiceConnection来绑定到App A的Service:
package com.example.myapplicationb
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.os.RemoteException
import android.util.Log
import androidx.activity.ComponentActivity
import com.example.myapplicationa.IMessageService
class MainActivity : ComponentActivity() {
private var mMessageService: IMessageService? = null
private var mIsBound = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
private val mConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
mMessageService = IMessageService.Stub.asInterface(service)
try {
val response = mMessageService!!.sendMessage("Hello from App B")
// 处理响应
Log.d("AIDL B", "Received response: $response")
} catch (e: RemoteException) {
e.printStackTrace()
}
mIsBound = true
}
override fun onServiceDisconnected(arg0: ComponentName) {
mMessageService = null
mIsBound = false
}
}
override fun onStart() {
super.onStart()
val intent = Intent()
intent.setComponent(ComponentName("com.example.myapplicationa", "com.example.myapplicationa.MessageService"))
bindService(intent, mConnection, BIND_AUTO_CREATE)
}
override fun onStop() {
super.onStop()
if (mIsBound) {
unbindService(mConnection)
mIsBound = false
}
}
}
运行程序,如果收到这个log,就说明成功了。
好了,以上就是AIDL的简单使用。
五、多进程需要注意什么问题?
1、静态成员和单例模式完全失效 。Android 为每一个进程分配一个独立的虚拟机,不同虚拟机在内存分配上有不同地址空间,这就导致多进程下访问同一个类的对象会产生多分副本。所以在一个进程中修改某个值,只会在当前进程有效,对其他进程不会造成任何影响。
2、线程同步机制完全失效。因为多进程的内存地址空间不同,锁的不是同一个对象,所以不管是锁对象还是锁全局对象都无法保证线程同步。
3、SharedPreferences 的可靠性下降。因为SharedPreferences 底层通过读写XML实现,并发读写显然是不安全的操作,甚至会出现数据错乱。
4、Application 会多次创建。
5.1 Application 会多次创建 的解决方法
在Application的onCreate中获取进程Id来判断不同进程,然后做不同的事情。
public class MyApplication extends BaseApplication {
public static MyApplication instances;
@Override
public void onCreate() {
super.onCreate();
instances = this;
if (isAppMainProcess()) {
.....
}
.....
}
}
public static boolean isAppMainProcess() {
try {
int pid = android.os.Process.myPid();
String process = getAppNameByPID(instances, pid);
if (TextUtils.isEmpty(process)) {
return true;
} else if ("com.xxx.xxx".equalsIgnoreCase(process)) {
return true;
} else {
return false;
}
} catch (Exception e) {
e.printStackTrace();
return true;
}
}