简单理解 Parcel 基本原理

Android 中 parcelable 和 parcel 的关系及源码可以看这篇文章, 不在多做描述, 只是根据源码结合之前学到的写出一个简单的 Parcel.

目录结构如下:


1. 定义 Native 方法

首先是先定义一个 ZParcel, 并定义出主要的 Native 方法.

package com.aaatest.study003;

public class ZParcel {
    static  {
        System.loadLibrary("study003");
    }

    //共享内存, 全局只会存在一个 ZParcel 实例
    //这就是 ZParcel 对象实例的首地址.
    //创建 ZParcel 对象时, 会返回这个地址, 每次操作 ZParcel 对象,  都需要传入 long 类型的值到 Native 层
    //native 拿到这个值后, 进行强制转换为 ZParcel 对象. 进行下面的操作.
    private  long mNativePtr = 0L;

    private ZParcel(long nativePtr){
        this.mNativePtr = nativePtr;
        mNativePtr = nativeCreate();
    }

    private  static  class  MyObject {
        private static ZParcel ZPARCEL = new ZParcel(0L);
    }

    public static ZParcel obtain() {
        return MyObject.ZPARCEL;
    }

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

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

    public final void setDataPosition(int pos) {
        nativeSetDataPosition(mNativePtr, pos);
    }

    private native long nativeCreate(); //构建对象指针
    private native void nativeWriteInt(long mNativePtr, int val); //写入
    private native void nativeSetDataPosition(long mNativePtr, int pos); //设置标记位,方便读写
    private native int nativeReadInt(long mNativePtr);//读取
}

2. 生成头文件

src/main/java 文件夹下使用命令 javah com.aaatest.study003.ZParcel, 接着将生成的 com_aaatest_study003_ZParcel.h 头文件放入 cpp 目录下.
下面代码是生成的头文件内容.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#include <string>
#include <android/log.h>
#include "ZParcel.h"

/* Header for class com_aaatest_study003_ZParcel */

#ifndef _Included_com_aaatest_study003_ZParcel
#define _Included_com_aaatest_study003_ZParcel
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_aaatest_study003_ZParcel
 * Method:    nativeCreate
 * Signature: ()J
 */
JNIEXPORT jlong JNICALL Java_com_aaatest_study003_ZParcel_nativeCreate
  (JNIEnv *, jobject);

/*
 * Class:     com_aaatest_study003_ZParcel
 * Method:    nativeWriteInt
 * Signature: (JI)V
 */
JNIEXPORT void JNICALL Java_com_aaatest_study003_ZParcel_nativeWriteInt
  (JNIEnv *, jobject, jlong, jint);

/*
 * Class:     com_aaatest_study003_ZParcel
 * Method:    nativeSetDataPosition
 * Signature: (JI)V
 */
JNIEXPORT void JNICALL Java_com_aaatest_study003_ZParcel_nativeSetDataPosition
  (JNIEnv *, jobject, jlong, jint);

/*
 * Class:     com_aaatest_study003_ZParcel
 * Method:    nativeReadInt
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_com_aaatest_study003_ZParcel_nativeReadInt
  (JNIEnv *, jobject, jlong);

#ifdef __cplusplus
}
#endif
#endif

3. 使用

接着在 Activity 中的一个按钮点击事件中使用.

  binding.sampleText.setOnClickListener{
            val zParcel = ZParcel.obtain()
            zParcel.writeInt(10)
            zParcel.writeInt(20)

            zParcel.setDataPosition(0)

            val num1 = zParcel.readInt()
            val num2 = zParcel.readInt()

            Log.e("TAG", "num1 = ${num1}, num2 = ${num2}")
        }
    }

4. 在 cpp 文件夹新建名为 ZParcelC++ 文件和头文件

ZParcel.h 头文件中定义方法

#include "jni.h"
#include <malloc.h>

#ifndef STUDY003_ZPARCEL_H
#define STUDY003_ZPARCEL_H


class ZParcel {
private:
    //ZParcel 对象在共享内存的首地址.
    char * mData = 0;
    //ZParcel 对象内存地址的指针移动位置
    int mDataPos = 0;
    //操作指针移动
    void changePos(int pos);
public:
    //构造
    ZParcel();
    //析构
    virtual ~ZParcel();
    //写
    void writeInt(int val);
    //设置指针位置
    void setDataPosition(int pos);
    //读
    int readInt();
};


#endif //STUDY003_ZPARCEL_H

ZParcel.cpp 文件实现定义的方法

#include "ZParcel.h"

//构造函数
ZParcel::ZParcel() {
    this->mData = static_cast<char *>(malloc(1024));
}

//析构函数, 释放
ZParcel::~ZParcel() {
    if(this->mData) {
        free(this->mData);
    }
    if(this->mDataPos) {
        this->mDataPos = 0;
    }
}


void ZParcel::writeInt(int val) {
    //将要写的值赋值给 (ZParcel 在内存地址中的首地址 + 指针偏移的位置) 进行取值
    //第一次写入 mDataPos = 0, 那就是从 ZParcel 首地址开始写入, 写入完成后, 指针移动. 变为 mDataPos  = 4.
    //第二次写入 mDataPos = 4, 那就是从 ZParcel 首地址向后偏移 4 位开始写入, 写入完成后, 指针移动, mDataPos  = 8
    //第三次    mDataPos = 8, ....偏移 8 位,........ mDataPos  = 12.
    * reinterpret_cast<int *>(this->mData + this->mDataPos) = val;
    changePos(sizeof(int));
}


void ZParcel::changePos(int pos) {
    this->mDataPos += pos;
}

int ZParcel::readInt() {
    int ret  = *reinterpret_cast<int *>(this->mData + this->mDataPos);
    changePos(sizeof(int ));
    return ret;
}

void ZParcel::setDataPosition(int pos) {
    this->mDataPos = pos;
}

5. 编写 Native 方法

将在 javah com.aaatest.study003.ZParcel.h 头文件中生成的方法声明复制到 native-lib.cpp 中, 并实现.

extern "C" 
JNIEXPORT jlong JNICALL Java_com_aaatest_study003_ZParcel_nativeCreate(JNIEnv * env, jobject job){
    auto * zParcel = new ZParcel();
    return reinterpret_cast<jlong>(zParcel);
}

extern "C" 
JNIEXPORT void JNICALL Java_com_aaatest_study003_ZParcel_nativeWriteInt(JNIEnv * env, jobject , jlong zParcel, jint val){
    auto * _zParcel = reinterpret_cast<ZParcel *>(zParcel);
    _zParcel->writeInt(val);
}

extern "C" 
JNIEXPORT void JNICALL Java_com_aaatest_study003_ZParcel_nativeSetDataPosition(JNIEnv * env, jobject , jlong zParcel, jint pos){
    auto * _zParcel = reinterpret_cast<ZParcel *>(zParcel);
    _zParcel->setDataPosition(pos);
}

extern "C" 
JNIEXPORT jint JNICALL Java_com_aaatest_study003_ZParcel_nativeReadInt(JNIEnv * env, jobject , jlong zParcel){
    auto * _zParcel = reinterpret_cast<ZParcel *>(zParcel);
    return _zParcel->readInt();
}

6. 最后编辑 CMakeLists.txt


cmake_minimum_required(VERSION 3.18.1)

project("study003")

file(GLOB allSource *.c *.cpp)

add_library(
        study003
        SHARED
        ${allSource})

find_library(
        log-lib
        log)

target_link_libraries(
        study003
        ${log-lib})

最后运行打印日志, 取值正确.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章