主頁 > 移動端開發 > Android Parcelable序列化原始碼決議

Android Parcelable序列化原始碼決議

2021-04-14 11:06:18 移動端開發

序列化使用的帖子:Java Serializable、Android Parcelable序列化學習記錄
Serializable序列化原始碼決議的帖子:Java Serializable序列化原始碼決議(ObjectOutputStream、ObjectInputStream)
ヾ(?°?°?)ノ゙歡迎大家去康康


本篇從使用Android Parcelable序列化一個簡單的物件,對其序列化后的十六進制資料進行分析,然后對序列化程序的原始碼進行解讀,
ヾ(?°?°?)ノ゙

文章目錄

      • 序列化資料分析
      • 原始碼相關類整理
      • 序列化原始碼決議
        • Java層Parcel物件的創建程序
        • Native層Parcel物件的創建程序
        • Parcel寫入物件決議
          • 型別識別符號以及類名稱的寫入
          • 變數值的寫入
          • native Parcel#writeString16函式決議
          • native Parcel#writeInt32函式決議
        • marshall方法
        • 小結
      • 反序列化原始碼決議
        • unmarshall方法決議
        • 為啥要在unmarshall后再setDataPosition(0)
        • readValue方法決議
        • 小結
      • 總結

序列化資料分析

先康康測驗代碼,將物件A序列化到檔案p_test.txt中

public class PTest {
    static class A implements Parcelable {
        private String name;

        public A(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return "A{" +
                    "name='" + name + '\'' +
                    '}';
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(this.name);
        }

        public void readFromParcel(Parcel source) {
            this.name = source.readString();
        }

        public A() {
        }

        protected A(Parcel in) {
            this.name = in.readString();
        }

        public static final Parcelable.Creator<A> CREATOR = new Parcelable.Creator<A>() {
            @Override
            public A createFromParcel(Parcel source) {
                return new A(source);
            }

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

    public static void write(){
        try {
            File file = new File("sdcard/p_test.txt");
            if(!file.exists()){
                file.createNewFile();
            }

            FileOutputStream fos = new FileOutputStream(file);
            Parcel parcel = Parcel.obtain();

            parcel.writeValue(new A("llk"));

            fos.write(parcel.marshall());

            parcel.setDataPosition(0);
            parcel.recycle();

            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void read(Context context){

        try {
            File file = new File("sdcard/p_test.txt");
            if(!file.exists()){
                Log.e("llk", "no file");
                return;
            }

            FileInputStream fis = new FileInputStream(file);
            Parcel parcel = Parcel.obtain();

            byte[] bytes = new byte[fis.available()];
            fis.read(bytes);

            parcel.unmarshall(bytes, 0, bytes.length);
            parcel.setDataPosition(0);

            Object obj = parcel.readValue(context.getClassLoader());
            Log.e("llk", "read a=" + obj);

            parcel.recycle();

            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


再打開p_text.txt之后,看看序列化后的資料,

//跟serialiable序列化后的資料一樣,這是什么鬼,看不懂,
0400 0000 1400 0000 6300 6f00 6d00 2e00
6c00 6c00 6b00 2e00 6b00 7400 2e00 7300
2e00 5000 5400 6500 7300 7400 2400 4100
0000 0000 0300 0000 6c00 6c00 6b00 0000

//讓我來翻譯一下,現在是不是好看很多了,
型別		   長度   	  c    o    m    .
0400 0000 1400 0000 6300 6f00 6d00 2e00
l    l    k    .    k    t    .    s
6c00 6c00 6b00 2e00 6b00 7400 2e00 7300
.    P    T    e    s    t    $    A
2e00 5000 5400 6500 7300 7400 2400 4100
		      長度		   l    l    k
0000 0000 0300 0000 6c00 6c00 6b00 0000
  
0400 0000:型別標識,代表序列化的類是什么型別
1400 0000:代表長度(轉為十進制后是20),表示后面是從 6300 - 4100 是一個整體
0300 0000:也是代表長度

原始碼相關類整理

Java

android.os.Parcel //java層Parcel類
android.os.Parcelable //序列化介面
android.os.Parcelable.Creator<T> //反序列化用到的創建類介面

C++

android-8.0/frameworks/base/core/jni/android_os_Parcel.cpp //java層定義的native方法進行關聯
android-8.0/frameworks/native/libs/binder/Parcel.cpp //native層序列化Parcel類

序列化原始碼決議

Parcel parcel = Parcel.obtain();
parcel.writeValue(new A("llk"));
byte[] bytes = parcel.marshall();
parcel.recycle(); //釋放Parcel物件

Java層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) {
                    pool[i] = null;
                    
                    ...
                      
                    //將讀寫工具類物件賦值給這個空Parcel物件
                    p.mReadWriteHelper = ReadWriteHelper.DEFAULT; 
                    return p;
                }
            }
        }
        return new Parcel(0);
    }

	//Parcel構造方法
	private Parcel(long nativePtr) {
    	...
    	init(nativePtr);
    }

    private void init(long nativePtr) {
      	//是否已經創建了native Parcel物件,如果沒有就執行創建
      	//并快取該native Parcel物件的指標地址,序列化都是通過這個native Parcel物件進行的
        if (nativePtr != 0) {
            mNativePtr = nativePtr;
            mOwnsNativeParcelObject = false;
        } else {
            mNativePtr = nativeCreate(); //nativeCreate是native方法了
            mOwnsNativeParcelObject = true;
        }
    }

obtain方法使用了享元模式,用于復用Parcel物件,在頻繁使用到某個物件的場景,就可以享元模式來避免物件的頻繁創建與銷毀,(Handler Message#obtain()中也是類似用法)


Native層Parcel物件的創建程序

//android_os_Parcel.cpp
{"nativeCreate",              "()J", (void*)android_os_Parcel_create},

//android_os_Parcel.cpp#android_os_Parcel_create
static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz)
{
  	//創建Parcel物件指標,并回傳指標地址
    Parcel* parcel = new Parcel();
  	//強轉成jlong后回傳
    return reinterpret_cast<jlong>(parcel);
}

//Parcel.cpp#建構式
Parcel::Parcel()
{
    LOG_ALLOC("Parcel %p: constructing", this);
    initState();
}

//Parcel.cpp#initState
void Parcel::initState()
{ //對一些變數進行初始化
    LOG_ALLOC("Parcel %p: initState", this);
    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);
  	//存盤的為flat_binder_object結構
		//指向flat_binder_object資料地址,mObjects為__u32型別,即4位元組的unsigned int,存的為各object寫入時的mDataPos,
		//具體可以看writeObject方法
    mObjects = NULL;
    mObjectsSize = 0; //flat_binder_object數目
    mObjectsCapacity = 0; //可存盤flat_binder_object容量
    mNextObjectHint = 0;
    mHasFds = false;
    mFdsKnown = true;
    mAllowFds = true;
    mOwner = NULL;
    mOpenAshmemSize = 0;
		...
}

native Parcel創建只是對一些資料進行初始化,最關鍵的就是把物件指標地址回傳到java層了,

后續操作都是通過java層傳入指標地址,獲取到native Parcel物件進行操作的,


Parcel寫入物件決議

型別識別符號以及類名稱的寫入
		//Parcel#writeValue
		public final void writeValue(@Nullable Object v) {
	  			//一大波型別判斷
	        if (v == null) {
	            writeInt(VAL_NULL);
	        } else if (v instanceof String) {
	            writeInt(VAL_STRING);
	            writeString((String) v);
	        } else if (v instanceof Integer) {
	            writeInt(VAL_INTEGER);
	            writeInt((Integer) v);
	        } else if (v instanceof Map) {
	            writeInt(VAL_MAP);
	            writeMap((Map) v);
	        } else if (v instanceof Bundle) {
	            // Must be before Parcelable
	            writeInt(VAL_BUNDLE);
	            writeBundle((Bundle) v);
	        } else if (v instanceof PersistableBundle) {
	            writeInt(VAL_PERSISTABLEBUNDLE);
	            writePersistableBundle((PersistableBundle) v);
	        } else if (v instanceof Parcelable) { //我們實作Parcelable,進來這里
	          	//序列化型別的寫入,這里就是0400 0000的寫入
	            writeInt(VAL_PARCELABLE); //private static final int VAL_PARCELABLE = 4;
	            writeParcelable((Parcelable) v, 0);
	        } else if (v instanceof Short) {
	            writeInt(VAL_SHORT);
	            writeInt(((Short) v).intValue());
	        } else if (v instanceof Long) {
	            writeInt(VAL_LONG);
	            writeLong((Long) v);
	        } else if (v instanceof Float) {
	            writeInt(VAL_FLOAT);
	            writeFloat((Float) v);
	        } else if (v instanceof Double) {
	            writeInt(VAL_DOUBLE);
	            writeDouble((Double) v);
	        } else if (v instanceof Boolean) {
	            writeInt(VAL_BOOLEAN);
	            writeInt((Boolean) v ? 1 : 0);
	        } else if (v instanceof CharSequence) {
	            // Must be after String
	            writeInt(VAL_CHARSEQUENCE);
	            writeCharSequence((CharSequence) v);
	        } else if (v instanceof List) {
	            writeInt(VAL_LIST);
	            writeList((List) v);
	        } else if (v instanceof SparseArray) {
	            writeInt(VAL_SPARSEARRAY);
	            writeSparseArray((SparseArray) v);
	        } else if (v instanceof boolean[]) {
	            writeInt(VAL_BOOLEANARRAY);
	            writeBooleanArray((boolean[]) v);
	        } else if (v instanceof byte[]) {
	            writeInt(VAL_BYTEARRAY);
	            writeByteArray((byte[]) v);
	        } else if (v instanceof String[]) {
	            writeInt(VAL_STRINGARRAY);
	            writeStringArray((String[]) v);
	        } else if (v instanceof CharSequence[]) {
	            writeInt(VAL_CHARSEQUENCEARRAY);
	            writeCharSequenceArray((CharSequence[]) v);
	        } else if (v instanceof IBinder) {
	            writeInt(VAL_IBINDER);
	            writeStrongBinder((IBinder) v);
	        } else if (v instanceof Parcelable[]) {
	            writeInt(VAL_PARCELABLEARRAY);
	            writeParcelableArray((Parcelable[]) v, 0);
	        } else if (v instanceof int[]) {
	            writeInt(VAL_INTARRAY);
	            writeIntArray((int[]) v);
	        } else if (v instanceof long[]) {
	            writeInt(VAL_LONGARRAY);
	            writeLongArray((long[]) v);
	        } else if (v instanceof Byte) {
	            writeInt(VAL_BYTE);
	            writeInt((Byte) v);
	        } else if (v instanceof Size) {
	            writeInt(VAL_SIZE);
	            writeSize((Size) v);
	        } else if (v instanceof SizeF) {
	            writeInt(VAL_SIZEF);
	            writeSizeF((SizeF) v);
	        } else if (v instanceof double[]) {
	            writeInt(VAL_DOUBLEARRAY);
	            writeDoubleArray((double[]) v);
	        } else {
	            Class<?> clazz = v.getClass();
	            if (clazz.isArray() && clazz.getComponentType() == Object.class) {
	                writeInt(VAL_OBJECTARRAY);
	                writeArray((Object[]) v);
	            } else if (v instanceof Serializable) { //居然還有Serializable的
	                // Must be last
	                writeInt(VAL_SERIALIZABLE);
	                writeSerializable((Serializable) v);
	            } else {
	                throw new RuntimeException("Parcel: unable to marshal value " + v);
	            }
	        }
	    }
	
			//Parcel#writeSerializable
			//八卦一下writeSerializable做了什么
			//暴風哭泣,Serializable還是用回Serializable的序列化方式
			public final void writeSerializable(@Nullable Serializable s) {
	        ...
	        String name = s.getClass().getName();
	        writeString(name);
	
	        ByteArrayOutputStream baos = new ByteArrayOutputStream();
	        try {
	            ObjectOutputStream oos = new ObjectOutputStream(baos);
	            oos.writeObject(s);
	            oos.close();
	            writeByteArray(baos.toByteArray());
	        } catch (IOException ioe) {
	            ...
	        }
	    }

		//Parcel#writeParcelable
		public final void writeParcelable(@Nullable Parcelable p, int parcelableFlags) {
	        ...
	        
	       	//寫入類的詳細名稱
	        writeParcelableCreator(p);
	      	//???直接呼叫了我們實作的Parcelable#writeToParcel方法,,,
	      	//也就是說,我們想給類寫入什么就寫入什么,序列化器不關心,全部交由我們處理
	        p.writeToParcel(this, parcelableFlags);
    	}

		//Parcel#writeParcelableCreator
		public final void writeParcelableCreator(@NonNull Parcelable p) {
	      	//這里會寫入 類名長度 + 類名
	        String name = p.getClass().getName();
	        writeString(name);
    	}

從上面看出,序列化器把需要序列化的內容丟回給我們自己去決定,我們想給類寫入什么就寫入什么,序列化器不關心,


變數值的寫入
		//PTest.A#writeToParcel	
		public void writeToParcel(Parcel dest, int flags) {
			dest.writeString(this.name);
		}

		//Parcel#writeString
		public final void writeString(@Nullable String val) {
        	mReadWriteHelper.writeString(this, val);
    	}

		//ReadWriteHelper#writeString
		public void writeString(Parcel p, String s) {
      		//這里是native方法了,傳入native Parcel指標地址以及需要序列化的內容
    		nativeWriteString(p.mNativePtr, s);
    	}

		//android_os_Parcel.cpp#android_os_Parcel_writeString
		{"nativeWriteString",         "(JLjava/lang/String;)V",(void*)android_os_Parcel_writeString},
	
		static void android_os_Parcel_writeString(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val)
	    {
	        Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); //通過地址強轉成Parcel物件指標
	        if (parcel != NULL) {
	            status_t err = NO_MEMORY;
	            if (val) {
	                //申請這個java string物件的使用權,防止val物件剛好正在GC導致本地代碼一直阻塞的問題
	                //后續會詳細說明Get/ReleaseStringCritical
	                const jchar* str = env->GetStringCritical(val, 0);
	                if (str) {
	                    //執行寫入內容、以及內容的長度
	                    err = parcel->writeString16(
	                        reinterpret_cast<const char16_t*>(str),
	                        env->GetStringLength(val));
	                    env->ReleaseStringCritical(val, str); //釋放這個java string物件使用權
	                }
	            } else {
	                err = parcel->writeString16(NULL, 0);
	            }
	            if (err != NO_ERROR) {
	                signalExceptionForError(env, clazz, err);
	            }
	        }
	    }

從上面看出,寫入邏輯主要是依靠native Parcel物件完成,那我們繼續分析一些native Parcel中的這些寫函式,


Get/ReleaseStringCritical
大佬文章:https://www.jianshu.com/p/e4dd44871d3d

在Java中創建的物件全都由GC(垃圾回收器)自動回收,不需要像C/C++一樣需要程式員自己管理記憶體,GC會實時掃描所有創建的物件是否還有參考,如果沒有參考則會立即清理掉,當我們創建一個像int陣列物件的時候,當我們在本地代碼想去訪問時,發現這個物件正被GC執行緒占用了,這時本地代碼會一直處于阻塞狀態,直到等待GC釋放這個物件的鎖之后才能繼續訪問,為了避免這種現象的發生,JNI提供了Get/ReleasePrimitiveArrayCritical這對函式,本地代碼在訪問陣列物件時會暫停GC執行緒,不過使用這對函式也有個限制,在Get/ReleasePrimitiveArrayCritical這兩個函式期間不能呼叫任何會讓執行緒阻塞或等待JVM中其它執行緒的本地函式或JNI函式,和處理字串的Get/ReleaseStringCritical函式限制一樣,這對函式和GetIntArrayElements函式一樣,回傳的是陣列元素的指標,
這是一對函式,有很多,格式為:Get/ReleaseXXXCritical


native Parcel#writeString16函式決議
		//Parcel.cpp#writeString16
		status_t Parcel::writeString16(const char16_t* str, size_t len)
	    {
	        if (str == NULL) return writeInt32(-1);
	
	      	//先寫入長度,如果沒出錯才寫入內容
	        status_t err = writeInt32(len);
	        if (err == NO_ERROR) {
	          	//sizeof(char16_t) = 2
	          	//以llk這個字串為例子的話,len *= sizeof(char16_t) = 6
	            len *= sizeof(char16_t);
	          	//len+sizeof(char16_t) = 8
	          	//writeInplace函式回傳要寫入的地址
	            uint8_t* data = (uint8_t*)writeInplace(len+sizeof(char16_t));
	            if (data) {
	              	//memcpy函式是c++的記憶體拷貝函式,通過memcpy將內容拷貝到data記憶體中,
	                memcpy(data, str, len);
	                ...
	                return NO_ERROR;
	            }
	            err = mError;
	        }
	        return err;
	    }

		//這個宏定義是為了保證s是4的倍數
		//先+3是為了保證s大于等于4
		//然后再&~3為了保證最低兩位為00(~3=00,s&00必定是...00),這樣就能保證s是4的倍數
		#define PAD_SIZE_UNSAFE(s) (((s)+3)&~3)
		
	    //Parcel.cpp#pad_size 是PAD_SIZE_UNSAFE宏定義的封裝
	    static size_t pad_size(size_t s) {
	        ...
	        return PAD_SIZE_UNSAFE(s);
	    }

		//Parcel.cpp#writeInplace
    	void* Parcel::writeInplace(size_t len)
	    {
	        ...
	
	        //保證寫入的內容是4的倍數
	        //也就是說上邊序列化資料分析中看到的
	        //以com.llk.kt.s.PTest$A為例,傳入的len = 20(字串長度) * 2 + 2 = 42,最終pad_size后得到44
	       	//再以llk為例,傳入的len = 3(字串長度) * 2 + 2 = 8,最終pad_size后得到8
	        const size_t padded = pad_size(len);
	
	        ...
	
	        //判斷當前記憶體總大小是否住夠寫入,如果不夠先執行擴容再goto回來
	        if ((mDataPos+padded) <= mDataCapacity) {
	    restart_write:
	            uint8_t* const data = mData+mDataPos; //計算寫入位置,并回傳
	
	            //說實話,下面這段我看不懂,溜了溜了
	          	//猜測這里的邏輯是補位,空位不上0000之類的
	          	//因為序列化資料看到寫入類名的時候,6300 - 4100后還有0000 0000估計是這里補的吧
	            if (padded != len) {
	    #if BYTE_ORDER == BIG_ENDIAN
	                static const uint32_t mask[4] = {
	                    0x00000000, 0xffffff00, 0xffff0000, 0xff000000
	                };
	    #endif
	    #if BYTE_ORDER == LITTLE_ENDIAN
	                static const uint32_t mask[4] = {
	                    0x00000000, 0x00ffffff, 0x0000ffff, 0x000000ff
	                };
	    #endif
	                *reinterpret_cast<uint32_t*>(data+padded-4) &= mask[padded-len];
	            }
	
	            finishWrite(padded);
	            return data;
	        }
	
	        status_t err = growData(padded); //執行擴容
	        if (err == NO_ERROR) goto restart_write; //回到restart_write位置再執行
	        return NULL;
	    }

		//Parcel.cpp#growData 申請一塊比現在大1.5倍的記憶體
	    status_t Parcel::growData(size_t len)
	    {
	        size_t newSize = ((mDataSize+len)*3)/2;
	        return (newSize <= mDataSize)
	                ? (status_t) NO_MEMORY
	                : continueWrite(newSize);
	    }
	
		//Parcel#finishWrite 給指標位置移位
	    status_t Parcel::finishWrite(size_t len)
	    {
	        ...
	
	        mDataPos += len;
	        if (mDataPos > mDataSize) {
	            mDataSize = mDataPos;
	        }
	        return NO_ERROR;
	    }

native Parcel#writeInt32函式決議
		//Parcel.cpp#writeInt32
		status_t Parcel::writeInt32(int32_t val)
	    {
	        return writeAligned(val);
	    }


		//Parcel.cpp#writeAligned
    	template<class T>
	    status_t Parcel::writeAligned(T val) {
	        COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
	
	      	//如果記憶體足夠,直接寫入
	        if ((mDataPos+sizeof(val)) <= mDataCapacity) {
	    restart_write:
	          	//這里寫入val值
	            *reinterpret_cast<T*>(mData+mDataPos) = val;
	            return finishWrite(sizeof(val));
	        }
	
	      	//如果記憶體不夠,會進去擴容然后goto回到restart_write再執行
	        status_t err = growData(sizeof(val));
	        if (err == NO_ERROR) goto restart_write;
	        return err;
	    }

marshall方法

		public final byte[] marshall() {
        	return nativeMarshall(mNativePtr);
    	}

		{"nativeMarshall",            "(J)[B", (void*)android_os_Parcel_marshall},

		static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong nativePtr)
	    {
	        //指標地址強轉成Parcel物件指標
	        Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
	        if (parcel == NULL) {
	           return NULL;
	        }
	
	        //如果parcel中有binder物件,就不能呼叫marshall
	        if (parcel->objectsCount())
	        {
	            jniThrowException(env, "java/lang/RuntimeException", "Tried to marshall a Parcel that contained Binder objects.");
	            return NULL;
	        }
	
	        //根據序列化的資料大小,創建一個java位元組陣列
	        jbyteArray ret = env->NewByteArray(parcel->dataSize());
	
	        if (ret != NULL)
	        {		
	          	//申請這個java位元組陣列物件的使用權
	            jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0);
	            if (array != NULL)
	            {
	                //將序列化的資料拷貝到這個java位元組陣列
	                memcpy(array, parcel->data(), parcel->dataSize());
	                env->ReleasePrimitiveArrayCritical(ret, array, 0); //釋放這個java位元組陣列物件的使用權
	            }
	        }
	
	        return ret; //回傳這個帶有序列化資料的java位元組陣列
	    }

marshall方法就是將序列化資料拷貝給一個java位元組陣列并回傳到java層,然后你就可以為所欲為了,比如我最上邊的例子是將它保存到檔案,


小結

可以看出,Parcel的序列化是直接寫入到記憶體的,而且也并沒有過多的包裝資料,就寫入了型別標識、類名、變數值等,

而且真正執行變數值寫入是根據我們實作的writeToParcel方法來進行的,所有我們可以通過writeToParcel來處理那些資料需要寫入,哪些不需要寫入,


反序列化原始碼決議

parcel.unmarshall(bytes, 0, bytes.length);
parcel.setDataPosition(0);
Object obj = parcel.readValue(context.getClassLoader());

unmarshall方法決議

		//Parcel#unmarshall
		public final void unmarshall(@NonNull byte[] data, int offset, int length) {
        	updateNativeSize(nativeUnmarshall(mNativePtr, data, offset, length));
    	}

		{"nativeUnmarshall",          "(J[BII)J", (void*)android_os_Parcel_unmarshall},

    	//android_os_Parcel.cpp#android_os_Parcel_unmarshall
    	static jlong android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jlong nativePtr,
                                              jbyteArray data, jint offset, jint length)
	    {
	        Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
	        if (parcel == NULL || length < 0) {
	           return 0;
	        }
	
	        //申請jbyteArray data的使用權
	        jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(data, 0);
	        if (array)
	        {
	            parcel->setDataSize(length); //設定記憶體長度
	            parcel->setDataPosition(0); //重置指標位置
	
	            void* raw = parcel->writeInplace(length);
	            memcpy(raw, (array + offset), length); //將資料拷貝到native層的parcel
	
	            //釋放jbyteArray data的使用權
	            env->ReleasePrimitiveArrayCritical(data, array, 0);
	        }
	        return parcel->getOpenAshmemSize();
	    }

unmarshall方法主要是進行了將序列化資料拷貝到native層的parcel,


為啥要在unmarshall后再setDataPosition(0)

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

	{"nativeSetDataPosition",     "(JI)V", (void*)android_os_Parcel_setDataPosition},

	//android_os_Parcel.cpp#android_os_Parcel_setDataPosition
	static void android_os_Parcel_setDataPosition(jlong nativePtr, jint pos)
    {
        Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
        if (parcel != NULL) {
            parcel->setDataPosition(pos);
        }
    }

	//Parcel.cpp#setDataPosition
	void Parcel::setDataPosition(size_t pos) const
    {
        ...
        mDataPos = pos;
        ...
    }

setDataPosition方法很簡答,就是對native層Parcel物件的指標位置進行設定而已,

那為什么要在unmarshall之后再設定一次mDataPos呢?在native層Parcel物件的unmarshall函式里邊不是已經設定過mDataPos = 0了嗎?

	//原因是在parcel->writeInplace(length);的時候,mDataPos指標位置又進行了偏移
	//writeInplace函式里邊最后再回傳之前呼叫了finishWrite函式
  
  	status_t Parcel::finishWrite(size_t len)
    {
        ...

        mDataPos += len; //在這里指標位置偏移了
        if (mDataPos > mDataSize) {
            mDataSize = mDataPos;
        }
        return NO_ERROR;
    }

如果沒有及時重置mDataPos位置的話就會讀不到資料,


readValue方法決議

	//Parcel#readValue
	public final Object readValue(@Nullable ClassLoader loader) {
        int type = readInt(); //讀取型別標識

        switch (type) {
        case VAL_NULL:
            return null;
        case VAL_STRING:
            return readString();
        case VAL_INTEGER:
            return readInt();
        case VAL_MAP:
            return readHashMap(loader);
        case VAL_PARCELABLE: //走這里咯
            return readParcelable(loader);
        case VAL_SHORT:
            return (short) readInt();
        case VAL_LONG:
            return readLong();
        case VAL_FLOAT:
            return readFloat();
        case VAL_DOUBLE:
            return readDouble();
        case VAL_BOOLEAN:
            return readInt() == 1;
        case VAL_CHARSEQUENCE:
            return readCharSequence();
        case VAL_LIST:
            return readArrayList(loader);
        case VAL_BOOLEANARRAY:
            return createBooleanArray();
        case VAL_BYTEARRAY:
            return createByteArray();
        case VAL_STRINGARRAY:
            return readStringArray();
        case VAL_CHARSEQUENCEARRAY:
            return readCharSequenceArray();
        case VAL_IBINDER:
            return readStrongBinder();
        case VAL_OBJECTARRAY:
            return readArray(loader);
        case VAL_INTARRAY:
            return createIntArray();
        case VAL_LONGARRAY:
            return createLongArray();
        case VAL_BYTE:
            return readByte();
        case VAL_SERIALIZABLE:
            return readSerializable(loader);
        case VAL_PARCELABLEARRAY:
            return readParcelableArray(loader);
        case VAL_SPARSEARRAY:
            return readSparseArray(loader);
        case VAL_SPARSEBOOLEANARRAY:
            return readSparseBooleanArray();
        case VAL_BUNDLE:
            return readBundle(loader);
        case VAL_PERSISTABLEBUNDLE:
            return readPersistableBundle(loader);
        case VAL_SIZE:
            return readSize();
        case VAL_SIZEF:
            return readSizeF();
        case VAL_DOUBLEARRAY:
            return createDoubleArray();
        default:
            int off = dataPosition() - 4;
            throw new RuntimeException(
                "Parcel " + this + ": Unmarshalling unknown type code " + type + " at offset " + off);
        }
   }

	//Parcel#readParcelable
	public final <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader) {
      	//獲取類物件里邊的Parcelable.Creator
        Parcelable.Creator<?> creator = readParcelableCreator(loader);
        if (creator == null) {
            return null;
        }
        ...
        //直接呼叫createFromParcel方法
        return (T) creator.createFromParcel(this);
   }

	//Parcel#readParcelableCreator 主要邏輯就是,反序列化類名,然后反射獲取其CREATOR屬性,最后回傳
	public final Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader loader) {
        String name = readString(); //反序列化類名
        if (name == null) {
            return null;
        }
        Parcelable.Creator<?> creator;
        synchronized (mCreators) {
            //從快取里邊去
            HashMap<String,Parcelable.Creator<?>> map = mCreators.get(loader);
            if (map == null) {
                map = new HashMap<>();
                mCreators.put(loader, map);
            }
            creator = map.get(name);
            //如果快取沒有就通過類名反射,獲取到Class物件
            //然后再反射獲取其內部的屬性CREATOR
            if (creator == null) {
                try {
                    ClassLoader parcelableClassLoader =
                            (loader == null ? getClass().getClassLoader() : loader);
                    Class<?> parcelableClass = Class.forName(name, false /* initialize */,
                            parcelableClassLoader);
                    if (!Parcelable.class.isAssignableFrom(parcelableClass)) {
                        throw new BadParcelableException("Parcelable protocol requires subclassing "
                                + "from Parcelable on class " + name);
                    }
                    Field f = parcelableClass.getField("CREATOR");
                    if ((f.getModifiers() & Modifier.STATIC) == 0) {
                        throw new BadParcelableException("Parcelable protocol requires "
                                + "the CREATOR object to be static on class " + name);
                    }
                    Class<?> creatorType = f.getType();
                    if (!Parcelable.Creator.class.isAssignableFrom(creatorType)) {
                        throw new BadParcelableException("Parcelable protocol requires a "
                                + "Parcelable.Creator object called "
                                + "CREATOR on class " + name);
                    }
                    creator = (Parcelable.Creator<?>) f.get(null);
                }
                catch (IllegalAccessException e)
                ...
                //添加到快取
                map.put(name, creator);
            }
        }

       return creator;
   }

最終讀取邏輯,還是回到我們代碼實作的Parcelable.Creator里邊去,序列化流程是依靠writeToParcel方法里邊的呼叫順序來進行的寫入的話,那么反序列化流程也必須準遵循這個順序進行讀取,不然就會讀取資料出錯,


		//PTest#A
		protected A(Parcel in) {
            this.name = in.readString();
        }

        public static final Parcelable.Creator<A> CREATOR = new Parcelable.Creator<A>() {
            @Override
            public A createFromParcel(Parcel source) {
                return new A(source);
            }

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

readString、readInt的原始碼就不進行深入分析了,其讀取流程也是通過native的parcel物件進行讀取,序列化是怎么寫入的,反序列化就是怎么讀取,就不繼續往下分析了,

溜了溜了,


小結

unmarshall方法后必須先重置下指標位置,才能夠正常讀取資料;

Parcelable方式的序列化唯一用到反射的地方,就是在反序列化中獲取類中的 CREATOR屬性;

序列化、反序列化程序中,讀寫變數值順序必須保持一致,才能讀取到正確值,


總結

通過決議Parcelable序列化程序可以看出,它序列化的資料非常簡潔,而且序列化程序非常簡單而直接,是一個輕度的序列化方案,適合用在對性能有一定要求的場景,所有Android才專門設計出Parcelable序列化,提供給行程間通訊使用的,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/275822.html

標籤:其他

上一篇:jni開發(一):撰寫第一個多so的app

下一篇:App多渠道簽名打包

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more