1、Activity、Dialog、PopupWindow、Toast 與Window的關系
簡單的從創(chuàng)建方式的角度來說一說:
Activity。在Activity創(chuàng)建過程中所創(chuàng)建的PhoneWindow,是層級最小的Window,叫做應用Window,層級范圍1-99。(層級范圍大的Window可以覆蓋層級小的Window)
Dialog。Dialog的顯示過程和Activity基本相同,也是創(chuàng)建了PhoneWindow,初始化DecorView,并將Dialog的視圖添加到DecorView中,最終通過addView顯示出來。
但是有一點不同的是,Dialog的Window并不是應用窗口,而是子窗口,層級范圍1000-1999,子Window的顯示必須依附于應用窗口,也會覆蓋應用級Window。這也就是為什么Dialog傳入的上下文必須為Activity的Context了。
PopupWindow。PopupWindow的顯示就有所不同了,它沒有創(chuàng)建PhoneWindow,而是直接創(chuàng)建了一個View(PopupDecorView),然后通過WindowManager的addView方法顯示出來了。
沒有創(chuàng)建PhoneWindow,是不是就跟Window沒關系了呢?
并不是,其實只要是調用了WindowManager的addView方法,那就是創(chuàng)建了Window,跟你有沒有創(chuàng)建PhoneWindow無關。View就是Window的表現形式,只不過PhoneWindow的存在讓Window形象更立體了一些。
所以PopupWindow也是通過Window展示出來的,而它的Window層級屬于子Window,必須依附與應用窗口。
Toast。Toast和PopupWindow比較像,沒有新建PhoneWindow,直接通過addView方法顯示View即可。不同的是它屬于系統(tǒng)級Window,層級范圍2000-2999,所以無須依附于Activity。
四個比較下來,可以發(fā)現,只要想顯示View,就會涉及到WindowManager的addView方法,也就用到了Window這個概念,然后會根據不同的分層依次顯示覆蓋到界面上。
不同的是,Activity和Dialog涉及到了布局比較復雜,還會有布局主題等元素,所以用到了PhoneWindow進行一個解耦,幫助他們管理View。而PopupWindow和Toast結構比較簡單,所以直接新建一個類似DecorView的View,通過addView顯示到界面。
2、onSaveInstanceState()什么時候會被調用呢?
概括的講,onSaveInstanceState 這個方法會在activity 將要被kill之前被調用以保存每個實例的狀態(tài),以保證在將來的某個時刻回來時可以恢復到原來的狀態(tài),但和activity 的生命周期方法onStop 和 onPause 不一樣,與兩者并沒有絕對的先后調用順序,或者說并非所有場景都會調用onSaveInstanceState 方法。
那么onSaveInstanceState 方法何時會被調用呢,或者這么問,什么時候activity 會被系統(tǒng)kill 掉呢?
有以下幾種比較常見的場景:
(1)用戶主動按下home 鍵,系統(tǒng)不能確認activity 是否會被銷毀,實際上此刻系統(tǒng)也無法預測將來的場景,比如說內存占用,應用運行情況等,所以系統(tǒng)會調用onSaveInstanceState保存activity狀態(tài) ;
(2)activity位于前臺,按下電源鍵,直接鎖屏;
(3)橫豎屏切換;
(4)activity B啟動后位于activity A之前,在某個時刻activity A因為系統(tǒng)回收資源的問題要被kill掉,A通過onSaveInstanceState保存狀態(tài)。
換句話說,onSaveInstanceState()的調用遵循一個重要原則,即當系統(tǒng)存在“未經你許可”時銷毀了我們的Activity,則onSaveInstanceState()會被系統(tǒng)調用,這是系統(tǒng)的職責,因為它必須要提供一個機會讓用戶保存數據。
3、Android 數據持久化之 SharedPreferences
Android之SharedPreferences內部原理淺析
剖析 SharedPreference apply 引起的 ANR 問題
總結:
sSharedPrefsCache 是一個 ArrayMap<String,ArrayMap<File,SharedPreferencesImpl>>,它會保存加載到內存中的 SharedPreferences 對象,ContextImpl 類中并沒有定義將 SharedPreferences 對象移除 sSharedPrefsCache 的方法,所以一旦加載到內存中,就會存在直至進程銷毀。相對的,也就是說,SP 對象一旦加載到內存,后面任何時間使用,都是從內存中獲取,不會再出現讀取磁盤的情況
SharedPreferences 和 Editor 都只是接口,真正的實現在 SharedPreferencesImpl 和 EditorImpl ,SharedPreferences 只能讀數據,它是在內存中進行的,Editor 則負責存數據和修改數據,分為內存操作和磁盤操作
獲取 SP 只能通過 ContextImpl#getSharedPerferences 來獲取,它里面首先通過 mSharedPrefsPaths 根據傳入的 name 拿到 File ,然后根據 File 從 ArrayMap<File, SharedPreferencesImpl> cache 里取出對應的 SharedPrederenceImpl 實例
SharedPreferencesImpl 實例化的時候會啟動子線程來讀取磁盤文件,但是在此之前如果通過 SharedPreferencesImpl#getXxx 或者 SharedPreferences.Editor 會阻塞 UI 線程,因為在從 SP 文件中讀取數據或者往 SP 文件中寫入數據的時候必須等待 SP 文件加載完
在 EditorImpl 中 putXxx 的時候,是通過 HashMap 來存儲數據,提交的時候分為 commit 和 apply,它們都會把修改先提交到內存中,然后在寫入磁盤中。只不過 apply 是異步寫磁盤,而 commit 可能是同步寫磁盤也可能是異步寫磁盤,在于前面是否還有寫磁盤任務。對于 apply 和 commit 的同步,是通過 CountDownLatch 來實現的,它是一個同步工具類,它允許一個線程或多個線程一致等待,直到其他線程的操作執(zhí)行完之后才執(zhí)行
SP 的讀寫操作是線程安全的,它對 mMap 的讀寫操作用的是同一把鎖,考慮到 SP 對象的生命周期與進程一致,一旦加載到內存中就不會再去讀取磁盤文件,所以只要保證內存中的狀態(tài)是一致的,就可以保證讀寫的一致性
注意事項以及優(yōu)化建議
強烈建議不要在 SP 里面存儲特別大的 key/value ,有助于減少卡頓 / ANR
請不要高頻的使用 apply,盡可能的批量提交;commit 直接在主線程操作,更要注意了
不要使用 MODE_MULTI_PROCESS
高頻寫操作的 key 與高頻讀操作的 key 可以適當的拆分文件,以減少同步鎖競爭
不要連續(xù)多次 edit,每次 edit 就是打開一次文件,應該獲取一次 edit,然后多次執(zhí)行 putXxx,減少內存波動,所以在封裝方法的時候要注意了
apply 在 QueueWork 維護的單線程池調用,雖然是異步的但是可能會阻塞 Service.onStop 和 Activity.onPause 方法,可能會導致 ANR
ANR 容易發(fā)生的地方:
sp.getXxx,首先會調用 awaitLoadedLocked 等待首次 sp 文件創(chuàng)建與讀取操作完成
sp.apply 雖然是異步的但是可能會在 Service Activity 等生命周期期間 mcr.writtenToDiskLatch.await() 等待過久
sp.commit 最終會調用 sp.writeToFile 方法,很耗時
ContextImpl.getSharedPreferences,主線程直接調用的話,如果 sp 文件很大處理時間也就會變成
4、Activity的啟動過程
應用啟動過程
Launcher通過Binder進程間通信機制通知AMS,它要啟動一個Activity
AMS通過Binder進程間通信機制通知Launcher進入Paused狀態(tài)
Launcher通過Binder進程間通信機制通知AMS,它已經準備就緒進入Paused狀態(tài),于是AMS就創(chuàng)建一個新的線程,用來啟動一個ActivityThread實例,即將要啟動的Activity就是在這個ActivityThread實例中運行
ActivityThread通過Binder進程間通信機制將一個ApplicationThread類型的Binder對象傳遞給AMS,以便以后AMS能夠通過這個Binder對象和它進行通信
AMS通過Binde進程間通信機制通知ActivityThread,現在一切準備就緒,它可以真正執(zhí)行Activity的啟動操作了
5、Service生命周期
startService() --> onCreate() --> onStartCommand() --> Service running --> onDestory()
bindService() --> onCreate() --> onBind() --> Service running --> onUnbind() --> onDestory()
onCreate():
系統(tǒng)在Service第一次創(chuàng)建時執(zhí)行此方法,來執(zhí)行只運行一次的初始化工作,如果service已經運行,這個方法不會調用。
onStartCommand():
每次客戶端調用startService()方法啟動該Service都會回調該方法(多次調用),一旦這個方法執(zhí)行,service就啟動并且在后臺長期運行,通過調用stopSelf()或stopService()來停止服務。
onBind():
當組件調用bindService()想要綁定到service時,系統(tǒng)調用此方法(一次調用),一旦綁定后,下次在調用bindService()不會回調該方法。在你的實現中,你必須提供一個返回一個IBinder來使客戶端能夠使用它與service通訊,你必須總是實現這個方法,但是如果你不允許綁定,那么你應返回null
onUnbind():
當前組件調用unbindService(),想要解除與service的綁定時系統(tǒng)調用此方法(一次調用,一旦解除綁定后,下次再調用unbindService()會拋異常)
onDestory():
系統(tǒng)在service不在被使用并且要銷毀的時候調用此方法(一次調用)。service應在此方法中釋放資源,比如線程,已注冊的監(jiān)聽器、接收器等等。
三種情況下Service的生命周期
-
startService / stopService
生命周期:onCreate --> onStartCommand --> onDestory
如果一個Service被某個Activity調用Context.startService 方法啟動,那么不管是否有Activity使用bindService綁定或unbindService解除綁定到該Service,該Service都在后臺運行,直到被調用stopService,或自身的stopSelf方法。當然如果系統(tǒng)資源不足,Android系統(tǒng)也可能結束服務,還有一種方法可以關閉服務,在設置中,通過應用 --> 找到自己應用 --> 停止。
注意:
第一次startService會觸發(fā)onCreate和onStartCommand,以后在服務運行過程中,每次startService都只會觸發(fā)onStartCommand
不論startService多少次,stopService一次就會停止服務
-
bindService / unbindService
生命周期:onCreate --> onBind --> onUnbind --> onDestory
如果一個Service在某個Activity中被調用bindService方法啟動,不論bindService被調用幾次,Service的onCreate方法只會執(zhí)行一次,同時onStartCommand方法始終不會調用。
當建立連接后,Service會一直運行,除非調用unbindService來解除綁定、斷開連接或調用該Service的Context不存在了(如Activity被finish --- 即通過bindService啟動的Service的生命周期依附于啟動它的Context),系統(tǒng)會在這時候自動停止該Service。
注意:
第一次bindService會觸發(fā)onCreate和inBind,以后在服務運行過程中,每次bindService都不會觸發(fā)任何回調
-
混合型
當一個Service再被啟動(startService)的同時又被綁定(bindService),該Service將會一直在后臺運行,不管調用幾次,onCreate方法始終只會調用一次,onStartCommand的調用次數與startService調用的次數一致(使用bindService方法不會調用onStartCommand)。同時,調用unBindService將不會停止Service,必須調用stopService或Service自身的stopSelf來停止服務。
三種情況下的應用場景
如果你只是想啟動一個后臺服務長期進行某項任務,那么使用startService便可以了。
如果你想與正在運行的Service取的聯(lián)系,那么有兩種方法,一種是使用broadcast,另外是使用bindService。前者的缺點是如果交流較為頻繁,容易造成性能上的問題,并且BroadcastReceiver本身執(zhí)行代碼的時間是很短的(也許執(zhí)行到一半,后面的代碼便不會執(zhí)行),而后者則沒有這些問題,因此我們肯定選擇使用bindService(這個時候便同時使用了startService和bindService了,這在Activity中更新Service的某些運行狀態(tài)是相當有用的)
如果你的服務只是公開了一個遠程接口,供連接上的客戶端(Android的Service是C/S架構)遠程調用執(zhí)行方法。這個時候你可以不讓服務一開始就運行,而只用bindService,這樣在第一次bindService的時候才會創(chuàng)建服務的實例運行它,這會節(jié)約很多系統(tǒng)資源,特別是如果你的服務是Remote Service,那么該效果會越明顯。
6、BroadcastReceiver
應用場景:
不同組件之間的通信(包括應用內 / 不同應用之間)
與Android系統(tǒng)在特定情況下的通信,如當電話呼入時,網絡可用時
多線程通信
實現原理
使用了觀察者模式:基于消息的發(fā)布/訂閱事件模型。
-
模型中有三個角色:消息訂閱者(廣播接收者)、消息發(fā)布者(廣播發(fā)布者)和消息中心(AMS,即Activity Manager Service)
[圖片上傳中...(image-100ae0-1663300893576-0)]
原理描述
-
廣播接收者通過Binder機制在AMS注冊
廣播發(fā)送者通過Binder機制向AMS發(fā)送廣播
AMS根據廣播發(fā)送者要求,在已注冊列表中,尋找合適的廣播接收者,尋找依據:IntentFilter / Permission
AMS將廣播發(fā)送到合適的廣播接收者相應的消息循環(huán)隊列
廣播接收者通過消息循環(huán)拿到此廣播,并回調onReceive()
注意:廣播發(fā)送者和廣播接收者的執(zhí)行是異步的,發(fā)出去的廣播不會關心有沒有接收者接收,也不確定接收者何時能接受到。
廣播的類型主要分為5類:
普通廣播(Normal Broadcast)
系統(tǒng)廣播(System Broadcast)
有序廣播(Ordered Broadcast)
粘性廣播(Sticky Broadcast)Android 5.0 & API 21中已經失效
App應用內廣播(Local Broadcast)
動態(tài)廣播最好在Activity的onResume()注冊,onPause()注銷,否則會導致內存泄漏,當然,重復注冊和重復注銷也不允許。
7、ContentProvider
作用
進程間進行數據交互&共享,即跨進程通信
原理
ContentProvider的底層采用Android中的Binder機制
統(tǒng)一資源標識符(RUI)
作用:唯一標識ContentProvider & 其中的數據,外界進程通過URI找到對應的ContentProvider & 其中的數據,再進行數據操作
具體使用:
URI分為系統(tǒng)預置 & 自定義,分別對應系統(tǒng)內置的數據(如通訊錄、日程表等等)和自定義數據庫。
8、Context
Android應用模型是基于組件的應用設計模式,組件的運行要有一個完整的Android工程環(huán)境。在這個工程環(huán)境下,Activity、Service等系統(tǒng)組件才能夠正常工作,而這些組件并不能采用普通的Java對象創(chuàng)建方式,new一下就能創(chuàng)建實例了,而是要有它們各自的上下文環(huán)境,也就是Context,Context是維持Android程序中各組件能夠正常工作的一個核心功能類。
如何生動形象的理解Context?
一個Android程序可以理解為一部電影,Activity、Service、BroadcastReceiver和ContentProvider這四大組件就好比戲了的四個主角,它們是劇組(系統(tǒng))一開始定好的,主角并不是大街上隨便拉個人(new 一個對象)都能演的。有了演員當然也得有攝像機拍攝啊,它們必須通過鏡頭(Context)才能將戲傳給觀眾,這也就正對應說四大組件必須工作在Context環(huán)境下。那么Button、TextView等等控件就相當于群演,顯然沒那么重用,隨便一個路人甲都能演(可以new一個對象),但是它們也必須在面對鏡頭(工作在Context環(huán)境下),所以Button mButtom = new Button(context) 是可以的。
[圖片上傳中...(image-df38f2-1663300893577-4)]
它有兩個具體實現類:ContextImpl和ContextWrapper。
其中ContextWrapper類,是一個包裝類而已,ContextWrapper構造函數中必須包含一個真正的Context引用,同時ContextWrapper中提供了attachBaseContext()用于給ContextWrapper對象指定真正的Context對象,調用ContextWrapper的方法都會被轉向其包含的真正的Context對象。ContextThemeWrapper類,其內部包含了與主題Theme相關的接口,這里所說的主題就是指在AndroidManifest,xml中通過android:theme為Application元素或者Activity元素指定的主題。當然,只有Activity才需要主題,Service是不需要主題的,所以Service直接繼承與ContextWrapper,Application同理。而ContextImpl類則真正實現了Context中的所有函數,應用程序中所調用的各種Context類的方法,其實現均來源于該類。Context得兩個子類分工明確,其中ContextImpl是Context的具體實現類,ContextWrapper是Context的包裝類。 Activity、Application、Service雖都繼承自ContextWrapper(Activity繼承自ContextWrapper的子類ContextThemeWrapper),但它們初始化的過程中都會創(chuàng)建ContextImpl對象,由ContextImpl實現Context中的方法。
一個應用程序有幾個Context?
在應用程序中Context的具體實現子類就是:Activity、Service和Application。那么Context數量=Activity數量+Service數量+1。那么為什么四大組件中只有Activity和Service持有Context呢?BroadcastReceiver和ContextPrivider并不是Context的子類,它們所持有的Context都是其他地方傳過去的,所以并不計入Context總數。
Context的作用域
雖然Context神通廣大,但并不是隨便拿到一個Context實例就可以為所欲為,它的使用還是有一些規(guī)則限制的。由于Context的具體實例是由ContextImpl類去實現的,因此在絕大多數場景下,Activity、Service和Application這三種類型的Context都是可以通用的。不過有幾種場景比較特殊,比如啟動Activity,還有彈出Dialog。出于安全原因的考慮,Android是不允許Activity或Dialog憑空出現的,一個Activity的啟動必須要建立在另一個Activity的基礎之上,也就是以此形成返回棧。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),因此在這種場景下,我們只能使用Activity類型的Context,否則將會報錯。
[圖片上傳中...(image-d795d0-1663300893576-2)]
從上圖我們可以發(fā)現Activity所持有的Context的作用域最廣,無所不能,因此Activity繼承至ContextThemeWrapper,而Application和Service繼承至ContextWrapper,很顯然ContextThemeWrapper在ContextWrapper的基礎上又做了一些操作使得Activity變得更強大。著重講一下不推薦使用的兩種情況:
-
如果我們用ApplicationContext去啟動一個LaunchMode為standard的Activity的時候會報錯:
android.util.AndroidRuntimeException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
這是因為非Activity類型的Context并沒有所謂的任務棧,所以待啟動的Activity就找不到棧了。解決這個問題的方法就是為待啟動的Activity指定FLAG_ACTIVITY_NEW_TASK標記位,這樣啟動的時候就為它創(chuàng)建一個新的任務棧,而此時Activity是以singleTask模式啟動的。所有這種用Application啟動Activity的方式都不推薦,Service同Application。
在Application和Service中去LayoutInflate也是合法的,但是會使用系統(tǒng)默認的主題樣式,如果你自定義了某些樣式可能不會被使用,這種方式也不推薦使用。
一句話總結:凡是跟UI相關的,都應該使用Activity作為Context來處理;其他的一些操作,Service、Activity、Application等實例都可以,當然了注意Context引用的持有,防止內存泄露。
如何獲取Context?
有四種方法:
View.getContext 返回當前View對象的Context對象,通常是當前正在展示的Activity對象。
Activity.getApplicationContext 獲取當前Activity所在的進程的Context對象,通常我們使用Context對象時,要優(yōu)先考慮這個全局的進程Context。
ContextWrapper.getBaseContext() 用來獲取一個ContextWrapper進行裝飾之前的Context,可以使用這個方法,這個方法在實際開發(fā)中使用的不多,也不建議使用。
Activity.this 返回當前Activity實例,如果是UI控件需要使用Activity作為Context對象,但是默認的Toast實際上使用ApplicationContext也可以。
getApplication()和getApplicationContext()的區(qū)別?
其內存地址是一樣的。Application本身就是一個Context,這里獲取getApplicationContext得到的結果就是Application本身的實例。getApplication方法的語義性很強,就是用來獲取Application實例的,但是這個方法只有在Activity和Service中才能調用的到。那么也許在絕大多數情況下我們都是在Activity或者Service中使用Application,但是如果在一些其他的場景,比如BroadcastReceiver中也想獲取Application實例,這時就可以借助getApplicationContext方法了。
9、Android APK編譯打包流程
[圖片上傳中...(image-e1bab6-1663300893576-1)]
AAPT(Android Asset Packaging Tools)工具會打包應用中的資源文件,如AndroidManifest.xml、layout布局中的xml等,并將xml文件編譯成二進制形式,當然assets文件夾中的文件不會被編譯,圖片以及raw文件夾中的資源也會保持原有的形態(tài),需要注意的是raw文件夾中的資源也會生成資源ID。AAPT編譯完成后會生成R.java文件。
AIDL工會將所有的aidl接口轉換為java接口。
所有的Java源代碼、R文件、接口都會編譯器編譯成.class文件。
Dex工具會將上述產生的.class文件以及第三方庫和其他class文件轉化為dex(Dalvik虛擬機可執(zhí)行文件)文件,dex文件最終會被打包進APK文件。
apkbuilder會把編譯后的資源和其他資源文件同dex文件一起打入APK中。
生成APK文件之后,,需要對其簽名才能安裝到設備上,平時測試都會使用debug keystore,當發(fā)布應用時必須使用release版的keystore對應用進行簽名。
如果對APK正式簽名,還需要使用zipalign工具對APK進行對齊操作,這樣做的好處是當應用運行時能提高速度,但是會相應的增加內存開銷。
總結:編譯 --> DEX --> 打包 --> 簽名和對齊
10、Window、Activity、DecorView以及ViewRoot之間的關系
Activity
Activity并不負者視圖控制,它只是控制生命周期和處理事件。真正控制視圖的是Window。一個Activity包含了一個Window,Window才是真正代表一個窗口。Activity就像一個控制器,統(tǒng)籌視圖的添加與顯示,以及通過其他回調方法,來與Window以及View進行交互。
Window
Window是視圖的承載器,內部持有一個DecorView,而這個DecorView才是view的跟布局。Window是一個抽象類,實際在Activity中持有的是其子類PhoneWindow。PhoneWindow中有個內部類DecorView,通過創(chuàng)建DecorView來加載Activity中設置的布局。Window通過WindowManager將DecorView加載其中,并將DecorView交給ViewRoot,進行視圖繪制以及其他交互。
DecorView
DecorView是FrameLayout的子類,它可以被認為是Android視圖樹的根節(jié)點視圖。
DecorView作為頂級View,一般情況下它內部包含一個豎直方向的LinearLayout,在這個LinearLayout里面有上下三個部分,上面是個ViewStub,延遲加載的視圖(應該是設置ActionBar,根據Theme設置),中間的是標題欄(根據Theme設置,有的布局沒有),下面是內容欄。在Activity中通過setContentView所設置的布局文件其實就是被加到內容欄之中的,成為其唯一子View。
ViewRoot
ViewRoot可能比較陌生,但是其作用非常重大。所有View的繪制以及事件分發(fā)等交互都是通過它來執(zhí)行或傳遞的。
ViewRoot對應ViewRootImpl類,它是連接WindowManagerService和DecorView的紐帶,View的三大流程(測量、布局、繪制)均通過ViewRoot來完成。
ViewRoot并不屬于View樹的一份子。從源碼實現上來看,它既是非View的子類,也是非View的父類,但是,它實現了ViewParent接口,這讓它可以作為View的名義上的父視圖。RootView繼承了Handler類,可以接收事件并分發(fā),Android的所有觸屏事件,按鍵事件、界面刷新等事件都是通過ViewRoot來進行分發(fā)的。
[圖片上傳中...(image-6f8e3a-1663300893576-3)]
總結
Activity就像個控制器,不負責視圖部分。Window像個承載器,裝著內部視圖。DecorView就是個頂級視圖,是所有View的最外層布局。ViewRoot像個連接器,負者溝通,通過硬件感知來通知視圖,進行用戶之間的交互。
11、Assets目錄與res目錄的區(qū)別
assets目錄與res下的raw、drawable目錄一樣,也可用來存放資源文件,但它們三者區(qū)別如下:
res/raw和assets的區(qū)別:
res/raw中的文件會被映射到R.java文件中,訪問的時候直接使用資源ID即可,assets文件夾下的文件不會被映射到R文件中,
訪問的時候需要AssetManager類。
res/raw不可以有目錄結構,而assets則可以有目錄結構,也就是assets目錄下可以再建立文件夾。
讀取res/raw下的文件資源,通過以下方式獲取輸入流來進行寫操作:
InputStream is = getResources().openRawResource(R.id.filename)
注意:
AssertManager中不能處理單個超過1M的文件,而raw沒有這個限制
assets文件夾是存放不進行編譯加工的原生文件,即該文件夾里面的文件不會像xml、java文件被預編譯,可以存放一些圖片、html、js等等
112、View視圖繪制過程原理
View視圖繪制需要搞清楚兩個問題,一個是從哪里開始繪制,一個是怎么繪制?
從哪里開始繪制?我們平常使用Activity的時候,都會調用setContentView來設置布局文件,沒錯,視圖繪制就是從這個方法開始。
怎么繪制?
在我們的Activity中調用了setContentView之后,會轉而執(zhí)行PhoneWindow的setContentView,在這個方法里面會判斷我們存放內容的ViewGroup(這個ViewGroup可以是DecorView也可以是DecorView的子View)是否存在。不存在的話,則會創(chuàng)建一個DecorView處理,并且會創(chuàng)建出相應的窗體風格,存在的話則會刪除原先的ViewGroup上面已有的View,接著會調用LayoutInflater的inflate方法以pull解析的方式將當前布局文件中存在的View通過addView的方式添加到ViewGroup上面來,接著在addView方法里面就會執(zhí)行我們常見的invalidate方法了,這個方法不只是在View視圖繪制的過程中經常用到,其實動畫的實現原理也是不斷的調用這個方法來實現視圖不斷重繪的,執(zhí)行這個方法的時候會調用父View的invalidateChild方法,這個方法是屬于ViewParent的,ViewGroup以及ViewRootImpl中都會他進行了實現,invalidateChild里面主要做的是就是通過do while循環(huán)一層一層計算出當前View的四個點所對應的矩陣在ViewRoot中所對應的位置,那么有了這個矩陣的位置之后最終都會執(zhí)行ViewRootImpl的invalidateChildInParent方法,執(zhí)行這個方法的時候首先會檢查當前線程是不是主線程,因為我們要開始準備更新UI了,不是主線程的話是不允許更新UI的,接著就會執(zhí)行scheduleTraversals方法了,這個方法會通過handler來執(zhí)行doTraversal方法,在這個方法里面就見到了我們平常所熟悉的View視圖繪制的起點方法performTraversals了。
那么接下來就是真正的視圖繪制流程了,大體上講View的繪制流程經歷了Measure測量、Layout布局以及Draw繪制的三個過程,具體來講是從ViewRootImpl的performTraversals方法開始,首先執(zhí)行的將是performMeasure方法,這個方法里面會傳入兩個MeasureSpec類型的參數,它在很大程度上決定了View的尺寸規(guī)格,對于DecorView來說寬高的MeasureSpec值的獲取與窗口尺寸以及自身的LayoutParams有關,對于普通View來說其寬高的MeasureSpec值獲取由父容器以及自身的LayoutParams屬性共同決定,在performMeasure里面會執(zhí)行measure方法,在measure方法里面會執(zhí)行onMeasure方法,到這里Measure測量過程對View與ViewGroup來說是沒有區(qū)別的,但是從onMeasure開始兩者有差別了,因為View本身已經不存在子View了,所以他onMeasure方法將執(zhí)行setMeasuredDimension方法,該方法會設置View的測量值,但是對于ViewGroup來說,因為它里面還存在著子View,那么我們就需要繼續(xù)測量它里面的子View了,調用的方法是measureChild方法,該方法內部又會執(zhí)行measure方法,而measure方法轉而又會執(zhí)行onMeasure方法,這樣不斷的遞歸進行下去,直到整個View樹測量結束,這樣performMeasure方法執(zhí)行結束了。接著便是執(zhí)行performLayout方法了,performMeasure只是測量出了View樹中View的大小了,但是還不知道View的位置,所以也就出現了performLayout方法了,performLayout方法首先會執(zhí)行l(wèi)ayout方法,以確定View自身的位置,如果當前View是ViewGroup的話,則會執(zhí)行onLayout方法。在onLayout方法里面又會遞歸的執(zhí)行l(wèi)ayout方法,直到當前遍歷到的View不再是ViewGroup為止,這樣整個layout布局過程就結束了。在View樹中View的大小以及位置都確定之后,接下來就是真正的繪制View顯示在界面的過程了,該過程首先從performDraw方法開始,performDraw首先會執(zhí)行draw方法,在draw方法中首先繪制背景,接著調用onDraw方法繪制自己,如果當前View是ViewGroup的話,還要調用dispatchDraw方法繪制當前ViewGroup的子View,而dispatchDraw方法里面實際上是通過drawChild方法間接調用draw方法形成遞歸繪制整個View樹,直到當前View不再是ViewGroup為止,這樣整個View的繪制過程就結束了。
總結:
ViewRootImpl會調用performTraversals(),其內部會調用performMeasure()、performLayout、performDraw
performMeasure會調用最外層的ViewGroup的measure() --> onMeasure() ,ViewGroup的onMeasure()是抽象方法,但其提供了measureChildren(),這之中會遍歷子View然后循環(huán)調用measureChild(),傳入MeasureSpec參數,然后調用子View的measure()到View的onMeasure() -->setMeasureDimension(getDefaultSize(),getDefaultSize()),getDefaultSize()默然返回measureSpec的測量數值,所以繼承View進行自定義的wrap_content需要重寫。
performLayout()會調用最外層的ViewGroup的layout(l,t,r,b),本View在其中使用setFrame()設置本View的四個頂點位置。在onLayout(抽象方法)中確定子View的位置,如LinearLayout會遍歷子View,循環(huán)調用setChildFrame() --> 子View.layout()
performDraw()會調用最外層的ViewGroup的draw()方法,其中會先后調用background.draw()繪制背景,onDraw(繪制自己),dispatchDraw(繪制子View)、onDrawScrollBars(繪制裝飾)
MeasureSpec由兩位SpecMode(UNSPECIFIED、EXACTLY(對于精確值和match_parent)、AL_MOST(對應warp_content))和三十位SpecSize組成一個int,DecorView的MeasureSpec由窗口大小和其LayoutParams決定,其他View有父View的MeasureSpec和本View的LayoutParams決定。ViewGroup中有getChildMeasureSpec()來獲取子View的MeasureSpec。
113、IntentService
IntentService是繼承并處理異步請求的一個類,其本質上是一個Service,因為它是繼承至Service,所以開啟IntentService和普通的Service一致。但是他和普通的Service不同之處在于它可以處理異步任務,在任務處理完之后會自動結束。另外,我們可以啟動多次IntentService,而每一個耗時任務會以工作隊列的方式在IntentService的onHandleIntent回調方法中執(zhí)行,并且是串行執(zhí)行。其實IntentService的內部是通過HandleThread和Handle來實現異步操作的。
14、requestLayout、invalidate、postInvalidate 的區(qū)別
requestLayout 會回掉 onMeasure、onLayout、onDraw(ViewGroup.setWillNotDraw(fasle)情況下)方法
invalidate 只會回掉 onDraw 方法
postInvalidate 只會回掉 onDraw 方法(可以在非 UI 線程中調用)
15、深入理解Android插件化技術
16、美團外賣Android Crash治理之路
17、對 Activity.runOnUiThread 的理解
當前線程不是ui線程,即發(fā)送post消息切換到ui線程(這個和sendMessage是有區(qū)別的,sendMessage是在非ui線程發(fā)送消息,這個在當前線程發(fā)送消息,然后因為activity初始化的時候就有l(wèi)ooper、和MessageQueue,就能直接處理消息,從而將mUiThread切換到當前線程,再次執(zhí)行就直接進行action.run())
是ui線程,即直接實現方法
18、什么是 RemoteViews?使用場景有哪些?
RemoteViews
RemoteViews翻譯過來就是遠程視圖.顧名思義,RemoteViews不是當前進程的View,是屬于SystemServer進程.應用程序與RemoteViews之間依賴Binder實現了進程間通信.
用法
通常是在通知欄
//1.創(chuàng)建RemoteViews實例
RemoteViews mRemoteViews=new RemoteViews("com.example.remoteviewdemo", R.layout.remoteview_layout);
//2.構建一個打開Activity的PendingIntent
Intent intent=new Intent(MainActivity.this,MainActivity.class);
PendingIntent mPendingIntent=PendingIntent.getActivity(MainActivity.this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
//3.創(chuàng)建一個Notification
mNotification = new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(mPendingIntent)
.setContent(mRemoteViews)
.build();
//4.獲取NotificationManager
manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//彈出通知
manager.notify(1, mNotification);
}
});
19、談談 AIDL
AIDL 是一種輔助工具,不用AIDL ,一樣可以實現跨進程通訊
AIDL 的原理是binder,真正有跨進程通訊能力的也是 Binder,所以 AIDL 只是一個能幫你少寫代碼,少出錯的輔助工具,由于設計的太好,使用太方便,所以非常常用
就像 retrofit 和okhttp 關系一樣, retrofit 提供 更加友好的api,真正的網絡請求還是由 okhttp發(fā)起的
20、Android進程間的通信方式?
21、Binder機制:
1.為了保證進程空間不被其他進程破壞或干擾,Linux中的進程是相互獨立或相互隔離的。
2.進程空間分為用戶空間和內核空間。用戶空間不可以進行數據交互;內核空間可以進行數據交互,所有進程共用一個內核空間。
3.Binder機制相對于Linux內傳統(tǒng)的進程間通信方式:(1)性能更好;Binder機制只需要拷貝數據一次,管道、消息隊列、Socket等都需要拷貝數據兩次;而共享內存雖然不需要拷貝,但實現復雜度高。(2)安全性更高;Binder機制通過UID/PID在內核空間添加了身份標識,安全性更高。
4.Binder跨進程通信機制:基于C/S架構,由Client、Server、Server Manager和Binder驅動組成。
5.Binder驅動實現的原理:通過內存映射,即系統(tǒng)調用了mmap()函數。
6.Server Manager的作用:管理Service的注冊和查詢。
7.Binder驅動的作用:(1)傳遞進程間的數據,通過系統(tǒng)調用mmap()函數;(2)實現線程的控制,通過Binder驅動的線程池,并由Binder驅動自身進行管理。
8.Server進程會創(chuàng)建很多線程處理Binder請求,這些線程采用Binder驅動的線程池,由Binder驅動自身進行管理。一個進程的Binder線程池默認最大是16個,超過的請求會阻塞等待空閑的線程。
9.Android中進行進程間通信主要通過Binder類(已經實現了IBinder接口),即具備了跨進程通信的能力。
首先 所有的server都會在 serviceManager 中注冊
client 訪問 server時 要向 serviceManager 發(fā)起請求,
serviceManager 找到 這個 server 并通過 binder驅動 生成一個 server代理 返回給client
client得到了 代理之后 訪問 代理server
代理server 相應方法被調用后 會向 binder驅動中去調用真正的server方法。