Parcelable最強解析

10752 ワード

この2,3日、T extends BaseBeanは、BaseBeanクラスにparceableインタフェースを実現し、あるActivityで別のActivityにジャンプしたときにintent.PutExtra(「key」,childBean)は、BaseBeanを直接継承するChildBeanオブジェクトに用いられ、ChildBeanのデータ情報が別のActivityでは取得できないと感じ、ChildBean=getIntent()を使用する場合でも使用する.getParcelableExtra()のときにタイプ変換エラーが発生し、BaseBean=getIntent()を使用する.getParcelableExtra()は確かに問題なく、一時的に親に対してparcelableインタフェースを実現し、子クラスがparcelableインタフェースを実現する必要があるかどうか、そして伝値に論争が生じ、多くの学生もこのような困惑があると信じているので、この文章があります(ここのBasebeanとChildBeanは親クラスと子クラスを指し、本文もこの意味です)

1.Java serialization algorithm


答え:オブジェクトにSerializableインタフェースを実装すると、シーケンス化メカニズムというクラスがシーケンス化可能であることを示します.javaはファイルストリームの形式でobjectをファイルfileに書きます.
public static void main(String args[]) throws IOException {
    FileOutputStream fos = new FileOutputStream("temp.out");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    TestSerial ts = new TestSerial();
    oos.writeObject(ts);
    oos.flush();
    oos.close();
}

ここではObjectOutputStreamの構造オブジェクトに注意し、フローのヘッダーのように書かれています.ここではcodeの後ろの注釈に注意します.例の上には上が向いているからです.
public ObjectOutputStream(OutputStream out) throws IOException {
        verifySubclass();
        ......
        writeStreamHeader();
        .....
    }
protected void writeStreamHeader() throws IOException {
        bout.writeShort(STREAM_MAGIC);// 
        bout.writeShort(STREAM_VERSION);// 
    }
protected void writeStreamHeader() throws IOException {
        bout.writeShort(STREAM_MAGIC);
        bout.writeShort(STREAM_VERSION);
    }

具体的な実装は次のとおりです.
    private void writeObject0(Object obj, boolean unshared) throws IOException
    {
            ······
            else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared);
            } 
            ······
       ·····
    }

上のコードに示すように、最初はオブジェクトがSerializableなのでwriteOrdinaryObject(obj,desc,unshared)を歩きます.方法:
 private void writeOrdinaryObject(Object obj,ObjectStreamClass desc,boolean unshared) throws IOException
    {
        ······
        try {
            desc.checkSerialize();

            bout.writeByte(TC_OBJECT);// TC_OBJECT
            writeClassDesc(desc, false);// classDesc
            ······
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
    }

上にTC_を書き込みますOBJECTの後、writeClassDescメソッドを呼び出します.ここでは分析を続けません.文章の重点はSerializableの分析ではありません.次はjavaコードの呼び出しです.ソースコードもあります.興味があれば、ソースコードを勝手に見て分析できると信じています.ここではみんなの時間を無駄にしませんが、書くときは、まず自分のクラスの説明を書きます.次に、親がいる場合は親の説明を書き、自分のクラスに含まれるフィールドがオブジェクトである場合は、そのオブジェクトの説明を書き、最後に字セグメントのデータを書き終えます.ここでは、1つのクラスに対して中のフィールドを取得し、方法などは、反射メカニズムを使用します.以下はオブジェクトの書き込みの例です.1つのクラスが次のように仮定します.
class TestSerial implements Serializable {
    public byte version = 100;
}

上記のように、ディスクに書き込む際に保存されるデータは次のようになります.
AC ED ( )
00 05 ( )
73     (TC_OBJECT.  )
72     (TC_CLASSDESC.  ) 
00 0A  ( )
53 65 72 69 61 6C 54 65 73 74 ( ) 
05 52 81 5A AC 66 02 F6 (SerialVersionUID)
02     (Various flags,0x02 ) 
00 01  ( ) 
49     ( int )
00 07  ( )
76 65 72 73 69 6F 6E (version,  )
78     (TC_ENDBLOCKDATA,  )
70     (TC_NULL)
00 00 00 64 (version )

上からseriaiableのシーケンス化と逆シーケンス化が大量のオブジェクトを作成し、データを書き込む場合、シーケンス化プロトコル、バージョンなど、実際のデータ以外のデータが書き込まれることがわかります.

2.Parcableメカニズムの原理?


まず、エンティティオブジェクトがparcelableを実装するとき、writeToParcelメソッドを書き換え、dest.を実行します.writeInt(this.offLineBtn);writeLongなどのデータは、実際にはnativeメソッドを実行しています.ここでは、さまざまなデータ型のアクセスを分析しません.intを代表して分析し、jniメソッドを見てみましょう.
static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jint nativePtr, jint val) {
    Parcel* parcel = reinterpret_cast(nativePtr);
    const status_t err = parcel->writeInt32(val);
    if (err != NO_ERROR) {
        signalExceptionForError(env, clazz, err);
    }
}

ここでは特に2つのパラメータに注意します.1つは、前に渡されたポインタと、保存する必要があるintデータです.この2つの値は、(jint nativePtr,jint val)まずこのポインタに基づいています.ここでは、ポインタは実際には整数アドレス値なので、ここではint値をparcelタイプに変換するポインタを強く回して実行し、このポインタを使用してnativeのparcelオブジェクトを操作します.すなわち、const status_t err = parcel->writeInt32(val);
writeInt 32はparcelを呼び出す方法であり、parcelの実装クラスはFramework/native/libsbinderParcelである.cpp、writeInt 32の方法を見てみましょう.
status_t Parcel::writeInt32(int32_t val)
{
    return writeAligned(val);
}
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:
        *reinterpret_cast(mData+mDataPos) = val;
        return finishWrite(sizeof(val));
    }

    status_t err = growData(sizeof(val));
    if (err == NO_ERROR) goto restart_write;
    return err;
}

上記を分析する前に、まずmData、mDataPos、mDataCapacityの3つの変数の意味を知る必要があります.mDataはparcelキャッシュのヘッダアドレスを指し、mDataCapacityはparcelキャッシュ容量(サイズ)を表し、mDataPosはparcelキャッシュの空き領域のヘッダアドレスを指し、parcelキャッシュ全体が連続したメモリです.物理アドレス=有効アドレス+オフセットアドレスは、まず、先に書き込まれたintデータのバイト数がdataの容量を超えているか否かを判断し、超えていなければ、データの書き込みが実行され、reinterpret_castはc++の再解釈であり、強制変換では、まずmData+mData Posを物理アドレスに変換し、Tタイプを指すポインタ(Tタイプはあなたが伝えた変数のタイプ)に変換し、ポインタが指す内容にvalを付与します.次にオフセットアドレスを変更し、finishWrite(sizeof(val):
status_t Parcel::finishWrite(size_t len)
{
    if (len > INT32_MAX) {
        // don't accept size_t values which may have come from an
        // inadvertent conversion from a negative int.
        return BAD_VALUE;
    }

    //printf("Finish write of %d
", len); mDataPos += len; ALOGV("finishWrite Setting data pos of %p to %zu", this, mDataPos); if (mDataPos > mDataSize) { mDataSize = mDataPos; ALOGV("finishWrite Setting data size of %p to %zu", this, mDataSize); } //printf("New pos=%d, size=%d
", mDataPos, mDataSize); return NO_ERROR; }

上は主にオフセットアドレスを変更し、オフセットアドレスに新しく追加されたデータのバイト数を加算します.増加したデータが容量より大きい場合は、まずparcelのキャッシュスペースを拡張し、growData(sizeof(val):
status_t Parcel::growData(size_t len)
{
    if (len > INT32_MAX) {
        // don't accept size_t values which may have come from an
        // inadvertent conversion from a negative int.
        return BAD_VALUE;
    }

    size_t newSize = ((mDataSize+len)*3)/2;
    return (newSize <= mDataSize)
            ? (status_t) NO_MEMORY
            : continueWrite(newSize);
}

拡張に成功したらgoto restart_を続けますwrite、writeAlignedメソッドにrestart_がありますwrite,restart_を実行するwriteの後ろにcodeがあり、データが書き込まれます.
上記の説明ではintタイプのデータがparcelキャッシュに書き込まれていることが分かったと信じています.データを格納することを知っている以上、データを取得することも理解しなければなりません.データを取得するときは、thisを通過します.age = in.readInt();intタイプデータを取得する
static jint android_os_Parcel_readInt(jlong nativePtr)
{
    Parcel* parcel = reinterpret_cast(nativePtr);
    if (parcel != NULL) {
        return parcel->readInt32();
    }
    return 0;
}

呼び出しparcelのreadInt 32メソッド:
int32_t Parcel::readInt32() const
{
    return readAligned();
}
T Parcel::readAligned() const {
    T result;
    if (readAligned(&result) != NO_ERROR) {
        result = 0;
    }

    return result;
}
status_t Parcel::readAligned(T *pArg) const {
    COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));

    if ((mDataPos+sizeof(T)) <= mDataSize) {
        const void* data = mData+mDataPos;
        mDataPos += sizeof(T);
        *pArg =  *reinterpret_cast(data);
        return NO_ERROR;
    } else {
        return NOT_ENOUGH_DATA;
    }
}

データを読み込むときは、まずparcelの先頭アドレス+parcelからアドレスをオフセットして、読み取ったデータのアドレスを取得して、データを取り出して、parcelのオフセットアドレス+取り出したデータのバイト数をポインタが次のデータを指すことができます.これは抽象的です.例えば、私たちは今オブジェクトを持っています.中には
stu{
    int age = 32;
    double score = 99;
}

私たちはデータを書く時、1つのparcelのメモリアドレスの中で、32,99を書いて、それから読み取る時、開始アドレス+から読み取ったバイト数、一つ一つ読み取って、まずparcelの開始アドレスが指すデータを読み取って、32を取り出して、それからポインタアドレスをint字の節数にオフセットして、ポインタが99のアドレスを指して、それから99を読み取って、それからデータを取り出して、これはparcelableが実装される際になぜ格納と読み出しの順序が一致する必要があるのかという原因である.

3.parcelableの実現原理を理解したとき、引用上の問題を解くことができます。


3.1 BaseBeanクラスに対してparceableインタフェースを実現し、あるActivityの中で別のActivityにジャンプする時、intent.putExtra(「key」,childBean)、もう一つのActivityでデータを入手できますか?


答え:親のBaseBeanにはBaseBeanのフィールドの読み書きが実現されているので、BaseBeanのフィールドのデータは入手できます.

3.2 ChildBean=getIntent()を使用する.getParcelableExtra()のときにタイプ変換エラーが発生し、BaseBean=getIntent()を使用する.getParcelableExtra()は問題ありませんか?


答え:実はここではBaseBeanでデータを読むのですが、返される対象は何ですか.
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
        @Override
        public BaseBean createFromParcel(Parcel source) {
            return new BaseBean(source);
        }

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

明らかに、ここで戻ってきたBaseBeanのオブジェクトは、ChildBeanで受信すると必ずタイプ変換エラーが発生しますが、ChildBeanで受信したいと思ったら(強迫症が前提)、createFromParcelメソッドを書き換えることができます
    @Override
    public BaseBean createFromParcel(Parcel source) {
            ChildBean childBean = new ChildBean();
            childBean.setName(source.readString());
            childBean.setPrice(source.readDouble());
            return childBean;
        }

これはChildBeanに戻らなければいいのではないでしょうか.もちろん、あなたがどんな方法であれ、childBeanがparceableを実現しなければ、childBeanのフィールドには伝わりません.attention:これはSerializableの実装とは異なり、Serializableは親がSerializableを実装し、子がSerializableを実装する必要がなく、子のデータも伝達できるようになった.データを書き込む判断(obj instanceof Serializable)では、親がSerializableを実装すれば、子もinstanceof Serializableであるに違いない.

3.3共通のインタフェースを使用する必要がある場合、この共通のインタフェースは汎用T t=getIntent()を通過する可能性がある.getParcelableExtra()でデータを取得するソリューションは?


答え:ここで私たちのBaseBeanはクラスではありません.最も適切なのはinterfaceです.例えば、私たちの共通インタフェースはt.getName()を使って表示されたデータを得ることです.このとき
class ChildBean implements BaseBean,Parcelable{
    ...
    @Override
    public String getName(){
        return "WelliJohn";
    }
    ...
}

伝達値が使用されると、ChildBean自身がParcelableインタフェースを実現し、コードが完璧になります.このように本当に共通インタフェースに特殊なタイプがあれば,Tのタイプ(ChildBean.class.isInstance(t))を判断し,強い回転である特殊なデータ処理を行うことができる.

4.まとめ


serialization
parcable
ファイル操作、反射
個別のメモリ容量、高速
大量の読み書きオブジェクトが作成されます
直接操作メモリ読み書き
シンプル化
複雑で、読み取りと取得のデータが一致する
書き込みの際、フィールド名、長さなどがあります
データを書き込むだけで、リソースを節約
ファイルに書くので、データを永続化するのに適しています
データの永続化には適しておらず、変化する可能性があります.
3.3の解決策に対してもっと良い処理の構想があると感じたら、共同で検討することを歓迎します.もし文章があなたに啓示的な役割を果たしていると思ったら、「いいね」をクリックしたり、注目したりしてください.ありがとうございます.