【Android】IPC机制—Serializable、Parcelable、Binder用法

时间:2024-11-18 09:38:14

简介

IPC(Inter-Process Communication,进程间通信)是指多个进程之间相互传递数据或信号,以便协调完成某些任务的机制。

进程和线程是操作系统中两个重要的概念,主要用于管理和执行计算任务。它们具有不同的特点和作用。

  1. 进程 (Process)
  • 定义:进程是一个正在运行的程序的实例,是资源分配的基本单位。每个进程都包含程序代码、数据、资源(如文件、内存等)以及一些控制信息。
  • 独立性:进程之间是相互独立的,它们的内存空间相互隔离,拥有各自的内存地址空间和系统资源。因此,进程之间的通信相对复杂,需要使用进程间通信(IPC)机制,如管道、消息队列、共享内存等。
  • 开销:进程创建和切换的开销较大,因为它需要分配独立的资源和内存空间。
  • 并发性:操作系统可以调度多个进程同时运行,每个进程的执行顺序是独立的,进程之间的调度称为多任务处理。
  1. 线程 (Thread)
  • 定义:线程是进程中的一个执行单元,是处理器调度和执行的基本单位。一个进程可以包含多个线程,它们共享该进程的内存空间和资源。
  • 共享资源:同一个进程中的线程共享内存和系统资源,因此线程之间的通信更加高效和方便,能够直接访问同一进程内的数据。
  • 轻量级:线程的创建和切换开销较小,因为线程共享进程的资源,不需要分配额外的内存空间。
  • 并发性:在多线程模型中,一个进程可以同时运行多个线程,这样可以在一个进程内执行并行任务,从而提高程序的执行效率。

进程在PC和移动设备上指一个程序或者应用,线程是CPU调度的最小单元。

基础概念介绍

Serializable接口

Serializable是Java所提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。

使用Serializable来实现序列化相当简单,只需要在类的声明中指定一个类似下面的标识即可自动实现默认的序列化过程:

    private static final long serialVersionUID = 8711368828010083044L;

serialVersionUID也并不是必须的,不声明serialVersionUID也同样可以实现序列化,但是会对反序列化过程产生影响。

下面是一个User类实现了Serializable接口:

public class User implements Serializable {
    private static final long serialVersionUID = 519067123721295773L;
    public int userId; 
    public String userName;
    public boolean isMale; 
} 

实现了序列化很简单,我们只需要采用ObjectOutputStream和ObjectInputStream就可以实现反序列化:

		//序列化过程
        User user = new User(0, "jake", true);
        try {
            ObjectOutputStream out = null;
            out = new ObjectOutputStream(new FileOutputStream("cache.txt"));
            out.writeObject(user);
            out.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //反序列化过程
        ObjectInputStream in = null;
        try {
            in = new ObjectInputStream( new FileInputStream("cache.txt"));
            User newUser = (User) in.readObject(); in.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }	

只需要把实现了Serializable接口的User对象写到文件中就可以快速恢复了,恢复后的对象newUser和user的内容完全一样,但是两者并不是同一个对象。

serialVersionUID是用来辅助序列化和反序列化过程的。在进行序列化的时候,系统会把当前的类的serialVersionUID写入序列化文件中,在反序列化的时候会去检测文件中的serialVersionUID,看是否和当前类的serialVersionUID一致,如果一致就说明序列化的类的版本和当前类的版本是相同的,可以被反序列化;否则就说明类发生了某些变化,无法正常进行反序列化,所以一般需要我们手动指定serialVersionUID的值(1L),也可以让Eclipse根据当前类结构自动生成它的hash值。

如果类结构发生了非常规性改变,比如修改了类名,修改了成员变量的类型,这个时候尽管serialVersionUID验证通过了,但是反序列化过程还是会失败,因为类结构有了毁灭性的改变,根本无法从老版本的数据中还原出一个新的类结构的对象。

以下两点需要特别提一下:首先静态成员变量属于类不属于对象,所以不会参与序列化过程;其次用transient关键字标记的成员变量不参与序列化过程。

Parcelable接口

Parcelable也是一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。

Parcel内部包装了可序列化的数据,可以在Binder中*传输。序列化功能由writeToToParcel方法来完成,最终是通过Parcel中的一系列write方法来完成的,反序列化通过 CREATOR 完成,CREATOR 内部定义了如何从 Parcel 中创建序列化对象和数组,并通过一系列 Parcelread 方法实现反序列化。反序列化功能由CREATATOR来完成,其内部标明了如何创建序列化对象和数组,并通过 Parcel 的一系列 read 方法来完成反序列化过程.内容描述功能由 describeContents 方法来完成,几乎在所有情况下这个方法都应该返回0,仅当当前对象中存在文件描述符时,此方法返回1。

public class User implements Parcelable {
    private int id;
    private String name;

    // 构造函数
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    // 从 Parcel 反序列化
    protected User(Parcel in) {
        id = in.readInt();
        name = in.readString();
    }

    // 序列化到 Parcel
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(name);
    }

    @Override
    public int describeContents() {
        return 0; // 无特殊内容
    }

    // CREATOR 用于反序列化
    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
}

对比

属性 Parcelable Serializable
效率 高效(推荐在 Android 使用) 较低(依赖反射)
实现难度 复杂,需要手动实现序列化和反序列化方法 简单,只需实现接口
内存开销 较低 较高
可移植性 Android 专用 Java 标准,跨平台支持
使用场景 Android 开发中的数据传递(Intent 等) 数据持久化或网络传输

推荐使用 Parcelable

  • 如果在 Android 中开发,特别是需要频繁传递数据(如 IntentBundleAIDL)。

选择 Serializable

  • 如果需要将对象保存到文件或通过网络传输,且不在意性能。
  • 在需要跨平台或非 Android 环境下使用时。

Binder

  • Binder 是 Android 系统中进程间通信(IPC,Inter-Process Communication)的核心机制。
  • 它是一种高效的、基于 C/S 模式的 IPC 实现,允许一个进程向另一个进程发送数据。
  • Binder 被设计为 Android 的底层通信框架,几乎所有的系统服务(如 ActivityManager、WindowManager)都通过它来实现交互。

Binder 的用法

在 Android 中,Binder 通常用于实现进程间通信(IPC)。其使用方式可以分为直接使用 Binder 和通过 AIDL(Android Interface Definition Language)间接使用。以下是具体的用法和步骤:

1. 直接使用 Binder

直接使用 Binder 类实现简单的进程间通信。

服务端

服务端创建一个继承自 Binder 的类,并实现通信逻辑。

  • 服务端代码
public class MyBinder extends Binder {
    public String getMessage() {
        return "Hello from MyBinder!";
    }
}

public class MyService extends Service {
    private final MyBinder binder = new MyBinder();

    @Override
    public IBinder onBind(Intent intent) {
        return binder; // 返回 Binder 实例
    }
}

客户端

客户端通过 ServiceConnection 获取 Binder 实例,并调用服务端方法。

  • 客户端代码
public class MainActivity extends AppCompatActivity {
    private MyBinder myBinder;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myBinder = (MyBinder) service; // 获取 Binder 实例
            String message = myBinder.getMessage();
            Log.d("MainActivity", message); // 输出 "Hello from MyBinder!"
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            myBinder = null;
        }
    };

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(connection);
    }
}

2. 使用 AIDL 实现复杂通信

AIDL 是基于 Binder 的一种跨进程通信工具,适合需要传递复杂数据的场景。

定义 AIDL 接口

创建一个 .aidl 文件,用于定义服务接口。例如:

// IMyAidlInterface.aidl
package com.example.myapp;

interface IMyAidlInterface {
    String getMessage();
}

编译后,Android Studio 会自动生成对应的 Java 接口。

服务端

实现 AIDL 接口,并通过 onBind 方法返回 Binder

  • 服务端代码
public class MyAidlService extends Service {
    private final IMyAidlInterface.Stub binder = new IMyAidlInterface.Stub() {
        @Override
        public String getMessage() throws RemoteException {
            return "Hello from AIDL Service!";
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
}

客户端

在客户端绑定服务后,通过 AIDL 接口调用服务端方法。

  • 客户端代码
public class MainActivity extends AppCompatActivity {
    private IMyAidlInterface myAidlService;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myAidlService = IMyAidlInterface.Stub.asInterface(service); // 获取 AIDL 接口
            try {
                String message = myAidlService.getMessage();
                Log.d("MainActivity", message); // 输出 "Hello from AIDL Service!"
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            myAidlService = null;
        }
    };

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent(this, MyAidlService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(connection);
    }
}

Android中的IPC方式

使用Bundle

四大组件中的三大组件(Activity 、Service 、Receiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便地在不同的进程间传输。基于这一点,当我们在一个进程中启动了另一个进程的Activity、Service和Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。

我们传输的数据必须能够被序列化,比如基本类型、实现了Parcellable接口的对象、实现了Serializable接口的对象以及一些Android支持的特殊对象,具体内容可以看Bundle这个类,就可以看到所有它支持的类型。

Bundle 的基本用法

1. 创建与设置数据

通过 putXxx 方法存入数据:

Bundle bundle = new Bundle();
bundle.putString("key_string", "Hello Bundle!");
bundle.putInt("key_int", 123);
bundle.putBoolean("key_boolean", true);

2. 传递数据

  • 在 Activity 之间传递

    Intent intent = new Intent(this, SecondActivity.class);
    intent.putExtras(bundle);
    startActivity(intent);
    
  • 在 Fragment 之间传递

    Fragment fragment = new ExampleFragment();
    fragment.setArguments(bundle);
    
  • 传递给 Service

    Intent intent = new Intent(this, MyService.class);
    intent.putExtras(bundle);
    startService(intent);
    

3. 获取数据

通过 getXxx 方法取出数据:

// 从 Intent 中获取
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
    String stringData = bundle.getString("key_string");
    int intData = bundle.getInt("key_int");
    boolean booleanData = bundle.getBoolean("key_boolean");
}

// 从 Fragment 中获取
Bundle args = getArguments();
if (args != null) {
    String stringData = args.getString("key_string");
}

Bundle 支持的数据类型

  • 基本类型Stringintbooleanfloatdouble 等。
  • 数组:如 String[]int[] 等。
  • 集合类型ArrayList<String>ArrayList<Integer> 等。
  • ParcelableSerializable 对象。

使用文件共享

共享文件是两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B进程通过读取这个文件来获取数据。

在 Android 中,文件共享指的是通过文件系统或者 ContentProvider 在不同应用之间共享文件或数据。以下是两种常见的文件共享方式:

  1. 通过文件系统共享
  2. 通过 ContentProvider 共享

1. 通过文件系统共享

Android 提供了通过外部存储(例如 SD 卡)来共享文件的方式。这个方法适用于没有特定数据格式要求的共享,用户可以直接存取共享文件。

步骤 1:将文件保存到外部存储

首先,应用需要将文件保存在共享的外部存储中。

FileOutputStream fos = null;
try {
    File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "shared_file.txt");
    fos = new FileOutputStream(file);
    fos.write("This is a shared file.".getBytes());
} catch (IOException e) {
    e.printStackTrace();
} finally {
    try {
        if (fos != null) fos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

步骤 2:申请权限

如果你要访问外部存储,必须在 AndroidManifest 中申请必要的权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

步骤 3:共享文件

可以通过 Intent 和文件路径来共享文件,允许其他应用访问这个文件。

File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), "shared_file.txt");

Uri uri = FileProvider.getUriForFile(this, "com.example.myapp.fileprovider", file);
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_STREAM, uri);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 给予其他应用读取文件权限
startActivity(Intent.createChooser(intent, "Share file"));

步骤 4:配置 FileProvider

如果共享文件时使用 FileProvider,需要在 AndroidManifest.xml 中配置:

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.example.myapp.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true"
    android:permission="android.permission.MANAGE_DOCUMENTS">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

res/xml/file_paths.xml 中配置文件的共享路径:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="shared_files"
        path="Documents/" />
</paths>

2. 通过 ContentProvider 共享

ContentProvider 是 Android 提供的用于在应用之间共享数据的机制。它允许你将数据(如文件、数据库等)暴露给其他应用。

步骤 1:定义 ContentProvider

首先,定义一个 ContentProvider,用于暴露文件。

public class FileContentProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // 实现插入数据的方法
        return null;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        // 实现查询数据的方法
        return null;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public String getType(Uri uri) {
        return "vnd.android.cursor.item/vnd.com.example.myapp.sharedfile";
    }
}

步骤 2:配置 ContentProvider

AndroidManifest.xml 中注册 ContentProvider

<provider
    android:name=".FileContentProvider"
    android:authorities="com.example.myapp.provider"
    android:exported