1锯岖、Activity、Dialog甫何、PopupWindow出吹、Toast 與Window的關(guān)系
簡單的從創(chuàng)建方式的角度來說一說:
Activity。在Activity創(chuàng)建過程中所創(chuàng)建的PhoneWindow辙喂,是層級最小的Window捶牢,叫做應(yīng)用Window,層級范圍1-99巍耗。(層級范圍大的Window可以覆蓋層級小的Window)
Dialog秋麸。Dialog的顯示過程和Activity基本相同,也是創(chuàng)建了PhoneWindow炬太,初始化DecorView,并將Dialog的視圖添加到DecorView中灸蟆,最終通過addView顯示出來。
但是有一點不同的是亲族,Dialog的Window并不是應(yīng)用窗口炒考,而是子窗口可缚,層級范圍1000-1999,子Window的顯示必須依附于應(yīng)用窗口斋枢,也會覆蓋應(yīng)用級Window帘靡。這也就是為什么Dialog傳入的上下文必須為Activity的Context了。
PopupWindow瓤帚。PopupWindow的顯示就有所不同了描姚,它沒有創(chuàng)建PhoneWindow,而是直接創(chuàng)建了一個View(PopupDecorView)戈次,然后通過WindowManager的addView方法顯示出來了轩勘。
沒有創(chuàng)建PhoneWindow,是不是就跟Window沒關(guān)系了呢朝扼?
并不是赃阀,其實只要是調(diào)用了WindowManager的addView方法,那就是創(chuàng)建了Window擎颖,跟你有沒有創(chuàng)建PhoneWindow無關(guān)榛斯。View就是Window的表現(xiàn)形式,只不過PhoneWindow的存在讓W(xué)indow形象更立體了一些搂捧。
所以PopupWindow也是通過Window展示出來的驮俗,而它的Window層級屬于子Window,必須依附與應(yīng)用窗口允跑。
Toast王凑。Toast和PopupWindow比較像,沒有新建PhoneWindow聋丝,直接通過addView方法顯示View即可索烹。不同的是它屬于系統(tǒng)級Window,層級范圍2000-2999,所以無須依附于Activity弱睦。
四個比較下來百姓,可以發(fā)現(xiàn),只要想顯示View况木,就會涉及到WindowManager的addView方法垒拢,也就用到了Window這個概念,然后會根據(jù)不同的分層依次顯示覆蓋到界面上火惊。
不同的是求类,Activity和Dialog涉及到了布局比較復(fù)雜,還會有布局主題等元素屹耐,所以用到了PhoneWindow進行一個解耦尸疆,幫助他們管理View。而PopupWindow和Toast結(jié)構(gòu)比較簡單,所以直接新建一個類似DecorView的View仓技,通過addView顯示到界面鸵贬。
2、onSaveInstanceState()什么時候會被調(diào)用呢脖捻?
概括的講阔逼,onSaveInstanceState 這個方法會在activity 將要被kill之前被調(diào)用以保存每個實例的狀態(tài),以保證在將來的某個時刻回來時可以恢復(fù)到原來的狀態(tài)地沮,但和activity 的生命周期方法onStop 和 onPause 不一樣嗜浮,與兩者并沒有絕對的先后調(diào)用順序,或者說并非所有場景都會調(diào)用onSaveInstanceState 方法摩疑。
那么onSaveInstanceState 方法何時會被調(diào)用呢危融,或者這么問,什么時候activity 會被系統(tǒng)kill 掉呢雷袋?
有以下幾種比較常見的場景:
(1)用戶主動按下home 鍵吉殃,系統(tǒng)不能確認activity 是否會被銷毀,實際上此刻系統(tǒng)也無法預(yù)測將來的場景楷怒,比如說內(nèi)存占用蛋勺,應(yīng)用運行情況等,所以系統(tǒng)會調(diào)用onSaveInstanceState保存activity狀態(tài) 鸠删;
(2)activity位于前臺抱完,按下電源鍵,直接鎖屏刃泡;
(3)橫豎屏切換巧娱;
(4)activity B啟動后位于activity A之前,在某個時刻activity A因為系統(tǒng)回收資源的問題要被kill掉烘贴,A通過onSaveInstanceState保存狀態(tài)禁添。
換句話說,onSaveInstanceState()的調(diào)用遵循一個重要原則桨踪,即當系統(tǒng)存在“未經(jīng)你許可”時銷毀了我們的Activity,則onSaveInstanceState()會被系統(tǒng)調(diào)用上荡,這是系統(tǒng)的職責,因為它必須要提供一個機會讓用戶保存數(shù)據(jù)馒闷。
3、Android 數(shù)據(jù)持久化之 SharedPreferences
Android之SharedPreferences內(nèi)部原理淺析
剖析 SharedPreference apply 引起的 ANR 問題
總結(jié):
sSharedPrefsCache 是一個 ArrayMap<String,ArrayMap<File,SharedPreferencesImpl>>叁征,它會保存加載到內(nèi)存中的 SharedPreferences 對象纳账,ContextImpl 類中并沒有定義將 SharedPreferences 對象移除 sSharedPrefsCache 的方法,所以一旦加載到內(nèi)存中捺疼,就會存在直至進程銷毀疏虫。相對的,也就是說,SP 對象一旦加載到內(nèi)存卧秘,后面任何時間使用呢袱,都是從內(nèi)存中獲取,不會再出現(xiàn)讀取磁盤的情況
SharedPreferences 和 Editor 都只是接口翅敌,真正的實現(xiàn)在 SharedPreferencesImpl 和 EditorImpl 羞福,SharedPreferences 只能讀數(shù)據(jù),它是在內(nèi)存中進行的蚯涮,Editor 則負責存數(shù)據(jù)和修改數(shù)據(jù)治专,分為內(nèi)存操作和磁盤操作
獲取 SP 只能通過 ContextImpl#getSharedPerferences 來獲取,它里面首先通過 mSharedPrefsPaths 根據(jù)傳入的 name 拿到 File 遭顶,然后根據(jù) File 從 ArrayMap<File, SharedPreferencesImpl> cache 里取出對應(yīng)的 SharedPrederenceImpl 實例
SharedPreferencesImpl 實例化的時候會啟動子線程來讀取磁盤文件张峰,但是在此之前如果通過 SharedPreferencesImpl#getXxx 或者 SharedPreferences.Editor 會阻塞 UI 線程,因為在從 SP 文件中讀取數(shù)據(jù)或者往 SP 文件中寫入數(shù)據(jù)的時候必須等待 SP 文件加載完
在 EditorImpl 中 putXxx 的時候棒旗,是通過 HashMap 來存儲數(shù)據(jù)喘批,提交的時候分為 commit 和 apply,它們都會把修改先提交到內(nèi)存中铣揉,然后在寫入磁盤中饶深。只不過 apply 是異步寫磁盤,而 commit 可能是同步寫磁盤也可能是異步寫磁盤老速,在于前面是否還有寫磁盤任務(wù)粥喜。對于 apply 和 commit 的同步,是通過 CountDownLatch 來實現(xiàn)的橘券,它是一個同步工具類额湘,它允許一個線程或多個線程一致等待,直到其他線程的操作執(zhí)行完之后才執(zhí)行
SP 的讀寫操作是線程安全的旁舰,它對 mMap 的讀寫操作用的是同一把鎖锋华,考慮到 SP 對象的生命周期與進程一致,一旦加載到內(nèi)存中就不會再去讀取磁盤文件箭窜,所以只要保證內(nèi)存中的狀態(tài)是一致的毯焕,就可以保證讀寫的一致性
注意事項以及優(yōu)化建議
強烈建議不要在 SP 里面存儲特別大的 key/value ,有助于減少卡頓 / ANR
請不要高頻的使用 apply磺樱,盡可能的批量提交纳猫;commit 直接在主線程操作,更要注意了
不要使用 MODE_MULTI_PROCESS
高頻寫操作的 key 與高頻讀操作的 key 可以適當?shù)牟鸱治募褡剑詼p少同步鎖競爭
不要連續(xù)多次 edit芜辕,每次 edit 就是打開一次文件,應(yīng)該獲取一次 edit块差,然后多次執(zhí)行 putXxx侵续,減少內(nèi)存波動绳匀,所以在封裝方法的時候要注意了
apply 在 QueueWork 維護的單線程池調(diào)用胳施,雖然是異步的但是可能會阻塞 Service.onStop 和 Activity.onPause 方法,可能會導(dǎo)致 ANR
ANR 容易發(fā)生的地方:
sp.getXxx,首先會調(diào)用 awaitLoadedLocked 等待首次 sp 文件創(chuàng)建與讀取操作完成
sp.apply 雖然是異步的但是可能會在 Service Activity 等生命周期期間 mcr.writtenToDiskLatch.await() 等待過久
sp.commit 最終會調(diào)用 sp.writeToFile 方法薪铜,很耗時
ContextImpl.getSharedPreferences含衔,主線程直接調(diào)用的話股耽,如果 sp 文件很大處理時間也就會變成
4颜曾、Activity的啟動過程
應(yīng)用啟動過程
Launcher通過Binder進程間通信機制通知AMS,它要啟動一個Activity
AMS通過Binder進程間通信機制通知Launcher進入Paused狀態(tài)
Launcher通過Binder進程間通信機制通知AMS眶根,它已經(jīng)準備就緒進入Paused狀態(tài)蜀铲,于是AMS就創(chuàng)建一個新的線程,用來啟動一個ActivityThread實例属百,即將要啟動的Activity就是在這個ActivityThread實例中運行
ActivityThread通過Binder進程間通信機制將一個ApplicationThread類型的Binder對象傳遞給AMS记劝,以便以后AMS能夠通過這個Binder對象和它進行通信
AMS通過Binde進程間通信機制通知ActivityThread,現(xiàn)在一切準備就緒族扰,它可以真正執(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已經(jīng)運行怒竿,這個方法不會調(diào)用。
onStartCommand():
每次客戶端調(diào)用startService()方法啟動該Service都會回調(diào)該方法(多次調(diào)用)扩氢,一旦這個方法執(zhí)行耕驰,service就啟動并且在后臺長期運行,通過調(diào)用stopSelf()或stopService()來停止服務(wù)录豺。
onBind():
當組件調(diào)用bindService()想要綁定到service時朦肘,系統(tǒng)調(diào)用此方法(一次調(diào)用),一旦綁定后双饥,下次在調(diào)用bindService()不會回調(diào)該方法媒抠。在你的實現(xiàn)中,你必須提供一個返回一個IBinder來使客戶端能夠使用它與service通訊咏花,你必須總是實現(xiàn)這個方法趴生,但是如果你不允許綁定,那么你應(yīng)返回null
onUnbind():
當前組件調(diào)用unbindService()昏翰,想要解除與service的綁定時系統(tǒng)調(diào)用此方法(一次調(diào)用苍匆,一旦解除綁定后,下次再調(diào)用unbindService()會拋異常)
onDestory():
系統(tǒng)在service不在被使用并且要銷毀的時候調(diào)用此方法(一次調(diào)用)棚菊。service應(yīng)在此方法中釋放資源锉桑,比如線程,已注冊的監(jiān)聽器窍株、接收器等等民轴。
三種情況下Service的生命周期
-
startService / stopService
生命周期:onCreate --> onStartCommand --> onDestory
如果一個Service被某個Activity調(diào)用Context.startService 方法啟動,那么不管是否有Activity使用bindService綁定或unbindService解除綁定到該Service球订,該Service都在后臺運行后裸,直到被調(diào)用stopService,或自身的stopSelf方法冒滩。當然如果系統(tǒng)資源不足微驶,Android系統(tǒng)也可能結(jié)束服務(wù),還有一種方法可以關(guān)閉服務(wù)开睡,在設(shè)置中因苹,通過應(yīng)用 --> 找到自己應(yīng)用 --> 停止。
注意:
第一次startService會觸發(fā)onCreate和onStartCommand篇恒,以后在服務(wù)運行過程中扶檐,每次startService都只會觸發(fā)onStartCommand
不論startService多少次,stopService一次就會停止服務(wù)
-
bindService / unbindService
生命周期:onCreate --> onBind --> onUnbind --> onDestory
如果一個Service在某個Activity中被調(diào)用bindService方法啟動胁艰,不論bindService被調(diào)用幾次款筑,Service的onCreate方法只會執(zhí)行一次,同時onStartCommand方法始終不會調(diào)用腾么。
當建立連接后奈梳,Service會一直運行,除非調(diào)用unbindService來解除綁定解虱、斷開連接或調(diào)用該Service的Context不存在了(如Activity被finish --- 即通過bindService啟動的Service的生命周期依附于啟動它的Context)攘须,系統(tǒng)會在這時候自動停止該Service。
注意:
第一次bindService會觸發(fā)onCreate和inBind殴泰,以后在服務(wù)運行過程中于宙,每次bindService都不會觸發(fā)任何回調(diào)
-
混合型
當一個Service再被啟動(startService)的同時又被綁定(bindService),該Service將會一直在后臺運行艰匙,不管調(diào)用幾次限煞,onCreate方法始終只會調(diào)用一次,onStartCommand的調(diào)用次數(shù)與startService調(diào)用的次數(shù)一致(使用bindService方法不會調(diào)用onStartCommand)员凝。同時署驻,調(diào)用unBindService將不會停止Service,必須調(diào)用stopService或Service自身的stopSelf來停止服務(wù)健霹。
三種情況下的應(yīng)用場景
如果你只是想啟動一個后臺服務(wù)長期進行某項任務(wù)旺上,那么使用startService便可以了。
如果你想與正在運行的Service取的聯(lián)系糖埋,那么有兩種方法宣吱,一種是使用broadcast,另外是使用bindService瞳别。前者的缺點是如果交流較為頻繁征候,容易造成性能上的問題杭攻,并且BroadcastReceiver本身執(zhí)行代碼的時間是很短的(也許執(zhí)行到一半,后面的代碼便不會執(zhí)行)疤坝,而后者則沒有這些問題兆解,因此我們肯定選擇使用bindService(這個時候便同時使用了startService和bindService了,這在Activity中更新Service的某些運行狀態(tài)是相當有用的)
如果你的服務(wù)只是公開了一個遠程接口跑揉,供連接上的客戶端(Android的Service是C/S架構(gòu))遠程調(diào)用執(zhí)行方法锅睛。這個時候你可以不讓服務(wù)一開始就運行,而只用bindService历谍,這樣在第一次bindService的時候才會創(chuàng)建服務(wù)的實例運行它现拒,這會節(jié)約很多系統(tǒng)資源,特別是如果你的服務(wù)是Remote Service望侈,那么該效果會越明顯印蔬。
6、BroadcastReceiver
應(yīng)用場景:
不同組件之間的通信(包括應(yīng)用內(nèi) / 不同應(yīng)用之間)
與Android系統(tǒng)在特定情況下的通信甜无,如當電話呼入時扛点,網(wǎng)絡(luò)可用時
多線程通信
實現(xiàn)原理
使用了觀察者模式:基于消息的發(fā)布/訂閱事件模型。
-
模型中有三個角色:消息訂閱者(廣播接收者)岂丘、消息發(fā)布者(廣播發(fā)布者)和消息中心(AMS陵究,即Activity Manager Service)
[圖片上傳中...(image-100ae0-1663300893576-0)]
原理描述
-
廣播接收者通過Binder機制在AMS注冊
廣播發(fā)送者通過Binder機制向AMS發(fā)送廣播
AMS根據(jù)廣播發(fā)送者要求,在已注冊列表中奥帘,尋找合適的廣播接收者铜邮,尋找依據(jù):IntentFilter / Permission
AMS將廣播發(fā)送到合適的廣播接收者相應(yīng)的消息循環(huán)隊列
廣播接收者通過消息循環(huán)拿到此廣播,并回調(diào)onReceive()
注意:廣播發(fā)送者和廣播接收者的執(zhí)行是異步的寨蹋,發(fā)出去的廣播不會關(guān)心有沒有接收者接收松蒜,也不確定接收者何時能接受到。
廣播的類型主要分為5類:
普通廣播(Normal Broadcast)
系統(tǒng)廣播(System Broadcast)
有序廣播(Ordered Broadcast)
粘性廣播(Sticky Broadcast)Android 5.0 & API 21中已經(jīng)失效
App應(yīng)用內(nèi)廣播(Local Broadcast)
動態(tài)廣播最好在Activity的onResume()注冊已旧,onPause()注銷秸苗,否則會導(dǎo)致內(nèi)存泄漏,當然运褪,重復(fù)注冊和重復(fù)注銷也不允許惊楼。
7、ContentProvider
作用
進程間進行數(shù)據(jù)交互&共享秸讹,即跨進程通信
原理
ContentProvider的底層采用Android中的Binder機制
統(tǒng)一資源標識符(RUI)
作用:唯一標識ContentProvider & 其中的數(shù)據(jù)檀咙,外界進程通過URI找到對應(yīng)的ContentProvider & 其中的數(shù)據(jù),再進行數(shù)據(jù)操作
具體使用:
URI分為系統(tǒng)預(yù)置 & 自定義璃诀,分別對應(yīng)系統(tǒng)內(nèi)置的數(shù)據(jù)(如通訊錄弧可、日程表等等)和自定義數(shù)據(jù)庫。
8劣欢、Context
Android應(yīng)用模型是基于組件的應(yīng)用設(shè)計模式棕诵,組件的運行要有一個完整的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)才能將戲傳給觀眾,這也就正對應(yīng)說四大組件必須工作在Context環(huán)境下谎脯。那么Button葱跋、TextView等等控件就相當于群演,顯然沒那么重用源梭,隨便一個路人甲都能演(可以new一個對象)娱俺,但是它們也必須在面對鏡頭(工作在Context環(huán)境下),所以Button mButtom = new Button(context) 是可以的废麻。
[圖片上傳中...(image-df38f2-1663300893577-4)]
它有兩個具體實現(xiàn)類:ContextImpl和ContextWrapper荠卷。
其中ContextWrapper類,是一個包裝類而已烛愧,ContextWrapper構(gòu)造函數(shù)中必須包含一個真正的Context引用油宜,同時ContextWrapper中提供了attachBaseContext()用于給ContextWrapper對象指定真正的Context對象,調(diào)用ContextWrapper的方法都會被轉(zhuǎn)向其包含的真正的Context對象屑彻。ContextThemeWrapper類验庙,其內(nèi)部包含了與主題Theme相關(guān)的接口,這里所說的主題就是指在AndroidManifest,xml中通過android:theme為Application元素或者Activity元素指定的主題社牲。當然粪薛,只有Activity才需要主題,Service是不需要主題的搏恤,所以Service直接繼承與ContextWrapper违寿,Application同理湃交。而ContextImpl類則真正實現(xiàn)了Context中的所有函數(shù),應(yīng)用程序中所調(diào)用的各種Context類的方法藤巢,其實現(xiàn)均來源于該類搞莺。Context得兩個子類分工明確,其中ContextImpl是Context的具體實現(xiàn)類掂咒,ContextWrapper是Context的包裝類才沧。 Activity、Application绍刮、Service雖都繼承自ContextWrapper(Activity繼承自ContextWrapper的子類ContextThemeWrapper)温圆,但它們初始化的過程中都會創(chuàng)建ContextImpl對象,由ContextImpl實現(xiàn)Context中的方法孩革。
一個應(yīng)用程序有幾個Context岁歉?
在應(yīng)用程序中Context的具體實現(xiàn)子類就是:Activity、Service和Application膝蜈。那么Context數(shù)量=Activity數(shù)量+Service數(shù)量+1锅移。那么為什么四大組件中只有Activity和Service持有Context呢?BroadcastReceiver和ContextPrivider并不是Context的子類饱搏,它們所持有的Context都是其他地方傳過去的非剃,所以并不計入Context總數(shù)。
Context的作用域
雖然Context神通廣大窍帝,但并不是隨便拿到一個Context實例就可以為所欲為努潘,它的使用還是有一些規(guī)則限制的。由于Context的具體實例是由ContextImpl類去實現(xiàn)的坤学,因此在絕大多數(shù)場景下疯坤,Activity、Service和Application這三種類型的Context都是可以通用的深浮。不過有幾種場景比較特殊压怠,比如啟動Activity,還有彈出Dialog飞苇。出于安全原因的考慮菌瘫,Android是不允許Activity或Dialog憑空出現(xiàn)的,一個Activity的啟動必須要建立在另一個Activity的基礎(chǔ)之上布卡,也就是以此形成返回棧雨让。而Dialog則必須在一個Activity上面彈出(除非是System Alert類型的Dialog),因此在這種場景下忿等,我們只能使用Activity類型的Context栖忠,否則將會報錯。
[圖片上傳中...(image-d795d0-1663300893576-2)]
從上圖我們可以發(fā)現(xiàn)Activity所持有的Context的作用域最廣,無所不能庵寞,因此Activity繼承至ContextThemeWrapper狸相,而Application和Service繼承至ContextWrapper,很顯然ContextThemeWrapper在ContextWrapper的基礎(chǔ)上又做了一些操作使得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并沒有所謂的任務(wù)棧脓鹃,所以待啟動的Activity就找不到棧了。解決這個問題的方法就是為待啟動的Activity指定FLAG_ACTIVITY_NEW_TASK標記位古沥,這樣啟動的時候就為它創(chuàng)建一個新的任務(wù)棧瘸右,而此時Activity是以singleTask模式啟動的。所有這種用Application啟動Activity的方式都不推薦岩齿,Service同Application尊浓。
在Application和Service中去LayoutInflate也是合法的,但是會使用系統(tǒng)默認的主題樣式纯衍,如果你自定義了某些樣式可能不會被使用,這種方式也不推薦使用苗胀。
一句話總結(jié):凡是跟UI相關(guān)的襟诸,都應(yīng)該使用Activity作為Context來處理;其他的一些操作基协,Service歌亲、Activity、Application等實例都可以澜驮,當然了注意Context引用的持有陷揪,防止內(nèi)存泄露。
如何獲取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ū)別脑又?
其內(nèi)存地址是一樣的。Application本身就是一個Context,這里獲取getApplicationContext得到的結(jié)果就是Application本身的實例挂谍。getApplication方法的語義性很強叔壤,就是用來獲取Application實例的,但是這個方法只有在Activity和Service中才能調(diào)用的到口叙。那么也許在絕大多數(shù)情況下我們都是在Activity或者Service中使用Application炼绘,但是如果在一些其他的場景,比如BroadcastReceiver中也想獲取Application實例妄田,這時就可以借助getApplicationContext方法了俺亮。
9、Android APK編譯打包流程
[圖片上傳中...(image-e1bab6-1663300893576-1)]
AAPT(Android Asset Packaging Tools)工具會打包應(yīng)用中的資源文件疟呐,如AndroidManifest.xml脚曾、layout布局中的xml等,并將xml文件編譯成二進制形式启具,當然assets文件夾中的文件不會被編譯本讥,圖片以及raw文件夾中的資源也會保持原有的形態(tài),需要注意的是raw文件夾中的資源也會生成資源ID鲁冯。AAPT編譯完成后會生成R.java文件拷沸。
AIDL工會將所有的aidl接口轉(zhuǎn)換為java接口。
所有的Java源代碼薯演、R文件撞芍、接口都會編譯器編譯成.class文件。
Dex工具會將上述產(chǎn)生的.class文件以及第三方庫和其他class文件轉(zhuǎn)化為dex(Dalvik虛擬機可執(zhí)行文件)文件跨扮,dex文件最終會被打包進APK文件序无。
apkbuilder會把編譯后的資源和其他資源文件同dex文件一起打入APK中。
生成APK文件之后衡创,帝嗡,需要對其簽名才能安裝到設(shè)備上,平時測試都會使用debug keystore璃氢,當發(fā)布應(yīng)用時必須使用release版的keystore對應(yīng)用進行簽名丈探。
如果對APK正式簽名,還需要使用zipalign工具對APK進行對齊操作拔莱,這樣做的好處是當應(yīng)用運行時能提高速度碗降,但是會相應(yīng)的增加內(nèi)存開銷。
總結(jié):編譯 --> DEX --> 打包 --> 簽名和對齊
10塘秦、Window讼渊、Activity、DecorView以及ViewRoot之間的關(guān)系
Activity
Activity并不負者視圖控制尊剔,它只是控制生命周期和處理事件爪幻。真正控制視圖的是Window。一個Activity包含了一個Window,Window才是真正代表一個窗口挨稿。Activity就像一個控制器仇轻,統(tǒng)籌視圖的添加與顯示,以及通過其他回調(diào)方法奶甘,來與Window以及View進行交互篷店。
Window
Window是視圖的承載器,內(nèi)部持有一個DecorView臭家,而這個DecorView才是view的跟布局疲陕。Window是一個抽象類,實際在Activity中持有的是其子類PhoneWindow钉赁。PhoneWindow中有個內(nèi)部類DecorView蹄殃,通過創(chuàng)建DecorView來加載Activity中設(shè)置的布局。Window通過WindowManager將DecorView加載其中你踩,并將DecorView交給ViewRoot诅岩,進行視圖繪制以及其他交互。
DecorView
DecorView是FrameLayout的子類带膜,它可以被認為是Android視圖樹的根節(jié)點視圖按厘。
DecorView作為頂級View,一般情況下它內(nèi)部包含一個豎直方向的LinearLayout钱慢,在這個LinearLayout里面有上下三個部分,上面是個ViewStub卿堂,延遲加載的視圖(應(yīng)該是設(shè)置ActionBar束莫,根據(jù)Theme設(shè)置),中間的是標題欄(根據(jù)Theme設(shè)置草描,有的布局沒有)览绿,下面是內(nèi)容欄。在Activity中通過setContentView所設(shè)置的布局文件其實就是被加到內(nèi)容欄之中的穗慕,成為其唯一子View饿敲。
ViewRoot
ViewRoot可能比較陌生,但是其作用非常重大逛绵。所有View的繪制以及事件分發(fā)等交互都是通過它來執(zhí)行或傳遞的怀各。
ViewRoot對應(yīng)ViewRootImpl類,它是連接WindowManagerService和DecorView的紐帶术浪,View的三大流程(測量瓢对、布局、繪制)均通過ViewRoot來完成胰苏。
ViewRoot并不屬于View樹的一份子硕蛹。從源碼實現(xiàn)上來看,它既是非View的子類,也是非View的父類法焰,但是秧荆,它實現(xiàn)了ViewParent接口,這讓它可以作為View的名義上的父視圖埃仪。RootView繼承了Handler類乙濒,可以接收事件并分發(fā),Android的所有觸屏事件贵试,按鍵事件琉兜、界面刷新等事件都是通過ViewRoot來進行分發(fā)的。
[圖片上傳中...(image-6f8e3a-1663300893576-3)]
總結(jié)
Activity就像個控制器毙玻,不負責視圖部分豌蟋。Window像個承載器,裝著內(nèi)部視圖桑滩。DecorView就是個頂級視圖梧疲,是所有View的最外層布局。ViewRoot像個連接器运准,負者溝通幌氮,通過硬件感知來通知視圖,進行用戶之間的交互胁澳。
11该互、Assets目錄與res目錄的區(qū)別
assets目錄與res下的raw、drawable目錄一樣韭畸,也可用來存放資源文件宇智,但它們?nèi)邊^(qū)別如下:
res/raw和assets的區(qū)別:
res/raw中的文件會被映射到R.java文件中,訪問的時候直接使用資源ID即可胰丁,assets文件夾下的文件不會被映射到R文件中随橘,
訪問的時候需要AssetManager類。
res/raw不可以有目錄結(jié)構(gòu)锦庸,而assets則可以有目錄結(jié)構(gòu)机蔗,也就是assets目錄下可以再建立文件夾。
讀取res/raw下的文件資源甘萧,通過以下方式獲取輸入流來進行寫操作:
InputStream is = getResources().openRawResource(R.id.filename)
注意:
AssertManager中不能處理單個超過1M的文件萝嘁,而raw沒有這個限制
assets文件夾是存放不進行編譯加工的原生文件,即該文件夾里面的文件不會像xml扬卷、java文件被預(yù)編譯酿愧,可以存放一些圖片、html邀泉、js等等
112嬉挡、View視圖繪制過程原理
View視圖繪制需要搞清楚兩個問題钝鸽,一個是從哪里開始繪制,一個是怎么繪制庞钢?
從哪里開始繪制拔恰?我們平常使用Activity的時候,都會調(diào)用setContentView來設(shè)置布局文件基括,沒錯颜懊,視圖繪制就是從這個方法開始。
怎么繪制风皿?
在我們的Activity中調(diào)用了setContentView之后河爹,會轉(zhuǎn)而執(zhí)行PhoneWindow的setContentView,在這個方法里面會判斷我們存放內(nèi)容的ViewGroup(這個ViewGroup可以是DecorView也可以是DecorView的子View)是否存在桐款。不存在的話咸这,則會創(chuàng)建一個DecorView處理,并且會創(chuàng)建出相應(yīng)的窗體風格魔眨,存在的話則會刪除原先的ViewGroup上面已有的View媳维,接著會調(diào)用LayoutInflater的inflate方法以pull解析的方式將當前布局文件中存在的View通過addView的方式添加到ViewGroup上面來,接著在addView方法里面就會執(zhí)行我們常見的invalidate方法了遏暴,這個方法不只是在View視圖繪制的過程中經(jīng)常用到侄刽,其實動畫的實現(xiàn)原理也是不斷的調(diào)用這個方法來實現(xiàn)視圖不斷重繪的,執(zhí)行這個方法的時候會調(diào)用父View的invalidateChild方法朋凉,這個方法是屬于ViewParent的州丹,ViewGroup以及ViewRootImpl中都會他進行了實現(xiàn),invalidateChild里面主要做的是就是通過do while循環(huán)一層一層計算出當前View的四個點所對應(yīng)的矩陣在ViewRoot中所對應(yīng)的位置杂彭,那么有了這個矩陣的位置之后最終都會執(zhí)行ViewRootImpl的invalidateChildInParent方法墓毒,執(zhí)行這個方法的時候首先會檢查當前線程是不是主線程,因為我們要開始準備更新UI了盖灸,不是主線程的話是不允許更新UI的,接著就會執(zhí)行scheduleTraversals方法了磺芭,這個方法會通過handler來執(zhí)行doTraversal方法赁炎,在這個方法里面就見到了我們平常所熟悉的View視圖繪制的起點方法performTraversals了。
那么接下來就是真正的視圖繪制流程了钾腺,大體上講View的繪制流程經(jīng)歷了Measure測量徙垫、Layout布局以及Draw繪制的三個過程,具體來講是從ViewRootImpl的performTraversals方法開始放棒,首先執(zhí)行的將是performMeasure方法姻报,這個方法里面會傳入兩個MeasureSpec類型的參數(shù),它在很大程度上決定了View的尺寸規(guī)格间螟,對于DecorView來說寬高的MeasureSpec值的獲取與窗口尺寸以及自身的LayoutParams有關(guān)吴旋,對于普通View來說其寬高的MeasureSpec值獲取由父容器以及自身的LayoutParams屬性共同決定损肛,在performMeasure里面會執(zhí)行measure方法,在measure方法里面會執(zhí)行onMeasure方法荣瑟,到這里Measure測量過程對View與ViewGroup來說是沒有區(qū)別的治拿,但是從onMeasure開始兩者有差別了,因為View本身已經(jīng)不存在子View了笆焰,所以他onMeasure方法將執(zhí)行setMeasuredDimension方法劫谅,該方法會設(shè)置View的測量值,但是對于ViewGroup來說嚷掠,因為它里面還存在著子View捏检,那么我們就需要繼續(xù)測量它里面的子View了,調(diào)用的方法是measureChild方法不皆,該方法內(nèi)部又會執(zhí)行measure方法贯城,而measure方法轉(zhuǎn)而又會執(zhí)行onMeasure方法,這樣不斷的遞歸進行下去粟焊,直到整個View樹測量結(jié)束冤狡,這樣performMeasure方法執(zhí)行結(jié)束了。接著便是執(zhí)行performLayout方法了项棠,performMeasure只是測量出了View樹中View的大小了悲雳,但是還不知道View的位置,所以也就出現(xiàn)了performLayout方法了香追,performLayout方法首先會執(zhí)行l(wèi)ayout方法合瓢,以確定View自身的位置,如果當前View是ViewGroup的話透典,則會執(zhí)行onLayout方法晴楔。在onLayout方法里面又會遞歸的執(zhí)行l(wèi)ayout方法,直到當前遍歷到的View不再是ViewGroup為止峭咒,這樣整個layout布局過程就結(jié)束了税弃。在View樹中View的大小以及位置都確定之后,接下來就是真正的繪制View顯示在界面的過程了凑队,該過程首先從performDraw方法開始则果,performDraw首先會執(zhí)行draw方法,在draw方法中首先繪制背景漩氨,接著調(diào)用onDraw方法繪制自己西壮,如果當前View是ViewGroup的話,還要調(diào)用dispatchDraw方法繪制當前ViewGroup的子View叫惊,而dispatchDraw方法里面實際上是通過drawChild方法間接調(diào)用draw方法形成遞歸繪制整個View樹款青,直到當前View不再是ViewGroup為止,這樣整個View的繪制過程就結(jié)束了霍狰。
總結(jié):
ViewRootImpl會調(diào)用performTraversals()抡草,其內(nèi)部會調(diào)用performMeasure()饰及、performLayout、performDraw
performMeasure會調(diào)用最外層的ViewGroup的measure() --> onMeasure() 渠牲,ViewGroup的onMeasure()是抽象方法旋炒,但其提供了measureChildren(),這之中會遍歷子View然后循環(huán)調(diào)用measureChild()签杈,傳入MeasureSpec參數(shù)瘫镇,然后調(diào)用子View的measure()到View的onMeasure() -->setMeasureDimension(getDefaultSize(),getDefaultSize()),getDefaultSize()默然返回measureSpec的測量數(shù)值答姥,所以繼承View進行自定義的wrap_content需要重寫铣除。
performLayout()會調(diào)用最外層的ViewGroup的layout(l,t,r,b)无埃,本View在其中使用setFrame()設(shè)置本View的四個頂點位置柱蟀。在onLayout(抽象方法)中確定子View的位置,如LinearLayout會遍歷子View蝇庭,循環(huán)調(diào)用setChildFrame() --> 子View.layout()
performDraw()會調(diào)用最外層的ViewGroup的draw()方法敲长,其中會先后調(diào)用background.draw()繪制背景郎嫁,onDraw(繪制自己),dispatchDraw(繪制子View)祈噪、onDrawScrollBars(繪制裝飾)
MeasureSpec由兩位SpecMode(UNSPECIFIED泽铛、EXACTLY(對于精確值和match_parent)、AL_MOST(對應(yīng)warp_content))和三十位SpecSize組成一個int辑鲤,DecorView的MeasureSpec由窗口大小和其LayoutParams決定盔腔,其他View有父View的MeasureSpec和本View的LayoutParams決定。ViewGroup中有g(shù)etChildMeasureSpec()來獲取子View的MeasureSpec月褥。
113弛随、IntentService
IntentService是繼承并處理異步請求的一個類,其本質(zhì)上是一個Service宁赤,因為它是繼承至Service舀透,所以開啟IntentService和普通的Service一致。但是他和普通的Service不同之處在于它可以處理異步任務(wù)决左,在任務(wù)處理完之后會自動結(jié)束愕够。另外,我們可以啟動多次IntentService哆窿,而每一個耗時任務(wù)會以工作隊列的方式在IntentService的onHandleIntent回調(diào)方法中執(zhí)行链烈,并且是串行執(zhí)行厉斟。其實IntentService的內(nèi)部是通過HandleThread和Handle來實現(xiàn)異步操作的挚躯。
14、requestLayout擦秽、invalidate码荔、postInvalidate 的區(qū)別
requestLayout 會回掉 onMeasure漩勤、onLayout、onDraw(ViewGroup.setWillNotDraw(fasle)情況下)方法
invalidate 只會回掉 onDraw 方法
postInvalidate 只會回掉 onDraw 方法(可以在非 UI 線程中調(diào)用)
15缩搅、深入理解Android插件化技術(shù)
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線程,即直接實現(xiàn)方法
18半哟、什么是 RemoteViews酬滤?使用場景有哪些?
RemoteViews
RemoteViews翻譯過來就是遠程視圖.顧名思義,RemoteViews不是當前進程的View,是屬于SystemServer進程.應(yīng)用程序與RemoteViews之間依賴Binder實現(xiàn)了進程間通信.
用法
通常是在通知欄
//1.創(chuàng)建RemoteViews實例
RemoteViews mRemoteViews=new RemoteViews("com.example.remoteviewdemo", R.layout.remoteview_layout);
//2.構(gòu)建一個打開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寓涨、談?wù)?AIDL
AIDL 是一種輔助工具,不用AIDL ,一樣可以實現(xiàn)跨進程通訊
AIDL 的原理是binder,真正有跨進程通訊能力的也是 Binder,所以 AIDL 只是一個能幫你少寫代碼,少出錯的輔助工具,由于設(shè)計的太好,使用太方便,所以非常常用
就像 retrofit 和okhttp 關(guān)系一樣, retrofit 提供 更加友好的api,真正的網(wǎng)絡(luò)請求還是由 okhttp發(fā)起的
20盯串、Android進程間的通信方式?
21缅茉、Binder機制:
1.為了保證進程空間不被其他進程破壞或干擾嘴脾,Linux中的進程是相互獨立或相互隔離的。
2.進程空間分為用戶空間和內(nèi)核空間蔬墩。用戶空間不可以進行數(shù)據(jù)交互译打;內(nèi)核空間可以進行數(shù)據(jù)交互,所有進程共用一個內(nèi)核空間拇颅。
3.Binder機制相對于Linux內(nèi)傳統(tǒng)的進程間通信方式:(1)性能更好奏司;Binder機制只需要拷貝數(shù)據(jù)一次,管道樟插、消息隊列韵洋、Socket等都需要拷貝數(shù)據(jù)兩次;而共享內(nèi)存雖然不需要拷貝黄锤,但實現(xiàn)復(fù)雜度高搪缨。(2)安全性更高;Binder機制通過UID/PID在內(nèi)核空間添加了身份標識鸵熟,安全性更高副编。
4.Binder跨進程通信機制:基于C/S架構(gòu),由Client流强、Server痹届、Server Manager和Binder驅(qū)動組成呻待。
5.Binder驅(qū)動實現(xiàn)的原理:通過內(nèi)存映射,即系統(tǒng)調(diào)用了mmap()函數(shù)队腐。
6.Server Manager的作用:管理Service的注冊和查詢蚕捉。
7.Binder驅(qū)動的作用:(1)傳遞進程間的數(shù)據(jù),通過系統(tǒng)調(diào)用mmap()函數(shù)柴淘;(2)實現(xiàn)線程的控制迫淹,通過Binder驅(qū)動的線程池,并由Binder驅(qū)動自身進行管理为严。
8.Server進程會創(chuàng)建很多線程處理Binder請求千绪,這些線程采用Binder驅(qū)動的線程池,由Binder驅(qū)動自身進行管理梗脾。一個進程的Binder線程池默認最大是16個荸型,超過的請求會阻塞等待空閑的線程。
9.Android中進行進程間通信主要通過Binder類(已經(jīng)實現(xiàn)了IBinder接口)炸茧,即具備了跨進程通信的能力瑞妇。
首先 所有的server都會在 serviceManager 中注冊
client 訪問 server時 要向 serviceManager 發(fā)起請求,
serviceManager 找到 這個 server 并通過 binder驅(qū)動 生成一個 server代理 返回給client
client得到了 代理之后 訪問 代理server
代理server 相應(yīng)方法被調(diào)用后 會向 binder驅(qū)動中去調(diào)用真正的server方法。