解锁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成为跨平台的数据序列化标准。让我们拭目以待吧!

相关推荐
H10029 分钟前
重构(二)
android·重构
拓端研究室40 分钟前
R基于贝叶斯加法回归树BART、MCMC的DLNM分布滞后非线性模型分析母婴PM2.5暴露与出生体重数据及GAM模型对比、关键窗口识别
android·开发语言·kotlin
zhangphil2 小时前
Android简洁缩放Matrix实现图像马赛克,Kotlin
android·kotlin
m0_512744642 小时前
极客大挑战2024-web-wp(详细)
android·前端
lw向北.2 小时前
Qt For Android之环境搭建(Qt 5.12.11 Qt下载SDK的处理方案)
android·开发语言·qt
不爱学习的啊Biao2 小时前
【13】MySQL如何选择合适的索引?
android·数据库·mysql
Clockwiseee2 小时前
PHP伪协议总结
android·开发语言·php
mmsx9 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
众拾达人12 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
吃着火锅x唱着歌13 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习