Android之Parcel和Parcelable

2 篇文章 0 订阅

总结:

  1. Parcel 类是一个容器,能装各种类型的数据,并能在C/CPP底层传输。
  2. Parcel 可以在Binder 驱动为不同进程实现数据交互,为什么它能充当这样的角色呢?
    系统设计使然。虽然不同进程在Java层是相互独立,有着不同内存空间,但底层有binder驱动(binder.cpp)将它们统一起来实现互通,而Parcel 由于实现了3层代码(Java-JNI-Native,当然,Binder也是),可通过java操作C层。因此利用Parcel装载数据,再使用工具类binder驱动(binder.cpp)中对它进行处理,结果通过Native层回调给Java层,这样的操作配合,是最合适不过的方案。

 

 

相对于Parcel,我们更常接触到Parcelable,两个单词很相像,它们有什么区别吗?

  • 相同点:两个都是专门为 Android 设计的系统类。
  • Parcel 是一个实体类,用于进程间通讯,传递数据。
  • Parcelable 是一个接口,用于Android 高性能序列化(详细请访问
public final class Parcel {.....}
public interface Parcelable {.....}

在了解过Parcelable 是什么东西后,让我们认识一下Parcel 类。

 

在AIDL 自动生成的文件中,有以下示例代码:

// 添加书籍
@Override
public void addBook(com.test.aidl_demo.Book book) 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 ((book != null)) {
                 _data.writeInt(1);
                 book.writeToParcel(_data, 0); // Book是自定义对象,实现了Parcelable接口
          } else {
                  _data.writeInt(0);
          }
          mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
          _reply.readException();
       } finally {
          _reply.recycle();
          _data.recycle();
       }
}

 

Parcel类

Parcel,原为“包裹、打包”之意,在Android中,Parcel是一个容器,主要用于存储序列化数据,然后可以通过Binder在进程间传递这些数据。传递数据类型包括:

  • 原始数据类型(用各种对应的方法写入,比如writeInt()、writeFloat()等),
  • Parcelable对象(如:writeParcelable()、writeParcelableCreator()等)
  • IBinder对象的引用(如:writeStrongBinder() )

通过搜索代码发现(需要下载Andorid源码),Android 在Java-Jni-C 都有Parcel类,

  • Java层:/frameworks/base/core/java/android/os/Parcel.java
  • JNI  层:/frameworks/base/core/jni/android_os_Parcel.cpp
  • C++层:/frameworks/native/libs/binder/Parcel.cpp

可以发现:Java 层代码只是一个封装代理,真正的实现在C++ 层。

 

调用过程

  1. 获取Parcel 对象:通过obtain()静态方法,
  2. 数据的存储和读取:通过writeXXX()和 readXXX()实现,
  3. 数据序列化和反序列化:marshall()和 unmarshall()
  4. 回收资源:通过recycle()方法

 

 

1、如何获取一个Parcel 对象?

在Parcel.java中:

    // 默认初始化一个Parcel池,因为进程间频繁通讯,不断创建Parcel会损耗性能,因此采取了复用
    // Parcel 池本质是一个数组
    private static final int POOL_SIZE = 6;
    private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];


    public static Parcel obtain() {
        final Parcel[] pool = sOwnedPool;
        synchronized (pool) {
            Parcel p;
            for (int i=0; i<POOL_SIZE; i++) {
                p = pool[i];
                if (p != null) { // 若parcel池有对象,复用该对象,同时把池中该位置的对象清空
                    pool[i] = null; // 清空原因见下
                    if (DEBUG_RECYCLE) {
                        p.mStack = new RuntimeException();
                    }
                    return p;  
                }
            }
        }
        return new Parcel(0); // 如果Parcel池中全部空,则新建一个。注意传入了0
    }
    // 以上方法,或许有人会问:
    // 不是复用parcel池吗,为什么要清空某个位置的元素,新建后也没见加入池啊?
    // 答案是recycle()方法!在退出业务流程调用recycle()时,重新把parcel对象加入池子。
    // 该位置为空才能插入,这样就实现了重用,后面会介绍
    




    // ptr = pointer,即指针 
    private Parcel(long nativePtr) {
        if (DEBUG_RECYCLE) {
            mStack = new RuntimeException();
        }
        //Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack);
        init(nativePtr);
    }


    private void init(long nativePtr) {
        if (nativePtr != 0) {
            mNativePtr = nativePtr;
            mOwnsNativeParcelObject = false;
        } else { 
            // 0 表示新创建一个对象
            mNativePtr = nativeCreate(); // 创建成功后,返回对象指针
            mOwnsNativeParcelObject = true;
        }
    }

    private static native long nativeCreate();






在JNI层(android_os_Parcel.cpp,下同):


static const JNINativeMethod gParcelMethods[] = {
    ......    
    // java 层方法,对应着JNI 层的实现方法
    {"nativeCreate",              "()J", (void*)android_os_Parcel_create},

    {"nativeDestroy",             "(J)V", (void*)android_os_Parcel_destroy},
    ......
};


static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
    Parcel* parcel = new Parcel(); // 调用了Native层的类

    // reinterpret_cast 是 C++里的强制类型转换符,把一个指针(parcel)转换成一个整数(long)
    return reinterpret_cast<jlong>(parcel); 
}



在Native层(Parcel.cpp):


Parcel::Parcel()
{
    initState();
}

// 把对象置为初始状态
void Parcel::initState()
{
    mError = NO_ERROR;  // 错误类型
    mData = 0;          // 存储区域1,一块连续的内存地址,用于存储是基本数据类型
    mDataSize = 0;      // 总数据大小
    mDataCapacity = 0;  // 总空间(包括已用和可用)大小,这个空间是变长的
    mDataPos = 0;       // 当前数据可写入的内存其实位置

    ALOGV("initState Setting data size of %p to %zu", this, mDataSize);
    ALOGV("initState Setting data pos of %p to %zu", this, mDataPos);

    mObjects = NULL;    // 存储区域2,用于存储Binder数据类型
    mObjectsSize = 0;
    mObjectsCapacity = 0;
    mNextObjectHint = 0;
    mHasFds = false;
    mFdsKnown = true;
    mAllowFds = true;
    mOwner = NULL;
}

Parcel内部的存储区域主要有两个,是mData和mObjects,mData存储是基本数据类型,mObjects存储Binder数据类型。
mData指向parcel缓存的首地址,
mDataCapacity表示parcel缓存容量(大小),
mDataPos指向parcel缓存中空闲区域的首地址,整个parcel缓存是一块连续的内存。

物理地址 = 有效地址+偏移地址,
reinterpret_cast是c++的一种再解释,强制转换

 

2、数据的写入

这里以最简单的例子---传递整数来说明

Java层(Parcel.java)

    public final void writeInt(int val) {
        nativeWriteInt(mNativePtr, val);
    }

    private static native void nativeWriteInt(long nativePtr, int val);


JNI 层:

static const JNINativeMethod gParcelMethods[] = {
    ......
    {"nativeWriteInt",            "(JI)V", (void*)android_os_Parcel_writeInt},
    {"nativeWriteLong",           "(JJ)V", (void*)android_os_Parcel_writeLong},
    ......    
};



static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) {
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); // 把地址转换成指针对象
    const status_t err = parcel->writeInt32(val);
    if (err != NO_ERROR) {
        signalExceptionForError(env, clazz, err);
    }
}



在Native 层中:
status_t Parcel::writeInt32(int32_t val) // 32位即4个字节
{
    return writeAligned(val); // 对齐写入
}

template<class T>
status_t Parcel::writeAligned(T val) {
    // 断言检查
    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));

    // 判断是否超过数据容量值,超过则需要先扩容
    if ((mDataPos+sizeof(val)) <= mDataCapacity) { // 若没超过,不需要扩容

// goto 语句分支
restart_write:
        // 获取当前地址,强制转化指针类型,然后赋值。
        *reinterpret_cast<T*>(mData+mDataPos) = val; // 相当于*p = value
        return finishWrite(sizeof(val)); // 直接返回,不执行下面的扩容操作

    }

    // 若超过了容量值,先扩容
    status_t err = growData(sizeof(val));
    if (err == NO_ERROR) goto restart_write; // 然后执行goto语句,跳转到上面
    return err;
}



status_t Parcel::finishWrite(size_t len)
{
    mDataPos += len;
    ALOGV("finishWrite Setting data pos of %p to %zu", this, mDataPos);
    if (mDataPos > mDataSize) {
        // mDataSize:记录当前mData中写入数据的大小
        // mDataPos下标刚好指向写入数据的末尾,为防止下次写入时出错,这里将设成一样大小。
        mDataSize = mDataPos; 
        ALOGV("finishWrite Setting data size of %p to %zu", this, mDataSize);
    }
    return NO_ERROR;
}



status_t Parcel::growData(size_t len)
{
    size_t newSize = ((mDataSize+len)*3)/2; // 增容大小为(原大小 + 新存放大小)* 1.5倍
    return (newSize <= mDataSize)
            ? (status_t) NO_MEMORY
            : continueWrite(newSize);
}

3、数据的读出

Java层:

    // 注意这个指针地址,可以利用它强制转换得到Parcel 对象
    private long mNativePtr; // used by native code

    public final int readInt() {
        return nativeReadInt(mNativePtr);
    }

    private static native int nativeReadInt(long nativePtr);




JNI层:

static jint android_os_Parcel_readInt(JNIEnv* env, jclass clazz, jlong nativePtr)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
        return parcel->readInt32();
    }
    return 0;
}




在Native 层:

int32_t Parcel::readInt32() const
{
    return readAligned<int32_t>();
}
// int32_t:int_t 为一个结构的标注,可以理解为type/typedef的缩写,
   int32_t   : typedef signed int;
   uint32_t  : typedef unsigned int;
// 表示它是通过typedef定义的,而不是一种新的数据类型。因为跨平台,不同的平台会有不同的字长,
// 所以利用预编译和typedef可以最有效的维护代码。



template<class T>
status_t Parcel::readAligned(T *pArg) const {
    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE(sizeof(T)) == sizeof(T));

    if ((mDataPos+sizeof(T)) <= mDataSize) { // 关键
        const void* data = mData+mDataPos; 
        mDataPos += sizeof(T); // 指针指向下一个数据
        *pArg =  *reinterpret_cast<const T*>(data);
        return NO_ERROR;
    } else {
        return NOT_ENOUGH_DATA;
    }
}

关键:读取数据的时候,
1、首先我们会从parcel的起始地址(mData) + parcel偏移地址(mDataPos),得到物理地址,
2、然后取出数据,然后将parcel的偏移地址 + 取出的数据的字节数,
3、这样指针就可以指向下一个数据


上面首先会将mData+mDataPos得到物理地址,转成指向T类型的指针(T类型就是你传进来的变量的类型),然后将val赋值给指针指向的内容。然后修改偏移地址


 

4、如何回收一个Parcel 对象?

在Java 层(Parcel.java):

    public final void recycle() {
        if (DEBUG_RECYCLE) mStack = null;
        freeBuffer();

        final Parcel[] pool;
        if (mOwnsNativeParcelObject) {
            pool = sOwnedPool;
        } else {
            mNativePtr = 0;
            pool = sHolderPool;
        }

        synchronized (pool) {

             // 遍历池中每一个位置,若为空,则把parcel插入该位置,以便下次复用
             // 若没有空余的位置,就不再执行插入操作,退出返回
            for (int i=0; i<POOL_SIZE; i++) {
                if (pool[i] == null) {
                    pool[i] = this;
                    return;
                }
            }
        }
    }



    private void freeBuffer() {
        if (mOwnsNativeParcelObject) {
            nativeFreeBuffer(mNativePtr);
        }
    }


    private static native void nativeFreeBuffer(long nativePtr);





在JNI 层:

static const JNINativeMethod gParcelMethods[] = {
    {"nativeCreate",              "()J", (void*)android_os_Parcel_create},

    {"nativeFreeBuffer",          "(J)V", (void*)android_os_Parcel_freeBuffer},

    {"nativeDestroy",             "(J)V", (void*)android_os_Parcel_destroy},
    {"nativeMarshall",            "(J)[B", (void*)android_os_Parcel_marshall},
    {"nativeUnmarshall",          "(J[BII)V", (void*)android_os_Parcel_unmarshall},
};


static void android_os_Parcel_freeBuffer(JNIEnv* env, jclass clazz, jlong nativePtr)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
        parcel->freeData();
    }
}




在Native 层:

void Parcel::freeData()
{
    freeDataNoInit(); // 释放内存
    initState();      // 复位初始状态
}

void Parcel::freeDataNoInit()
{
    if (mOwner) {
        //ALOGI("Freeing data ref of %p (pid=%d)", this, getpid());
        mOwner(this, mData, mDataSize, mObjects, mObjectsSize, mOwnerCookie);
    } else {
        releaseObjects();
        if (mData) free(mData);
        if (mObjects) free(mObjects);
    }
}



void Parcel::initState()
{
    mError = NO_ERROR;
    mData = 0;
    mDataSize = 0;
    mDataCapacity = 0;
    mDataPos = 0;
    ALOGV("initState Setting data size of %p to %zu", this, mDataSize);
    ALOGV("initState Setting data pos of %p to %zu", this, mDataPos);
    mObjects = NULL;
    mObjectsSize = 0;
    mObjectsCapacity = 0;
    mNextObjectHint = 0;
    mHasFds = false;
    mFdsKnown = true;
    mAllowFds = true;
    mOwner = NULL;
}



void Parcel::releaseObjects()
{
    const sp<ProcessState> proc(ProcessState::self());
    size_t i = mObjectsSize;
    uint8_t* const data = mData;
    binder_size_t* const objects = mObjects;
    while (i > 0) {
        i--;
        const flat_binder_object* flat
            = reinterpret_cast<flat_binder_object*>(data+objects[i]);
        release_object(proc, *flat, this);
    }
}


void release_object(const sp<ProcessState>& proc,
    const flat_binder_object& obj, const void* who)
{
    switch (obj.type) {
        case BINDER_TYPE_BINDER:
            if (obj.binder) {
                LOG_REFS("Parcel %p releasing reference on local %p", who, obj.cookie);
                reinterpret_cast<IBinder*>(obj.cookie)->decStrong(who);
            }
            return;
        case BINDER_TYPE_WEAK_BINDER:
            if (obj.binder)
                reinterpret_cast<RefBase::weakref_type*>(obj.binder)->decWeak(who);
            return;
        case BINDER_TYPE_HANDLE: {
            const sp<IBinder> b = proc->getStrongProxyForHandle(obj.handle);
            if (b != NULL) {
                LOG_REFS("Parcel %p releasing reference on remote %p", who, b.get());
                b->decStrong(who);
            }
            return;
        }
        case BINDER_TYPE_WEAK_HANDLE: {
            const wp<IBinder> b = proc->getWeakProxyForHandle(obj.handle);
            if (b != NULL) b.get_refs()->decWeak(who);
            return;
        }
        case BINDER_TYPE_FD: {
            if (obj.cookie != 0) close(obj.handle);
            return;
        }
    }

    ALOGE("Invalid object type 0x%08x", obj.type);
}

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Android Parcelable Code Generators是一种用于自动生成Android Parcelable接口相关代码的工具。在Android开发中,Parcelable接口常常用于在Activity之间传递自定义对象。但是手动实现Parcelable接口并编写相关的代码是繁琐且容易出错的,因此开发人员常常使用Parcelable Code Generators来自动生成这些代码。 Parcelable Code Generators可以帮助开发人员简化Parcelable接口的实现过程。它通过解析自定义对象的属性和方法,生成相应的Parcelable接口实现。开发人员只需要在自定义对象上添加一些注解或配置,然后使用Parcelable Code Generators生成器工具即可自动生成Parcelable相关的代码,包括序列化和反序列化的方法。 使用Parcelable Code Generators的好处是提高了开发效率,减少了手动实现Parcelable接口所需的工作量。开发人员只需要关注对象的定义和注解的配置,而无需费心编写复杂的序列化和反序列化代码。此外,Parcelable Code Generators还可以保证生成的代码的正确性和稳定性,避免了手动编写代码过程中可能出现的错误。 总之,Android Parcelable Code Generators是一种实用工具,能够帮助开发人员自动生成Parcelable相关代码,提高开发效率,减少错误。使用它可以简化开发过程,使得在Activity之间传递自定义对象变得更加方便和可靠。 ### 回答2: Android中实现Parcelable接口可以用于实现对象的序列化和反序列化,方便数据的传递和保存。然而,手动编写Parcelable的代码需要写入大量重复的代码,比较繁琐且易出错。因此,有一些代码生成器可以用来自动生成Parcelable代码,简化并加速开发过程。 一种常用的Android Parcelable代码生成器是插件"Parcelable Code Generator"。这个插件可以作为Android Studio的一个扩展来安装和使用。Parcelable Code Generator使用注解处理器,可以根据模板自动生成Parcelable相关代码。 使用这个插件,我们只需要在需要实现Parcelable的Java类上添加一个注解,例如"@Parcelable"。然后,编译项目时插件会自动扫描注解,根据类的字段自动生成Parcelable的代码。生成的代码中包含了Parcelable接口的实现和parcelable的函数,这样就可以将对象序列化到Parcel中,或者将Parcel中的数据还原到对象中。 Parcelable Code Generator还支持一些配置选项,比如可以指定Parcelable的名称、包名、是否实现Parcelable.Creator等。此外,插件还支持自定义Parcelable代码的模板,可以根据需求自定义生成的Parcelable代码的格式和内容。 总之,Android Parcelable Code Generator插件简化了Parcelable代码的编写,提高了开发效率和代码质量。使用这个插件,可以省去手动写入Parcelable的代码,减少了出错的可能性,节省了开发时间。它是Android开发中的一种常见工具,为我们提供了方便快捷的Parcelable代码生成功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值