編輯:關於Android編程
(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方法的執行順序是:
但是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中進行注冊,通過在
兩種方式的區別:
方式1不是常駐型廣播,該中方式注冊的廣播跟隨程序的生命周期,確切點來說如果我們想通過廣播更新UI的話一般會在Activity的onCreate裡面進行廣播的注冊,在Activity的onDestroy中進行廣播的取消;
方式2是常駐型廣播,也就是在我們的應用程序關閉之後,如果有與
(20):內存溢出和內存洩露的區別?
內存溢出指的是程序運行時內存超過可用的最大值,Android會為每個應用程序分配一個可用的內存最大值,這時候會報OOM異常;內存洩露指的是一些已經不再用到的引用或者對象仍然長期保存在內存中,造成內存資源的浪費;
(21):啟動Activity的方式
啟動Activity有兩種方式,顯式調用和隱式調用,顯示調用就是在我們的代碼中通過startActivity方式明確指定被啟動對象的組件信息,比如包名和類名;隱式調用則不需要明確指定組件信息,我們可以設置一些過濾規則來啟動那些符合規則的Activity即可;當顯式調用和隱式調用同時出現在同一個Intent上的時候,執行的將是顯式調用;
(22):IntentFilter的匹配規則
IntentFilter中的過濾信息有action、category、data,他們是在四大組件的
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生命周期之間的關系可以通過官方的一張圖來表示:
(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線程與子線程之間的交互切換;
(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被殺死之前的狀態;一、問題描述基於百度地圖實現檢索指定城市指定公交的交通路線圖,效果如圖所示二、通用組件Application類,主要創建並初始化BMapManagerpublic cla
(一).前言:仿36Kr客戶端開發過程中,因為他們網站上面的新聞文章分類比較多,所以我這邊還是打算模仿網易新聞APP的主界面新聞標簽Tab以及頁面滑動效果來進
本文實例講述了Android實現仿通訊錄側邊欄滑動SiderBar效果代碼。分享給大家供大家參考,具體如下:之前看到某些應用的側邊欄做得不錯,想想自己也弄一個出來,現在分
添加公用單元文件com..java package com.example.myapplication;import android.a