Android源码设计模式——备忘录模式

本篇博客分享——备忘录模式。 它是一种行为模式,用于保存对象的当前状态,并且在之后的某个时间点,该对象可以恢复到之前的状态,也就是说:我后悔了,我想回到以前的状态。可以,这时候“备忘录模式”可以给你后悔的机会。

备忘录模式的定义

在不破坏对象封闭的前提下,捕获到对象的当前状态,并且在该对象之外保存这个状态,以便于以后该对象恢复到该状态。

备忘录模式的使用场景

  • 需要保存对象在某一时刻的状态和部分状态
  • 不希望外部直接访问其内部状态,通过中间对象,间接访问该对象的内部状态

备忘录模式的角色

  • Originator:对象,可以理解为备忘录的主角。备忘录要存储的状态就是这个对象的内部状态。
  • Memento:备忘录角色。 存储Originator的内部状态。Originator以外的对象不能访问Memento。
  • Caretaker:备忘录角色的管理者。他主要负责存储备忘录,但是自己又不能访问备忘录的数据进行操作和访问。只能够将备忘录传递给其他角色。

示例

经常打游戏,这个示例就以闯关打游戏为例,我们当前第一关,血量是100, 然后闯过一关,是第二关,血量是90,但是呢,此时我不想玩了,下次重启游戏,要恢复到第二关,血量90的状态。

/**
 * FileName:Originator
 * Create By:liumengqiang
 * Description:需要保存数据的对象
 */
public class Originator {

    private static final String TAG = Originator.class.getSimpleName();

    private int checkPoint = 1; //关卡
    private int lifeValue = 100; //生命值

    /**
     * 玩游戏
     */
    public void play() {
        Log.e(TAG, String.format("当前%d关: ", checkPoint) + String.format(", 当前生命值:%d", lifeValue));
        checkPoint ++;
        lifeValue -= 10;
        Log.e(TAG, String.format("到达%d关: ", checkPoint) + String.format(", 当前生命值:%d", lifeValue));
    }

    /**
     * 创建备忘录
     */
    private Memento storeStatus() {
        Memento memento = new Memento();
        memento.checkPoint = checkPoint;
        memento.lifeValue = lifeValue;
        return memento;
    }

    /**
     * 退出游戏
     */
    public Memento quit() {
        return this.storeStatus();
    }

    /**
     * 恢复状态
     */
    public void restoreStatus(Memento memento) {
        this.checkPoint = memento.checkPoint;
        this.lifeValue = memento.lifeValue;

        Log.e(TAG, String.format("恢复游戏后,当前闯关等级:%d,当前生命值:%d", checkPoint, lifeValue));
    }
}

/**
 * FileName:Memento
 * Create By:liumengqiang
 * Description:备忘录角色,除了Originator之外的对象都不能够操作该对象
 */
public class Memento {
    public int checkPoint = 1; //关卡
    public int lifeValue = 100; //生命值
}

/**
 * FileName:Caretaker
 * Create By:liumengqiang
 * Description:负责管理备忘录,但是自己不能操作备忘录
 */
public class Caretaker {
    private Memento memento;

    public void createMemento(Memento memento) {
        this.memento = memento;
    }

    public Memento getMemento() {
        return memento;
    }
}
        Originator originator = new Originator();
        //开始玩游戏
        originator.play();

        //
        Caretaker caretaker = new Caretaker();
        //保存数据,并退出游戏
        caretaker.createMemento(originator.quit());

        //再一次开始玩游戏
        Originator newOriginator = new Originator();
        //恢复数据
        newOriginator.restoreStatus(caretaker.getMemento());

在这里插入图片描述
上述代码比较简单,代码量也不大,不再多说,

自己可以实现一个笔记本:save,control+Z, control+ Y,三个功能。

Android设计模式所有Demo地址

Android 源码备忘录模式的实现

在开发中,我们经常遇到这种场景,当我们的APP切换的后台运行,但是由于各种原因,被系统杀死了,那数据也就不存在了,google给我们提供了onSaveInstanceState和onRestoreInstanceState方法,让我们在我们的APP在可能被系统杀死之前保存数据,并且在合适的时候恢复数据。 注意了,这里是:可能被系统杀死之前。

结合本片博客分享的设计模式——备忘录模式,是不是有点像? 也就是说,将Activity的数据对象保存在bundle中,并且在合适的时候,将bundle返回给Activity,使其恢复之前的数据。

那么具体的源码是怎么实现的呢?

    protected void onSaveInstanceState(Bundle outState) {
        outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
		
		// 保存Window的View视图状态
        outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
        //保存Fragment状态
        Parcelable p = mFragments.saveAllState();
        ....
        //是否在Application中设置了ActivityLifeCallbacks
        getApplication().dispatchActivitySaveInstanceState(this, outState);
    }

实际上重点是第一句话,将视图树存放在Bundle中,当之后在恢复视图树的时候,直接从Bundle中去数据就行了,以此来保证UI的一致性。Window的真正是是实现类是PhoneWindow,进入到PhoneWindow的saveHierarchyState方法看下:

   public Bundle saveHierarchyState() {
        Bundle outState = new Bundle();
        //这里的mContentParent就是我们setContentView的View布局
        if (mContentParent == null) {
            return outState;
        }
        //
        SparseArray<Parcelable> states = new SparseArray<Parcelable>();
        // 调用了我们布局ViewGroup的saveHierarchyState方法。
        mContentParent.saveHierarchyState(states);
        //把最终的视图树塞进Bundle中
        outState.putSparseParcelableArray(VIEWS_TAG, states);

        // Save the focused view ID.
        //获取当前视图有焦点ID的View
        final View focusedView = mContentParent.findFocus();
        if (focusedView != null && focusedView.getId() != View.NO_ID) {
        	//保存当前焦点ID的View,Key:ID, value:View
            outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
        }

        ......
		
		//保存ActionBar的状态
        if (mDecorContentParent != null) {
            SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
            mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
            outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
        }

        return outState;
    }

上面的代码总结做了三件事:

  1. 将视图树的相关信息保存在集合中。
  2. 保存当前获取到焦点的View
  3. 保存ActionBar的状态

看过setContentView的小伙伴应该是知道的,这里的mContentParent实际上就是我们的自己的Activity的布局View,调用了我们设置的布局ViewGroup的saveHierarchyState方法,其实,不管我们的布局是LinearLayout还是FrameLayout,都没有重写saveHierarchyState方法,都是在超级父类View中定义实现的:

    public void saveHierarchyState(SparseArray<Parcelable> container) {
        dispatchSaveInstanceState(container);
    }

这里仅仅就一句话:调用了dispatchSaveInstanceState方法,那么这个方法最终是它的子类ViewGroup重写了:

    @Override
    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
        super.dispatchSaveInstanceState(container);
        final int count = mChildrenCount;
        final View[] children = mChildren;
        //遍历所有的子View
        for (int i = 0; i < count; i++) {
            View c = children[i];
            if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
            	//执行每个子View的dispatchSaveInstanceState方法
                c.dispatchSaveInstanceState(container);
            }
        }
    }

主要是循环遍历子View,如果子View是ViewGroup, 那么继续递归循环,知道子 View是不是继承自ViewGroup的View为止。也就是说:最终要进入的方法是View的dispatchSaveInstanceState方法:

    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
    	//这个View是否在XML中设置了ID,如果设置了ID,就执行onSaveInstanceState方法
        if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
            mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
            //执行View的onSaveInstanceState方法
            Parcelable state = onSaveInstanceState();
            if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
                throw new IllegalStateException(
                        "Derived class did not call super.onSaveInstanceState()");
            }
            if (state != null) {
                // Log.i("View", "Freezing #" + Integer.toHexString(mID)
                // + ": " + state);
                //这里将要保存的数据添加到在PhoneWindow中创建的集合中。
                container.put(mID, state);
            }
        }
    }

实际上这里最外层的判断就是是否给View设置了ID,如果没设置ID,那么就不会保存View的状态,如果设置了ID,那么执行onSavenInstanceState方法,向TextView,EditText的子View都重写了该方法,然后保存该View的相关数据。都保存在了PhoneWindow中创建的那个集合SparseArray中,然后将SparseArray塞入到了Bundle中,保存起来。

上面我们分析了Activity中onSaveInstanceState一旦调用,内部进行的相关流程操作,那么有个重要的问题那就是:Activity中的onSaveInstanceState方法在哪里回调执行的呢?

实际上这个问题猜也能猜出来,关于Activity生命周期的问题,基本都在ActivityThread中能够找的答案。

    private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown,
            ......
            callActivityOnStop(r, saveState, reason);
    }
     private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) {
        .....
        //
        callActivityOnSaveInstanceState(r);
        .....
        //执行Activity的onStop方法。
         r.activity.performStop(false /*preserveWindow*/, reason);
         .......
    }
   private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
       ......
       //实际上还是调用的Instrumentation的方法;
       mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
       ......
    }

进入到Instrumentation的callActivityOnSaveInstanceState方法中,一探究竟:

    public void callActivityOnSaveInstanceState(Activity activity, Bundle outState) {
        activity.performSaveInstanceState(outState);
    }

额,里面执行的是Activity的performSaveInstanceState方法。

    final void performSaveInstanceState(Bundle outState) {
    	//执行的是onSaveInstanceState方法
        onSaveInstanceState(outState);
        ......
    }

最终执行到了onSaveInstanceState方法。

从头到尾捋一遍思路:首先当进程可能被杀死的时候,会从AMS发来消息,最终调用Activity的onSaveInstanceState方法,然后交给PhoneWindow保存视图树的相关数据信息,然后我们自己也可以在onSaveInstanceState方法中保存自己的数据。

至于onRestoreInstanceState方法是恢复数据的,其源码流程和onSaveInstanceState是对应的。

在这个示例中,Activity就可以看作是:Original;Bundle可以看作是:Memento;AMS可以看作是:Caretaker;

上述就是分享的备忘录 模式,纯手打,如果错误,欢迎指正,共同学习进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值