2020-03-30-系统配置改变时Activity状态的保存

最近遇到了一个问题,在我的应用页面切换暗色模式的时候,切换前后页面的数据和状态出现混乱。
所以仔细看了一下配置变化的监听和如何对Activity状态进行保存。
下面画了一张图,是Activity生命周期的变化:


Activity生命周期.jpg

onConfigurationChanged

onConfigurationChanged主要是用来监听系统配置变化,比如最简单的横竖屏切换,如果不监听orientation,Activity会重建,生命周期变化是

onPause->onStop->onSaveInstanceState->onDestroy->onCreate->onRestoreInstanceState->onResume

这个生命周期很重要,如果我们选择在onCreate方法中对UI状态进行初始化,那么就有可能因为时序问题被onRestoreInstanceState方法覆盖。
而监听并重写onConfigurationChanged之后,可以直接调用这个方法,不用重建。
在我们不想要重建的情况下,就可以在这个方法中处理一些逻辑。

onSaveInstanceState

需要注意的是,就算我们没有重写这个方法,系统也会自动保存某些UI状态。
这里使用了一种比较重要的数据结构Bundle,它是一个map类型的数据结构,会保存一些key-value键值对。但是从源码中并没有看到对数据持久化的操作,所以这种保存数据的方式是不太可靠的。并且如果应用是主动finish的,比如back键退出,就不会调用这个方法。

Bundle

Bundle本身实现了Parcelable接口,并且继承了基类BaseBundle,是可序列化的。

public final class Bundle extends BaseBundle implements Cloneable, Parcelable {
    //……
}

BaseBundle中维持了一个ArrayMap数据结构。
接下来看看数据存取的过程。以putString为例,代码实现很简单,就是注释1处,在ArrayMap中插入一个键值对。

public class BaseBundle {
    //……
    @UnsupportedAppUsage
    ArrayMap mMap = null;
    //……
    /**
     * Inserts a String 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 String, or null
     */
    public void putString(@Nullable String key, @Nullable String value) {
        unparcel();
        mMap.put(key, value);//1
    }
    //……
}

根据我们前面分析过的Parcel序列化的过程,如果bundle数据需要进行跨进程传输,比如用于IBinder调用,那么就会通过writeToParcel方法,调用父类的writeToParcelInner,将map中的数据进行序列化保存到Parcel对象中。

    /**
     * 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);
        }
    }
    /**
     * 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(mParcelledByNative ? BUNDLE_MAGIC_NATIVE : 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);
    }

onRestoreInstanceState

在onRestoreInstanceState方法中可以读取我们保留的值。以getString为例。

    /**
     * Returns the value associated with the given key, or null if
     * no mapping of the desired type exists for the given key or a null
     * value is explicitly associated with the key.
     *
     * @param key a String, or null
     * @return a String value, or null
     */
    @Nullable
    public String getString(@Nullable String key) {
        unparcel();//1
        final Object o = mMap.get(key);
        try {
            return (String) o;
        } catch (ClassCastException e) {
            typeWarning(key, o, "String", e);
            return null;
        }
    }

注释1处,在读取数据之前需要进行反序列化。
接下来看看反序列化的过程:

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

注释1处,从Parcel类型的数据中解析我们需要的信息。

    private void initializeFromParcelLocked(@NonNull Parcel parcelledData, boolean recycleParcel,
            boolean parcelledByNative) {
        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;
            mParcelledByNative = false;
            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 {
            if (parcelledByNative) {
                // If it was parcelled by native code, then the array map keys aren't sorted
                // by their hash codes, so use the safe (slow) one.
                parcelledData.readArrayMapSafelyInternal(map, count, mClassLoader);
            } else {
                // If parcelled by Java, we know the contents are sorted properly,
                // so we can use ArrayMap.append().
                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;
            mParcelledByNative = false;
        }
        if (DEBUG) {
            Log.d(TAG, "unparcel " + Integer.toHexString(System.identityHashCode(this))
                    + " final map: " + mMap);
        }
    }

onPostCreate

另外还有一个生命周期方法想提一下,就是onPostCreate,它的时序在onCreate和onResume之间,一般不需要重写,是在应用启动之后调用。因为它的调用时序在onStart和onRestoreInstanceState之后,不会被覆盖,所以有时候也可以在这里做一些操作。

参考:

Android之什么时候调用onSaveInstance方法
2019校招Android面试题详解
Activity中的onSaveInstanceState()和onRestoreInstanceState()
Activity页面状态保存 持久化

你可能感兴趣的:(2020-03-30-系统配置改变时Activity状态的保存)