【Android面試】(二):你不能不知道的view---加id和不加id的區別?,androidview---

來源:互聯網
上載者:User

【Android面試】(二):你不能不知道的view---加id和不加id的區別?,androidview---

請尊重原創勞動成果,轉載請註明出處:http://blog.csdn.net/cyp331203/article/details/45313125,非允許請勿用於商業或盈利用途。


        上次面試,Android開發,被問到:你知道android中,布局檔案中加id和不加id有什麼區別?這個我真的不知道,蒙了,只能硬著頭皮說:加了id會在R檔案中產生對應id的數值,然後扯了點view樹,總之答非所問。。。雖然最後面試也過了,但是這個問題一直縈繞在心頭,揮之不去。剛好今天複習Activity生命週期的時候,看到了相關知識點。


有關Activity的onSaveInstantceState(Bundle outState)方法的一些基礎知識在上篇文章中有提到過,大家可以去看看:【Android面試】(一):Android中activity儲存狀態和資料到底該在哪個方法中進行,必須承認,上篇文章中調侃的語氣太重,如果有冒犯,提前說句抱歉,本人還是很尊重面試官的,畢竟肯定要比我強才來面試我。


Activity中的onSaveInstantceState


這回還是要從activity中的onSaveInstantceState(Bundle outState)方法說起,下面快速的過一下onSaveInstantceState(Bundle outState)的幾個要點:


1、onSaveInstantceState(Bundle outState)會在activity能夠被銷毀之前被調用,也就是所謂的(killble)狀態,這個在上篇中有提到


2、onSaveInstantceState(Bundle outState)會在onStop()方法之前被調用,但不保證會在onPause()方法之前還是之後被調用。


3、重點!!!onSaveInstantceState(Bundle outState)不是一定會被調用的,什麼時候會被調用呢?簡單一句話:當Activity要進入這麼一種狀態:“系統可能會以非應用行為退出Activity方式幹掉Activity”之前,系統就會調用onSaveInstantceState(Bundle outState)方法。


4、非應用行為退出

什麼是非應用行為退出?應用行為退出Activity:比如主動調用finish()方法,或者主動按Back鍵,讓Activity結束。非應用行為退出:比如一個Activity在後台,過了很長時間也沒有被重新調用顯示出來;又或者系統當前資源非常緊張,主動kill掉當前activity,釋放資源以供其他應用使用。


這樣設計的邏輯是很清晰的:當系統不確定會不會什麼時候在未經“允許”的突發情況下結束掉Activity,在進入這種狀態之前,肯定需要儲存一下我們想要的資料,比如Activity中有控制項有狀態值,可以通過onSaveInstantceState(Bundle outState)進行儲存,但是就像上一篇文章中說的,onSaveInstantceState(Bundle outState)不保證一定會被調用,因為它不是Activity生命週期中的方法。


5、假設onSaveInstantceState(Bundle outState)方法被調用了,且也儲存了資料到Bundle對象,那麼什麼時候會將其取出來?

上面的第3點中提到過,在系統要進入可以使用“非應用行為”殺死Activity狀態之前,會調用onSaveInstantceState(Bundle outState)方法,而Bundle對象可能被取到的條件,就是系統確實使用“非應用行為”殺死了Activity,而在要重建Activity時,會首先將Bundle對象傳給onCreate,然後再傳給onRestoreInstanceState(Bundle savedInstanceState)方法。如果onSaveInstantceState(Bundle outState)方法調用之後,Activity沒有“意外殺死”,那麼再次啟動Activity時,只會調用onStart--onResume,而不會調用onRestoreInstanceState(Bundle savedInstanceState)方法。


onSaveInstantceState例子


下面把一個Activity在啟動到被旋屏之後重新建立的過程列印結果展示出來,這裡在onSaveInstantceState方法中往bundle中存一個目前時間,然後在onCreate方法和onRestoreInstanceState方法中將其取出,onCreate方法中會對Bundle進行判空:


啟動:




旋屏之後:




下面給Activit中加兩個按鈕,讓它點擊跳轉到第二個activity,不同的是,一個按鈕會在點擊時調用finish方法,而另一個則不會:




跳轉調用finish()方法:




跳轉不調用finish()方法:




發現在onCreate第一次調用時,Bundle為null,而在旋屏之後,onCreate和onRestoreInstanceState方法中都拿到了傳過來的時間。

而在主動調用finish結束activity時,沒有調用onSaveInstantceState方法;而如果不finish掉activity1,直接跳轉activity2,則會在activity1的onStop之前調用onSaveInstantceState方法。


View中的onSaveInstantceState和id的關係


好了,說了一大堆,貌似還沒有進入本文關注的焦點。。。下面就來了:

上面說了onSaveInstantceState方法,下面來看看這個方法裡到底幹了什麼:(你mei的,怎麼還是onSaveInstantceState方法?!汗Σ( ° △ °|||)︴,就快到了)


來看看Activity中的源碼:

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



主要做了這麼幾件事:


1、mWindow.saveHierarchyState()中的資料,放入到Bundle對象中


2、將Fragments中的state資料存放到Bundle對象中


3、將Bundle對象通過Application的dispatchActivitySaveInstanceState進行分發。


今天本文關注第一個問題:mWindow.saveHierarchyState()


發現返回的是一個mWindow,而這個mWindow是一個Activity類中 Window類型的成員變數:


private Window mWindow;


可能你已經在猜測這個window和PhoneWindow的關係了,Window是一個抽象類別,其中的setContentView方法也是一個抽象方法,並沒有實現。來看看Window類的注釋:


The only existing implementation of this abstract class is android.policy.PhoneWindow, which you should instantiate when needing a Window. 


意思是說:Window類只有一個實作類別,那就是PhoneWindow。


這下明白了,我們再去看看PhoneWindow類的源碼,這個類我們並不能直接使用,它位於:Android源碼目錄/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java


找到saveHierarchyState()方法,我們來看看它幹了些什麼事:


 /** {@inheritDoc} */    @Override    public Bundle saveHierarchyState() {        Bundle outState = new Bundle();//建立一個Bundle對象,用於存放狀態        if (mContentParent == null) {            return outState;        }        SparseArray<Parcelable> states = new SparseArray<Parcelable>();//建立了一個SparseArray,裡面維護了一個key數組和一個value數組,類似於Map        mContentParent.saveHierarchyState(states);//調用view裡面的saveHierarchyState方法,將狀態值儲存到        outState.putSparseParcelableArray(VIEWS_TAG, states);將SparseArray中的資料存放區到Bundle對象中        // save the focused view id        View focusedView = mContentParent.findFocus();        if (focusedView != null) {            if (focusedView.getId() != View.NO_ID) {//注意這裡,如果當前焦點view有設定id,才會進入到下面                outState.putInt(FOCUSED_ID_TAG, focusedView.getId());//特別儲存當前焦點view的id值            } else {                if (false) {                    Log.d(TAG, "couldn't save which view has focus because the focused view "                            + focusedView + " has no id.");                }            }        }        // save the panels 儲存panel狀態        SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();        savePanelState(panelStates);        if (panelStates.size() > 0) {            outState.putSparseParcelableArray(PANELS_TAG, panelStates);        }        if (mActionBar != null) {//儲存actionBar狀態            SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();            mActionBar.saveHierarchyState(actionBarStates);            outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);        }        return outState;    }


PhoneWindow類的成員變數mContentParent用來描述一個類型為DecorView的視圖對象,或者這個類型為DecorView的視圖對象的一個子視圖對象,用作UI容器.當它的值等於null的時候,就說明正在處理的應用程式視窗的視圖對象還沒有建立.


簡而言之,只要我們給Activity設定了顯示內容,不管是布局檔案還是view,就會掛載在這個mContentParent之下。所以一般情況下,mContentParent不會為null


好了,上面其實已經看出來了一點東西,如果一個焦點view的id沒有設定的話,那麼就無法向Bundle對象中儲存當前焦點view的id,焦點標識是使用的:FOCUSED_ID_TAG這個常亮。


我們再來看看mContentParent.saveHierarchyState(states)幹了些什麼:

由於:private ViewGroup mContentParent;是一個viewgroup,而viewGroup裡沒有saveHierarchyState方法,於是實際上調用的view中的saveHierarchyState方法:


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



再來看看dispatchSaveInstanceState方法:

    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {        if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {//只有有id的情況下才能進入到裡面,添加view的狀態            mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;            Parcelable state = onSaveInstanceState();//調用view自己的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);            }        }    }


看到這裡基本上真相大白了,如果不給一個view設定一個id,那麼在Activity調用onSaveInstantceState(Bundle outState)方法時,就沒辦法儲存它的狀態,而且即使它當前是焦點view,也沒辦法將其焦點狀態記錄在Bundle對象中,這會導致在需要取出Bundle狀態物件時,出現問題。


上面還可以看到,view的狀態,是由onSaveInstanceState()方法來擷取的:


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


總結


1、實際上view預設的onSaveInstanceState方法中什麼都沒做,返回的是一個空狀態,這個方法是一個protected方法,於是在view的各個子類中,可能會有不同的實現,因為畢竟不是每個view都需要儲存狀態,而且不同類型的view要儲存的狀態值也不近相同,比如textview可能需要儲存文字狀態,而scrollview就需要儲存滾動到的位置值等等。


2、我們可以通過自訂View,重寫onSaveInstanceState和onRestoreInstanceState方法,來儲存和取出一些應對特定環境時比較重要的狀態值。


3、值得注意的是,container.put(mID, state);狀態值是由id的值來作為key值來儲存的,所以如果同類的view,在使用相同的id時,在取狀態值的時候,就可能會出現問題,來看看scrollview中的onSaveInstanceState方法:


@Override    protected Parcelable onSaveInstanceState() {        if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {            // Some old apps reused IDs in ways they shouldn't have.            // Don't break them, but they don't get scroll state restoration.            return super.onSaveInstanceState();        }        Parcelable superState = super.onSaveInstanceState();        SavedState ss = new SavedState(superState);        ss.scrollPosition = mScrollY;        return ss;    }

源碼中寫的很清楚: Some old apps reused IDs in ways they shouldn't have.  Don't break them, but they don't get scroll state restoration.


如果你在應用中使用兩個ScrollView,且都指定一樣的id,那麼在onSaveInstanceState時,後調用的那個則會覆蓋掉之前的那個ScrollView的Scroll的值,導致在之後取出的時候,會讓兩個ScrollView的滑動進度總是一樣。


而且看上面的判斷條件:if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2),表示在4.3(包括)以前ScrollView的scroll state是不會儲存的,所以在這之前要實現對應的功能,只能自訂一個view繼承ScrollView,然後重寫onSaveInstanceState相關方法了。


好了,今天就到這裡,謝謝大家!



請尊重原創勞動成果,轉載請註明出處:http://blog.csdn.net/cyp331203/article/details/45313125,非允許請勿用於商業或盈利用途。
















聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.