Android Parcelableソースコード解析


Intentで非基本的なタイプのデータを伝えるには、Parcelableを実現する方法と、Serializableインタフェースを実現する方法の2つがあることはよく知られています.
今日はSerializableインタフェースはともかく、Parcelableだけです.
Parcelableはインタフェースにすぎず、いくつかの重要な方法があります.
writeToParcel
   /**
     * Flatten this object in to a Parcel.
     * 
     * @param dest The Parcel in which the object should be written.
     * @param flags Additional flags about how the object should be written.
     * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
     */
    public void writeToParcel(Parcel dest, @WriteFlags int flags);

この方法では、現在保存する必要があるデータをParcelに書き込むことができます.flagsは0と書いてもいいしPARCLABLEと書いてもいいです.WRITE_RETURN_VALUE.この二つの違いは何ですか.後で話しましょう.
この中で、Parcelオブジェクトdestを呼び出して、必要なデータを渡す必要があります.Such as:
 *     public void writeToParcel(Parcel out, int flags) {
 *         out.writeInt(mData);
 *     }

オブジェクトを復元するためにCreatorを実装する必要があります.このCreatorが実装されていない場合、リカバリ時にエラーが発生します.
    /**
     * Interface that must be implemented and provided as a public CREATOR
     * field that generates instances of your Parcelable class from a Parcel.
     */
    public interface Creator {
        /**
         * Create a new instance of the Parcelable class, instantiating it
         * from the given Parcel whose data had previously been written by
         * {@link Parcelable#writeToParcel Parcelable.writeToParcel()}.
         * 
         * @param source The Parcel to read the object's data from.
         * @return Returns a new instance of the Parcelable class.
         */
        public T createFromParcel(Parcel source);
        
        /**
         * Create a new array of the Parcelable class.
         * 
         * @param size Size of the array.
         * @return Returns an array of the Parcelable class, with every entry
         * initialized to null.
         */
        public T[] newArray(int size);
    }

createFromParcel(Parcel source);
この方法は、オブジェクトを復元すると、sourceを渡して読み込ませます.
公式に与えられた例:
 *
 *     public static final Parcelable.Creator MyParcelable> CREATOR
 *             = new Parcelable.Creator<MyParcelable>() {
 *         public MyParcelable createFromParcel(Parcel in) {
 *             return new MyParcelable(in);
 *         }
 *
 *         public MyParcelable[] newArray(int size) {
 *             return new MyParcelable[size];
 *         }
 *     };
 *     
 *     private MyParcelable(Parcel in) {
 *         mData = in.readInt();
 *     }

では、なぜこれらの方法で1つのオブジェクトをintentに入れることができるのでしょうか.そして取り出すことができますか?
ソースコードを見てみましょう.
  /**
     * Add extended data to the intent.  The name must include a package
     * prefix, for example the app com.android.contacts would use names
     * like "com.android.contacts.ShowAll".
     *
     * @param name The name of the extra data, with package prefix.
     * @param value The Parcelable data value.
     *
     * @return Returns the same Intent object, for chaining multiple calls
     * into a single statement.
     *
     * @see #putExtras
     * @see #removeExtra
     * @see #getParcelableExtra(String)
     */
    public @NonNull Intent putExtra(String name, Parcelable value) {
        if (mExtras == null) {
            mExtras = new Bundle();
        }
        mExtras.putParcelable(name, value);
        return this;
    }


実はmExtrasの中に入っていることがわかります.
    private Bundle mExtras;

彼は実はBundleだ.Bundleも実はParcelableインタフェースを実現しています
public final class Bundle extends BaseBundle implements Cloneable, Parcelable {

Bundle putParcelableの実装を見てみましょう.
    /**
     * Inserts a Parcelable value into the mapping of this Bundle, replacing
     * any existing value for the given key.  Either key or value may be null.
     *
     * @param key a String, or null
     * @param value a Parcelable object, or null
     */
    public void putParcelable(@Nullable String key, @Nullable Parcelable value) {
        unparcel();
        mMap.put(key, value);
        mFlags &= ~FLAG_HAS_FDS_KNOWN;
    }


unparcel()に入ります.
    /**
     * If the underlying data are stored as a Parcel, unparcel them
     * using the currently assigned class loader.
     */
    /* package */ void unparcel() {
        synchronized (this) {
            final Parcel source = mParcelledData;
            if (source != null) {
                initializeFromParcelLocked(source, /*recycleParcel=*/ true);
            } else {
                if (DEBUG) {
                    Log.d(TAG, "unparcel "
                            + Integer.toHexString(System.identityHashCode(this))
                            + ": no parcelled data");
                }
            }
        }
    }

通常、mParcelledDataはnullです.実はこの中には簡単なputが入っているだけです.
OK、データを渡すとき、Bundleは過去に渡すので、writeToParcelを呼び出すに違いありません.
  /**
     * Writes the Bundle contents to a Parcel, typically in order for
     * it to be passed through an IBinder connection.
     * @param parcel The parcel to copy this bundle to.
     */
    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        final boolean oldAllowFds = parcel.pushAllowFds((mFlags & FLAG_ALLOW_FDS) != 0);
        try {
            super.writeToParcelInner(parcel, flags);
        } finally {
            parcel.restoreAllowFds(oldAllowFds);
        }
    }

superを呼び出しました.writeToParcelInner(parcel, flags); BaseBundleのwriteToParcelInner(parcel,flags):

    /**
     * Writes the Bundle contents to a Parcel, typically in order for
     * it to be passed through an IBinder connection.
     * @param parcel The parcel to copy this bundle to.
     */
    void writeToParcelInner(Parcel parcel, int flags) {
        // If the parcel has a read-write helper, we can't just copy the blob, so unparcel it first.
        if (parcel.hasReadWriteHelper()) {
            unparcel();
        }
        // Keep implementation in sync with writeToParcel() in
        // frameworks/native/libs/binder/PersistableBundle.cpp.
        final ArrayMap map;
        synchronized (this) {
            // unparcel() can race with this method and cause the parcel to recycle
            // at the wrong time. So synchronize access the mParcelledData's content.
            if (mParcelledData != null) {
                if (mParcelledData == NoImagePreloadHolder.EMPTY_PARCEL) {
                    parcel.writeInt(0);
                } else {
                    int length = mParcelledData.dataSize();
                    parcel.writeInt(length);
                    parcel.writeInt(BUNDLE_MAGIC);
                    parcel.appendFrom(mParcelledData, 0, length);
                }
                return;
            }
            map = mMap;
        }

        // Special case for empty bundles.
        if (map == null || map.size() <= 0) {
            parcel.writeInt(0);
            return;
        }
        int lengthPos = parcel.dataPosition();
        parcel.writeInt(-1); // dummy, will hold length
        parcel.writeInt(BUNDLE_MAGIC);

        int startPos = parcel.dataPosition();
        parcel.writeArrayMapInternal(map);
        int endPos = parcel.dataPosition();

        // Backpatch length
        parcel.setDataPosition(lengthPos);
        int length = endPos - startPos;
        parcel.writeInt(length);
        parcel.setDataPosition(endPos);
    }

中にはたくさん書いてありますが、肝心なのはparcelです.writeArrayMapInternal(map); この文はmapをparcelに書いた.
ParcelのwriteArrayMapInternalメソッドを見てみましょう.
    /**
     * Flatten an ArrayMap into the parcel at the current dataPosition(),
     * growing dataCapacity() if needed.  The Map keys must be String objects.
     */
    /* package */ void writeArrayMapInternal(ArrayMap val) {
        if (val == null) {
            writeInt(-1);
            return;
        }
        // Keep the format of this Parcel in sync with writeToParcelInner() in
        // frameworks/native/libs/binder/PersistableBundle.cpp.
        final int N = val.size();
        writeInt(N);
        if (DEBUG_ARRAY_MAP) {
            RuntimeException here =  new RuntimeException("here");
            here.fillInStackTrace();
            Log.d(TAG, "Writing " + N + " ArrayMap entries", here);
        }
        int startPos;
        for (int i=0; i

まず長さを書いて、それからkを書いて、valueを書きます.ここのwriteValueの方法を見てみましょう
    public final void writeValue(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) {
            // IMPOTANT: cases for classes that implement Parcelable must
            // come before the Parcelable case, so that their specific VAL_*
            // types will be written.
            writeInt(VAL_PARCELABLE);
            writeParcelable((Parcelable) v, 0);
        } else if (v instanceof Short) {
            writeInt(VAL_SHORT);
            writeInt(((Short) v).intValue());
        }
		....
    }

Parcelableと書いてあるのを見つけたらwriteParcelable
    public final void writeParcelable(Parcelable p, int parcelableFlags) {
        if (p == null) {
            writeString(null);
            return;
        }
        writeParcelableCreator(p);
        p.writeToParcel(this, parcelableFlags);
    }
    public final void writeParcelableCreator(Parcelable p) {
        String name = p.getClass().getName();
        writeString(name);
    }

ここではまずParcelableオブジェクトのクラス名を書き、ParcelableオブジェクトのwriteToParcelを呼び出します.つまり自分で実現する方法は、私たちが伝えたいデータをParcelに書きます.
OK、これでParcelableインタフェースのwriteToParcelメソッドが呼び出されます.
ParcelのreadFromParcelを見てみましょう
    /**
     * Reads the Parcel contents into this Bundle, typically in order for
     * it to be passed through an IBinder connection.
     * @param parcel The parcel to overwrite this bundle from.
     */
    public void readFromParcel(Parcel parcel) {
        super.readFromParcelInner(parcel);
        mFlags = FLAG_ALLOW_FDS;
        maybePrefillHasFds();
    }

super.readFromParcelInner(parcel);
    private void readFromParcelInner(Parcel parcel, int length) {
        if (length < 0) {
            throw new RuntimeException("Bad length in parcel: " + length);

        } else if (length == 0) {
            // Empty Bundle or end of data.
            mParcelledData = NoImagePreloadHolder.EMPTY_PARCEL;
            return;
        }

        final int magic = parcel.readInt();
        if (magic != BUNDLE_MAGIC) {
            throw new IllegalStateException("Bad magic number for Bundle: 0x"
                    + Integer.toHexString(magic));
        }

        if (parcel.hasReadWriteHelper()) {
            // If the parcel has a read-write helper, then we can't lazily-unparcel it, so just
            // unparcel right away.
            synchronized (this) {
                initializeFromParcelLocked(parcel, /*recycleParcel=*/ false);
            }
            return;
        }

        // Advance within this Parcel
        int offset = parcel.dataPosition();
        parcel.setDataPosition(MathUtils.addOrThrow(offset, length));

        Parcel p = Parcel.obtain();
        p.setDataPosition(0);
        p.appendFrom(parcel, offset, length);
        p.adoptClassCookies(parcel);
        if (DEBUG) Log.d(TAG, "Retrieving "  + Integer.toHexString(System.identityHashCode(this))
                + ": " + length + " bundle bytes starting at " + offset);
        p.setDataPosition(0);

        mParcelledData = p;
    }

簡単で、現在のmParcelledDataに値を割り当てます.
getParcelableを呼び出すと、まずunparcel()になります.
    public  T getParcelable(@Nullable String key) {
        unparcel();
        Object o = mMap.get(key);
        if (o == null) {
            return null;
        }
        try {
            return (T) o;
        } catch (ClassCastException e) {
            typeWarning(key, o, "Parcelable", e);
            return null;
        }
    }

    /* package */ void unparcel() {
        synchronized (this) {
            final Parcel source = mParcelledData;
            if (source != null) {
                initializeFromParcelLocked(source, /*recycleParcel=*/ true);
            } else {
                if (DEBUG) {
                    Log.d(TAG, "unparcel "
                            + Integer.toHexString(System.identityHashCode(this))
                            + ": no parcelled data");
                }
            }
        }
    }

private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel) {
        if (LOG_DEFUSABLE && sShouldDefuse && (mFlags & FLAG_DEFUSABLE) == 0) {
            Slog.wtf(TAG, "Attempting to unparcel a Bundle while in transit; this may "
                    + "clobber all data inside!", new Throwable());
        }

        if (isEmptyParcel(parcelledData)) {
            if (DEBUG) {
                Log.d(TAG, "unparcel "
                        + Integer.toHexString(System.identityHashCode(this)) + ": empty");
            }
            if (mMap == null) {
                mMap = new ArrayMap<>(1);
            } else {
                mMap.erase();
            }
            mParcelledData = null;
            return;
        }

        final int count = parcelledData.readInt();
        if (DEBUG) {
            Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
                    + ": reading " + count + " maps");
        }
        if (count < 0) {
            return;
        }
        ArrayMap map = mMap;
        if (map == null) {
            map = new ArrayMap<>(count);
        } else {
            map.erase();
            map.ensureCapacity(count);
        }
        try {
            parcelledData.readArrayMapInternal(map, count, mClassLoader);
        } catch (BadParcelableException e) {
            if (sShouldDefuse) {
                Log.w(TAG, "Failed to parse Bundle, but defusing quietly", e);
                map.erase();
            } else {
                throw e;
            }
        } finally {
            mMap = map;
            if (recycleParcel) {
                recycleParcel(parcelledData);
            }
            mParcelledData = null;
        }
        if (DEBUG) {
            Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
                    + " final map: " + mMap);
        }
    }

parcelledData.readArrayMapInternal(map, count, mClassLoader); 最終的にParcelクラスのreadArrayMapInternalが呼び出されました
    /* package */ void readArrayMapInternal(ArrayMap outVal, int N,
        ClassLoader loader) {
        if (DEBUG_ARRAY_MAP) {
            RuntimeException here =  new RuntimeException("here");
            here.fillInStackTrace();
            Log.d(TAG, "Reading " + N + " ArrayMap entries", here);
        }
        int startPos;
        while (N > 0) {
            if (DEBUG_ARRAY_MAP) startPos = dataPosition();
            String key = readString();
            Object value = readValue(loader);
            if (DEBUG_ARRAY_MAP) Log.d(TAG, "  Read #" + (N-1) + " "
                    + (dataPosition()-startPos) + " bytes: key=0x"
                    + Integer.toHexString((key != null ? key.hashCode() : 0)) + " " + key);
            outVal.append(key, value);
            N--;
        }
        outVal.validate();
    }

readValueが呼び出されました
public final Object readValue(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);

    .......
    }

readParcelable(loader);
    public final  T readParcelable(ClassLoader loader) {
        Parcelable.Creator> creator = readParcelableCreator(loader);
        if (creator == null) {
            return null;
        }
        if (creator instanceof Parcelable.ClassLoaderCreator>) {
          Parcelable.ClassLoaderCreator> classLoaderCreator =
              (Parcelable.ClassLoaderCreator>) creator;
          return (T) classLoaderCreator.createFromParcel(this, loader);
        }
        return (T) creator.createFromParcel(this);
    }
 public final Parcelable.Creator> readParcelableCreator(ClassLoader loader) {
        String name = readString();
        if (name == null) {
            return null;
        }
        Parcelable.Creator> creator;
        synchronized (mCreators) {
            HashMap> map = mCreators.get(loader);
            if (map == null) {
                map = new HashMap<>();
                mCreators.put(loader, map);
            }
            creator = map.get(name);
            if (creator == null) {
                try {
                    // If loader == null, explicitly emulate Class.forName(String) "caller
                    // classloader" behavior.
                    ClassLoader parcelableClassLoader =
                            (loader == null ? getClass().getClassLoader() : loader);
                    // Avoid initializing the Parcelable class until we know it implements
                    // Parcelable and has the necessary CREATOR field. http://b/1171613.
                    Class> parcelableClass = Class.forName(name, false /* initialize */,
                            parcelableClassLoader);
                    if (!Parcelable.class.isAssignableFrom(parcelableClass)) {
                        throw new BadParcelableException("Parcelable protocol requires that the "
                                + "class implements Parcelable");
                    }
                    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)) {
                        // Fail before calling Field.get(), not after, to avoid initializing
                        // parcelableClass unnecessarily.
                        throw new BadParcelableException("Parcelable protocol requires a "
                                + "Parcelable.Creator object called "
                                + "CREATOR on class " + name);
                    }
                    creator = (Parcelable.Creator>) f.get(null);
                }
                catch (IllegalAccessException e) {
                    Log.e(TAG, "Illegal access when unmarshalling: " + name, e);
                    throw new BadParcelableException(
                            "IllegalAccessException when unmarshalling: " + name);
                }
                catch (ClassNotFoundException e) {
                    Log.e(TAG, "Class not found when unmarshalling: " + name, e);
                    throw new BadParcelableException(
                            "ClassNotFoundException when unmarshalling: " + name);
                }
                catch (NoSuchFieldException e) {
                    throw new BadParcelableException("Parcelable protocol requires a "
                            + "Parcelable.Creator object called "
                            + "CREATOR on class " + name);
                }
                if (creator == null) {
                    throw new BadParcelableException("Parcelable protocol requires a "
                            + "non-null Parcelable.Creator object called "
                            + "CREATOR on class " + name);
                }

                map.put(name, creator);
            }
        }

        return creator;
    }

中にはParcelクラスがロードされ、creatorがないことに気づいたら異常を投げます.待って、最終的にあなたのクラスのcreateFromParcelを呼び出しました.
OK、プロセス全体はここで終わります.Parcleableインタフェースの呼び出しは皆さんご存知でしょう.