Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 十二、備忘錄設計模式

十二、備忘錄設計模式

編輯:關於Android編程

1. 備忘錄設計模式介紹

在不破壞封閉的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,這樣,以後就可將該對象恢復到原先保存的狀態。

2. 備忘錄設計模式使用場景

需要保存一個對象在某一個時刻的狀態或部分狀態。 如果用一個接口來讓其它對象得到這些狀態,將會暴露對象的實現細節並破壞對象的封裝性,一個對象不希望外界直接訪問其內部狀態,通過中間對象可以間接訪問其內部狀態。

3. 備忘錄設計模式UML類圖

備忘錄設計模式UML類圖

UML類圖角色介紹

Originator:負責創建一個備忘錄,可以記錄、恢復自身的內部狀態。同時Originator可以根據需要決定Memento存儲自身的那些內部狀態。

Memento: 備忘錄角色,用於存儲Originator的內部狀態,並且可以防止Originator以外的對象訪問Memento。

Caretaker: 負責存儲備忘錄,不能對備忘錄的內容進行操作和訪問,只能夠將備忘錄傳遞給其它對象。

4. 備忘錄設計模式的簡單實現

情景如下:我們都玩過單機游戲,單機裡面有一個很重要的功能就是存檔,存儲當前游戲進度。下次再進入游戲時,恢復上一次的進度,繼續游戲。

(1)、游戲類:Game
public class Game {
    private int mCheckpoint = 1; //分數
    private int mLifeValue = 100; //生命

    //玩游戲
    public void play() {
        mLifeValue -= 10;
        mCheckpoint++;
    }

    public void quit() {
        System.out.println("退出游戲");
    }

    //顯示當前游戲信息
    public void showInfos() {
        System.out.println(this.toString());
    }

    public Memoto createMemoto() {
        //創建存檔
        Memoto memoto = new Memoto();
        memoto.mLifeValue = mLifeValue;
        memoto.mChackpoint = mCheckpoint;
        return memoto;
    }

    //恢復存檔
    public void restoreMemoto(Memoto memoto) {
        this.mCheckpoint = memoto.mChackpoint;
        this.mLifeValue = memoto.mLifeValue;
        System.out.println("恢復進度");
    }

    @Override
    public String toString() {
        return "當前生命值:" + mLifeValue + ",分數:" + mCheckpoint;
    }
}

上面的代碼,最終要的部分就是createMemoto()方法,將當前狀態信息存儲在Memoto對象裡面。

(2)、備忘錄類:
public class Memoto {
    public int mChackpoint;
    public int mLifeValue;
    public String mWeapon;
}

備忘錄類用來存儲游戲類裡面一個或至多個信息。

(3)、備忘錄操作者:Caretaker角色:
public class Caretaker {
    Memoto memoto;//備忘錄

    public void archive(Memoto memoto) {
        this.memoto = memoto;
    }

    //獲取存檔
    public Memoto getMemoto() {
        return memoto;
    }
}

該類的作用就是操作備忘錄類Memoto本身的,並不對備忘錄裡面的信息讀取操作。

(4)、測試類:
public class Client {
        public static void main(String[] args) {
            Game game = new Game();
            //打游戲
            game.play();
            //存檔
            Caretaker caretaker = new Caretaker();
            caretaker.archive(game.createMemoto());
            game.showInfos();
            //退出游戲
            game.quit();

            System.out.println("-----");

            //恢復游戲
            Game newGame = new Game();
            newGame.restoreMemoto(caretaker.getMemoto());
            //顯示當前游戲信息
            newGame.showInfos();
        }
}

上面的測試類就是創建一個游戲類,接著游戲類修改自身的屬性,接著游戲類創建備份,然後新建游戲類,恢復備份,顯示游戲進度和之前的游戲對象進度一模一樣。

5. 備忘錄設計模式在Android源碼中

在Android源碼中,狀態模式的應用表現在Activity的狀態保存,在onSaveIinstanceState和onRestoreInstanceState方法中

當Activity不是正常方式退出,且Activity在隨後的時間內被系統殺死之前會調用這兩個方法讓開發人員可以有機會存儲Activity相關信息,並且下次再返回式恢復這些數據。

首先我們來說下onSaveInstanceState()方法裡面干了什麼事:

這是onSaveInstanceState()裡面的方法,

protected void onSaveInstanceState(Bundle outState) {
        //1.存儲窗口的視圖樹狀態
        outState.putBundle(WINDOW_HIERARCHY_TAG,
        mWindow.saveHierarchyState());
        //2.存儲Fragment的狀態
        Parcelable p = mFragments.saveAllState();
        if (p != null) {
            outState.putParcelable(FRAGMENTS_TAG, p);
        }
        //3.調用Activity的ActivityLifecycleCallbacks的onSaveInstanceState函數進行狀態存儲
                  getApplication().dispatchActivitySaveInstanceState(this, outState);
}
(1)、存儲窗口的視圖樹狀態 (2)、存儲Fragment的狀態 (3)、調用Activity的ActivityLifecycleCallbacks的onSaveInstanceState函數進行狀態存儲

上面三個步驟都是存儲,我們首先來分析下存儲窗口的視圖狀態:

mWindow.saveHierarchyState());

這句代碼就是存儲窗口的視圖樹狀態,這裡的mwindow對象是PhoneWindow ,我們在PhoneWindow找到這個方法:

saveHierachyState()方法簡化如下:

@Override
public Bundle saveHierarchyState() {
    Bundle outState = new Bundle();
    if (mContentParent == null) {
        return outState;
    }

    //存儲整顆視圖樹的結構
    SparseArray states = new SparseArray();
    mContentParent.saveHierarchyState(states);
    outState.putSparseParcelableArray(VIEWS_TAG, states);

    // 保存當前獲取了焦點的View

    //存儲整個面板的狀態

    //存儲ActionBar的狀態

    return outState;
}

在以上saveHierarchyState函數中,主要存儲了與當前UI、ActionBar相關的View狀態。

這裡我們分析存儲整顆視圖樹:

代碼中的mContentParent就是我們通過Activity的setContentView設置的內容視圖,它是整個視圖樹的根節點。 mContentParent是一個ViewGroup對象,我們在ViewGroup的父類View中到saveHierarchyState()方法:
public void saveHierarchyState(SparseArray container) {
        dispatchSaveInstanceState(container);
}

protected void dispatchSaveInstanceState(SparseArray container) {
                //如果View沒有設置Id,那麼該View的狀態信息將不會被存儲
                if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
                    mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
                    //調用那個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);
                        //存入View狀態信息,key為view的id。
                        container.put(mID, state);
                    }
                }
    }

上面的代碼意思大致如下:

如果View沒有設置id,那麼該View的狀態信息將不會被保存 調用onSaveInsttanceState()方法獲取自身狀態信息。 當前View的id為key,狀態信息為value,存入之前創建的SparseArray 中,本質上是一個Object數組。

以上是View類的中saveHierarchySate函數中dispatchSaveInstanceState函數來存儲自身的狀態

如果是ViewGroup呢?下面是ViewGroup中的dispatchSaveInstanceState函數:

protected void dispatchSaveInstanceState(SparseArray container) {
        //調用父類View的dispatchInstanceState方法存儲自身狀態
        super.dispatchSaveInstanceState(container);
        final int count = mChildrenCount;
        final View[] children = mChildren;
        //遍歷所有的子視圖調用其dispatchInstanceState方法存儲它們的狀態
        for (int i = 0; i < count; i++) {
            View c = children[i];
            if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
                c.dispatchSaveInstanceState(container);
            }
        }
    }

上面的代碼做了以下兩件事:

首先調用父類View的dispatchSaveInstanceState方法存儲了自身的狀態信息 接著遍歷所有的子類,調用其dispatchSaveInstanceState方法存儲它們的狀態信息

在View的saveHierarchyState方法裡面有如下代碼:

//調用那個onSaveInstanceState()方法獲取自身狀態信息
    Parcelable state = onSaveInstanceState();

這句代碼的意思是獲取自身狀態信息,我們點進去查看源代碼如下:

protected Parcelable onSaveInstanceState() {
        mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
        if (mStartActivityRequestWho != null) {
            BaseSavedState state = new BaseSavedState(AbsSavedState.EMPTY_STATE);
            state.mStartActivityRequestWhoSaved = mStartActivityRequestWho;
            return state;
        }
        return BaseSavedState.EMPTY_STATE;
    }

上面的代碼意思是:返回一個存儲了當前View狀態信息的Parcelable對象,如果沒有任何信息,返回null,默認返回null。我們可以得出以下信息

View視圖的狀態信息的存儲在Parcelable對象裡面 如果我們要保存View的狀態信息,需要覆寫onSaveInstanceState()方法,將需要保存的信息存放在Parcelable裡面,然後返回。

到這裡我們畫張圖小結一下View和ViewGroup存儲的流程:

 

android備忘錄模式調用圖

 

上面分析了存儲了Window的視圖樹狀態信息。

存儲了Window的視圖樹狀態信息後,便會執行存儲Fragment中的狀態信息、回退棧等 。

下面我們來分析存儲了狀態信息的Bundle數據存儲在哪裡?

我們知道onSveInstanceState是在Activity被銷毀之前,onStop調用之前。onStop方法在ActivityThread的performStopActivity函數中,這裡就不列出代碼了,主要的步驟大致如下:

(1)、判斷是否需要存儲Activity (2)、如果需要存儲Activity狀態,調用onSaveInstanceState函數獲取狀態信息。 (3)、將狀態信息存儲到ActivityClientRecord對象的state字段 (3)、系統維護了一個Acitivity信息表mActivities,將AcitivityClientRecord對象存儲到Acitivity信息表中。 (4)、調用Activity的onStop()函數

當Activity重新啟時:

從mActivities查詢對應的ActivityClientRecord,如果這個記錄對象中包含有狀態信息,那麼調用Activity的onRestoreInstanceState函數,然後將這些狀態信息傳遞給onCreat方法

總結一下onSaveInstanceState的調用時機:

當系統未經我們允許時銷毀了Acitivity,onSaveInstanceState()方法會被調用。常見的幾種場景:

當用戶按下Home鍵時 按下電源鍵時 啟動一個新的Activity時 來電話時 屏幕發生旋轉時

6. 備忘錄設計模式在Android開發中

如下場景:簡單實現上面的onSaveInstanceState()的使用,當我我們輸入用戶名和密碼後,通過旋轉屏幕Activity會被銷毀,如果不存儲,相關信息可能會丟失,所以在onSaveInstanceState保存相關信息,在onCreate方法裡面獲取信息,重新填充即可。

簡單演示:

 

Android備忘錄模式演示

 

代碼簡單實現如下:

public class MainActivity extends AppCompatActivity {
        private EditText et_name;
        private EditText et_psw;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            et_name = (EditText) findViewById(R.id.et_name);
            et_psw = (EditText) findViewById(R.id.et_psw);
            if (savedInstanceState!=null){
                String username = savedInstanceState.getString("username");
                String psw = savedInstanceState.getString("psw");
                System.out.println("username:" + username + ",psw:" + psw);
                et_name.setText(username);
                et_psw.setText(psw);
            }
        }

        @Override
        protected void onSaveInstanceState(Bundle outState) {
            outState.putString("username", et_name.getText().toString());
            outState.putString("psw", et_psw.getText().toString());
            super.onSaveInstanceState(outState);
        }
}

7、總結

優點:
給用戶提供了一種可以恢復狀態的機制。可以是用戶能夠比較方便地回到某個歷史的狀態。 實現了信息的封裝。使得用戶不需要關心狀態的保存細節。 缺點:
消耗資源。如果類的成員變量過多,勢必會占用比較大的資源,而且每一次保存都會消耗一定的內存。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved