解锁Android高效数据传输的秘钥 - Parcelable剖析

作为Android开发者,我们经常需要在不同的组件(Activity、Service等)之间传输数据。这里的"传输"往往不仅仅是简单的数据复制,还可能涉及跨进程的内存复制操作。当传输的数据量较大时,这种操作可能会带来严重的性能问题。而Android系统为我们提供了Parcelable这一高效的序列化传输机制,很好地解决了这一痛点。今天,就让我们一起来探讪Parcelable的神奇之处。

一、Parcelable架构与原理

Parcelable是Android中一种高效的序列化机制,用于实现进程间通信(IPC)中的对象传递。

Parcelable相对于Serializable的使用相对复杂一些,但Parcelable的效率相对Serializable也高很多,这一直是Google工程师引以为傲的,Parcelable和Serializable的效率对比Parcelable vs Serializable号称快10倍的效率。

与Serializable接口不同,Parcelable采用的是手工编码的方式,序列化后的数据更为紧凑。系统将数据打包到一个全局内存区域中,可供不同线程/进程共享访问。

1、Parcelable的设计理念

Parcelable的设计理念是在保证一定性能的前提下,尽可能节省内存和CPU开销。

从架构上来看,Parcelable涉及到了Binder驱动、Parcel容器和IPCThreadState等几个关键组件,共同构成了高效的序列化通道:

(1)、Binder驱动

Binder驱动是Android的核心组件之一,负责进程间的数据传输。它在内核层为每个进程维护了一块受保护的共享内存区域,用于在进程间传递Parcelable对象。

(2)、Parcel容器

Parcel对象是存储序列化数据的临时载体。开发者需要先将对象写入Parcel中,然后由Binder驱动完成Parcel在进程间的拷贝和传递。

(3)、IPCThreadState

IPCThreadState是一个线程私有数据结构,负责在进程间管理请求和应答的Parcel对象数据。每个线程在与其他进程通信时,都会使用自己的IPCThreadState实例。

(4)、Parcelable接口

Parcelable接口定义了将对象写入和从Parcel容器读取的抽象协议,开发者需要手动实现这两个序列化方法。系统会按此协议完成对象的编码/解码操作。

序列化的基本流程如下:

  1. 当一个进程需要向另一个进程传输数据时,会先初始化一个Parcel容器对象;
  2. 将要传递的Parcelable对象通过writeToParcel()方法写入Parcel容器;
  3. Binder驱动从发送方进程拷贝这个Parcel容器到内核共享内存区域;
  4. 接收方进程从共享内存区读取Parcel数据,并通过Parcelable.Creator反序列化出原始对象;
  5. 接收进程的目标组件(如Activity)即可使用这个反序列化出的对象数据。

整个过程无需经过Java层的序列化操作,因此效率极高。Parcel容器采用面向流的编码格式存储数据,格式紧凑,内存占用小。

此外,Parcelable的实现细节还包括:

  • 支持平台默认Java数据类型的高效编解码;
  • 使用标志位压缩编码,节省空间;
  • 引入Parcel窗口缓存,加快读写效率;
  • 等等一系列优化手段。

从架构和流程上看,Parcelable不仅拥有简单的接口定义,而且在系统层得到了全方位的优化支持,使其在Android世界中成为高效低耗的序列化标准。当然,与之对应的是开发者必须自行编写序列化方法的工作量。但从性能的角度来看,这一点工作量是完全值得的。

二、Parcelable接口的使用

要使用Parcelable,需要自己实现这两个接口方法。

// 定义一个数据类MyData,实现Parcelable接口
public class MyData implements Parcelable {
    private int id;
    private String name;
    private boolean isAdult;

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

    // 从Parcel反序列化时使用的特殊构造函数
    protected MyData(Parcel in) {
        id = in.readInt();
        name = in.readString();
        isAdult = in.readByte() != 0;
    }

    // writeToParcel方法,将数据写入Parcel
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(name);
        dest.writeByte((byte) (isAdult ? 1 : 0));
    }

    // 生成用于反序列的CREATOR对象
    public static final Creator<MyData> CREATOR = new Creator<MyData>() {
        @Override
        public MyData createFromParcel(Parcel in) {
            return new MyData(in);
        }

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

    // describeContents是一个内部接口标志
    @Override
    public int describeContents() {
        return 0;
    }

    // getter/setter ...
}

接着就可以通过Intent、Binder等方式传输这个Parcelable对象了。系统在底层会自动完成序列化和反序列化的工作。

// 使用示例:传递Parcelable对象
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 创建MyData对象
        MyData data = new MyData(1, "Jack", true);

        // 使用Intent传递MyData
        Intent intent = new Intent(this, SecondActivity.class);
        intent.putExtra("data", data);
        startActivity(intent);
    }
}

// 接收Parcelable对象
public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        // 从Intent中取出MyData对象
        MyData data = getIntent().getParcelableExtra("data");
        if (data != null) {
            int id = data.getId();
            String name = data.getName();
            boolean isAdult = data.isAdult();
            // 处理data...
        }
    }
}

在这个例子中:

  1. 我们定义了一个MyData类,实现了Parcelable接口。
  2. 在MyData类中,我们提供了一个用于Parcel反序列化的特殊构造函数,以及writeToParcel和describeContents方法。
  3. 同时生成了一个CREATOR对象,用于从Parcel重构MyData实例。
  4. 在MainActivity中,我们创建了一个MyData对象,并通过Intent将它传递给SecondActivity。
  5. 在SecondActivity中,我们从Intent中取出了MyData对象,可以使用其中的数据。

通过这个案例,你可以看到使用Parcelable传递一个自定义数据对象是非常简单的。只需要完成几个基本方法的实现,就可以实现对象的高效序列化和反序列化。

与手动实现序列化和Binder传递相比,使用Parcelable的代码更加简洁、安全,并得到了系统级的性能优化。这就是Parcelable作为Android高效IPC解决方案的魅力所在。

三、Parcelable的使用场景

以下是Parcelable主要用于的数据传输场景,以及结合案例代码演示和使用Parcelable的优点分析。

1、Activity间传递数据

当从一个Activity导航到另一个Activity时,可以使用Intent携带数据。如果数据对象实现了Parcelable接口,可以直接在Intent中使用。

// 创建Parcelable对象
MyData myData = new MyData("Hello", 123);

// 通过Intent传递数据
Intent intent = new Intent(CurrentActivity.this, NextActivity.class);
intent.putExtra("MY_DATA_KEY", myData);
startActivity(intent);

在接收的Activity中:

// 接收Parcelable数据
Intent intent = getIntent();
MyData myData = intent.getParcelableExtra("MY_DATA_KEY");

2、Activity与Service传递数据

Parcelable也可以用于ActivityService之间的数据传输。可以通过Intent发送数据到Service,或者Service返回结果给Activity

// Activity发送数据到Service
Intent serviceIntent = new Intent(this, MyService.class);
serviceIntent.putExtra("MY_DATA_KEY", myData);
startService(serviceIntent);

3、通过Binder传输数据

在使用AIDL(Android Interface Definition Language)定义服务时,Parcelable可以用来在客户端和服务器之间传递数据。

// 在AIDL接口定义中
parcelable MyData;

4、将对象保存在Bundle或保存实例状态

Activity的生命周期中,可以在onSaveInstanceState方法中使用Bundle保存Parcelable对象。

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putParcelable("MY_DATA_KEY", myData);
}

@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    myData = savedInstanceState.getParcelable("MY_DATA_KEY");
}

四、Parcelable与Serializable的比较

与Java中的Serializable相比,Parcelable有以下优势:

1、性能更优

Parcelable序列化后的码流要比Serializable小得多,内存开销和CPU损耗也更低。

2、使用成本低

Parcelable无需使用反射,只需手写几个方法即可。

3、没有安全隐患

Parcelable不会自动完成数据的深复制,避免了Serializable可能带来的安全隐患。

当然,其缺点是需要手动编码实现序列化逻辑,并维护代码与类结构的同步,工作量较高。而Serializable则可以自动完成序列化。

五、Parcelable的性能优化建议

尽管Parcelable已经相当高效,但我们在实际使用时仍可以通过一些优化手段达到更佳的性能表现:

1、尽量使用标量类型

标量类型(int/long)可以直接通过writeInt/writeLong方法进行序列化,性能较高。

2、减少自动装箱操作

避免对装箱对象进行序列化,如Integer等。应直接使用基本类型。

3、编写高效的Parcelable方法

在writeToParcel方法中应先写入有效数据,而不是创建临时对象。

4、启用Parcelable代码生成器

Parceler等工具可以自动生成Parcelable代码,提高开发效率。

六、不得不提及的Bundler

Parcelable的强大远不止于上述简单用法。在Android 10开始,Google引入了Bundler框架,可以将任意的应用程序数据自动打包成一个Parcelable的Bundle,从而实现高效的跨进程通信。Bundler极大地简化了使用Parcelable的难度。

这一强大功能曾广受期待。令人惋惜的是,Bundler目前的可用性和成熟度似乎还有待提高。但不可否认,Google为我们展现了Parcelable在未来更大的应用前景。

无论是Android系统的Binder,还是Chrome浏览器的IPC数据传输,Parcelable都扮演着举足轻重的角色。它使得Android应用能够高效、安全地在进程间传输数据。面向未来,或许Parcelable的序列化能力会不断增强,甚至取代JVM的Serializable成为跨平台的数据序列化标准。让我们拭目以待吧!

相关推荐
长亭外的少年33 分钟前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿3 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神4 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛4 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
Y多了个想法5 小时前
RK3568 android11 适配敦泰触摸屏 FocalTech-ft5526
android·rk3568·触摸屏·tp·敦泰·focaltech·ft5526
NotesChapter6 小时前
Android吸顶效果,并有着ViewPager左右切换
android
_祝你今天愉快7 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl7 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
麦田里的守望者江8 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
Dnelic-8 小时前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记