马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
一、Android IPC 基础概念
在 Android 开辟中,IPC(Inter-Process Communication)即历程间通信,是指在不同历程之间进行数据交互和通信的机制。由于 Android 系统的每个应用都运行在独立的历程中,且拥有独立的内存空间,当应用需要与其他应用或系统服务进行通信时,就需要利用 IPC。比方,一个音乐播放应用大概需要与系统的音频服务进行通信,以实现播放、停息、切换歌曲等利用,这就涉及到了 IPC。
1.1 历程与线程
历程是程序的一次动态执行过程,对应从代码加载、执行至执行完毕的完整过程,也是历程自己从产生、发展至消亡的过程。每个历程都有自己独立的内存空间和系统资源,其内部数据和状态完全独立。比方,当我们打开手机上的微信应用时,系统就会为微信创建一个历程,该历程拥有独立的内存空间,用于存储微信运行所需的数据和代码。
线程是历程中执行运算的最小单元,一个历程可以包含多个线程。线程必须在某个历程内执行,是历程内部的一个执行单元,可完成一个独立任务的顺序控制流程。比如,在微信中,大概有一个线程负责吸取和处理网络消息,另一个线程负责更新界面显示,这些线程都运行在微信历程中。
历程和线程的关系紧密。一个历程至少要有一个线程,资源分配给历程,同一历程的所有线程共享该历程的所有资源,而处理机实际分配给线程,即真正在处理机上运行的是线程。比方,在一个视频播放应用中,播放视频的任务大概由一个线程负责,而更新播放进度条的任务由另一个线程负责,它们都共享视频播放历程的资源,如内存、文件句柄等。多线程程序可以带来更好的用户体验,避免因程序执行过慢而导致盘算机出现死机大概白屏的情况,还能最大限度地提高盘算机系统的利用服从,如迅雷的多线程下载,通过多个线程同时下载文件的不同部分,加快下载速率。
1.2 多历程模式
多历程模式指的是一个应用中存在多个历程的情况。在 Android 中,开启多历程模式紧张通过在 AndroidManifest.xml 文件中为四大组件(Activity、Service、Receiver、ContentProvider)指定 android:process 属性来实现。比方:
- <activity android:name=".MainActivity" />
- <activity android:name=".SecondActivity" android:process=":remote" />
- <service android:name=".TestService" android:process="com.example.remote" />
复制代码 上述代码中,SecondActivity 指定了 android:process=“:remote”,表示它运行在一个名为 “:remote” 的私有历程中,而 TestService 指定了 android:process=“com.example.remote”,表示它运行在一个名为 “com.example.remote” 的全局历程中。历程名以 “:” 开头的历程属于当前应用的私有历程,其他应用的组件不能和它运行在同一个历程中;不以 “:” 开头的历程属于全局历程,其他应用通过 shareUID 方式可以和它跑在同一个历程中。两个应用通过 ShareUID 运行在同一个历程中,需要满意两个应用有相同的 UID 并且签名相同。
多历程模式的运行机制是,Android 会为每个历程分配一个独立的虚拟机,不同虚拟机在内存分配上有不同的地址空间。这就导致在不同虚拟机中访问同一个类的对象会产生多个副本,运行在不同历程中的四大组件,只要它们之间通过内存来共享数据,都会共享失败。比方,在一个应用中,有一个类的静态成员变量,在一个历程中修改了这个变量的值,在另一个历程中访问这个变量时,其值并不会改变,由于它们处于不同的历程空间。利用多历程还会造成静态成员和单例模式完全失效、线程同步机制完全失效、SharePreference 的可靠性下降以及 Application 会多次创建等问题 。如在一个多历程应用中,本来的单例模式无法包管在不同历程中只有一个实例,线程同步机制在不同历程中也无法发挥作用,由于不同历程锁的不是同一个对象。
二、IPC 基础概念与序列化
在进行 IPC 通信时,数据的通报是必不可少的。而在通报复杂对象时,就需要对对象进行序列化,将对象转换为字节省,以便在不同历程之间传输。Android 中紧张有两种序列化方式,分别是实现 Serializable 接口和 Parcelable 接口。
2.1 Serializable 接口
Serializable 接口是 Java 提供的一个序列化接口,它是一个空接口,仅用于标识一个类可以被序列化。当一个类实现了 Serializable 接口,就表示该类的对象可以被转换为字节序列,从而实现对象的存储和传输。其紧张作用包括:
- 对象存储:将对象的状态保存到存储介质(如文件)中,以便在需要时可以重新创建出相同状态的对象。比方,在一个游戏应用中,游戏的当前进度、玩家的脚色信息等对象可以通过 Serializable 接口进行序列化并保存到当地文件,下次玩家打开游戏时可以从文件中读取并反序列化这些对象,恢复游戏的状态。
- 对象传输:在网络通信中,将对象转换为字节省进行传输。比如,在一个外交应用中,用户的个人信息对象(包含用户名、头像、简介等)需要通过网络发送给服务器进行存储或其他处理,就可以利用 Serializable 接口将该对象序列化后在网络上传输。
利用 Serializable 接口非常简单,只需在类的定义中声明实现该接口即可。比方:
- import java.io.Serializable;
- public class User implements Serializable {
- private static final long serialVersionUID = 6477564458830772334L;
- private int userId;
- private String userName;
- private int age;
- public User(int userId, String userName, int age) {
- this.userId = userId;
- this.userName = userName;
- this.age = age;
- }
- @Override
- public String toString() {
- return "User=[userName=" + userName + ",age=" + age + ",userId=" + userId + "]";
- }
- }
复制代码 在上述代码中,User 类实现了 Serializable 接口,并声明了一个 serialVersionUID。serialVersionUID 是一个序列化版本号,用于在反序列化时验证类的版本兼容性。如果在序列化和反序列化过程中,类的 serialVersionUID 不同等,会抛出 InvalidClassException 异常。可以利用自动天生的 serialVersionUID,也可以手动指定,如上述代码中手动指定为 6477564458830772334L。
接下来是对象的序列化和反序列化示例代码:
- import java.io.*;
- public class SerializableTest {
- public static void main(String[] args) {
- // 序列化
- User user = new User(1, "test", 20);
- File file = new File("user.txt");
- try {
- ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
- out.writeObject(user);
- out.close();
- System.out.println("序列化成功:");
- } catch (IOException e) {
- e.printStackTrace();
- System.out.println("IOException:" + e.toString());
- }
- // 反序列化
- try {
- ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
- User user1 = (User) in.readObject();
- in.close();
- System.out.println("反序列化成功:" + user1.toString());
- } catch (IOException e) {
- e.printStackTrace();
- System.out.println("反序列化失败IOException:" + e.toString());
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- System.out.println("反序列化失败ClassNotFoundException:" + e.toString());
- }
- }
- }
复制代码 在上述代码中,起首创建了一个 User 对象,然后通过 ObjectOutputStream 将其序列化并写入到文件 user.txt 中。接着,通过 ObjectInputStream 从文件中读取字节省并反序列化为 User 对象,从而实现了对象的存储和恢复。
2.2 Parcelable 接口
Parcelable 接口是 Android 特有的序列化接口,紧张用于在 Android 组件(如 Activity、Service 等)之间通报数据,尤其是在 Intent 通报对象和 IPC 通信中利用。它的设计目标是为了提高序列化和反序列化的服从,实用于在内存中进行数据通报的场景。
利用 Parcelable 接口需要实现以下几个方法:
- describeContents:该方法用于描述对象的内容,返回值通常为 0,只有当对象中包含文件描述符时才返回 1。比方:
- @Override
- public int describeContents() {
- return 0;
- }
复制代码
- writeToParcel:该方法用于将对象的属性写入 Parcel 对象中,实现对象的序列化。在写入时,需要按照一定的顺序将属性写入,以便在反序列化时能够正确读取。比方:
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(userId);
- dest.writeString(userName);
- dest.writeInt(age);
- }
复制代码
- CREATOR:这是一个静态常量,用于创建和反创建对象。它需要实现 Parcelable.Creator 接口中的两个方法:createFromParcel用于从 Parcel 中读取数据并创建对象,newArray用于创建指定长度的对象数组。比方:
- public static final Creator<User> CREATOR = new Creator<User>() {
- @Override
- public User createFromParcel(Parcel source) {
- int userId = source.readInt();
- String userName = source.readString();
- int age = source.readInt();
- return new User(userId, userName, age);
- }
- @Override
- public User[] newArray(int size) {
- return new User[size];
- }
- };
复制代码 下面是一个完整的实现 Parcelable 接口的示例:
- import android.os.Parcel;
- import android.os.Parcelable;
- public class User implements Parcelable {
- private int userId;
- private String userName;
- private int age;
- public User(int userId, String userName, int age) {
- this.userId = userId;
- this.userName = userName;
- this.age = age;
- }
- // 从Parcel中读取数据并创建对象
- protected User(Parcel in) {
- userId = in.readInt();
- userName = in.readString();
- age = in.readInt();
- }
- @Override
- public int describeContents() {
- return 0;
- }
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(userId);
- dest.writeString(userName);
- dest.writeInt(age);
- }
- public static final Creator<User> CREATOR = new Creator<User>() {
- @Override
- public User createFromParcel(Parcel source) {
- return new User(source);
- }
- @Override
- public User[] newArray(int size) {
- return new User[size];
- }
- };
- public int getUserId() {
- return userId;
- }
- public void setUserId(int userId) {
- this.userId = userId;
- }
- public String getUserName() {
- return userName;
- }
- public void setUserName(String userName) {
- this.userName = userName;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- @Override
- public String toString() {
- return "User=[userName=" + userName + ",age=" + age + ",userId=" + userId + "]";
- }
- }
复制代码 在 Activity 中利用 Parcelable 通报对象的示例代码如下:
- // 在发送方Activity中
- Intent intent = new Intent(this, SecondActivity.class);
- User user = new User(1, "test", 20);
- intent.putExtra("user", user);
- startActivity(intent);
- // 在接收方Activity中
- User receivedUser = getIntent().getParcelableExtra("user");
- if (receivedUser!= null) {
- System.out.println("接收的用户信息:" + receivedUser.toString());
- }
复制代码 在上述代码中,在发送方 Activity 中创建了一个 User 对象,并通过 Intent 将其通报给吸取方 Activity。在吸取方 Activity 中,通过getIntent().getParcelableExtra(“user”)获取通报过来的 User 对象,实现了对象在不同 Activity 之间的通报。
2.3 两者对比
Serializable 接口和 Parcelable 接口各有优缺点,在不同的场景下应选择不同的序列化方式。
- 性能方面:Parcelable 的性能要优于 Serializable。Parcelable 是基于 Android 的 Parcel 机制实现的,它在序列化和反序列化过程中直接利用内存利用,不需要进行大量的 I/O 利用,因此速率更快。而 Serializable 利用 Java 的默认序列化机制,在序列化和反序列化时会产生大量的临时变量,引起频仍的 GC 利用,性能开销较大。比方,在一个需要频仍在 Activity 之间通报对象的应用中,如果利用 Serializable 接口,由于频仍的 GC 利用,大概会导致应用的卡顿,而利用 Parcelable 接口则可以避免这种情况,提高应用的流畅性。
- 实现难度方面:Serializable 接口的实现非常简单,只需在类中声明实现该接口并添加 serialVersionUID 即可,对开辟者的要求较低。而 Parcelable 接口的实现相对复杂,需要手动实现describeContents、writeToParcel方法以及CREATOR常量,编写的代码量较多,对开辟者的要求较高。比如,对于一个简单的 JavaBean 类,利用 Serializable 接口大概只需要添加两行代码,而利用 Parcelable 接口则需要编写多个方法和常量。
- 利用场景方面:Parcelable 紧张用于在 Android 内存中进行数据通报,如在 Intent 通报对象、Binder 通信等场景中,由于它的高效性可以满意对性能要求较高的场景。而 Serializable 更实用于将对象持久化到存储设备(如文件)或通过网络进行传输,由于它是 Java 标准的序列化接口,通用性更强,并且在这些场景下对性能的要求相对较低。比方,在将用户的设置信息保存到当地文件时,可以利用 Serializable 接口,由于文件的读写利用自己就比力耗时,Serializable 接口的性能劣势在这种情况下影响不大;而在 Activity 之间通报一个包含大量数据的对象时,为了提高服从,应利用 Parcelable 接口。
总的来说,在 Android 开辟中,如果是在内存中进行数据通报,优先选择 Parcelable 接口;如果是进行对象的持久化存储或网络传输,应选择 Serializable 接口。
三、Android IPC 的实现方式
3.1 Binder 机制
Binder 机制是 Android 系统中一种基于 C/S 架构的历程间通信(IPC)机制,它答应不同历程间进行通信。这种机制的焦点在于其独特的驱动层实现,简化了历程间通信的复杂性,使得跨历程通信看起来就像是进行当地方法调用一样简单 。
在 Binder 机制中,存在服务端和客户端两个紧张脚色。服务端会实现一个 Binder 对象,该对象包含了服务端可以提供给客户端调用的方法接口。然后,服务端通过 ServiceManager 将这个 Binder 对象注册为一个服务,如许客户端就可以通过 ServiceManager 查询到这个服务,并获取到 Binder 对象的代理(Proxy)。客户端通过这个代理对象,就可以像调用当地方法一样调用服务端提供的方法,从而实现历程间的通信。
Binder 机制的工作原理可以概括为以下几个步调:
- 服务端创建并实现 Binder 对象:服务端定义好可以提供给客户端调用的方法接口,创建一个 Binder 对象来实现这些接口。比方,一个音乐播放服务的服务端,会创建一个 Binder 对象,实现播放、停息、切换歌曲等方法接口。
- 服务端注册服务:服务端通过 ServiceManager 将 Binder 对象注册为一个服务,将自己袒露给客户端。比如,音乐播放服务的服务端将创建好的 Binder 对象注册到 ServiceManager,告知 ServiceManager 自己可以提供音乐播放相干的服务。
- 客户端查询服务并获取代理:客户端通过 ServiceManager 查询所需服务,并获取到 Binder 对象的代理。假设一个音乐播放应用的客户端需要利用音乐播放服务,它就会通过 ServiceManager 查询音乐播放服务,并获取到对应的 Binder 代理对象。
- 客户端调用服务端方法:客户端通过代理对象调用服务端的方法,完成历程间通信。客户端拿到代理对象后,就可以像调用当地方法一样调用音乐播放服务的播放、停息等方法,代理对象会将这些调用转发给服务端的 Binder 对象,从而实现跨历程通信。
Binder 机制在 Android 系统中的应用非常广泛,它不仅是应用历程与系统服务历程进行通信的基础,也是实现四大组件(Activity、Service、BroadcastReceiver、ContentProvider)之间通信的紧张手段。比方,当一个应用启动一个 Service 时,就大概通过 Binder 机制与 Service 进行通信,通报参数、获取服务状态等。在系统层面,很多系统服务如 ActivityManagerService、WindowManagerService 等都是通过 Binder 机制与应用历程进行交互的。
Binder 机制的优点明显:
- 高性能:Binder 接纳了内存映射(mmap)技能,减少了数据拷贝次数,提高了通信服从。在传统的历程间通信方式中,如管道、Socket 等,数据需要在用户空间和内核空间之间多次拷贝,而 Binder 通过内存映射,在内核空间创建数据吸取的缓存空间,使得数据可以直接从发送方历程的用户空间映射到吸取方历程的用户空间,减少了数据拷贝的开销,从而提高了通信服从。
- 安全性高:Binder 机制具有系统内置的权限管理,可以确保应用和服务只能访问它们答应访问的数据。在 Binder 通信过程中,每个历程都有自己的 UID 和 PID,Binder 驱动会根据这些信息对通信进行权限验证,只有具有相应权限的历程才华进行通信和访问数据,这有效防止了非法访问和数据泄露,保障了系统的安全性。
- 面向对象:Binder 机制基于接口定义,接纳面向对象的方式进行设计,使得通信接口更加清晰、易于理解和维护。开辟者可以像定义普通的 Java 接口一样定义 Binder 接口,通过接口来规范服务端和客户端之间的通信,提高了代码的可维护性和可扩展性。
然而,Binder 机制也存在一些缺点:
- 学习成本较高:Binder 机制涉及到利用系统内核、驱动等底层知识,其原理和利用相对复杂,对于开辟者来说,理解和把握 Binder 机制需要花费一定的时间和精神,学习成本较高。
- 实现复杂度高:Binder 机制的实现涉及到多个组件和复杂的交互过程,如 ServiceManager、Binder 驱动、客户端和服务端等,在实际开辟中,实现一个基于 Binder 的通信功能需要编写较多的代码,处理各种细节问题,实现复杂度较高。比方,在编写服务端代码时,需要创建 Binder 对象、实现接口方法、注册服务等;在客户端代码中,需要查询服务、获取代理对象、处理远程调用的异常等,这些都增长了开辟的难度和工作量。
3.2 AIDL(Android Interface Definition Language)
AIDL(Android Interface Definition Language)即 Android 接口定义语言,是一种用于在 Android 设备上实现两个历程之间进行历程通信的接口定义语言。它内部紧张通过 Binder 机制来实现,实用于历程之间交互频仍、通信数据量小的场景。在利用 AIDL 进行跨历程通信的时候,通常将请求通信的一方称之为客户端(Client),客户端的紧张工作就是发送数据;而吸取通信数据的一方称之为服务端(Server),服务端紧张工作是处理客户端发送过来的数据,并通过回调(Callback)的方式返回客户端数据,实现双向通信。
AIDL 支持以下数据类型:
- Java 的 8 种基本类型:即 int、long、char、short、byte、boolean、float、double。
- String 和 CharSequence:这两种常用的字符串类型也在 AIDL 支持范围内。
- List:其中的每个元素都必须是 AIDL 支持的。客户端实际吸取的具体类始终是 ArrayList,但天生的方法利用的是 List 接口。比方,可以定义一个 List类型的参数在 AIDL 接口中通报。
- Map:其中的每个元素都必须是 AIDL 支持的。客户端实际吸取的具体类始终是 HashMap,但天生的方法利用的是 Map 接口。比如,可以利用 Map<String, Integer> 类型在 AIDL 中进行数据通报。
- Parcelable:必须要显式 import,即使它跟.aidl 是同一个包下。当需要通报自定义的复杂对象时,可通过实现 Parcelable 接口来使其能在 AIDL 中利用。
- AIDL 接口:必须要显式 import,即使它跟.aidl 是同一个包下。在 AIDL 中可以定义接口,实现更复杂的通信逻辑。
AIDL 中的方法可有零、一或多个参数,可有返回值或 void。除了基本数据类型(默以为 in,不能是其他方向),其他类型的参数必须标上方向:
- in:表示输入型参数,由客户端赋值,服务端吸取并利用该参数。
- out:表示输出型参数,由服务端赋值,客户端吸取服务端返回的该参数值。
- inout:表示输入输出型参数,可由客户端或服务端赋值,既可以作为输入参数通报给服务端,也可以在服务端处理后作为输出参数返回给客户端。
下面通过一个具体的代码示例来展示如何利用 AIDL 实现跨历程通信:
- 定义 AIDL 文件:在项目标 main 目录下创建 aidl 文件夹,然后在其中新建一个.aidl 文件,比方定义一个名为 IMyAidlInterface.aidl 的文件,内容如下:
- // IMyAidlInterface.aidl
- package com.example.aidl;
- // 声明任何非默认类型都需要导入
- import com.example.aidl.User;
- interface IMyAidlInterface {
- void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString);
- // 自定义方法,接收一个User对象
- void sendUser(User user);
- }
复制代码 上述代码中,定义了一个 AIDL 接口 IMyAidlInterface,包含一个系统自动天生的 basicTypes 方法(可根据需求保留或删除),以及一个自定义的 sendUser 方法,用于吸取一个 User 对象。其中 User 是一个自定义的实现了 Parcelable 接口的类,用于在历程间通报数据。
- 天生 AIDL 对应的 Java 文件:定义好 AIDL 文件后,点击 Make Project 按钮,Android Studio 会自动天生对应的 Java 文件。在天生的 Java 文件中,焦点部分是一个继承自 Binder 的 Stub 抽象类,它是实现 AIDL 接口的关键。比方,天生的部分代码如下:
- public interface IMyAidlInterface extends android.os.IInterface {
- public static abstract class Stub extends android.os.Binder implements IMyAidlInterface {
- private static final java.lang.String DESCRIPTOR = "com.example.aidl.IMyAidlInterface";
- public Stub() {
- this.attachInterface(this, DESCRIPTOR);
- }
- public static IMyAidlInterface asInterface(android.os.IBinder obj) {
- if ((obj == null)) {
- return null;
- }
- android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
- if (((iin!= null) && (iin instanceof IMyAidlInterface))) {
- return ((IMyAidlInterface) iin);
- }
- return new IMyAidlInterface.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 {
- switch (code) {
- case INTERFACE_TRANSACTION: {
- reply.writeString(DESCRIPTOR);
- return true;
- }
- case TRANSACTION_basicTypes: {
- data.enforceInterface(DESCRIPTOR);
- int _arg0;
- _arg0 = data.readInt();
- long _arg1;
- _arg1 = data.readLong();
- boolean _arg2;
- _arg2 = data.readInt()!= 0;
- float _arg3;
- _arg3 = data.readFloat();
- double _arg4;
- _arg4 = data.readDouble();
- java.lang.String _arg5;
- _arg5 = data.readString();
- this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
- reply.writeNoException();
- return true;
- }
- case TRANSACTION_sendUser: {
- data.enforceInterface(DESCRIPTOR);
- com.example.aidl.User _arg0;
- if ((0!= data.readInt())) {
- _arg0 = com.example.aidl.User.CREATOR.createFromParcel(data);
- } else {
- _arg0 = null;
- }
- this.sendUser(_arg0);
- reply.writeNoException();
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
- private static class Proxy implements IMyAidlInterface {
- 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 basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) 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(anInt);
- _data.writeLong(aLong);
- _data.writeInt(aBoolean? 1 : 0);
- _data.writeFloat(aFloat);
- _data.writeDouble(aDouble);
- _data.writeString(aString);
- mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
- _reply.readException();
- } finally {
- _reply.recycle();
- _data.recycle();
- }
- }
- @Override
- public void sendUser(com.example.aidl.User user) throws android.os.RemoteException {
- android.os.Parcel _data = android.os.Parcel.obtain();
- android.os.Parcel _reply = android.os.Parcel.obtain();
- try {
- _data.writeInterfaceToken(DESCRIPTOR);
- if (user!= null) {
- _data.writeInt(1);
- user.writeToParcel(_data, 0);
- } else {
- _data.writeInt(0);
- }
- mRemote.transact(Stub.TRANSACTION_sendUser, _data, _reply, 0);
- _reply.readException();
- } finally {
- _reply.recycle();
- _data.recycle();
- }
- }
- }
- static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
- static final int TRANSACTION_sendUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
- }
- void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws android.os.RemoteException;
- void sendUser(com.example.aidl.User user) throws android.os.RemoteException;
- }
复制代码
- 实现服务端:在服务端创建一个 Service,并在其中实现 AIDL 接口。比方:
- package com.example.aidlserver;
- import android.app.Service;
- import android.content.Intent;
- import android.os.IBinder;
- import android.os.RemoteException;
- import android.util.Log;
- public class AIDLService extends Service {
- private static final String TAG = "AIDLService";
- @Override
- public IBinder onBind(Intent intent) {
- return new MyBinder();
- }
- class MyBinder extends IMyAidlInterface.Stub {
- @Override
- public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
- Log.d(TAG, "basicTypes: anInt = " + anInt + ", aLong = " + aLong + ", aBoolean = " + aBoolean + ", aFloat = " + aFloat + ", aDouble = " + aDouble + ", aString = " + aString);
- }
- @Override
- public void sendUser(User user) throws RemoteException {
- if (user != null) {
- Log.d(TAG, "sendUser: user = " + user.toString());
- }
- }
- }
- }
复制代码 在上述服务端代码中,创建了一个 AIDLService,在 onBind 方法中返回一个实现了 IMyAidlInterface 接口的 MyBinder 对象。MyBinder 类重写了 AIDL 接口中的方法,在方法中对客户端通报过来的数据进行处理,这里只是简单地打印日志。
- 设置服务端 Service:在 AndroidManifest.xml 文件中注册服务端的 Service,如下:
- <service
- android:name=".AIDLService"
- android:enabled="true"
- android:exported="true">
- </service>
复制代码
- 实现客户端:在客户端绑定服务,并通过 AIDL 接口与服务端进行通信。比方:
- package com.example.aidlclient;
- 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 android.widget.Button;
- import android.widget.EditText;
- import android.widget.Toast;
- import androidx.appcompat.app.AppCompatActivity;
- import com.example.aidl.IMyAidlInterface;
- import com.example.aidl.User;
- public class MainActivity extends AppCompatActivity {
- private static final String TAG = "MainActivity";
- private IMyAidlInterface mAidlInterface;
- private ServiceConnection mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mAidlInterface = IMyAidlInterface.Stub.asInterface(service);
- try {
- mAidlInterface.basicTypes(1, 2L, true, 3.0f, 4.0, "Hello AIDL");
- User user = new User(1, "张三", 20);
- mAidlInterface.sendUser(user);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mAidlInterface = null;
- }
- };
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Button bindButton = findViewById(R.id.bind_button);
- bindButton.setOnClickListener(v -> {
- Intent intent = new Intent();
- intent.setComponent(new ComponentName("com.example.aidlserver", "com.example.aidlserver.AIDLService"));
- bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
- });
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (mServiceConnection!= null) {
- unbindService(mServiceConnection);
- }
- }
- }
复制代码 在客户端代码中,创建了一个 ServiceConnection 对象,在 onServiceConnected 方法中获取到服务端的 IMyAidlInterface 接口实例,然后通过该接口调用服务端的方法,通报基本数据类型和自定义的 User 对象。在 onCreate 方法中,点击绑定按钮时,通过 bindService 方法绑定服务端的 Service。在 onDestroy 方法中,排除对服务的绑定。
通过以上步调,就完成了利用 AIDL 实现跨历程通信的过程,客户端可以向服务端发送数据,服务端可以吸取并处理数据,实现了不同历程之间的交互。
3.3 Messenger
Messenger 是一种基于消息通报的历程间通信工具,它的底层实现是对 Binder 的一个简单封装,与 Handler 类似,可以用它来发送和处理消息 。
四、多历程模式与 IPC
4.1 多历程模式的开启
在 Android 中,开启多历程模式紧张通过在 AndroidManifest.xml 文件中为四大组件(Activity、Service、Receiver、ContentProvider)指定 android:process 属性来实现。比方:
- <activity android:name=".MainActivity" />
- <activity android:name=".SecondActivity" android:process=":remote" />
- <service android:name=".TestService" android:process="com.example.remote" />
复制代码 上述代码中,SecondActivity 指定了 android:process=“:remote”,表示它运行在一个名为 “:remote” 的私有历程中,而 TestService 指定了 android:process=“com.example.remote”,表示它运行在一个名为 “com.example.remote” 的全局历程中。历程名以 “:” 开头的历程属于当前应用的私有历程,其他应用的组件不能和它运行在同一个历程中;不以 “:” 开头的历程属于全局历程,其他应用通过 shareUID 方式可以和它跑在同一个历程中。两个应用通过 ShareUID 运行在同一个历程中,需要满意两个应用有相同的 UID 并且签名相同 。
4.2 多历程模式带来的问题
多历程模式虽然能办理一些特定的问题,但也会带来一系列的问题,紧张包括以下几个方面:
- 静态成员和单例模式失效:由于每个历程都有自己独立的虚拟机和内存空间,不同历程中访问同一个类的对象会产生多个副本。这就导致在一个历程中修改了类的静态成员变量或单例对象的状态,在其他历程中是无法感知到的,由于它们访问的是不同的副本。比方,在一个历程中定义了一个单例类来管理用户的登录状态,当在这个历程中用户登录成功后修改了单例对象中的登录状态,而在另一个历程中获取这个单例对象时,其登录状态仍然是未登录,由于两个历程中的单例对象是不同的。
- 线程同步机制失效:线程同步机制(如 synchronized 关键字、Lock 接口等)是基于同一个对象的锁机制来实现的。在多历程模式下,不同历程中的对象是相互独立的,即使利用相同的同步代码,也无法包管不同历程之间的线程同步。比方,在一个历程中利用 synchronized 关键字对一个对象进行加锁,以包管同一时间只有一个线程可以访问该对象的某个方法,但在另一个历程中,同样的代码无法对这个对象进行有效的同步控制,由于它们是不同历程中的不同对象。
- SharedPreferences 可靠性下降:SharedPreferences 是 Android 中用于存储简单键值对数据的一种方式,其底层是通过读写 XML 文件来实现的。在多历程模式下,多个历程同时读写 SharedPreferences 文件时,大概会出现数据不同等或丢失的情况。比方,当一个历程正在写入 SharedPreferences 文件时,另一个历程也尝试读取或写入,就大概导致数据的冲突和错误。这是由于 SharedPreferences 自己并不支持多历程并发读写,在多历程情况下其可靠性会受到影响。
- Application 多次创建:当一个组件运行在新的历程中时,系统会为该历程分配独立的虚拟机,这就相当于启动一个新的应用,自然会创建新的 Application 实例。比方,在一个应用中,定义了一个自定义的 Application 类,并在其中进行了一些初始化利用,如数据库连接的初始化、全局变量的设置等。当开启多历程后,每个历程都会创建一个新的 Application 实例,导致这些初始化利用被重复执行,大概会造成资源的浪费和冲突 。
4.3 办理方案
针对多历程模式带来的上述问题,可以采取以下相应的办理方案:
- 利用 Intent 通报数据:在不同历程的组件之间通报数据时,可以通过 Intent 来实现。Intent 可以携带基本数据类型、实现了 Parcelable 或 Serializable 接口的对象等。比方,在一个 Activity 中启动另一个历程中的 Activity,并通报一个 User 对象,可以利用如下代码:
- // 发送方Activity
- Intent intent = new Intent(this, SecondActivity.class);
- User user = new User(1, "张三", 20);
- intent.putExtra("user", user);
- startActivity(intent);
- // 接收方Activity
- User receivedUser = getIntent().getParcelableExtra("user");
- if (receivedUser!= null) {
- Log.d("SecondActivity", "接收的用户信息:" + receivedUser.toString());
- }
复制代码 如允许以在不同历程的 Activity 之间通报复杂对象,办理数据共享的部分问题。
- 利用文件共享:通过文件来共享数据是一种简单有效的方式。不同历程可以读取和写入同一个文件,从而实现数据的共享。比方,可以将一些设置信息、缓存数据等存储在文件中,不同历程通过读取和写入文件来获取和更新这些数据。在利用文件共享时,需要留意文件的读写权限和并发访问的问题,可以通过加锁等方式来包管数据的同等性。比方,利用 Java 的 RandomAccessFile 类来进行文件的读写利用,并利用 synchronized 关键字对文件利用进行同步:
- public class FileUtil {
- private static final String FILE_PATH = "data.txt";
- public static synchronized void writeToFile(String data) {
- try (RandomAccessFile file = new RandomAccessFile(FILE_PATH, "rw")) {
- file.seek(file.length());
- file.writeBytes(data + "\n");
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- public static synchronized String readFromFile() {
- StringBuilder data = new StringBuilder();
- try (RandomAccessFile file = new RandomAccessFile(FILE_PATH, "r")) {
- String line;
- while ((line = file.readLine())!= null) {
- data.append(line).append("\n");
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- return data.toString();
- }
- }
复制代码
- 利用 AIDL:AIDL(Android Interface Definition Language)是一种用于实现历程间通信的接口定义语言,它基于 Binder 机制实现。通过 AIDL,可以定义跨历程通信的接口,实现不同历程之间的方法调用和数据通报。比方,在一个音乐播放应用中,服务端历程提供音乐播放的服务,客户端历程可以通过 AIDL 接口调用服务端的播放、停息、切换歌曲等方法。具体实现步调如前文所述,包括定义 AIDL 文件、实现服务端和客户端等。
- 利用 ContentProvider:ContentProvider 是 Android 提供的一种跨历程数据共享的机制,它可以对外提供数据访问接口,其他应用可以通过 ContentResolver 来访问其提供的数据。比方,系统的联系人应用通过 ContentProvider 将联系人数据袒露出来,其他应用可以通过 ContentResolver 查询联系人信息。在利用 ContentProvider 时,需要在 AndroidManifest.xml 中注册,并实现其 query、insert、update、delete 等方法来提供数据的访问利用。比方,创建一个自定义的 ContentProvider 来提供用户数据的查询:
- public class UserContentProvider extends ContentProvider {
- private static final String AUTHORITY = "com.example.userprovider";
- private static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/users");
- @Override
- public boolean onCreate() {
- // 初始化操作
- return true;
- }
- @Override
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
- // 从数据库或其他数据源查询数据
- SQLiteDatabase db = getContext().openOrCreateDatabase("users.db", Context.MODE_PRIVATE, null);
- return db.query("users", projection, selection, selectionArgs, null, null, sortOrder);
- }
- // 其他方法的实现(insert、update、delete等)
- }
复制代码 然后在 AndroidManifest.xml 中注册:
- <provider
- android:name=".UserContentProvider"
- android:authorities="com.example.userprovider"
- android:exported="true" />
复制代码 在其他应用中,可以通过 ContentResolver 来查询数据:
- ContentResolver resolver = getContentResolver();
- Cursor cursor = resolver.query(UserContentProvider.CONTENT_URI, null, null, null, null);
- if (cursor!= null) {
- while (cursor.moveToNext()) {
- // 处理查询结果
- }
- cursor.close();
- }
复制代码
- 利用 Messenger:Messenger 是一种基于消息通报的历程间通信方式,它的底层也是基于 Binder 机制实现的。通过 Messenger,客户端可以向服务端发送消息,服务端可以处理这些消息并返回结果。比方,在一个应用中,客户端历程可以通过 Messenger 向服务端历程发送盘算任务的消息,服务端历程盘算完成后将结果返回给客户端。具体实现步调包括创建 Messenger、绑定服务、发送和吸取消息等。比方,服务端创建一个 Messenger 并处理客户端发送的消息:
- public class MessengerService extends Service {
- private static final int MSG_SUM = 1;
- private final Messenger mMessenger = new Messenger(new IncomingHandler());
- @Override
- public IBinder onBind(Intent intent) {
- return mMessenger.getBinder();
- }
- private static class IncomingHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_SUM:
- int num1 = msg.arg1;
- int num2 = msg.arg2;
- int result = num1 + num2;
- Messenger replyMessenger = msg.replyTo;
- Message replyMessage = Message.obtain(null, MSG_SUM);
- replyMessage.arg1 = result;
- try {
- replyMessenger.send(replyMessage);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- break;
- default:
- super.handleMessage(msg);
- }
- }
- }
- }
复制代码 客户端绑定服务并发送消息:
- public class MessengerClientActivity extends AppCompatActivity {
- private Messenger mService;
- private boolean mBound;
- private final Messenger mMessenger = new Messenger(new IncomingHandler());
- private ServiceConnection mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mService = new Messenger(service);
- mBound = true;
- Message message = Message.obtain(null, MessengerService.MSG_SUM);
- message.arg1 = 3;
- message.arg2 = 5;
- message.replyTo = mMessenger;
- try {
- mService.send(message);
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mService = null;
- mBound = false;
- }
- };
- private static class IncomingHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MessengerService.MSG_SUM:
- int result = msg.arg1;
- Log.d("MessengerClient", "计算结果:" + result);
- break;
- default:
- super.handleMessage(msg);
- }
- }
- }
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_messenger_client);
- Intent intent = new Intent(this, MessengerService.class);
- bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (mBound) {
- unbindService(mConnection);
- mBound = false;
- }
- }
- }
复制代码 通过以上这些方法,可以在一定程度上办理多历程模式带来的问题,实现不同历程之间的有效通信和数据共享。在实际开辟中,需要根据具体的业务需求和场景选择符合的办理方案 。
五、IPC 的应用场景与案例分析
5.1 应用场景
在 Android 开辟中,IPC 有着广泛的应用场景,以下是一些常见的场景:
- 系统服务调用:当应用需要利用系统提供的服务时,如 ActivityManagerService、WindowManagerService、NotificationManager 等,就需要通过 IPC 与这些系统服务进行通信。比方,应用调用 ActivityManagerService 来启动一个新的 Activity,大概获取正在运行的应用列表;通过 NotificationManager 来发送关照等,这些利用都涉及到历程间通信,由于应用历程和系统服务历程是不同的历程。
- 历程间数据共享:在一些复杂的应用中,大概会存在多个历程,这些历程之间需要共享数据。比方,一个新闻客户端应用,大概有一个历程负责网络数据的获取,另一个历程负责数据的存储和展示。负责网络数据获取的历程获取到新闻数据后,需要通过 IPC 将数据通报给负责展示的历程,以实现数据的共享和展示。
- 跨应用通信:不同应用之间也大概需要进行通信和数据交互。比如,一个支付应用和电商应用之间,当用户在电商应用中进行支付利用时,电商应用需要通过 IPC 调用支付应用的支付功能,将支付相干的数据通报给支付应用,支付应用完成支付利用后再将支付结果通过 IPC 返回给电商应用 。
- 多模块解耦:在大型应用开辟中,为了实现模块的解耦和独立开辟,大概会将不同的功能模块放在不同的历程中。比方,一个外交应用大概将消息推送模块、用户管理模块、聊天模块中分别放在不同的历程中,这些模块之间通过 IPC 进行通信,实现功能的协同工作,同时低落模块之间的耦合度,提高代码的可维护性和扩展性。
5.2 案例分析
以一个音乐播放应用为例,该应用包含两个历程,一个是主历程,负责界面展示和用户交互;另一个是音乐播放服务历程,负责音乐的播放、停息、切换等利用。
在这个案例中,利用 AIDL 来实现主历程和音乐播放服务历程之间的通信。
- 定义 AIDL 文件:在服务端(音乐播放服务历程)定义一个 AIDL 文件,比方 IMusicPlayer.aidl,内容如下:
- // IMusicPlayer.aidl
- package com.example.musicplayer;
- interface IMusicPlayer {
- void play();
- void pause();
- void next();
- void previous();
- }
复制代码 上述 AIDL 文件定义了一个音乐播放的接口,包含播放、停息、下一首、上一首等方法。
- 实现服务端:在音乐播放服务历程中创建一个 Service,并实现 IMusicPlayer 接口。比方:
- package com.example.musicplayer;
- import android.app.Service;
- import android.content.Intent;
- import android.media.MediaPlayer;
- import android.os.IBinder;
- import android.os.RemoteException;
- import android.util.Log;
- public class MusicPlayerService extends Service {
- private static final String TAG = "MusicPlayerService";
- private MediaPlayer mediaPlayer;
- @Override
- public IBinder onBind(Intent intent) {
- return new MyBinder();
- }
- class MyBinder extends IMusicPlayer.Stub {
- @Override
- public void play() throws RemoteException {
- if (mediaPlayer == null) {
- mediaPlayer = MediaPlayer.create(MusicPlayerService.this, R.raw.music);
- mediaPlayer.start();
- } else {
- mediaPlayer.start();
- }
- Log.d(TAG, "音乐开始播放");
- }
- @Override
- public void pause() throws RemoteException {
- if (mediaPlayer != null && mediaPlayer.isPlaying()) {
- mediaPlayer.pause();
- Log.d(TAG, "音乐暂停播放");
- }
- }
- @Override
- public void next() throws RemoteException {
- // 这里简单模拟下一首,实际应用中需要切换音乐资源等操作
- Log.d(TAG, "切换到下一首音乐");
- }
- @Override
- public void previous() throws RemoteException {
- // 这里简单模拟上一首,实际应用中需要切换音乐资源等操作
- Log.d(TAG, "切换到上一首音乐");
- }
- }
- @Override
- public void onCreate() {
- super.onCreate();
- // 初始化MediaPlayer等操作
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- if (mediaPlayer != null) {
- mediaPlayer.release();
- mediaPlayer = null;
- }
- }
- }
复制代码 在上述代码中,MusicPlayerService 创建了一个 MyBinder 类,实现了 IMusicPlayer 接口中的方法,在这些方法中实现了音乐播放、停息等实际利用。
- 设置服务端 Service:在 AndroidManifest.xml 文件中注册服务端的 Service,如下:
- <service
- android:name=".MusicPlayerService"
- android:enabled="true"
- android:exported="true">
- </service>
复制代码
- 实现客户端:在主历程中绑定服务,并通过 AIDL 接口与服务端进行通信。比方:
- package com.example.musicplayerclient;
- 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.view.View;
- import android.widget.Button;
- import android.widget.Toast;
- import androidx.appcompat.app.AppCompatActivity;
- import com.example.musicplayer.IMusicPlayer;
- public class MainActivity extends AppCompatActivity {
- private IMusicPlayer musicPlayer;
- private ServiceConnection connection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- musicPlayer = IMusicPlayer.Stub.asInterface(service);
- try {
- musicPlayer.play();
- Toast.makeText(MainActivity.this, "音乐开始播放", Toast.LENGTH_SHORT).show();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- musicPlayer = null;
- }
- };
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Button playButton = findViewById(R.id.play_button);
- Button pauseButton = findViewById(R.id.pause_button);
- playButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (musicPlayer!= null) {
- try {
- musicPlayer.play();
- Toast.makeText(MainActivity.this, "音乐开始播放", Toast.LENGTH_SHORT).show();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- } else {
- Intent intent = new Intent();
- intent.setComponent(new ComponentName("com.example.musicplayer", "com.example.musicplayer.MusicPlayerService"));
- bindService(intent, connection, BIND_AUTO_CREATE);
- }
- }
- });
- pauseButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (musicPlayer!= null) {
- try {
- musicPlayer.pause();
- Toast.makeText(MainActivity.this, "音乐暂停播放", Toast.LENGTH_SHORT).show();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
- }
- });
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (connection!= null) {
- unbindService(connection);
- }
- }
- }
复制代码 在客户端代码中,MainActivity 通过 ServiceConnection 绑定音乐播放服务,在 onServiceConnected 方法中获取到 IMusicPlayer 接口实例,然后就可以调用接口中的方法来控制音乐的播放和停息。当点击播放按钮时,如果已经绑定了服务,则直接调用 play 方法;如果未绑定服务,则先绑定服务再调用 play 方法。当点击停息按钮时,直接调用 pause 方法。
通过这个案例可以看出,利用 AIDL 实现 IPC 能够有效地实现不同历程之间的通信和功能交互,满意了音乐播放应用中界面展示和音乐播放服务分离的需求,提高了应用的可维护性和扩展性。
六、总结
Android IPC 在现代移动应用开辟中占据着至关紧张的地位。从系统服务调用到历程间数据共享,再到跨应用通信以及大型应用的多模块解耦,IPC 机制为 Android 应用的功能实现和架构设计提供了坚实的基础。不同的 IPC 实现方式,如 Binder 机制、AIDL、Messenger 等,各自有着独特的上风和实用场景,开辟者可以根据具体的业务需求进行公道选择。
随着移动应用的不断发展,对 IPC 的性能、安全性和易用性提出了更高的要求。未来,Android IPC 大概会朝着更加高效、安全和便捷的方向发展。比方,在性能方面,大概会进一步优化数据传输和处理的服从,减少通信开销;在安全性方面,会增强权限管理和数据加密,防止数据泄露和非法访问;在易用性方面,大概会提供更加简洁、直观的 API,低落开辟者的利用门槛 。同时,随着新技能的不断涌现,如 5G 网络、物联网等,Android IPC 也将面临新的机会和挑战,需要不断创新和发展,以满意日益增长的应用需求。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |