簡單理解 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})

最後運行打印日誌, 取值正確.

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