天天看點

Android面試題集錦(一)

2016.7.19開更...........................................................................

(1):事件分發機制概述

        首先應該搞清楚兩個問題:事件分發機制分發的是什麼?怎麼進行分發?

        分發的是MotionEvent事件了,因而我們讨論的問題就成了當MotionEvent事件生成之後,事件是怎麼傳遞到某一個View控件上面并且得到處理的過程;

        android事件産生後的傳遞過程是從Activity--->Window--->View的,即隧道式傳遞,而View又分為不包含子View的View以及包含子View的ViewGroup,事件産生之後首先傳遞到Activity上面,而Activity接着會傳遞到PhoneWindow上,PhoneWindow會傳遞給RootView,而RootView其實就是DecorView了,接下來便是從DecorView到View上的分發過程了,具體就可以分成ViewGroup和View的分發兩種情況了;

        對于ViewGroup而言,當事件分發到目前ViewGroup上面的時候,首先會調用他的dispatchTouchEvent方法,在dispatchTouchEvent方法裡面會調用onInterceptTouchEvent來判斷是否要攔截目前事件,如果要攔截的話,就會調用ViewGroup自己的onTouchEvent方法了,如果onInterceptTouchEvent傳回false的話表示不攔截目前事件,那麼事件将會繼續往目前ViewGroup的子View上面傳遞了,如果他的子View是ViewGroup的話,則重複ViewGroup事件分發過程,如果子View就是View的話,則轉到下面的View分發過程;

        對于View而言,事件傳遞過來首先當然也是執行他的dispatchTouchEvent方法了,如果我們為目前View設定了onTouchListener監聽器的話,首先就會執行他的回調方法onTouch了,這個方法的傳回值将決定事件是否要繼續傳遞下去了,如果傳回false的話,表示事件沒有被消費,還會繼續傳遞下去,如果傳回true的話,表示事件已經被消費了,不再需要向下傳遞了;如果傳回false,那麼将會執行目前View的onTouchEvent方法,如果我們為目前View設定了onLongClickListener監聽器的話,則首先會執行他的回調方法onLongClick,和onTouch方法類似,如果該方法傳回true表示事件被消費,不會繼續向下傳遞,傳回false的話,事件會繼續向下傳遞,為了分析,我們假定傳回false,如果我們設定了onClickListener監聽器的話,則會執行他的回調方法onClick,該方法是沒有傳回值的,是以也是我們事件分發機制中最後執行的方法了;可以注意到的一點就是隻要你的目前View是clickable或者longclickable的,View的onTouchEvent方法預設都會傳回true,也就是說對于事件傳遞到View上來說,系統預設是由View來消費事件的,但是ViewGroup就不是這樣了;

        上面的事件分發過程隻是正常情況下的,如果有這樣一種情況,比如事件傳遞到最裡層的View之後,調用該View的oonTouchEvent方法傳回了false,那麼這時候事件将通過冒泡式的方式向他的父View傳遞,調用它父View的onTouchEvent方法,如果正好他的父View的onTouchEvent方法也傳回false的話,這個時候事件最終将會傳遞到Activity的onTouchEvent方法了,也就是最終就隻能由Activity自己來處理了;

        事件分發機制需要注意的幾點:

        (1):如果說除Activity之外的View都沒有消費掉DOWN事件的話,那麼事件将不再會傳遞到Activity裡面的子View了,将直接由Activity自己調用自己的onTouchEvent方法來處理了;

        (2):一旦一個ViewGroup決定攔截事件,那麼這個事件序列剩餘的部分将不再會由該ViewGroup的子View去處理了,即事件将在此ViewGroup層停止向下傳遞,同時随後的事件序列将不再會調用onInterceptTouchEvent方法了;

        (3):如果一個View開始處理事件但是沒有消費掉DOWN事件,那麼這個事件序列随後的事件将不再由該View來處理,通俗點講就是你自己沒能力就别瞎BB,要不以後的事件就都不給你了;

        (4):View的onTouchEvent方法是否執行是和他的onTouchListener回調方法onTouch的傳回值息息相關的,onTouch傳回false,onTouchEvent方法不執行;onTouch傳回false,onTouchEvent方法執行,因為onTouchEvent裡面會執行onClick,是以造成了onClick是否執行和onTouch的傳回值有了關系;

(2):View視圖繪制過程原理

        View視圖繪制需要搞清楚兩個問題,一個是從哪裡開始繪制,一個是怎麼繪制?

        先說從哪裡開始繪制的問題:我們平常在使用Activity的時候,都會調用setContentView來設定布局檔案,沒錯,視圖繪制就是從這個方法開始的;

        再來說說怎麼繪制的:

        在我們的Activity中調用了setContentView之後,會轉而執行PhoneWindow的setContentView,在這個方法裡面會判斷我們存放内容的ViewGroup(這個ViewGroup可以是DecorView也可以是DecorView的子View)是否存在。不存在的話則會建立一個DecorView出來,并且會建立出相應的窗體風格,存在的話則會删除原先ViewGroup上面已有的View,接着會調用LayoutInflater的inflate方法以pull解析的方式将目前布局檔案中存在的View通過addView的方式添加到ViewGroup上面來,接着在addView方法裡面就會執行我們常見的invalidate方法了,這個方法不隻是在View視圖繪制的過程中經常用到,其實動畫的實作原理也是不斷的調用這個方法來實作視圖不斷重繪的,執行這個方法的時候會調用他的父View的invalidateChild方法,這個方法是屬于ViewParent的,ViewGroup以及ViewRootImpl中都對他進行了實作,invalidateChild裡面主要做的事就是通過do while循環一層一層計算出目前View的四個點所對應的矩陣在ViewRoot中所對應的位置,那麼有了這個矩陣的位置之後最終都會執行到ViewRootImpl的invalidateChildInParent方法,執行這個方法的時候首先會檢查目前線程是不是主線程,因為我們要開始準備更新UI了,不是主線程的話是不允許更新UI的,接着就會執行scheduleTraversals方法了,這個方法會通過handler來執行doTraversal方法,在這個方法裡面就見到了我們平常所熟悉的View視圖繪制的起點方法performTraversals了;

        那麼接下來就是真正的視圖繪制流程了,大體上講View的繪制經曆了Measure測量、Layout布局以及Draw繪制三個過程,具體來講是從ViewRootImpl的performTraversals方法開始,首先執行的将是performMeasure方法,這個方法裡面會傳入兩個MeasureSpec類型的參數,他在很大程度上決定了View的尺寸規格,對于DecorView來說寬高的MeasureSpec值的擷取與視窗尺寸以及自身的LayoutParams有關,對于普通View來說其寬高的MeasureSpec值的擷取由父容器以及自身的LayoutParams屬性共同決定,在performMeasure裡面會執行measure方法,在measure方法裡面會執行onMeasure方法,到這裡Measure測量過程對View與ViewGroup來說是沒有差別的,但是從onMeasure開始兩者有差别了,因為View本身已經不存在子View了,是以他onMeasure方法将執行setMeasuredDimension方法,該方法會設定View的測量值,但是對于ViewGroup來說,因為它裡面還存在着子View,那麼我們就需要繼續測量它裡面的子View了,調用的方法是measureChild方法,該方法内部又會執行measure方法,而measure方法轉而又會執行onMeasure方法,這樣不斷的遞歸進行下去,知道整個View樹測量結束,這樣performMeasure方法執行結束了;接着便是執行performLayout方法了,performMeasure隻是測量出View樹中View的大小了,但是還不知道View的位置,是以也就出現了performLayout方法了,performLayout方法首先會執行layout方法,以确定View自身的位置,如果目前View是ViewGroup的話,則會執行onLayout方法。在onLayout方法裡面又會遞歸的執行layout方法,直到目前周遊到的View不再是ViewGroup為止,這樣整個layout布局過程就結束了;在View樹中View的大小以及位置都确定之後,接下來就是真正的繪制View顯示在界面的過程了,該過程首先從performDraw方法開始,performDraw方法首先執行draw方法,在draw方法中首先繪制背景、接着調用onDraw方法繪制自己,如果目前View是ViewGroup的話,還要調用dispatchDraw方法繪制目前ViewGroup的子View,而dispatchDraw方法裡面實際上是通過drawChild方法間接調用draw方法形成遞歸繪制整個View樹,直到目前View不再是ViewGroup為止,這樣整個View的繪制過程就結束了;

(3):解決滑動沖突的方式

        在自定義View的過程經常會遇到滑動沖突問題,一般滑動沖突的類型有三種:(1)外部View滑動方向和内部View滑動方向不一緻;(2)外部View滑動方向和内部View滑動方向一緻;(3)上述兩種情況的嵌套;

        一般我們解決滑動沖突都是利用的事件分發機制,有兩種方式外部攔截法和内部攔截法:

        外部攔截法:實作思路是事件首先是通過父容器的攔截處理,如果父容器不需要該事件的話,則不攔截,将事件傳遞到子View上面,如果父容器決定攔截的話,則在父容器的onTouchEvent裡面直接處理該事件,這種方法符合事件分發機制;具體實作措施是修改父容器的onInterceptTouchEvent方法,在達到某一條件的時候,讓該方法直接傳回true就可以把事件攔截下來進而調用自己的onTouchEvent方法來處理了,但是有一點需要注意的是如果想要讓子View能夠收到事件,我們需要在onInterceptTouchEvent方法裡面判斷如果是DOWN事件的話,傳回false,這樣後續的MOVE以及UP事件才有機會傳遞到子View上面,如果你直接在onInterceptTouchEvent方法裡面DOWN情況下傳回了true,那麼後續的MOVE以及UP事件将由目前View的onTouchEvent處理了,這樣你的攔截将根本沒有意義的,攔截隻是在滿足一定條件才會攔截,并不是所有情況下都攔截;

        内部攔截法:實作思路是事件從父容器傳遞到子View上面,父容器不做任何幹預性的措施,所有的事件都會傳遞到子View上面,如果子元素需要改事件,那麼就由子元素消耗掉了,該事件也就不會回傳了,如果子元素不需要該事件,那麼他就會回傳給父容器來處理了;具體實作措施需要借助于requestDisallowInterceptTouchEvent方法,該方法用來告訴父容器要不要攔截目前事件,為了配合子View能夠調用這個方法成功,父容器必須預設能夠攔截除了DOWN事件以外的事件,為什麼要除了DOWN事件以外呢?因為如果一旦父容器攔截了DOWN事件,那麼後續事件将不再會傳遞到子元素了,内部攔截法也就失去作用了;

        個人認為外部攔截法是符合正常邏輯的,按照事件隧道式分發過程,如果父容器需要就直接攔截,不需要則傳遞到子View;内部攔截法相當于人為幹預分發這個過程,我會保證事件先都到子View上面,至于子View需不需要就要看我自己了,如果我不需要就回傳給父容器了,需要的話自己就消耗掉了;感覺這兩種方式隻是父容器和子View處理事件的優先級不同而已;

(4):Android動畫原理

        Android動畫可以分為View動畫、幀動畫、屬性動畫,其中View動畫又可以分為平移(Translate)、縮放(Scale)、旋轉(Rotate)、透明度(Alpha)四種,幀動畫可以認為是View動畫的一種,實作原理類似于放電影,通過一幀一幀的圖檔進行播放來達到動畫的效果,正是因為這點需要注意他可能會出現OOM異常,屬性動畫是3.0之後出現的,他也可以實作View動畫的效果;

        在講解動畫原理之前需要明白兩個概念,插值器和估值器:

        插值器:根據時間流逝的百分比來計算出屬性值改變的百分比,對應的接口是Interpolator;

        估值器:根據屬性改變的百分比計算出屬性的改變值,對應的接口是TypeEvaluator;

        先來說說View動畫實作原理,其實如果你看View動畫實作過程的源碼的話就會發現,View動畫其實就是在不斷的調用View的invalidate方法來進行View的繪制以達到動畫的效果的,是以了解View動畫的核心其實應該是首先了解View的繪制過程;

        我們使用View動畫都是通過View的startAnimation方法開始的,那麼分析View動畫原理自然應該從這個方法開始了,這個方法裡面會調用setAnimation設定目前View的動畫,并且随後調用了我們經常見的invalidate方法,這個方法具體執行過程上面已經說過了,最後都會執行到ViewRootImpl的performTraversals方法,該方法就是我們進行視圖繪制經常見到的開始方法了,經過一系列的measure測量以及layout布局過程執行到draw繪畫階段,這個階段是我們動畫比較關心的階段,畢竟要在界面顯示嘛,沒有draw怎麼做到,調用draw方法之後繪制流程是這樣的:首選繪制背景,接着繪制自己,随後調用dispatchDraw繪制自己的孩子,在調用每個子View的draw方法之前,需要繪制的View的繪制位置是Canvas通過translate方法切換了,這點也看出來View動畫實際上一直在動的是畫布,而并不是View本身,最後還要繪制滾動條等修飾内容,這裡調用了dispatchDraw方法,但是View沒有實作這個方法,ViewGroup作為View的子類實作了這個方法,在ViewGroup的dispatchDraw方法中會執行drawChild方法來繪制目前ViewGroup的子View,drawChild方法實際上調用的就是View的draw方法了,這個draw方法是不同于前面ViewGroup繪制自己的draw方法,這個draw方法中有一個時間參數和畫布參數Canvas,具體的繪制就是通過這個畫布參數實作的,但是ChildView的畫布是由其ParentView提供的,ParentView會根據ChildView在其内部的布局來調整Canvas,當子View調用,在該draw方法中會通過getAnimation擷取到我們設定到View上的動畫,接着便執行了drawAnimation方法來進行動畫繪制了,在drawAnimation方法裡面首先通過執行getChildTransformation方法獲得子View的Transformation值,那麼Transformation是什麼呢?它主要進行的是矩陣運算的,其中有兩個比較關鍵的屬性其中之一是Matrix用于存儲View的平移、縮放、旋轉資訊,還有一個alpha屬性,主要存儲的是View的透明度資訊的,接着就會執行getTransformation方法,把剛剛擷取的Transformation值以及目前時間作為參數傳入,在getTransformation方法裡面會通過目前時間計算出時間流逝的百分比,并且将該百分比作為參數調用插值器的getInterpolation方法,獲得時間流逝百分比對應的屬性改變的百分比,當然這裡你可以使用自己定義的插值器,有了屬性改變百分比之後我們就可以調用applyTransformation方法來進行具體的動畫實作了,當然如果你自己想要實作自己定義的動畫,可以重寫applyTransformation方法,這樣View動畫的第一幀就繪制好了,那麼後續的幀該怎麼繪制呢?如果你細心的話會發現getTransformation有一個boolean類型的傳回值,沒錯就是靠這個傳回值來進行後續幀繪制的,檢視getTransformation方法文檔說明會發現傳回真表示還有後續幀存在,具體判别方法當然就是通過比較目前時間是否超過動畫要求最遲時間了,傳回true則會繼續執行invalidate方法,相當于又回到最開始處進行遞歸的繪制,傳回false的話則動畫結束,這就是View動畫的執行過程了;

        幀動畫因為可以了解為電影的放映過程,是以他的一幀一幀過程是我們自己提供的,因為系統本身隻需要切換我們提供的資源圖檔就可以了,沒有多大原理需要解釋;

        屬性動畫實作原理:

        既然名字上有屬性兩個字,那麼肯定是通過改變View的屬性來達到動畫效果的,這點和View動畫是有很大差别的,View動畫隻是ParentView不斷的調整ChildView的畫布來實作動畫的,本質上View的屬性是沒有發生變化的,是以當你對移動到某個地方的View進行一些比如點選或者觸摸操作的時候是根本不會執行目前移動過來的View的事件方法的,原因就在于你移動過去的隻是原先View的影像而已,而屬性動畫就不一樣了,他是實實在在的改變View屬性真正的在移動的;屬性動畫要求動畫作用的對象必須提供想要改變屬性的set方法,如果你沒有傳遞初始值的話還需要提供該屬性的get方法,屬性動畫會根據你傳入的該屬性的初始值和最終值以動畫的效果(也就是計算出某一時刻屬性需要改變的值)多次通過反射調用set方法動态的改變作用對象的屬性值,随着時間的推移,這個值将越來越接近設定的最終值,以達到動畫的效果;

        Android動畫注意點:

        在使用幀動畫的時候,因為動畫圖檔資源是我們自己提供的,是以一定要注意可能會出現的OOM異常;

        View動畫隻是ParentView不斷調整ChildView的畫布實作的,隻是對View的影響做動畫,而不是真正的改變View的狀态;

        View動畫在移動之後,移動的位置處的View不能觸發事件,但是屬性動畫是可以的;

2016.7.20更新...........................................................................

(5):簡單說Activity生命周期

        Activity常用到的生命周期方法包括:onCreate、onstart、onResume、onRestart、onPause、onStop、onDestroy七種;

        另外還有兩個Activity被異常銷毀恢複的生命周期方法:onSaveInstanceState、onRestoreInstanceState

        下面從不同環境條件下分析執行的生命周期方法:

        (1):第一次啟動某一Activity

                 onCreate----->onstart----->onResume

        (2):從目前Activity跳轉到某一Activity或者按下Home鍵

                 onPause----->onStop

        (3):再次回到原來的Activity

                 onRestart----->onStart----->onResume

        那麼将(2)和(3)連起來了解就有一個問題出現了,再次傳回原先Activity是先執行原先Activity的onResume方法呢,還是先執行目前Activity的onPause方法呢?這個有點涉及到Activity棧的知識,你想想肯定是現在的Activity在棧頂了,那肯定是先執行目前Activity的onPause方法了,這樣他暫停之後才會執行棧内其他Activity的onResume方法了;

        (4):在目前Activity界面按下Back鍵

                 onPause----->onStop----->onDestroy

        (5):在目前Activity界面按下鎖屏鍵進行鎖屏操作

                 onPause----->onStop

        (6):從鎖屏狀态傳回到之前的Activity

                 onRestart----->onStart----->onResume

        (7):在目前Activity窗體中以彈窗的形式顯示另一個Activity

                 隻會執行目前Activity的onPause方法,并不會執行onStop方法,如果此時點選Back鍵退出彈窗Activity顯示出原先的Activity,則直接執行onResume方法,連onRestart與onStart方法都不執行;

        (8):在目前Activty上通過按鈕點選的形式彈出一個AlertDialog窗體,發現根本不會對Activity生命周期有任何影響,說明一點,AlertDialog其實是附在Activity上面的;

        (9):接下來說說onSaveInstanceState與onRestoreInstanceState,這兩個生命周期方法和onStop和onStart方法的執行順序是:

Android面試題集錦(一)

        但是onSaveInstanceState和onPause之間是沒有先後關系的;

        如果我們的Activity被異常關閉,比如你進行了橫豎屏切換或者目前Activity因為優先級比較低被系統殺死,系統就會調用onSaveInstanceState進行Activity狀态的儲存,比如說Edittext裡面的值或者你ListView上面滑動到哪個Item資訊,當該Activity重新建立的時候就會調用onRestoreInstanceState方法恢複之前在onSaveInstanceState裡面的資料了,注意的是onSaveInstanceState方法僅僅會出現在Activity被異常關閉的情況下,正常情況下是不會執行這個方法的,也就是正常情況下我們通過onCreate建立Activity的時候,他的Bundle參數是null的;

        關于Activity生命周期的一點總結:

        onCreate和onDestroy是一對相對的方法,分别标志Activity的建立和銷毀;

        onStart和onStop是一對相對的方法,表示Activity是否可見;

        onPause和onResume是一對相對的方法,表示Activity是否處于前台;

        在正常的銷毀Activity情況下是不會執行onSaveInstanceState的;

(6):橫豎屏切換對Activity生命周期的影響

       正常情況下,如果不進行特殊設定的話,橫豎屏切換會導緻Activity重新建立,也就是會重新執行onCreate方法,在之前Activity是異常銷毀的時候會執行他的onSaveInstanceState方法(正常銷毀的話該方法是不會執行的),該方法會儲存之前Activity已經有的一些資訊,比如EditText的内容啊,ListView滾動的位置啊等等,那麼這次調用onCreate的時候和普通的直接建立Activity調用onCreate方法是有差別的,直接建立的話onCreate方法的參數等于null,但是橫豎屏切換之後再執行的onCreate方法參數裡面是有值的,我們可以拿到這些值來恢複之前Activity的一些已有狀态,當然如果沒有在onCreate中恢複的話,系統會自動回調onRestoreInstanceState來進行恢複;

        如果我們不想在橫豎屏切換的時候進行Activity生命周期的重新加載,那麼就需要配置Activity的android:configChanges了,orientation表示消除橫豎屏的影響,keyboardHidden表示消除鍵盤的影響,screenSize表示消除螢幕大小的影響,适當的設定之後将不再會導緻Activity生命周期的重新加載,每次橫豎屏切換的時候将會回調onConfigurationChanged方法了;

        當然可以通過設定Activity的android:screenOrientation屬性,直接屏蔽掉橫豎屏切換操作,這樣橫豎屏功能将不能使用,這和android:configChanges是有差別的,一個是可以用但是不會導緻Activity生命周期重新加載,一個是幹脆不能用; 

(7):ThreadLocal工作原理

        為了了解清楚Handler的消息處理機制,首先需要了解的知識就是ThreadLocal了,這個類并不是Android所特有的,它來自于java,ThreadLocal主要用來幹什麼呢?答案是用于如果某些資料是以線程作為作用域,但是每個線程又還想要該資料的副本的情況下,通俗點可以這樣了解,有一塊空菜地,你和你鄰居都想在裡面種菜,但是如果這塊菜地分給你的話你鄰居要想在這塊菜地裡面種菜那肯定會影響到你種菜,反之你會影響你鄰居,那麼怎麼能解決這個問題呢?給你和你鄰居都分一塊菜地,自己種自己的,這樣就不互相影響了,這就是ThreadLocal幹的事了;java中的ThreadLocal實作原理是采用ThreadLocalMap的方式來存儲目前線程用到的各ThreadLocal軟引用及其對應值的,而android中ThreadLocal實作方式上差別于java,他的每個線程中都有一個Values類型的變量,而Values類型對象中有一個Object類型數組,數組大小隻能是2的指數倍數,這個數組就是用于存儲我們的ThreadLocal軟引用及其對應值的,具體存儲方式是ThreadLocal軟引用的存儲位置位于其值存儲位置的前一個位置;

        可能你會想使用ThreadLocal和使用synchronized有什麼差別呢?個人認為差別挺大的,ThreadLocal的話,每個線程做自己的事,兩者之間不互相影響,隻是他們的ThreadLocal初始化值是相等的而已,而synchronized實際上是同一時間隻有一個線程能夠修改某一個共享變量的值而已,修改之後的值是會影響到另一個線程開始修改的該變量的值的;

(8):Handler工作機制

        鑒于Android的UI線程不是線程安全的,這點也很好了解,如果有多個線程更改UI界面顯示的元素的話,最終界面到底會顯示出什麼将是不确定的,這點會讓人感覺莫名其妙,因而Android隻規定主線程可以更新UI了,那麼如果我的子線程想要更新UI該怎麼辦呢?難道就不能更新了嗎?No,這就是Handler出現的原因了,雖然我們通常将Handler用在子線程需要更新UI的場景下,但是他的作用不止這點,他可以使用在不同線程之間的切換,而不僅僅是切換到主線程更新UI這麼局限;

        Handler工作原理:

        先要弄清楚Handler消息進行中用到的一些概念,Message用于封裝将要傳送的資料内容,MessageQueue消息隊列用于暫存那些需要處理的Message消息,Looper用于不斷的從MessageQueue中取出消息進行處理,Handler消息的封裝者和處理者,通過他進行Message消息的生成,通過他接收Looper傳來的消息并且進行處理,有點類似于統領者的角色,那麼Looper是什麼鬼,好端端的冒出來他幹什麼呢?MessageQueue隻是Message消息的存放者,Handler怎麼知道什麼時候需要處理消息呢?答案就是靠Looper了,他會不斷的檢視MessageQueue,有消息的話就交給Handler來處理了,如此看來Android消息進行中的角色分工真的好明确啊!!注意一點,一個Handler要想真正起作用的話,他所在的線程中必須存在一個Looper,而在建立Looper的過程中就會建立一個MessageQueue出來,也就是Looper和MessageQueue是一一對應的;

        我們一般想要更新UI的話,都是在主線程中建立一個Handler對象,接着在子線程中使用它的sendMessage方法發送一條消息,然後該消息就會回調handler的handleMessage方法進行相應的更新操作了;

        那我們分析Handler機制首先就該從主線程開始了,在Activity啟動的時候會執行ActivityThread裡面的main方法,在該方法裡面會通過prepareMainLooper建立一個Looper對象出來,相應的也就建立了MessageQueue消息隊列了,并且會将目前Looper對象存儲到目前線程的ThreadLocal裡面,也就是存儲到主線程的ThreadLocal裡面了,是以這也就是解釋了你在主線程建立Handler的時候并沒有自己建立Looper出來程式不會報錯的原因了,因為主線程在Activity啟動的時候就建立好了,接着我們便是在主線程建立Handler對象了,在建立Handler對象的構造方法裡面會擷取到在ActivityThread的main方法裡面建立的Looper對象及其對應的MessageQueue對象,接着我們會在子線程中通過主線程的Handler對象調用他的sendMessage方法,該方法會傳入封裝有需要傳遞給主線程的資料的Message對象,sendMessage實際執行的操作是調用enqueueMessage方法将消息加入到MessageQueue消息隊列中,除此之外在ActivityThread的main裡面發現會調用Looper.loop(),也就是會讓目前Looper運轉起來,loop方法裡面存在一個死循環會不斷的去檢視MessageQueue裡面有沒有消息存在,有的話則進行出隊操作,擷取到隊頭消息,并且擷取到處理該消息所對應的Handler,具體來說其實就是Message的target屬性值了,然後調用target也就是Handler對象的dispatchMessage方法将消息分發出去,dispatchMessage轉而會執行handleMessage方法,這也就回到了我們主線程中了,是以我們可以在handleMessage裡面擷取到消息中封裝的資料進而進行一些界面上元素的修改了,這就是在主線程中使用Handler的消息執行流程了;

        那麼如果想要使用Handler一個線程傳遞資料到另一個線程中,但是兩個線程都不是主線程該怎麼辦呢?很明顯這種使用情況将不同于上面了,我們就該自己建立Looper對象以及其對應的MessageQueue隊列了,具體做法是:在接收資料的線程中通過Looper.prepare建立一個Looper對象及其對應的MrssageQueue隊列,接着調用Looper.loop方法讓該Looper運轉起來,可以在MessageQeueu裡面有消息的時候進行處理,建立一個Handler對象用來進行消息處理,并且在另一個線程中利用該消息進行消息發送即可,這裡有一點需要注意,就是我們的loop方法是個死循環,他又是位于線程内部的,如果loop方法不結束的話,線程将一直處于運作狀态,這會帶來一個問題,就是我們已經明确知道消息隊列裡面的消息已經處理結束了,沒有消息要處理了,Looper還是會不斷的檢視有沒有消息存在,這會帶來性能上的損失,解決這個問題的唯一方法就是想辦法能讓loop方法結束掉,檢視loop方法的源碼會發現,當Looper擷取到的消息為null時就會執行return結束掉死循環,那麼我們就該找到什麼時候會向消息隊列中插入一條null消息了,答案就是在Looper的quit方法裡面了,是以我們如果在某一時刻已經明确知道MessageQueue隊列沒有消息的話調用Looper的quit方法結束掉loop方法進而結束掉目前線程,避免性能丢失;

(9):HandlerThread原理剖析

        在(8)中我們分析了Handler消息處理機制,知道Handler要想真正起到作用的話需要借助于Looper,而Looper裡面會建立一個MessageQueue對象出來,在主線程中使用Handler的時候我們完全不用考慮建立Looer以及其對應MessageQueue消息隊列,以及Looper運作起來這些的事情,但是要想在子線程之間使用Handler,我們就必須通過Looper.prepare來建立Looper對象及其對應的MessageQueue對象,通過Looper.loop方法使得目前建立的Looper運轉起來了,這點本來就已經能夠滿足我們在子線程之間使用Handler的要求了,但是google為了能減少開發人員在子線程中使用Handler的麻煩,提供了HanderThread,他的實作原理其實就是我剛剛說的那些,隻不過做了封裝而已,我們在建立Handler之前會先建立一個HandlerThread對象,并且調用它的start方法,這個start方法就比較重要了,他會調用HandlerThread的run方法,為什麼呢?因為HandlerThread歸根結底也是Thread嘛,調用start之後輾轉都會執行到run方法,在run方法裡面就會通過Looper.prepare建立Looper對象及其對應的MessageQueue消息隊列了,同時會調用Looper.loop方法讓目前Looper運轉起來,是以這個run方法是最重要的了,之後建立Handler發送消息和接收消息的過程就和在主線程使用Handler一緻了,當然和我們自己在子線程中建立Looper使用Looper出現的問題一樣,通過HandlerThread方式使用Handler同樣也會帶來Looper對象的loop方法一直執行不會結束的情況,解決方法是調用HandlerThread的quit方法,該方法實際上還是調用的Looper的quit方法;

(10):IntentService原理分析

        上面我們分析了Handler消息處理機制以及HandlerThread裡面所涉及到的一些知識點,知道HandlerThread其實就是為了我們在子線程中減少自己建立Looper以及運轉Looper而出現的,那麼這次的IntentService其實封裝的更巧妙,使用HandlerThread的時候我們還需要建立Handler對象出來,但是使用IntentService連Handler對象也不用我們建立了,可見google為了讓程式員使用簡便做了多少工作,先來說說IntentService是幹什麼的,他是一個抽象類,因而我們在使用的時候需要建立一個實作他的類出來,它裡面僅有一個抽象方法就是onHandleIntent了,我們可以在這個方法裡面做一些處理Intent的操作了,作為Service的一種,IntentService自然也是在背景執行的,也是通過startService啟動的,他的優先級要高于一般的線程,那麼IntentService有什麼用處呢?适合于執行一些高優先級的背景耗時任務,高優先級的背景任務是Service的特點,但是由于Service是處于主線程的,他不适合處理耗時任務,但IntentService卻可以,原因就在于IntentService在建立的時候就會開啟一個線程出來,耗時任務是在該線程中進行的,具體點說這裡的線程其實就是HandlerThread了,在耗時任務處理結束之後該Service會自動停止;

        我們來看看IntentService具體是怎麼做到封裝了Handler來處理耗時任務的,在IntentService的構造方法裡面你會看到建立了一個HandlerThread線程出來,并且調用了他的start方法啟動了該線程,上面HandlerThread中已經講過會在該線程的run方法裡面建立Looper對象并且調用loop将Looper運轉起來,接着會通過建立的Looper對象建立一個ServiceHandler出來,其實就是Handler對象而已,該對象裡面有handleMessage方法,在我們通過startService方法啟動IntentService的時候會回調onStartCommand方法,該方法會執行IntentService的onStart方法,而正是在onStart方法裡面會将我們startService傳入的intent對象封裝成Message對象通過在構造函數中建立的ServiceHandler類型handler對象的sendMessage方法發送出去,那麼緊接着就會回調ServiceHandler的handleMessage方法了,handleMessage方法實際上執行的是onHandleIntent方法,也就是我們在實作IntentService抽象類的時候需要實作的方法,具體實作對Intent的操作,操作結束之後handleMessage方法會執行stopSelf方法結束目前IntentService;

(11):使用new Message()和obtainMessage兩種方式得到Message對象有什麼差別?

        我們在平常使用Handler sendMessage方法的時候都要傳遞Message參數進去,通常建立Message對象有兩種方式,一種就是常用的通過構造函數的方式建立對象,一種就是通過Handler的obtainMessage了,既然都能new了說明Message的構造函數是public的,那麼還來個obtainMessage幹嘛呢?答案就是為了節省記憶體資源,如果你檢視Message的定義的話,會發現它裡面有一個next字段,這個字段的屬性值是Message類型的,是以從這種角度看的話Message本身就可以作為連結清單存在,我們的Message消息池其實就是存儲着第一個Message消息而已,之後的消息都是通過next字段連結到一起的,使用obtainMessage首先會去檢視目前消息池中有沒有消息存在,存在的話則直接取到該消息并且将該消息從消息池中删除同時将消息池大小減一即可,也就是将連結清單長度減一,如果消息池中不存在消息的話才會通過new Message的方式建立消息出來,我們每次使用完消息之後通過執行Message的recycle會将目前使用過的消息對象添加到消息池中,也就是加傳入連結表中,當然在加入之前需要将原消息中的内容資訊全部置位,這樣有效減緩了你頻繁通過new Message方式建立消息的記憶體開銷,保證了隻有在目前消息池不再存在可用消息的情況下才去建立消息出來,so perfect!!!

(12):AsyncTask工作原理淺析

    要想了解清楚AsyncTask的工作原理首先就應該搞清楚Handler的工作機制,前面已經分析過啦,那我們就直接開始了,我們平常使用AsyncTask是建立AsyncTask對象之後執行execute,建立AsyncTask對象的時候會同時建立一個WorkerRunnable對象,并且以這個WorkerRunnable對象為參數會建立一個FutureTask對象,那麼分析AsyncTask的原理就該從execute方法開始了,執行execute方法首先會執行executeOnExecutor方法,并且傳入一個SerialExecutor類型的對象,SerialExecutor是一個串行線程池,一個線程裡面的所有AsyncTask全部都在這個串行的線程池中排隊執行,在executeOnExecutor裡面首先會執行onPreExecute方法,該方法是在我們建立AsyncTask對象的時候自己實作的,運作在主線程中,我們可以在這個方法裡面進行任務開始的提示性操作,接着線程池開始執行,也就是從這一步開始切換到了子線程中,傳入的對象就是我們建立AsyncTask對象的時候生成的FutureTask對象,在SerialExecutor線程池的execute方法中首先會把目前FutureTask對象插入到任務隊列中,如果目前任務隊列中沒有正在活動的AsyncTask任務的話,則會執行scheduleNext方法從隊列中取得一個AsyncTask任務,同時當一個AsyncTask任務執行結束之後會在finally中調用scheduleNext方法執行任務隊列中的下一個AsyncTask任務,從這裡也看出來預設情況下AsyncTask是串行執行的,那麼真正的執行操作就該在scheduleNext方法裡面了,可以看到這個方法裡面真正執行任務的線程池是THREAD_POOL_EXECUTOR,很多人都在想那剛剛的SerialExecutor線程池是用來幹嘛的呢,它主要是用來任務排隊的,保證預設情況下的串行執行而已,而THREAD_POOL_EXECUTOR才是真正的任務執行者,此外在AsyncTask裡面還有一個InternalHandler對象,其實他就是一個Handler對象而已,他存在的作用就是為了從子線程切換到主線程中,為了便于在子線程執行的過程中進行一些與界面元素的互動過程,比如下載下傳進度條的更新等等,那麼也就必須要求該InternalHandler對象在主線程中建立了,檢視源碼你會發現InternalHandler對象是static的,也就是在AsyncTask對象建立的時候他就會建立,是以隻要保證AsyncTask對象在主線程中建立就可以了,是以我們使用AsyncTask的時候一定要注意在主線程中建立他的對象,扯的有點遠了,THREAD_POOL_EXECUTOR會執行他的execute方法,該方法實際上執行的是FutureTask的run方法,而FutureTask的run方法實際上執行的是建立FutureTask對象的時候傳入的參數WorkerRunnable對象的call方法,檢視call方法可以看到執行了doInBackground方法,該方法也是需要我們在建立AsyncTask對象的時候自己實作的,我們可以在這個方法裡面執行一些比較耗時的操作,它運作在子線程中,在該方法中我們可以通過publishProgress來發送一些耗時任務已經處理的進度資訊,該方法運作在子線程中,該方法中會通過InternalHandler将進度消息發送出去,接着在InternalHandler裡面的handleMessage裡面會發現是通過onProgressUpdate進行消息處理的,該方法運作在主線程中,可以進行更新進度條的一些操作,在doInBackground方法執行結束後會将傳回結果作為參數傳遞給postResult方法,該方法同樣會通過InternalHandler發送消息,最後在InternalHandler裡面的handleMessage裡面處理該消息,調用的是finish方法,也就是将線程切換到了主線程中了,在finish方法中會根據主線程有沒有被暫停來執行onCancelled或者onPostExecute方法,這兩個方法是運作在主線程的,到這裡AsyncTask的執行結束了;

       關于AsyncTask中需要注意的幾點:

        (1):預設情況下AsyncTask之間是串行執行的;

          (2):AsyncTask裡面存在兩個線程池,一個是SerialExecutor類型的,它主要是用來進行AsyncTask任務排隊的,一個是THREAD_POOL_EXECUTOR線程池,它才是任務的真正執行者;

        (3):AsyncTask内部有一個InternalHandler類型的變量,主要用于在任務執行的過程中主線程和子線程之間的切換的,是以他必須在主線程中建立,因為他在AsyncTask中是static修飾的,是以在AsyncTask加載的時候他就被建立了,是以間接要求AsyncTask在主線程建立了;

(13):AsyncTask中各個方法哪些在主線程執行哪些在子線程執行?

       onPreExecute在主線程執行;

        doInBackground方法在子線程中執行;

        publishProgress在子線程中執行;

        onProgressUpdate在主線程中執行;

        onCancelled在主線程中執行;

        onPostExecute在主線程中執行;

(14):AsyncTask可以并行執行嗎?

        可以的,從Android3.0開始,我們可以通過直接調用AsyncTask方法的executeOnExecutor方法傳入自己定義的線程池,沒錯,這個方法也是預設情況下調用AsyncTask的execute方法真正執行的方法,但是預設情況下傳入的是SerialExecutor類型的線程池,他會對AsyncTask任務進行排隊,雖然THREAD_POOL_EXECUTOR線程池本身是可以并行處理任務的,但是因為任務都是靠SerialExecutor線程池串行取出來的,是以也就造成了AsyncTask預設情況下串行執行的特點了;但是如果我們直接傳入自己定義的線程池的話,預設線程池是可以并行處理的,你也可以傳入AsyncTask内部已經定義的THREAD_POOL_EXECUTOR線程池,這樣也行;

(15):IntentService和Service的對比

        (1):首先從名字上看兩者都是Service,是以呢都有Service的特點,都可以處理背景任務;

        (2):IntentService是可以處理耗時任務的,原因在于在建立他的時候建立了一個HandlerThread類型的線程;而Service本身是不可以處理耗時任務的,因為它運作在主線程中,也就是說你在Servicve裡面進行耗時操作會出現ANR異常,但是IntentService裡面是不會的;   

        (3):IntentService在他的所有任務執行結束之後會自動調用stopSelf來結束該IntentService,但是Service卻需要我們通過stopService方式來結束;

        (4):IntentService是以串行的方式執行任務的,原因在于IntentService内部消息處理的實作原理是通過Handler加MessageQueue加Looper來實作的;

2017.7.21更新...........................................................................

(16):Activity啟動模式

        Activity的啟動模式分為:standard、singleTop、singleTask、singleInstance,可以在Activity的标簽下通過android;launchMode來進行設定,為什麼要有Activity的啟動模式呢?預設情況下我們多次啟動同一個Activity的時候預設會建立多個執行個體放入到任務棧中,這樣重複建立執行個體的做法顯然有點不太科學,我們有時候完全可以直接利用之前建立的執行個體就行了,Activity的啟動模式就是做這個的;

       standard:每次啟動Activity都會建立新的Activity執行個體,即使在目前Activity棧中已經存在同樣的Activity執行個體,這是預設的啟動模式;

       singleTop:通俗點就是棧頂複用模式,每次在啟動Activity的時候首先會去檢視目前Activity棧的棧頂位置的Activity執行個體是否是需要啟動的Activity,如果是的話,則Activity将不再會建立,即不會執行onCreate、onStart方法,同時它的onNewIntent方法将會被調用;如果棧頂不是要激活的Activity的話,則會建立一個Activity出來,并且将它壓入棧頂;

       singleInTask:通俗點講是棧内複用模式,什麼意思呢?就是相當于棧内的單例模式吧,如果目前要啟動的Activity在目前Activity棧存在的話,那麼他會把棧内該Activity上面的所有Activity全部出棧,知道要用的Activity位于棧頂為止,然後調用它就可以了;如果目前Activity棧中不存在将要激活的Activity的話,則建立新的Activity出來,并且将該Activity壓入到棧中;

      singleInstance:單執行個體模式,可以認為是singleTask的加強模式,處于該模式的Activity隻能單獨位于一個任務棧中,也就是說該任務棧中将隻有一個Activity執行個體;

        上面多次提到了任務棧,判斷一個Activity到底屬于哪個任務棧這點會涉及到Activity的TaskAffinity屬性,我們可以在Activity的标簽下通過指定android:affinity來進行設定,預設情況下不進行設定的話Activity任務棧的名字就是目前Activity所在的包名;

(17):子線程中更新UI的方式

        我們都知道隻有主線程可以更新UI,那麼如果子線程想要更新UI怎麼辦呢?隻能是借助于Handler來實作了,那麼具體的實作方式有哪些呢?

        (1):最常見的方式就是通過Handler的sendMessage和handleMessage來進行處理了,這個比較簡單,不再舉例;

        (2):通過Handler的post方法,這種執行方式需要在post方法中傳入執行耗時任務的線程,接着在執行post方法的時候,會将該執行任務的線程封裝到Message裡面的callback屬性,之後當Handler裡面的Looper循環檢視MessageQueue消息隊列的時候會取到這條消息,取到消息中的執行耗時操作的線程,直接執行他的run方法就可以了,我們可以在該run方法中進行更新UI操作的;

        (3):通過View的post方法,這種方法實質上是通過Handler的post方法完成的;

        (4):通過Activity的runOnUiThread方法,該方法同樣還是通過的Handler的post方法實作的;

        詳情可以看這篇部落格      

(18):Android中assets檔案夾與raw檔案夾的差別

        相同點:兩者目錄下的檔案在打包之後都會原封不動的打包在apk檔案中,不會被編譯成二進制檔案;

        不同點:(1):res/raw中的檔案會在R.java中生成ID,但是assets檔案夾中的内容不會在R.java中生成ID;

                        (2):因為res/raw中的檔案在R.java中有ID,是以我們可以通過ID直接引用資源,但是對于assets中的檔案隻能通過AssetManager來處理;

                        (3):res/raw不可以有目錄結構,而assets可以有目錄結構,也就是可以在assets目錄下建立檔案夾;

(19):注冊廣播的方式有哪些,各自的應用場景?各有什麼優缺點?

        注冊廣播的方式有兩種,一種是通過代碼的方式,一種是通過在AndroifManifest.xml中注冊的方式;

        (1):通過代碼的方式,通過registerReceiver方式進行注冊,通過unregisterReceiver取消注冊,當Broadcast需要更新UI的時候會選擇這種方式進行注冊,在Activity可見的時候調用registerReceiver進行廣播接收,在Activity不可見的時候調用unregisterReceiver取消注冊;

        (2):通過在AndroidManifest.xml中進行注冊,通過在<receiver></receiver>标簽中設定<intent-filter></intent-filter>屬性進行廣播接收;

        兩種方式的差別:

        方式1不是常駐型廣播,該中方式注冊的廣播跟随程式的生命周期,确切點來說如果我們想通過廣播更新UI的話一般會在Activity的onCreate裡面進行廣播的注冊,在Activity的onDestroy中進行廣播的取消;

        方式2是常駐型廣播,也就是在我們的應用程式關閉之後,如果有與<intent-filter>比對的廣播到來,應用程式還是會接收的,這種方式的弊端在于它始終處于活動狀态,這多少會影響系統的性能;

(20):記憶體溢出和記憶體洩露的差別?

        記憶體溢出指的是程式運作時記憶體超過可用的最大值,Android會為每個應用程式配置設定一個可用的記憶體最大值,這時候會報OOM異常;記憶體洩露指的是一些已經不再用到的引用或者對象仍然長期儲存在記憶體中,造成記憶體資源的浪費;

(21):啟動Activity的方式

        啟動Activity有兩種方式,顯式調用和隐式調用,顯示調用就是在我們的代碼中通過startActivity方式明确指定被啟動對象的元件資訊,比如包名和類名;隐式調用則不需要明确指定元件資訊,我們可以設定一些過濾規則來啟動那些符合規則的Activity即可;當顯式調用和隐式調用同時出現在同一個Intent上的時候,執行的将是顯式調用;

(22):IntentFilter的比對規則

        IntentFilter中的過濾資訊有action、category、data,他們是在四大元件的<intent-filter></intent-filter>标簽下進行配置的,一個過濾清單中可以有多個action、category、data,需要同時比對清單中的action、category以及data才算比對成功,否則失敗;一個Activity可以有多個<intent-filter>比對規則清單,一個Intent隻要滿足其中之一就可以成功啟動該Activity了,也就是比對成功了;

        action比對規則:

        action的比對規則要求Intent中的action必須存在,并且必須和過濾規則中的其中一個action相同,他的比對是區分大小寫的,在代碼中通過setAction進行設定;

        category比對規則:

        category比對要求Intent中可以沒有category,但是隻要有category,不管你有幾個,每一個都要和過濾規則中的其中之一category比對,代碼中通過addCategory進行設定;那麼為什麼Intent可以沒有category而必須由action呢?原因在于系統在調用startActivity的時候預設會為Intent添加上"android.intent.category.DEFAULT"這個category,是以如果我們沒有為Intent設定category,預設他會比對包含有"android.intent.category.DEFAULT"的Activity的;

        data比對規則:

        data是由mineType和URI組成,mineType表示媒體類型,URI和我們平常通路的網址類似,URI的預設值為content和file,也就是說如果你的Intent沒有指定URI部分,那麼預設情況下URI的schema部分是content或者file的,data的比對規則和action類似,也就要求Intent中必須包含data資料,并且data資料可以完全比對過濾規則中的某一個data;

(23):在Activity啟動的時候獲得View寬高的方式

        因為View的measure過程和Activity的生命周期并不是同步的,是以在Activity調用了onCreate、onStart、onResume并不一定能夠保證擷取到View的寬高,很多情況下擷取到的值都是0,那麼我們該怎麼在Activity啟動的時候獲得View的寬高呢?

        方式1:通過設定點選按鈕,在onClick事件裡面獲得View的寬高,為什麼這種方式可行呢?原因在于在onClick執行的時候按鈕已經顯示出來了,說明View的繪制流程已經走完了,我們自然可以獲得View的寬高了;

        方式2:實作Activity的onWindowFocusChanged方法,在該方法中擷取View的寬高,原因在于onWindowFocusChanged會在Activity布局繪制結束或者Activity暫停的時候調用,但是有個缺點就是該方法在Activity得到或者失去焦點都會回調,調用的次數比較頻繁,當然你可以選擇适當的時候屏蔽;

        方式3:通過View樹的觀察者ViewTreeObserver實作,ViewTreeObserver執行個體的獲得是通過getViewTreeObserver實作的,暗示了ViewTreeObserver的構造函數是不允許對外通路的,在View樹的全局布局或者View樹中的某一個視圖狀态發生變化的時候就會回調onGlobalLayoutListener裡面的onGlobalLayout方法,我們可以在該方法裡面獲得View的寬高;

        方式4:采用post方法将一個Runnable對象添加到消息隊列的尾部,這樣在我們執行Runnable的run方法之前View已經初始化好了,自然可以拿到他的寬高了;

        詳情可以看這篇部落格;

(24):Fragment與Activity的關系

        (1):Fragment是Android3.0出現的,我們可以将它認為是小Activity,碎片Activity,他是依托于Activity存在的,由Activity的FragmentManager來管理,也就是它的生命周期受Activity的影響,隻有在Activity處于活動狀态下才能個進行Fragment各個生命周期的變化,Activity被銷毀的話,綁定在它上面的Fragment随之也會銷毀;

        (2):Fragment可以解決多Activity的問題,即可以将Activity的跳轉轉換為是Fragment的切換;

        (3):Fragment可以被重用,即可以在不同的Activity中共用同一個Fragment;

        (4):Activity是間接繼承了Context,但是Fragment不是,是以一個應用中Context的個數是不包括Fragment個數的;

        (5):在Fragment裡面可以通過getActivity獲得目前Fragment所在的Activity對象,在Activity中可以通過FragmentManager查找它所包含的Fragment;

(25):Fragment生命周期

        上面已經說了Fragment是依托于Activity存在的,也就是說Fragment不能單獨存在,需要有Activity作為載體,隻有在Activity處于活動狀态的情況下才可以進行Fragment各生命走起狀态間的轉換,Activity一旦銷毀它上面所附加的Fragment也将銷毀;

        Fragment生命周期所涉及到的方法有:onAttach、onCreate、onCreateView、onActivityCreated、onStart、onResume、onPause、onStop、onDestroyView、onDestroy、onDetach;

        其中onAttach和onDetach是一對方法,分别表示Fragment被添加到Activity中和Fragment被從Activity移出;

        onCreateView和onDestroyView是一對方法,分别在建立Fragment視圖和移出Fragment視圖的時候調用;

        還有一個onActivityCreated,這個是在Activity的onCreate方法傳回的時候調用的;

        使用通常在定義Fragment的時候都要重寫onCreateView方法,這個方法的傳回值就是我們想要顯示的View了,通常是通過LayoutInflater的inflate方法加載這個View的;

        Fragment與Activity生命周期之間的關系可以通過官方的一張圖來表示:

Android面試題集錦(一)

(26):Fragment與Fragment以及Fragment與Activity之間的通信

        在Fragment裡面可以通過getActivity獲得目前Fragment所在的Activity,在Activity中可以通過FragmentManager來管理目前Activity所用到的Fragment,這樣Fragment與Activity之間就可以進行通信了;那麼Fragment之間該怎麼進行通信呢?對于處于同一個Activity中的Fragment我們可以借助于他們都在該Activity上面這一點進行通信;當然上面所涉及到的Activity都是繼承自FragmentActivity的Activity,不是平常的Activity;

(27):android存儲資料的幾種方式?

        (1):使用sharedPreferences存儲,底層實作原理是基于XML檔案存儲的key-value鍵值對;

        (2):檔案存儲資料,Context提供了兩個方法來打開資料檔案裡面的IO流FileInputStream openFileInput(String name);FileOutputStream  openFileOutput(String name,int mode),可以用這兩個方法将資料存儲到檔案中;

        (3):使用SQLite存儲資料,作為輕量級嵌入式資料庫,當然可以進行資料的存儲了,但是SQLite缺點在于隻能在同一程式中共享存儲的資料;

        (4):那麼我們要是想在跨程序之間共享存儲的資料該怎麼辦呢?也就是該我們ContentProvider出現的時候,因為ContentProvider的底層實作是Binder,是以他也是适合程序間資料共享的,ContentProvider内部是通過表格的方式來組織資料的,有點類似于SQLite資料庫,但是ContentProvider對底層資料存儲方式沒有任何要求,每個應用程式對外都會提供一個公共的URI對象,如果某個應用程式有資料需要共享的時候,首先在該應用程式中會為這些資料定義一個URI,如果另外一個應用程式想要拿到這個應用程式的共享資料的話,就會通過ContentProvider傳入這個URI來擷取到資料,具體是怎麼擷取資料的就是通過ContentResolver對象來進行Insert、Update等等的操作了;

        (5):網絡資料存儲,前面四種都是本地資料存儲方式;

(28):HttpClient和HttpURLConnection他們各自的優缺點是什麼?

        (1):HttpClient是Apache提供的庫,提供了高效的、最新的支援HTTP協定的工具包,封裝了衆多的http請求、響應等方法,但有個确定啊就是太重量級了,API太多了;HttpURLConnection是SUN公司的類庫,他是輕量級的HTTP架構,它裡面的方法都是一些我們進行http操作常用的,因而如果你想進行實作一些比較進階的功能比如代理、會話或者Cookie方面的,就需要自己寫了;

        (2):HttpURLConnection直接支援GZIP壓縮,從2.3版本開始自動在每個發出的請求的請求頭中加入Accept-Encoding:gzip;HttpClient也支援,但是需要你自己寫;

        (3):HttpURLConnection支援系統級連接配接池,即打開的連接配接不會直接關閉,在一段時間内所有應用程式可以共用;HttpClient也能做到,但至少不如直接系統級支援好;

        (4):從4.0版本開始,HttpURLConnection在系統層面也開始支援緩存機制,加快了重複請求的次數;

(29):XML解析方式種類及其優缺點?

        Android中對于XML的解析有SAX、DOM、PULL三種解析方式;

        SAX解析器的優點是解析速度比較快,解析能立即開始,而不是等待所有的資料被處理,這點有别于DOM方式,是以不需要将資料存儲到記憶體中,占用的記憶體比較少,非常适合于對較大文檔的解析,他是基于事件模型的;

        DOM方式在記憶體中是通過樹狀結構存放的,目前常用的是DOM4J方式了,這種方式對于查詢或者檢索的話效率相對高點,因為整個文檔已經加載到記憶體中了,但是對于特别大的文檔,解析和加載文檔很耗費資源,需要占用較大記憶體資源,不适合移動端;

        PULL解析方式和SAX一樣,也是基于事件模式的,解析速度快,占用記憶體小,是以Android選擇采用他來進行XML檔案的解析,比如布局檔案的加載等等;

(30):什麼是ANR?造成ANR現象的原因?怎麼避免和解決ANR現象?

       ANR:Application Not Responding應用程式沒有響應

        ANR根據處理事件的不同分為三種類型:

        (1):KeyDispatchTimeout----->5s,指的是按鍵或者觸摸事件在5秒内未得到響應;

        (2):BroadcastTimeout----->10s,指的是BroadcastReceiver在10s内無法處理完成;

        (3):ServiceTimeout----->20s,指的是Service在20s内無法處理完成;

        造成ANR現象的原因:

        (1):目前的事件沒有機會得到處理,比如主線程(UI線程)正在處理前一個事件,但是前一個事件比較耗時遲遲沒有完成或者主線程中的looper因為某種原因阻塞了,因為我們知道在Activity啟動的時候會建立一個Looper并且會通過Looper.loop()方法讓該Looper運轉起來;

        (2):目前事件正在被處理,但是遲遲沒有處理完成,導緻使用者界面一直收不到執行結果,一直在等待;

        解決或者避免ANR的措施:

        對于網絡操作(當然現在網絡操作是不可能在主線程中發起啦,要不直接會抛異常滴)、資料庫操作或者IO等耗時的工作應該放到單獨的線程中去執行處理,通過Handler來進行UI線程與子線程之間的互動切換,BroadcastReceiver的onReceive中盡量減少耗時代碼的書寫;

(31):Android系統程序等級類别?

        Foreground Process(前台程序)

        Visible Process(可見程序),一個透明的Activity下面覆寫的哪個Activity屬于可見的,但是這個透明的Activity屬于前台的;

        Service Process(服務程序),也就是service;

        Background Process(背景程序)

        Empty Process(空程序)

        系統回收程序是按照從低到高的優先級進行的;

(32):實作Service不被殺死常駐記憶體的方式有哪些?

        (1):如果是安卓自身機制因為系統資源不足的時候殺死你的Service,那麼一般情況下會在一段時間之後系統會重新開機剛剛被殺死的Service那麼此時你該做的事就是怎麼恢複Service被殺之前app的一些狀态了,那麼該怎麼恢複呢?這裡用到了Service中的onStartCommand方法的傳回值,如果該方法的傳回值是START_STICKY的話,在kill該服務之前會保留該Service的狀态為開始狀态,但不保留Intent對象,随後系統資源充足的時候進行Service重新開機時會調用onStartCommand方法,但是此時傳入的該方法的Intent參數将為null;如果設定onStartCommand方法的傳回值是START_REDELIVER_INTENT的話,在Service要被系統kill掉之前同樣會保留Service狀态為開始狀态,同時也會保留Intent對象,随後在系統資源充足的時候仍然會啟動該Service,同時會回調onStartCommand方法,此時會将保留的Intent對象傳入到onStartCommand方法中,保證了恢複Service被殺死之前的狀态;

        (2):如果Service是被第三方防毒軟體或者清理軟體殺死的話,我們可以提升Service的優先級來防止被殺除,具體方法就是使用前台Service,360就是這麼做的,但使用前台Service有個壞處就是一直會有一個通知欄顯示在界面上,這種方式比較流氓,但是效果相對來說比較好,具體實作方法是:通過調用Service的startForeground(int id, Notification notification)方法即可,第一個參數是Notification的id,第二個參數是Notification 對象了;

        (3):據說某些殺毒應用軟體以及手機廠商的系統中中是有白名單的,隻是聽說哈,這就是為什麼你WX、QQ都不啟動還會收到消息的原因了;

        (4):還可以将Service單獨放在一個程序中,為什麼這麼做呢?因為他占用記憶體資源比較少,不怎麼會被盯上,同樣是鎖屏的狀态下,系統肯定是先殺占用記憶體比較大的應用了;

        (5):我們可以通過監聽廣播的方式實作,具體來說就是我們定義一個廣播接收器,在這個接收器裡面監聽開機廣播、網絡切換廣播、USB接口插入/拔出廣播系統螢幕解鎖廣播等等,一旦收到這些廣播之後,就去檢視我們的服務Service有沒有被啟動,如果沒有啟動的話,則啟動就可以了;

        (6):我們的Service單獨運作在一個程序中,而後在Service中調用JNI的代碼fork出一個子程序,在子程序中監聽Service的狀态,當他被殺死的時候就重新開機他,這種方式在5.0以下是可以實作Service常駐記憶體的,但是5.0以上版本同樣會殺死Service,原因就是5.0以上增加了killProcessGroup方法;

        (7):使用QQ黑科技,在應用推到背景之後,啟動一個隻有一像素的頁面停留在桌面上,讓自己始終保持前台狀态,保護自己不被背景清理工具殺死;

繼續閱讀