Android基礎知識

  1. Activity與Fragment的生命周期

Activity:


image.png

Activity生命周期須知:
(1)onStart和onResume的區別:onStart實際上表示Activity已經可見了,只是我們還看不到還不能交互而已,因為它還處在后臺。而onResume表示Activity已經顯示到前臺可見了,并且可以進行交互。
(2)當用戶按下home鍵,Activity經歷onPause-onStop的過程,這時重新進入Activity經歷onRestart-onStart-onResume過程;如果按下back鍵,經歷onPause-onStop-onDestroy的過程。
(3)當在當前ActivityA中打開一個新的ActivityB,要注意的是只有A的onPause執行完成后B的onCreate-…過程才能開始,而A的onStop則是在之后才會進行的。所以不應該在onPause中做耗時的操作,應該盡快讓B顯示出來進行操作才行。

Fragment:

image.png

對比:

image.png
  1. Acitivty的四中啟動模式與特點。

(1)標準模式(Standard)
系統的默認模式,每次啟動一個Activity都會重新創建一個新的Activity實例,不管是否已經存在。并且誰啟動了這個Activity,那么這個Activity就會運行在啟動它的那個Activity的任務棧中,如果我們用ApplicationContext來啟動標準模式的Activity就會報錯,因為它沒有所謂的任務棧,必須給Activity添加FLAG_ACTIVITY_NEW_TASK標志位。

(2)棧頂復用模式(singleTop)
在這個模式中,Activity還是會創建在啟動它的Activity的任務棧中,但是如果它已經位于棧頂,那么就不會重復創建,并且它的onNewIntent會被調用,但是要注意它的生命周期方法onCreate等等不會創建。并且如果它并不位于棧頂,如ABC,這時啟動B,還是會創建一個新的實例。

(3)棧內復用模式(singleTask)
在這個模式中,比如啟動Activity A,首先系統會尋找是否存在A想要的任務棧:
如果存在,就看任務棧中是否有A,如果有就有clearTop的效果,把A推到棧頂,并且調用onNewIntent,即CABD的任務棧,clearTop之后就是CA;如果沒有A,就創建A在棧頂。并且要注意一點,如果存在A的任務棧,那么任務棧自動回切換到前臺,如下圖所示:

image.png

Y的任務棧直接被切換到了前臺,這點和微信的聊天界面是一樣的。
如果不存在,就創建A所需要的任務棧并且創建A入棧。
使用場景:
一般來說棧內復用適用于在一個應用里我們希望唯一存在的界面,比如聊天界面,比如微信的聊天界面,我們在一個聊天界面點擊了頭像進入了新的界面,這個時候來了一條新的消息并且用戶點擊了消息,這個時候如果是Standard模式就會創建一個新的實例,這樣用戶在新的界面點擊back,用戶期望回到的是主界面,而實際上回到了之前的頭像界面,這就不符合常規了。如果把聊天設為棧內唯一模式,那么用戶點擊之后就會回到之前的聊天界面并且做一些改變,保證了聊天界面的唯一性。瀏覽器的例子也是這樣,我們肯定希望瀏覽器瀏覽的界面是唯一的,也需要用到這個模式。

(4)單實例模式(singleInstance)
啟動時,無論從哪里啟動都會給A創建一個唯一的任務棧,后續的創建都不會再創建新的A,除非A被銷毀了。

image.png

我們看到從MainActivity跳轉到SecondActivity時,重新啟用了一個新的棧結構,來放置SecondActivity實例,然后按下后退鍵,再次回到原始棧結構;圖中下半部分顯示的在SecondActivity中再次跳轉到MainActivity,這個時候系統會在原始棧結構中生成一個MainActivity實例,這時點擊back發現一個神奇的現象,回到的還是MainActivity,然后再點擊一次,注意,并沒有退出,而是回到了SecondActivity,為什么呢?是因為從SecondActivity跳轉到MainActivity的時候,在第一個返回棧中創建了新的實例,而Second所在的成為了后臺棧,所以說singleInstance不要用于中間頁面,如果用于中間頁面,跳轉會有問題,比如:A -> B (singleInstance) -> C,完全退出后,再次啟動,首先打開的是B。
使用情況:
假設,我們希望我們的應用能和另一個應用共享某個Activity的實例。
這樣說,可能很難以理解,那么舉例來說:
1.現在我們的手機上有“某某地圖”以及我們自己的應用。
2.我們希望在我們的應用里共享“某某地圖”當中的地圖功能界面。
那么,假定的操作就應該為,例如:
1.首先,假設我們在“某某地圖”里已經做了一定操作,地圖界面被我們定位到了成都市。
2.我們返回了HOME界面,打開了自己的應用,在自己的應用里打開了”某某地圖”的地圖界面。
3.那么,所謂共享該Activity實例,我們要達到的效果就是,當我們打開地圖界面,此時地圖上顯示的位置就應該是我們之前定位到的成都市。而不是地圖的初始化顯示方位。
那么,顯然,通過此前的3種啟動模式,我們是實現不了的。因為:
我們最初在“某某地圖”中進行定位時,activity是位于該應用的返回棧里的。
當我們在自己的應用再次調用地圖界面,系統的操作是,在我們自己的應用的返回棧里新建一個地圖界面Activity的實例對象。
所以實際上兩個“地圖界面”是位于兩個不同應用的各自的返回棧里的,兩個毫無關聯的Activity實例。
MainActivity位于Task“8”當中,當我們在自己的應用MainActivity當中調用SecondActivity,系統新建了返回棧Task“9”,并在該返回棧中放入一個全新的SecondActivity的實例對象。
此時,當我們再打開SecondActivity本身所在的應用,調用SecondActivity,系統則會復用Task“9”當中的對象,而不會去做其他操作了。
從而,也就是實現了我們的目的,在兩個應用間共享某個Activity。


  1. Activity緩存方法。
    Activity的 onSaveInstanceState() 和 onRestoreInstanceState()并不是生命周期方法,它們不同于 onCreate()、onPause()等生命周期方法,它們并不一定會被觸發。當應用遇到意外情況(如:內存不足、用戶直接按Home鍵)由系統銷毀一個Activity,onSaveInstanceState() 會被調用。但是當用戶主動去銷毀一個Activity時,例如在應用中按返回鍵,onSaveInstanceState()就不會被調用。除非該activity是被用戶主動銷毀的,通常onSaveInstanceState()只適合用于保存一些臨時性的狀態,而onPause()適合用于數據的持久化保存。

異常情況的出現分為兩種可能:
(1)資源相關的系統配置發生改變導致Activity被殺死并重新創建
這就是我們所說的橫豎屏幕切換的情況,在橫豎屏切換時由于系統配置發生了改變,Activity會被銷毀,其onPause,onStop,onDestroy均被調用,同時onSaveInstanceState會被調用,它的調用時機發生在onStop之前,和onPause沒有時間上的先后關系。
(2)內存不足導致低優先級的Activity被殺死
這種情況onSaveInstanceState會起作用,但是調用的時機不是被殺死時,因為被殺死時怎么會有機會調用呢?注意點2介紹了這個情況。

有兩點需要注意:
(1)使用onRestoreInstanceState來進行之前狀態的獲取優于onCreate,因為onRestoreInstanceState只要被調用一定說明是有值的(之前已經調用過onSaveInstanceState),但是onCreate無論是否有值一定會調用,所以沒有使用onRestoreInstanceState好。
(2)系統正常銷毀時,onSaveInstanceState不會被調用,如用戶按back鍵。但是如果Activity有機會重新顯示出來,那么onSaveInstanceState一定會調用,因為系統也不知道它是否一定會被殺死。系統不知道你按下HOME后要運行多少其他的程序,自然也不知道activity A是否會被銷毀,因此系統都會調用onSaveInstanceState(),讓用戶有機會保存某些非永久性的數據。以下幾種情況的分析都遵循該原則

  1. 當用戶按下HOME鍵時
  2. 長按HOME鍵,選擇運行其他的程序時
  3. 鎖屏時
  4. 從activity A中啟動一個新的activity時
  5. 屏幕方向切換時

  1. Service的生命周期,兩種啟動方法,有什么區別。
image

(1)A started service
  onCreate, onStartCommand, onBind 和 onDestroy。這幾個方法都是回調方法,都是由Android操作系統在合適的時機調用的,并且需要注意的是這幾個回調方法都是在主線程中被調用的。
1、onCreate: 執行startService方法時,如果Service沒有運行的時候會創建該Service并執行Service的onCreate回調方法;如果Service已經處于運行中,那么執行startService方法不會執行Service的onCreate方法。也就是說如果多次執行了Context的startService方法啟動Service,Service方法的onCreate方法只會在第一次創建Service的時候調用一次,以后均不會再次調用!!!!我們可以在onCreate方法中完成一些Service初始化相關的操作。
2、onStartCommand: 在執行了startService方法之后,有可能會調用Service的onCreate方法,在這之后一定會執行Service的onStartCommand回調方法。也就是說,如果多次執行了Context的startService方法,那么Service的onStartCommand方法也會相應的多次調用!!!onStartCommand方法很重要,我們在該方法中根據傳入的Intent參數進行實際的操作,比如會在此處創建一個線程用于下載數據或播放音樂等。
3、onBind: Service中的onBind方法是抽象方法,所以Service類本身就是抽象類,也就是onBind方法是必須重寫的,即使我們用不到。在通過startService使用Service時,我們在重寫onBind方法時,只需要將其返回null即可。onBind方法主要是用于給bindService方法調用Service時才會使用到。
4、onDestroy: 通過startService方法啟動的Service會無限期運行,只有當調用了Context的stopService或在Service內部調用stopSelf方法時,Service才會停止運行并銷毀,在銷毀的時候會執行Service回調函數。

(2)A bound service
被綁定的service是當其他組件(一個客戶)調用bindService()來創建的。客戶可以通過一個IBinder接口和service進行通信。客戶可以通過 unbindService()方法來關閉這種連接。一個service可以同時和多個客戶綁定,當多個客戶都解除綁定之后,系統會銷毀service。context.bindService()->onCreate()->onBind()->Service running-->onUnbind() -> onDestroy() ->Service stop,onBind將返回給客戶端一個IBind接口實例,IBind允許客戶端回調服務的方法,比如得到Service運行的狀態或其他操作。這個時候把調用者(Context,例如Activity)會和Service綁定在一起,當解除綁定時,只有完全沒有Client與它綁定時才會調用onUnbind和onDestroy,否則都不會調用。

image

注意:由于TestService已經處于運行狀態,所以ActivityB調用bindService時,不會重新創建TestService的實例,所以也不會執行TestService的onCreate回調方法,由于在ActivityA執行bindService的時候就已經執行了TestService的onBind回調方法而獲取IBinder實例,并且該IBinder實例在所有的client之間是共享的,所以當ActivityB執行bindService的時候,不會執行其onBind回調方法,而是直接獲取上次已經獲取到的IBinder實例!!!。并將其作為參數傳入ActivityB的ServiceConnection的onServiceConnected方法中,標志著ActivityB與TestService建立了綁定連接,此時有兩個客戶單client(ActivityA和ActivityB)與TestService綁定。

(3)注意點:
第一:這兩條路徑并不是完全分開的。
  即是說,你可以和一個已經調用了 startService()而被開啟的service進行綁定。
比如,一個后臺音樂service可能因調用 startService()方法而被開啟了,稍后,可能用戶想要控制播放器或者得到一些當前歌曲的信息,可以通過bindService()將一個activity和service綁定。這種情況下,stopService()或 stopSelf()實際上并不能停止這個service,除非所有的客戶都解除綁定。同樣,如果是startService開啟的服務及時所有客戶解除綁定,如果不調用stopService,也不能停止。
第二:除了onStartCommand可以多次調用,其他都不能
在Service每一次的開啟關閉過程中,只有onStart可被多次調用(通過多次startService調用),其他onCreate,onBind,onUnbind,onDestory在一個生命周期中只能被調用一次!!!!


  1. 怎么保證service不被殺死。
    (1)進程生命周期:
    官方文檔告訴我們,Android系統會盡量保持擁有service的進程運行,只要在該service已經被啟動(start)或者客戶端連接(bindService)到它。當內存不足時,需要保持,擁有service的進程具有較高的優先級。
    1. 如果service正在調用onCreate,onStartCommand或者onDestory方法,那么用于當前service的進程則變為前臺進程以避免被killed。
    2. 如果當前service已經被啟動(start),擁有它的進程則比那些用戶可見的進程優先級低一些,但是比那些不可見的進程更重要,這就意味著service一般不會被killed.
    3. 如果客戶端已經連接到service (bindService),那么擁有Service的進程則擁有最高的優先級,可以認為service是可見的。
    4. 如果service可以使用startForeground(int, Notification)方法來將service設置為前臺狀態,那么系統就認為是對用戶可見的,并不會在內存不足時killed。
    5. 如果有其他的應用組件作為Service,Activity等運行在相同的進程中,那么將會增加該進程的重要性。

(2)
方法一:onStartCommand中返回START_STICKY,如果內存不足被殺死,那么等內存足夠是系統會自動重啟Service;
方法二:提升service優先級,只是降低了被殺死的概率,但如果被殺死不會重啟;
方法三:Android中的進程是托管的,當系統進程空間緊張的時候,會依照優先級自動進行進程的回收。Android將進程分為6個等級,它們按優先級順序由高到低依次是:

1.前臺進程( FOREGROUND_APP)

2.可視進程(VISIBLE_APP )

  1. 次要服務進程(SECONDARY_SERVER )

4.后臺進程 (HIDDEN_APP)

5.內容供應節點(CONTENT_PROVIDER)

6.空進程(EMPTY_APP)

當service運行在低內存的環境時,將會kill掉一些存在的進程。因此進程的優先級將會很重要,可以使用startForeground將service放到前臺狀態。這樣在低內存時被kill的幾率會低一些。
白色方法:放一個可見的Notification,使用startForeground,如網易云音樂。
灰色方法:它是利用系統的漏洞來啟動一個前臺的Service進程,與普通的啟動方式區別在于,它不會在系統通知欄處出現一個Notification,看起來就如同運行著一個后臺Service進程一樣。這樣做帶來的好處就是,用戶無法察覺到你運行著一個前臺進程(因為看不到Notification),但你的進程優先級又是高于普通后臺進程的。
方法四:監聽鎖屏事件或者主Activity被關閉時,顯示一個1像素的透明Activity,讓進程成為前臺進程。
方法五:守護進程(Native層或者Java層),互相監聽。


  1. 廣播的兩種注冊方法,有什么區別。
    (1)兩種注冊方法
    BroadcastReceiver分為兩類:
    ? 靜態廣播接收者:通過AndroidManifest.xml的標簽來申明的BroadcastReceiver。
    ? 動態廣播接收者:通過AMS.registerReceiver()方式注冊的BroadcastReceiver,動態注冊更為靈活,可在不需要時通過unregisterReceiver()取消注冊。
    (2)區別
    1)靜態注冊:在AndroidManifest.xml注冊,android不能自動銷毀廣播接收器,也就是說當應用程序關閉后,還是會接收廣播。
    2)動態注冊:在代碼中通過registerReceiver()手工注冊.當程序關閉時,該接收器也會隨之銷毀。當然,也可手工調用unregisterReceiver()進行銷毀。

  1. Intent的使用方法,可以傳遞哪些數據類型。
    (1)使用方法
    顯示Intent比較簡單,傳入類即可,不再贅述。隱式Intent則需要能夠匹配目標組件IntentFilter所設置的過濾信息,不匹配則無法進行調用。IntentFilter中的過濾信息有action,category,data。只有這三個都匹配成功才算完全匹配,只有完全匹配才能成功啟動。并且一個Activity可以有多個IntentFilter,只需要匹配成功任何一組就可以啟動。
    1、action的匹配規則
    action的要求是必須存在,action也是我們創建Intent是傳入構造函數的值。action只需要與過濾規則中的任意一個action相同即可,字符串必須完全一樣(大小寫也要一致)。
    2、category的匹配規則
    也是只需要與其中一個category相同即可,如果不手動添加則默認為DEFAULT,那么想要啟動成功IntentFilter中必須要加DEFAULT。
    3、data匹配規則
    data的匹配規則與action類似,如果IntentFilter中定義了data,那么Intent也必須定義可以匹配的data。data由兩部分組成,mimeType與URI,mimeType指的是媒體類型,比如image/jpeg等,而URI的組成就是它所定義的結構,分為scheme,host,port以及路徑信息。只有mimeType相同并且URI匹配才行,而URI本身是有默認值的,默認值為content和file。

(2)可以傳遞的數據類型
1.基本類型
2.可序列化的類型(自定義類型需要實現序列化接口)


  1. ContentProvider使用方法。
    ContentProvider主要是用于給外界提供數據訪問的方式,用于在應用間傳遞數據。使用方式并不復雜,只需要根據需求實現CRUD操作,然后注冊在mainfest中即可。主要注意點如下:
    (1)如果要通過uri訪問不同數據,要做uri的解析工作;
    (2)對于更新操作,要使用同步機制防止并發問題
    (3)權限控制、防止sql注入等技術

  1. Thread、AsycTask、IntentService的使用場景與特點。
    答案:
    (1)AsyncTask
    介紹:
    AsyncTask是一種輕量級的異步任務類,可以在后臺線程池中執行后臺的任務,然后把執行的進度和最終的結果傳遞給主線程并在主線程中更新UI。從實現上來說,AsyncTask封裝了Thread和Handler。但它并不適合特別耗時的任務,對于特別耗時的任務應該使用線程池。
    它是一個泛型抽象類,Params表示參數的類型,Progress表示后臺任務進度的類型,而Result表示結果的返回類。

使用特點:
(1)它必須在主線程中創建,execute方法必須在主線程中調用
(2)execute方法只能執行一次,雖然可以傳很多個參數(任務)

工作原理:
AsyncTask實際上是對線程池和Handler進行了封裝。
(1)任務執行:
3.0之前,AsyncTask是并行執行的,而3.0之后就變為了串行執行,并且開發者可以選擇進行并行執行。原理是什么呢?實際上它內部有兩個線程池,sDefaultExecutor是一個自己實現的串行的線程池,它是static的,說明一個進程內的所有任務都是它來執行,它的任務很簡單,就是把任務放進一個隊列中,然后提醒另一個并行的線程池THREAD_POOL_EXECUTOR來取出執行,如果有可取的并且當前沒有任務在執行就會被這個并行的線程池來執行。如果有任務在執行自然不會執行,當這個任務執行完之后又會重新提醒并行的線程池THREAD_POOL_EXECUTOR來取出隊列中的任務進行執行。所以從這個原理我們看出來它是串行執行的,原因就是老版本是串行的并且很多代碼依賴于這個邏輯。
(2)任務結果分發
它的內部有一個static的handler,所以這也是它必須在UI線程中進行初始化的原因,這樣可以保證Handler被正常的初始化。當任務執行完成后,就會將結果發送給Handler,使得其在主線程被執行。

(2)IntentService
介紹:
僅僅是一個封裝了HandlerThread的Service而已,由于Service正常來說也是執行在主線程的,所以不能執行耗時的操作。而IntentService在內部維護有個HandlerThread,它擁有自身的Handler,對應于HandlerThread的Looper。當收到一個Intent時,它將Intent包裝到Message中直接發送給Handler來處理,從而避免了在主線程中進行操作。

使用:
重寫onHandleIntent,在其中處理Intent,并且這個是不在主線程中運行的。


  1. 五種布局: FrameLayout 、 LinearLayout 、 AbsoluteLayout 、 RelativeLayout 、 TableLayout 各自特點及繪制效率對比。

  1. Android的數據存儲形式。
    (1)使用SharedPreferences存儲數據
    特點:使用簡單,應用內數據共享,但只支持基本數據類型。
    (2)使用文件存儲
    (3)SQLite數據庫存儲
    (4)使用ContentProvider存儲數據(原理還是123中的一種,只是可以對外共享)

  1. Sqlite的基本操作。

  2. Android中的MVC模式以及與MVP的對比。
    (1)Android中的MVC模式
    傳統的MVC如下圖所示:


    image.png

當用戶出發事件的時候,view層會發送指令到controller層,接著controller去通知model層更新數據,model層更新完數據以后直接顯示在view層上,這就是MVC的工作原理。
對于原生的Android項目來說,layout.xml里面的xml文件就對應于MVC的view層,里面都是一些view的布局代碼,而各種java bean,還有一些類似repository類就對應于model層,至于controller層嘛,當然就是各種activity咯。比如你的界面有一個按鈕,按下這個按鈕去網絡上下載一個文件,這個按鈕是view層的,是使用xml來寫的,而那些和網絡連接相關的代碼寫在其他類里,比如你可以寫一個專門的networkHelper類,這個就是model層,那怎么連接這兩層呢?是通過button.setOnClickListener()這個函數,這個函數就寫在了activity中,對應于controller層。相較于傳統的MVC模式,model層應該要通知View來更新數據才對,而Android中由于View層的控制能力實在太弱,通知view的代碼全部放在了activity中,導致activity既算controller又算view,這樣activity的代碼行數太多,維護起來太過于麻煩。MVC還有一個重要的缺陷,大家看上面那幅圖,view層和model層是相互可知的,這意味著兩層之間存在耦合,耦合對于一個大型程序來說是非常致命的,因為這表示開發,測試,維護都需要花大量的精力。

(2)Android中的MVP模式


image.png
  1. Merge、ViewStub的作用。
  1. Json有什么優劣勢。

  1. 動畫有哪兩類,各有什么特點?

(1)View動畫(補間動畫)
View動畫的作用對象是View本身,支持四種動畫效果,分別是平移動畫、縮放動畫、旋轉動畫和透明度動畫。除了這四種,幀動畫也屬于View動畫,是一種特殊的View動畫。View動畫可以使用xml或者代碼來創建,使用set就可以把多個動畫組合在一起,調用startAnimation來進行動畫的調用。而幀動畫就好比是把動畫分為許多幀,使用xml來定義一個AnimationDrawable來進行幀動畫的使用,幀動畫的問題時圖片太多時——OOM。
View動畫的常見作用就是ListView的item的動畫,以及Activity之間變換的動畫,都是有View動畫實現的。而針對View動畫的自定義較難,涉及到矩陣變換等知識。

(2)屬性動畫
屬性動畫相較于View,更加的靈活和多樣。屬性動畫完成的效果其實就是在一個事件間隔內完成對象從一個屬性值到另一個屬性值的改變,效果更佳更自由。
1、使用注意點
屬性動畫要求提供屬性的get和set方法,可是如果一個控件沒有怎么辦?最簡單的方法就是使用包裝類,繼承自原始類并重寫相對應屬性的set和get方法,這是最簡單的一種實現方式。
2、原理分析
ObjectAnimator實際上繼承自ValueAnimator,主要細節如下:
(1)屬性值的設置:使用的是反射來調用,進行屬性值的獲取和設置,插值器和估值器的作用就在設置之前進行計算,用來確定屬性值。
(2)動畫效果類似于我們用post實現動畫一樣,animationHandler實際上是一個Runnable對象,存儲在當前線程的ThreadLocal中,start的時候開始執行scheduleAnimation函數, animationHandler一個線程只有一個,用于執行此線程的所有動畫。而一個動畫過程則是以ValueAnimator的形式存儲在animationHandler自己的一個隊列Animations中的。當對動畫完成了設置只有調用animationHandler的start函數,它里面使用了native的方法來保證UI系統每幀都會執行自己的run函數一次(發送給Looper執行run),run中它取出自己的隊列Animations中的ValueAnimator并且執行其中的doAnimationFrame。這就是真正執行動畫效果的地方,并且run中有一個FrameTime是當前動畫運行的時間,由native函數計算。
(3)doAnimationFrame的作用:它將傳入的FrameTime和mStartTime進行比較,因為有可能還未開始執行。之后根據他們的差值,加上插值器和估值器的使用來計算當前應該得到的值,之后利用反射來進行動畫的變化調用。


  1. Handler、Looper消息隊列模型,各部分的作用。
    答案:
    一、消息機制的角色分析
    首先我們介紹一下消息機制的幾位主人公,正是有它們的通力合作,消息機制才能正常的運行:
    1、Handler:處理器,負責的內容是消息的發送和具體處理流程,一般使用時由開發者重寫handleMessage函數,根據自定義的不同message做不同的UI更新操作;
    2、Message:消息對象,代表一個消息實體,存放消息的基本內容;
    3、MessageQueue:消息隊列,按順序存放消息實體,由單鏈表實現,被Looper(4)所使用(一個Looper具有一個消息隊列);
    4、Looper:循環器,也是我們的消息機制的主角,一個線程至多具有一個并且與線程一一對應(如何理解等會來說),負責的內容是不斷從消息隊列取出放入的消息并且交由消息對應的Handler進行處理(Handler的具體handleMessage操作);
    5、ThreadLocal:線程本地數據,是一個線程內部的數據存儲類,為每個線程存儲屬于自己的獨立的數據。
    二、消息機制總覽
    我們來用最簡短的語言說明一下消息循環的整個過程,有個整體性的認識,之后再進行逐一的進行源碼分析。
    1、首先,我們知道ThreadLocal是線程內部的數據存儲類,一個線程對應一個自己獨一無二的數據,而我們的主角Looper就是這樣一個對象,每個線程都可以有自己獨一無二的Looper,而Looper自己具有一個屬于自己的MessageQueue,它不斷地從MessageQueue中取Message(注意,Looper的代碼是運行在自己對應的線程中的),如果MessageQueue中沒有消息,Looper只會不斷地循環嘗試取消息(阻塞)。
    2、這時,我們在主線程創建了Handler對象,它需要它所在的線程(這里是主線程)所擁有的Looper對象(也就是說,沒有Looper的線程創建不了Handler,后面我們也會看到具體代碼),一旦我們用Handler發送了消息(可以在別的線程中,這里假設在某個子線程中),Handler就會把這個消息放入自己擁有的Looper對象的屬于這個Looper對象的MessageQueue中(這句有點拗口,就是放入Looper的MessageQueue中)。
    3、我們已經知道Looper會不斷地嘗試從自己的MessageQueue中取出消息,我們剛剛放入的消息立刻被Looper取出來了,它得到了消息就執行了發出消息的Handler(也就是2過程我們所創建的)的消息處理函數handleMessage,我們編寫的UI更新操作就在Looper對象的代碼中執行了,而我們剛才也說了這個Looper運行在我們創建Handler的線程中的,也就是主線程(UI線程),那這樣一來就達到了我們的目標,成功的把代碼執行從子線程切換到了主線程中,這個過程我們也就有個總覽了,
    底層Android的消息機制見博客。http://blog.csdn.net/ll530304349/article/details/52959415
  1. 怎樣退出終止App。
    建立一個基類Activity,在Activity創建時添加到全局表中,在Activity銷毀時移除全局表,調用全局退出時,對全局表Activity進行全部退出,達到完全退出的效果。
  1. Assets目錄與res目錄的區別。
    assets目錄與res下的raw、drawable目錄一樣,也可用來存放資源文件,但它們三者有區別,對比總結如下表:
image

(1)res/raw和asset中的文件不會被編譯成2進制文件,而是原樣復制到設備上,可以用文件流來讀取原始文件,所以可以用來存放通用的靜態文件,比如視頻等;
(2)與res/raw不同點在于,Assets支持任意深度的子目錄,這是它們的主要區別。這些文件不會生成任何資源ID,必須使用/assets開始(不包含它)的相對路徑名,而res中的所有文件都會生成索引來直接訪問。

  1. Android怎么加速啟動Activity。
  1. Android內存優化方法:ListView優化,及時關閉資源,圖片緩存等等。
    (1)Bitmap的高效加載
    核心是利用BitmapFactory加載一個圖片時(從文件系統、資源、輸入流以及字節數組)中加載一個Bitmap對象的時候,選擇合適的采樣率進行加載,即Options參數的采樣率參數對要加載的圖片進行縮放,變成合適ImageView的大小的圖片。縮放率是1/采樣率的平方。具體做法如下:
    第一:首先設置Options的inJustDecodeBounds為true并加載圖片,這樣只會獲取圖片的參數(長寬)
    第二:根據需要的長寬對圖片的長寬不停做/2操作,計算合適的采樣率
    第三:根據新的采樣率,重新加載圖片

(2)ListView(RecyclerView)的優化
1、ListView的基礎優化方式
(1)優化加載布局——convertView
通過在getView方法中使用convertView從而來避免View的重復加載,原理是復用已經離開屏幕的View,避免了View的重新加載。
(2)優化加載控件——ViewHolder
通過給復用的view設置Tag,實際上就是避免了重新find控件的過程,將控件的引用提前設置給ViewHolder,讓其持有,這樣重新設置數據復用是的速度更快。

2、ListView的加載優化方式
(1)加載圖片時使用壓縮方式——Bitmap的高效加載
(2)異步加載過程
(3)緩存加載,利用緩存避免重復加載
優化詳情見優化方法整理。

  1. Android中弱引用與軟引用的應用場景。
    (1)弱引用
    主要作用:防止內存泄漏。
    使用場景:全局Map用于保存某種映射的時候一定一定使用弱引用來保存對象,因為全局變量一般是static的,它的聲明周期一定長于單個對象,如果用弱引用保存對象,當對象被回收時,如果使用強引用,對象就會發生內存泄漏問題。
    (2)軟引用
    主要作用:緩存
    使用場景:對于Bitmap的加載,非常耗費時間,我們希望把加載過的Bitmap做緩存來節省加載時間,可是Bitmap非常吃內存,我們又不希望發生OOM的問題,所以應該使用軟引用來做緩存,這樣在系統內存不足時,此部分內存又會重新被回收,避免OOM的問題、

  2. Bitmap的四種屬性,與每種屬性隊形的大小。

  1. View與View Group分類。自定義View過程:onMeasure()、onLayout()、onDraw()。
    View的總體繪制過程:
    當Activity對象被創建完成,會將DecorView添加到Window中(顯示),同時創建ViewRoot的實現對象ViewRootImpl與之關聯。ViewRootImpl會調用performTraversals來進行View的繪制過程。經過measure,layout,draw三個流程才能完成一個View的繪制過程,分別是用于測量寬、高;確定在父容器中的位置;繪制在屏幕上三個過程。而measure方法會調用onMeasure函數,這其中又會調用子元素的measure函數,如此反復就能完成整個View樹的遍歷過程。其他兩個流程也同樣如此。
    measure決定了View的寬和高,測量之后就可以根據getMeasuredWidth和getMeasuredHeight來獲取View測量后的寬和高,幾乎等于最終的寬和高,但有例外;layout過程決定了View四個頂點的位置和實際的寬和高,完成之后可以根據getTop,getBottom,getLeft,getRight來獲得四個頂點的位置,并且可以使用getWidth和getHeight來獲取實際的寬和高;draw過程就決定了View的顯示,完成draw才能真正顯示出來。

1.測量Measure過程:
MeasureSpec是測量規格,它是系統將View的參數根據父容器的規則轉換而成的,之后根據它來測量出View的寬和高。它實際上是一個32位的int值,高二位表示SpecMode,就是測量的模式;低30位表示SpecSize,即在某種測量模式下的規格大小。
MeausureSpec有三種模式,常用的由兩種:EXACTLY和AT_MOST。EXACTLY表示父容器已經檢測出View所需要的精確大小(即父容器根據View的參數已經可以確定View的大小了),這時View的最終大小就是SpecSize的值,它對應于View參數中的match_parent和具體大小數值這兩種模式;AT_MOST表示父容器指定了一個可用大小的數值,記錄在SpecSize中,View的大小不能大于它,但具體的值還是看View的具體實現。它對應于View參數中的wrap_content。
DecorView(頂級View)的測量由窗口的大小和自身的LayoutParams決定,具體邏輯由getRootMeasureSpec決定,如果是具體值或者是match_parent,就是精確模式;如果是wrap_content就是最大模式;普通View的measure實際上是由父元素進行調用的(遍歷),父元素調用child的measure之前使用getChildMeasureSpec來轉換得到子元素的MeasureSpec(具體代碼:藝術探索P180-181),總結而來就是與自身的參數以及父元素的SpecMode有關:1、如果View的參數是具體值,那么不管父元素的Mode是什么,子元素的Mode都是精確模式并且大小就是參數的大小;2、如果View的參數是match_parent,如果父元素的mode是精確模式那么View也是精確模式并且大小是父元素剩余的大小;如果父元素的mode是最大模式,那么View也是最大模式;3、如果View的參數是wrap_content,那么View的模式一定是最大化模式,并且不能超過父容器的剩余空間。
View的measure過程:
View自身的onMeasure方法就是把MeasureSpec的Size設為最終的測量結果,這樣的測量問題就是match_parent和wrap_content是一樣的結果(因為wrap_content的Size是最大可用Size),所以如果自定義View直接繼承自View,就需要對wrap_content進行處理,ImageView等都對wrap_content進行了特殊處理。
ViewGroup的measure過程:
ViewGroup不同于View,它是一個抽象類,沒有實現onMeasure方法(因為具體的Layout布局特性各不相同),但它measure時會遍歷children調用measureChild,執行getChildMeasureSpec進行子元素的MeasureSpec創建,創建過程之前已經了解了,就是利用自身的Spec與子元素參數進行創建。

2.確定位置Layout過程:
Layout的作用是ViewGroup來確定子元素的位置,當ViewGroup的位置被確定了之后,它就在自己的onLayout函數中遍歷所有的子元素并調用其layout方法,確定子元素的位置,對于子元素View,layout中又會調用其onLayout函數。View和ViewGroup中都沒有真正實現onLayout方法。但View和ViewGroup的layout方法是一致的,作用都是用于確定自己的位置,layout方法會調用setFrame方法來設定View的四個頂點的位置,即初始化mLeft,mRight,mTop,mBottom四個值,這樣就確定了View在父元素中的位置。
問題:getMeasuredHeight和getHeight有什么區別(同Width)?
在measure之后就可以使用getMeasuredHeight來進行獲取測量的寬和高,而layout過程是晚于measure的,ViewGroup的setChildFrame會調用child的layout來確定child的真實位置,源代碼中也可以看出layout的bottom和top就是利用getMeasuredHeight和getMeasuredWidth來計算的,所以說如果child的layout不重寫,那么就是一樣的!如果child的layout函數被重寫,就會有不一樣的結果。

3.繪制draw過程:
OnDraw中進行繪制自己的操作。使用canvas進行繪制等等,簡單來說就是利用代碼畫自己需要的圖形。

繪制過程中的旋轉以及save和restore
Android中的旋轉rotate旋轉的是坐標系,也就是說旋轉畫布實際上畫布本身的內容是不動的,不會直接把畫布上已經存在的內容進行移動,只是旋轉坐標系,這樣旋轉之后的操作全部是針對這個新的坐標系的。
save就是保存當前的的坐標系,之后再調用restore時,坐標系復原到save保存的狀態,這兩個函數restore的次數不能大于save,否則會引發異常。

  1. Touch事件分發機制以及滑動沖突的解決。
    答案:
    一、View的事件分發機制
    事件分發機制傳遞的就是MotionEvent,也就是點擊事件,這個傳遞過程就是分發的過程。
    (1)點擊事件的傳遞規則
    三大函數:
    public boolean dispatchTouchEvent(MotionEvent ev)
    這個函數用于進行事件的分發,如果這個時間能夠傳遞給當前的View,那么這個方法一定會調用,返回的結果表示是否消耗當前事件,返回的結果受onInterceptTouchEvent和下級View的影響。
    public boolean onInterceptTouchEvent(MotionEvent ev)
    這個函數內部調用,用于判斷是否攔截某個事件,如果當前View攔截了某個事件,那么同一事件序列中,此方法不會被再次調用。
    public boolean onTouchEvent(MotionEvent ev)
    在dispatchTouchEvent中調用,用于處理點擊事件,其返回結果表示是否消耗當前事件,如果不消耗,那么同一事件序列中,當前View無法再接收到事件。

偽代碼:

public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume = false;
    if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
} else {
    consume = child. dispatchTouchEvent(ev);
}

  return consume;
}

通過上述偽代碼,我們可以大致得出傳遞的規則:
(1)對于一個根ViewGroup來說,點擊事件產生后,首先會傳遞給它自己,如果它的onInterceptTouchEvent返回true,那么就表示它要攔截當前事件,那么它的onTouchEvent函數就會被調用;如果返回false,那么就傳遞給子元素,直到事件被處理。
(2)當一個View需要進行事件處理時,如果它設置了OnTouchListener,那么它的onTouch方法就會被調用,這時事件如何處理還要看onTouch的返回值,如果返回false,那么當前View的onTouchEvent就會被調用;如果返回true,那么onTouchEvent方法將不會調用!!!!!!。由此可見,OnTouchListener的優先級高于onTouchEvent。在onTouchEvent方法中,如果當前設置有OnClickListener,那么它的onClick會被調用,其優先級最低,處于調用的末端。
(3)如果一個事件傳遞到View,如果此View的onTouchEvent返回false,就是不消耗事件,那么此View的父容器的onTouchEvent就會被調用,也就是說如果事件最終沒有View處理,那么處理的人就是Activity,也就是責任鏈模式。

一些結論:
(1)同一個事件序列指的是從手指接觸屏幕開始,到手指離開屏幕的過程,也就是DOWN—MOVE…MOVE—UP,這是一個事件序列。
(2)同一個事件序列只能被同一個View所消耗,因為一旦一個View攔截了某個事件,那么同一序列內的所有事件都會直接交給他處理。但是要注意,如果事件在之前又被別人攔截,根本不交給它處理的情況也會發生——事件攔截。
(3)某個View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不會再交給他處理,即父元素的onTouchEvent會被調用,交給父元素來處理(責任鏈模式)。
如果某個View不處理除了ACTION_DOWN之外的其他事件,那么這個點擊事件就會消失,并且當前View可以持續接收到后續事件(無論你選擇不選擇處理),最終消失的會被Activity處理。
(4)ViewGroup的onInterceptTouchEvent方法默認返回false,即不攔截任何事件,而View沒有onInterceptTouchEvent函數,即不能選擇是否攔截,必須攔截,但可以不處理。
(5)View的onTouchEvent默認都會消耗事件,返回true,除非它是不可點擊的。
(6)對于onTouch和onClick的總結
規律(總結):
(1)首先沒有設置OnClickListener的情況下,onTouch的返回值表示的就是View對點擊事件是否消耗,如果在DOWN事件傳遞過來時返回false,那么剩下的MOVE直到UP的事件都不會被onTouch接收到;如果在DOWN事件返回true,那么剩下的直到UP的事件都會接受到,無論你之后的返回值。
(2)在同時設置了OnTouchListener與OnClickListener之后,情況就有些復雜了:
情況1:如果onTouch在DOWN時返回了true,那么onTouch就和(1)一樣收到剩下的所有事件,但onClick就不會被執行;
情況2:如果onTouch在DOWN時返回了false,與(1)不同的是,onTouch盡管在DOWN時返回了false,但之后的所有事件仍能接受到,并且onClick會在之后被調用。

public boolean dispatchTouchEvent(MotionEvent event){  
    ... ...  
    if(onFilterTouchEventForSecurity(event)){  
        ListenerInfo li = mListenerInfo;  
        if(li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED  
            && li.mOnTouchListener.onTouch(this, event)) {  //(1)onTouch調用
            return true;  
        }  
        if(onTouchEvent(event)){  //(2)onTouchEvent調用
            return true;  
        }  
    }  
    ... ...  
    return false;  
}  

分析:
(1)如果沒有設置OnClickListener,只設置了OnTouchListener,那么在代碼(1)處就會調用onTouch,如果DOWN事件時返回了true,那么剩下的事件都會交由此View進行處理;如果返回了false,那么就會執行代碼(2)處的onTouchEvent函數,如果設置了OnClickListener,就會在其中進行調用,如果沒有設置,dispatchTouchEvent就會返回false,那么剩下的事件都不會交由此View進行處理;
(2)如果同時設置了OnTouchListener與OnClickListener,那么我們再按上面的兩種情況進行分析:
情況1:onTouch在DOWN時返回了true,那么代碼(1)處就得到了真的結果,直接就返回了true,可以知道后面代碼(2)處的onTouchEvent函數不會被執行,那么自然你的OnClickListener就不起作用了,onClick就不會被執行;
情況2:onTouch在DOWN時返回了false,那么當DOWN事件傳遞來的時候,代碼(1)處就不會得到真的結果,也就是說onTouch中你表示自己不會處理這個事件序列了,后面代碼(2)處的onTouchEvent函數就會得到執行,而如果你設置了OnClickListener,View就會處于CLICKABLE狀態,那么onTouchEvent函數就會返回true,又表示你可以處理這個點擊事件序列了,dispatchTouchEvent就會返回true,那么這時后面的事件由于DOWN時返回true,就會統統交由此View進行處理,自然你的onTouch中也能夠監聽到后面的所有事件!這樣上面的情況就能夠得到解釋了。

二、滑動沖突的解決方法
(1)滑動沖突的類型
滑動沖突分為三種類型,第一類是外部和內部滑動方向不一致,第二類是外部和內部滑動方向一致,第三類是前兩種嵌套的模式。
處理這三種類型的規則分為兩類,對于第一種類型,我們可以根據滑動方向來處理,符合處理方向的分配給對應的控件;對于2、3種類型,必須根據業務上的區別來處理,某種狀態的處理時間分發給對應的控件來處理。
(2)滑動沖突的解決方式
解決方式一:外部攔截法
外部攔截法指點擊事件首先都會經過父容器的攔截處理,父容器如果需要此事件就進行攔截,如果不需要此事件就不進行攔截,這樣就可以解決滑動沖突問題。外部攔截法主要就是重寫父容器的onInterceptTouchEvent方法,但是要注意,父容器攔截不能在ACTION_DOWN中返回true,否則之后的所有事件序列都會交給它處理,無論返回什么,因為不會再調用它的onInterceptTouchEvent函數了。所以父控件應該在ACTION_MOVE中選擇是否攔截。但是這種攔截的問題是,如果攔截了,那么子控件的onClick事件將無法再出發了。
偽代碼如下:

public boolean onInterceptTouchEvent(MotionEvent ev) {
    boolean intercepted = false;
    switch (ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            intercepted = false;
            break;
        case MotionEvent.ACTION_MOVE:
            if(父控件需要處理){
                intercepted = true;
            } else{
                intercepted = false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercepted = false;
            break;
    }

    return intercepted;
}

解決方法二:內部攔截法
內部攔截法指的是父容器不攔截任何事件,所有事件全部傳遞給子元素,如果子元素需要就進行消耗,否則交由父容器進行處理。這種方式需要配合ViewGroup的FLAG_DISALLOW_INTERCEPT標志位來使用。設置此標志為可以通過requestDisallowIntercept TouchEvent函數來設置,如果設置了此標志位,那么ViewGroup就無法攔截除了ACTION_DOWN之外的任何事件。這樣首先我們保證ViewGroup的onInterceptTouchEvent方法除了DOWN其他都返回true,DOWN返回false,這樣保證了不會攔截DOWN事件,交給它的子View進行處理;重寫View的dispatchTouchEvent函數,在DOWN中設置parent.requestDisallowInterceptTouchEvent(true),這樣父控件在默認的情況下DOWN之后的所有事件它都攔截不到,交由子View來處理,View在MOVE中判斷父控件需要時,調用parent.requestDisallow InterceptTouchEvent(false),這樣父控件的攔截又起作用了,相應的事件交給了父控件進行處理。偽代碼如下:
父控件中:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    int action = ev.getAction();
    if(action == MotionEvent.ACTION_DOWN){
        return false;
    } else {
        return true;
    }
}

子View中:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    
    switch (ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        case MotionEvent.ACTION_MOVE:
            if(父控件需要此點擊事件){
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
        case MotionEvent.ACTION_UP:
            break;
    }
}

滑動沖突處理案例:下拉刷新實現原理。

  1. Android長連接,怎么處理心跳機制。
  1. Zygote的啟動過程。
  1. Android IPC:Binder原理。
    見Binder整理文檔。
  1. 你用過什么框架,是否看過源碼,是否知道底層原理。
    見框架整理文檔。
  1. Android5.0、6.0新特性。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容