Android面試題-onSaveInstanceState源碼內核分析

源碼分析相關面試題

Activity相關面試題

與XMPP相關面試題

與性能優化相關面試題

與登錄相關面試題

與開發相關面試題

與人事相關面試題

經常有人問,後臺的activity被系統自動回收的話,怎麼回到界面的時候恢復數據,通過一個真實案例給大家講講如何保存狀態,然後帶着大家分析onSaveInstanceState的源碼。

當前頁面側滑菜單指向專題,用戶做了如下操作:

1)當用戶按下HOME鍵時。
2)長按HOME鍵,選擇運行其他的程序時。
3)按下電源按鍵(關閉屏幕顯示)時。
4)從activity A中啓動一個新的activity時。
5)屏幕方向切換時,例如從豎屏切換到橫屏時。

失去焦點,activity很可能被進程終止!被KILL掉了,這時候就需要能保存當前的狀態,不然下次用戶再次進來看到的還是新聞,這樣用戶體驗就不夠好,代碼有刪減,我自己項目就這樣使用的,解決方案如下:

@Override
public void onSaveInstanceState(Bundle outState) {
  outState.putInt("newsCenter_position", newsCenterPosition);
  outState.putInt("smartService_position", smartServicePosition);
  outState.putInt("govAffairs_position", govAffairsPosition);
  super.onSaveInstanceState(outState);
}

如上代碼可知:

1)界面被回收之後調用onSaveInstanceState方法保存當前的狀態,每個側滑菜單選項都有一個位置。

@Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null
    && savedInstanceState.containsKey("newsCenter_position")) {
    newsCenterPosition = savedInstanceState
                    .getInt("newsCenter_position");
    smartServicePosition = savedInstanceState
                    .getInt("smartService_position");
    govAffairsPosition = savedInstanceState
                    .getInt("govAffairs_position");
}

    super.onCreate(savedInstanceState);
}

由以上代碼可知:

1)判斷當前Bundle 是否有剛剛我們保存的位置,如果不爲空,從當前的Bundle取出來,給每一個位置賦值。

public void switchMenu(int type) {

switch (type) {
     case NEWS_CENTER:
     ......
     if (newsCenterAdapter == null) {
         newsCenterAdapter = new MenuAdapter(ct, newsCenterMenu);
         newsCenterclassifyLv.setAdapter(newsCenterAdapter);
      } else {
         newsCenterAdapter.notifyDataSetChanged();
      }
     newsCenterAdapter.setSelectedPosition(newsCenterPosition);
      break;
      case SMART_SERVICE:
     ......
     if (smartServiceAdapter == null) {
        smartServiceAdapter = new MenuAdapter(ct, smartServiceMenu);
                   smartServiceclassifyLv.setAdapter(smartServiceAdapter);
     } else {
        smartServiceAdapter.notifyDataSetChanged();
     }
                                      smartServiceAdapter.setSelectedPosition(smartServicePosition);
     break;
    case GOV_AFFAIRS:
        ......
    if (govAffairsAdapter == null) {
        govAffairsAdapter = new MenuAdapter(ct, govAffairsMenu);
         govAffairsclassifyLv.setAdapter(govAffairsAdapter);
     } else {
        govAffairsAdapter.notifyDataSetChanged();
     }
              govAffairsAdapter.setSelectedPosition(govAffairsPosition);
    break;
}

以上代碼可知:

1)根據當前的位置設置到adapter當中,這樣下次用戶進來就還是專題了。

總結下savedInstanceState的使用,代碼如下:

public class MainActivity extends Activity {  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
        if(savedInstanceState != null)  
            System.out.println("onCreate() : " + savedInstanceState.getString("octopus"));  
    }  

    @Override  
    protected void onRestoreInstanceState(Bundle savedInstanceState) {  
        super.onRestoreInstanceState(savedInstanceState);  
        System.out.println("onRestoreInstanceState() : " + savedInstanceState.getString("octopus"));  
    }  

    @Override  
    protected void onSaveInstanceState(Bundle outState) {  
        super.onSaveInstanceState(outState);  

        outState.putString("octopus", "www.baidu.com");  
        System.out.println("onSaveInstanceState() : save date www.baidu.com");  
    }  

}  

橫豎屏切換,打印結果如下:

I/System.out( 8167): onSaveInstanceState() : save date www.baidu.com  
I/System.out( 8167): onCreate() : www.baidu.com 
I/System.out( 8167): onRestoreInstanceState() : www.baidu.com  

從打印結果可以看出來,當前Activity被系統回收之後,會調用onSaveInstanceState()保存狀態,然後在activity判斷bundler是否有當前狀態,如果只是到這,估計你們就會吐槽沒啥含金量,沒辦法硬着頭皮上,接着咱們來分onSaveInstanceState()源碼,請看如下代碼:

 @Override
 public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

 }

以上代碼可知

1)調用父類Activity源碼裏面的onSaveInstanceState方法,代碼如下:

protected void onSaveInstanceState(Bundle outState) {
   outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
   Parcelable p = mFragments.saveAllState();
   if (p != null) {
     outState.putParcelable(FRAGMENTS_TAG, p);
    }
    ......
}

以上代碼可知

1)outState.put一個tag調用了mWindow裏面的saveHierarchyState方法,繼續分析Window源代碼。

2)window是抽象類調用子類PhoneWindow裏面的saveHierarchyState方法代碼如下:


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

  SparseArray<Parcelable> states = new SparseArray<Parcelable>();
  mContentParent.saveHierarchyState(states);
  outState.putSparseParcelableArray(VIEWS_TAG, states);
  ......
  return outState;
}

以上代碼可知

1 ) Bundle outState = new Bundle()初始化Bundle對象,Bundle實現了Parcelable接口。

2)states = new SparseArray()並且把自己放到outState當中。

3)mContentParent.saveHierarchyState(states),整個View樹的頂層視圖保存了層級狀態代碼如下:

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

以上代碼可知:

1)調相應的dispatchSaveInstanceState方法,代碼如下:

protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
   if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
    mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
    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);
     container.put(mID, state);
   }
  }
 }

以上代碼可知:

1) mID != NO_ID 判斷一個View必須有一個id,也就是說你要麼在xml裏通過android:id指定要麼在代碼裏通過setId,但是你從如上代碼壓根是看不出來谷歌想幹啥,必須全局搜索NO_ID 和 mID ,一般在源碼裏面都會有谷歌工程師的註釋方便我們理解,搜索NO_ID 可知代碼如下:

/**
  * Used to mark a View that has no ID.
  */
  public static final int NO_ID = -1;

原來NO_ID用來標記沒有id的View,搜索mID可知原來在如下代碼賦值

public void setId(@IdRes int id) {
        mID = id;
        if (mID == View.NO_ID && mLabelForId != View.NO_ID) {
            mID = generateViewId();
        }
    }

經常當我們看不懂谷歌源碼的時候,可以通過曲線救國的方式,看看英文註釋,看看源碼哪個地方用到當前的類或者方法或者變量,這樣就好理解了,好了扯遠了,繼續分析代碼;

2)通過if判斷,檢測子類是否調用父類的onSaveInstanceState()方法,否則會拋異常,突然看到這才明白,還記得剛剛開始學Android的時候,經常一不小心就把代碼裏面的super.onCreate(savedInstanceState);這行代碼刪掉,報了錯誤還看不懂,原來系統在這裏檢測了,都怪自己曾經太年輕。

3)container.put(mID, state)這行代碼,將state放進SparseArray中,以view自身的id爲key,並且從註釋來看打印mID的Hex值用來保證每頁的id必須是唯一的,難怪每當我給view取id的時候,一個頁面有重複的id就會報錯,谷歌大嬸在這裏做判斷了,膩害了word哥,總是百思不得其姐,憑啥不讓我共用id(因爲取名字太難了),原來是想把id做爲key來使用。

4)走到這onSaveInstanceState(),調用如下代碼:

@CallSuper
protected Parcelable onSaveInstanceState() {
        mPrivateFlags |= PFLAG_SAVE_STATE_CALLED;
        ......
        return BaseSavedState.EMPTY_STATE;
}

以上代碼可知:

1)設置位標誌, 默認不save任何東西,狀態爲空,這就是爲啥我們每次隨便寫個類繼承activity實現onCreate方法的時候可以使用參數savedInstanceState保存狀態,因爲默認爲null,代碼如下:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        savedInstanceState.putString("key","value");
}        

至此整個savedInstanceState保存狀態源碼分析完畢。

  • 歡迎關注微信公衆號,長期推薦技術文章和技術視頻

  • 微信公衆號名稱:Android乾貨程序員

發佈了76 篇原創文章 · 獲贊 63 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章