為了面試,為了高工資,廢話不多說,不定期更新。
1. Activity正常和異常情況下的生命周期分析。
Activity生命周期是一個(gè)老生常談的問題,但是有些刁鉆的問題也會一時(shí)半會的影響大家的思路,我們還是耐著性子來分析下吧。
I.正常生命周期
- onCreate
Avtivity啟動時(shí)調(diào)用的第一個(gè)方法,表示Activity正在被創(chuàng)建。應(yīng)該在此初始化Activity的必需組件,最重要的是必需在該方法中調(diào)用setContentView方法來設(shè)置Activity的布局。
不應(yīng)在此接口中執(zhí)行耗時(shí)操作,否則會影響Activity的顯示。
- onStart
在Activity即將對用戶可見之前調(diào)用(該接口與onResume對應(yīng),onResume代表已經(jīng)可見了),可以理解為已經(jīng)在后臺準(zhǔn)備就緒了。
- onResume
在Activity即將開始與用戶進(jìn)行交互前調(diào)用,此時(shí)Activity處于Activity堆棧的頂層,并具有用戶輸入焦點(diǎn)。
- onPause
當(dāng)系統(tǒng)開始顯示另外一個(gè)Activity(Dialog不算,Dialog形式的Activity算)時(shí)調(diào)用,通常用于確認(rèn)對持久性數(shù)據(jù)的保存更改,停止動畫及其他可能消耗CPU的內(nèi)容。它應(yīng)該快速執(zhí)行所需操作,因?yàn)樗祷睾?,下一個(gè)Activity才能繼續(xù)執(zhí)行。
此時(shí)Activity對于用戶來講是部分可見的,例如彈出一個(gè)Dialog形式的Activity。
- onStop
在Activity對用戶完全不可見時(shí)調(diào)用,可以認(rèn)為處于后臺。
- onDestory
在Activity被銷毀前調(diào)用,這是Activity將收到的最后的調(diào)用。
一般會在此銷毀啟動的線程,或者處理一些可能導(dǎo)致內(nèi)存泄露的問題。
- onRestart
系統(tǒng)由不可見變?yōu)榭梢姇r(shí)調(diào)用,調(diào)用順序:onRestart-onstart-onResume.
Note: 除非程序在onCreate()方法里面就調(diào)用了finish()方法,系統(tǒng)通常是在執(zhí)行了onPause()與onStop() 之后再調(diào)用onDestroy() 。在某些情況下,例如我們的activity只是做了一個(gè)臨時(shí)的邏輯跳轉(zhuǎn)的功能,它只是用來決定跳轉(zhuǎn)到哪一個(gè)activity,這樣的話,需要在onCreate里面調(diào)用finish方法,這樣系統(tǒng)會直接調(diào)用onDestory,跳過生命周期中的其他方法。
II.異常生命周期
- 資源配置發(fā)生變更導(dǎo)致的Activity殺死重建
當(dāng)系統(tǒng)配置發(fā)生變化后,例如屏幕方向切換、系統(tǒng)語言切換,生命周期方法調(diào)用如下
onPause---onSaveInstanceState---onStop---onDestory---onCreate---onRestoreInstanceState--onStart--onResume
在Activity異常終止時(shí),系統(tǒng)會調(diào)用onSaveInstanceState來保存一些狀態(tài)信息(在onStop方法之前調(diào)用,但是和onPause方法并無明確的調(diào)用順序)。
重建Activity時(shí),onSaveInstanceState保存的信息Bundle會以參數(shù)的形式傳入onCreate和onRestoreInstanceState(在onStart方法之后調(diào)用)方法中,所以我們可以在這兩個(gè)方法中進(jìn)行一些狀態(tài)恢復(fù)。
Note:onCreate方法的Bundle參數(shù)可能為空,所以我們一般配對使用onSaveInstanceState和onRestoreInstanceState。
保存數(shù)據(jù)的思想:
Activity被意外終止時(shí),Activity會調(diào)用onSavedInstanceState去保存數(shù)據(jù),然后Activity會委托Window去保存數(shù)據(jù),接著Window再委托它上面的頂級容器去保存數(shù)據(jù),頂層容器是一個(gè)ViewGroup,一般來說它很可能是一個(gè)DecorView.最后頂層容器再去一一通知它的子元素來保存數(shù)據(jù),這樣整個(gè)數(shù)據(jù)保存過程就完成了,可以發(fā)現(xiàn),這是一種典型的委托思想,上層委托下層,父容器委托子容器去處理一些事情。
- 資源或者內(nèi)存不足導(dǎo)致被殺死
對于Activity我們分為三種:
a.前臺Activity(高)
b.可見非前臺Activity(中)
c.后臺Activity(低)
當(dāng)系統(tǒng)發(fā)現(xiàn)內(nèi)存不足時(shí),會按照上面的優(yōu)先級選擇殺死Activity所在的進(jìn)程,并在后續(xù)(需要再顯示的時(shí)候)進(jìn)行恢復(fù)。
Note:系統(tǒng)只恢復(fù)那些被開發(fā)者指定過id的控件,如果沒有為控件指定id,則系統(tǒng)就無法恢復(fù)了。
2. Android啟動模式分析
2.1 Application&Task&Process
Application
通俗的講,Application就是一組組件的集合,每一個(gè)應(yīng)用都是一個(gè)Application。
Task
Task是在程序運(yùn)行時(shí),只針對Activity的概念,Task是一組相互關(guān)聯(lián)的Activity的集合,它存在framework層的一個(gè)概念,控制界面的跳轉(zhuǎn)和返回。
Process
進(jìn)程是操作系統(tǒng)內(nèi)核的一個(gè)概念,表示接受內(nèi)核調(diào)度的執(zhí)行單位。
在默認(rèn)情況下,一個(gè)應(yīng)用程序的所有組件運(yùn)行在同一個(gè)進(jìn)程中。
2.2 啟動模式
Activity存在四種啟動模式:standard,singleTop,singleTask,singleInstance,使用時(shí)需要在AndroidManifest文件中配置。
standard:標(biāo)準(zhǔn)
標(biāo)準(zhǔn)模式,也是系統(tǒng)默認(rèn)的Activity啟動模式。
Activity可以多次實(shí)例化,而且每個(gè)實(shí)例可以屬于不同的任務(wù),并且一個(gè)任務(wù)可以擁有多個(gè)實(shí)例。
誰啟動的Activity,Activity就和它處于同一個(gè)任務(wù)棧中。
在使用ApplicationContext或者Service中的Context啟動Activity時(shí),需要添加FLAG_ACTIVITY_NEW_TASK的標(biāo)志才能正常啟動Activity;因?yàn)榉茿ctivity的Context沒有所謂的任務(wù)棧。
singleTop:棧頂復(fù)用
棧頂復(fù)用模式。
如果當(dāng)前任務(wù)棧頂已經(jīng)存在Activity的一個(gè)實(shí)例,系統(tǒng)會調(diào)用該實(shí)例的onNewIntent方法向其傳遞Intent,而不會創(chuàng)建Activity的新實(shí)例。(該Activity的onCreate、onStart方法不會被系統(tǒng)調(diào)用,因?yàn)樗]有發(fā)生改變)
Activity可以多次實(shí)例化,而每個(gè)實(shí)例均可屬于不同的任務(wù),并且一個(gè)任務(wù)可以擁有多個(gè)實(shí)例(但前提是位于返回棧頂部的 Activity并不是Activity的現(xiàn)有實(shí)例)。
例如,假設(shè)任務(wù)返回棧包含Activity A 以及 Activity B、C 和位于頂部的 D(堆棧是 A-B-C-D;D 位于頂部)。
收到針對 D 類 Activity 的 Intent。
如果 D 具有默認(rèn)的 "standard" 啟動模式,則會創(chuàng)建該類的新實(shí)例,且堆棧會變成 A-B-C-D-D。
但是,如果 D 的啟動模式是 "singleTop",則 D 的現(xiàn)有實(shí)例會通過 onNewIntent() 接收 Intent,因?yàn)樗挥诙褩5捻敳?;而堆棧仍?A-B-C-D。
但是,如果收到針對 B 類 Activity 的 Intent,則會向堆棧添加B新實(shí)例,即便其啟動模式為 "singleTop" 也是如此。
singleTask:棧內(nèi)復(fù)用
棧內(nèi)復(fù)用模式。
啟動一個(gè)singleTask的Activity實(shí)例時(shí),如果任務(wù)棧中已經(jīng)存在這樣一個(gè)實(shí)例,就會將這個(gè)實(shí)例調(diào)度到任務(wù)的棧頂,并清除它當(dāng)前所在任務(wù)中位于它上面的所有的activity,不會重新實(shí)例化該Activity;如果任務(wù)棧中不存在該Activity實(shí)例,則創(chuàng)建實(shí)例后入棧。
和SingleTop一樣,系統(tǒng)會回調(diào)它的oneNewInent方法。
singleInstance:單例
單例模式。
總是在新的任務(wù)中開啟,并且這個(gè)新的任務(wù)中有且只有這一個(gè)實(shí)例,也就是說被該實(shí)例啟動的其他activity會自動運(yùn)行于另一個(gè)任務(wù)中。當(dāng)再次啟動該activity的實(shí)例時(shí),會重用已存在的任務(wù)和實(shí)例。并且會調(diào)用這個(gè)實(shí)例的onNewIntent()方法,將Intent實(shí)例傳遞到該實(shí)例中。
同一時(shí)刻在系統(tǒng)中只會存在一個(gè)這樣的Activity實(shí)例。
taskAffinity
taskAffinity表示Activity所處的任務(wù)棧,默認(rèn)情況下一個(gè)應(yīng)用中所有的Activity具有相同的taskAffinity(處于同一個(gè)任務(wù)中),默認(rèn)的taskAffinity為應(yīng)用的包名。
可以指定taskAffinity為空字符串,代表Activity不屬于任何任務(wù)。
taskActivity主要結(jié)合singleTask啟動模式或者allowTaskReparenting屬性配對使用,在其他情況下無意義。
面試的時(shí)候,給面試官舉例說明,更加簡潔明了,省的表述不清。
啟動模式指定
啟動模式的指定存在兩種方式:
- launchMode指定:無法指定FLAG_ACTIVITY_CLEAR_TOP.
- Intent Flag指定(優(yōu)先級高):無法指定singleInstance.
FLAG
FLAG_ACTIVITY_SINGLE_TOP
相當(dāng)于singleTop
FLAG_ACTIVITY_NEW_TASK
沒有對應(yīng)的啟動模式,它的部分特性與singleTask相同。
默認(rèn)的跳轉(zhuǎn)類型,會重新創(chuàng)建一個(gè)新的Activity,比方說Task1中有A,B,C三個(gè)Activity,此時(shí)在C中啟動D的話,如果在Manifest.xml文件中給D添加了Affinity的值和Task中的不一樣的話,則會在新標(biāo)記的Affinity所存在的Task中壓入這個(gè)Activity。如果是默認(rèn)的或者指定的Affinity和Task一樣的話,就和標(biāo)準(zhǔn)模式一樣了啟動一個(gè)新的Activity.
FLAG_ACTIVITY_CLEAR_TOP
當(dāng)Intent對象包含這個(gè)標(biāo)記時(shí),如果在棧中發(fā)現(xiàn)存在Activity實(shí)例,則清空這個(gè)實(shí)例之上的Activity,使其處于棧頂。
在使用默認(rèn)的“standard”啟動模式下,如果沒有在Intent使用到FLAG_ACTIVITY_SINGLE_TOP標(biāo)記,那么它將關(guān)閉后重建,如果使用了這個(gè)FLAG_ACTIVITY_SINGLE_TOP標(biāo)記,則會使用已存在的實(shí)例;對于其他啟動模式,無需再使用FLAG_ACTIVITY_SINGLE_TOP,它都將使用已存在的實(shí)例,Intent會被傳遞到這個(gè)實(shí)例的onNewIntent()中。
3. 進(jìn)程保活
參考鏈接:Android 進(jìn)程?;钫惺酱笕?/a>
進(jìn)程?;畎▋蓚€(gè)方面:
- 提升進(jìn)程優(yōu)先級,降低進(jìn)程被殺死的概率。
- 進(jìn)程被殺死后,進(jìn)行拉活。
進(jìn)程優(yōu)先級
Android系統(tǒng)將盡量長時(shí)間地保持應(yīng)用進(jìn)程,但為了新建進(jìn)程或運(yùn)行更重要的進(jìn)程,最終需要清除舊進(jìn)程來回收內(nèi)存。 為了確定保留或終止哪些進(jìn)程,系統(tǒng)會根據(jù)進(jìn)程中正在運(yùn)行的組件以及這些組件的狀態(tài),將每個(gè)進(jìn)程放入“重要性層次結(jié)構(gòu)”中。 必要時(shí),系統(tǒng)會首先消除重要性最低的進(jìn)程,然后是清除重要性稍低一級的進(jìn)程,依此類推,以回收系統(tǒng)資源。

按照進(jìn)程的優(yōu)先級,進(jìn)程分為以下五類:
- 前臺進(jìn)程
- 可見進(jìn)程
- 服務(wù)進(jìn)程
- 后臺進(jìn)程
- 空進(jìn)程
前臺進(jìn)程
用戶操作所必需的進(jìn)程,如果一個(gè)進(jìn)程滿足以下任何一個(gè)條件,即為前臺進(jìn)程:
- 托管用戶正在交互的 Activity(已調(diào)用 Activity 的 onResume() 方法)
- 托管某個(gè)Service,后者綁定到用戶正在交互的Activity
- 托管正在“前臺”運(yùn)行的Service(服務(wù)已調(diào)用 startForeground())
- 托管正執(zhí)行一個(gè)生命周期回調(diào)的Service(onCreate()、onStart() 或 onDestroy())
- 托管正執(zhí)行其onReceive()方法的 BroadcastReceiver
通常,在任意給定時(shí)間前臺進(jìn)程都為數(shù)不多。
只有在內(nèi)存不足以支持它們同時(shí)繼續(xù)運(yùn)行這一萬不得已的情況下,系統(tǒng)才會終止它們。
此時(shí),設(shè)備往往已達(dá)到內(nèi)存分頁狀態(tài),因此需要終止一些前臺進(jìn)程來確保用戶界面正常響應(yīng)。
可見進(jìn)程
沒有任何前臺組件、但仍會影響用戶在屏幕上所見內(nèi)容的進(jìn)程。
如果一個(gè)進(jìn)程滿足以下任一條件,即視為可見進(jìn)程:
- 托管不在前臺、但仍對用戶可見的 Activity(已調(diào)用其 onPause() 方法)。
例如,如果前臺 Activity 啟動了一個(gè)對話框,允許在其后顯示上一 Activity,則有可能會發(fā)生這種情況。 - 托管綁定到可見(或前臺)Activity 的 Service。
可見進(jìn)程被視為是極其重要的進(jìn)程,除非為了維持所有前臺進(jìn)程同時(shí)運(yùn)行而必須終止,否則系統(tǒng)不會終止這些進(jìn)程。
服務(wù)進(jìn)程
盡管服務(wù)進(jìn)程與用戶所見內(nèi)容沒有直接關(guān)聯(lián),但是它們通常在執(zhí)行一些用戶關(guān)心的操作(例如,在后臺播放音樂或從網(wǎng)絡(luò)下載數(shù)據(jù))。因此,除非內(nèi)存不足以維持所有前臺進(jìn)程和可見進(jìn)程同時(shí)運(yùn)行,否則系統(tǒng)會讓服務(wù)進(jìn)程保持運(yùn)行狀態(tài)。
正在運(yùn)行 startService() 方法啟動的服務(wù),且不屬于上述兩個(gè)更高類別進(jìn)程的進(jìn)程。
后臺進(jìn)程
包含目前對用戶不可見的 Activity 的進(jìn)程(已調(diào)用 Activity 的 onStop() 方法)。
這些進(jìn)程對用戶體驗(yàn)沒有直接影響,系統(tǒng)可能隨時(shí)終止它們,以回收內(nèi)存供前臺進(jìn)程、可見進(jìn)程或服務(wù)進(jìn)程使用。
通常會有很多后臺進(jìn)程在運(yùn)行,因此它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進(jìn)程最后一個(gè)被終止。
如果某個(gè) Activity 正確實(shí)現(xiàn)了生命周期方法,并保存了其當(dāng)前狀態(tài),則終止其進(jìn)程不會對用戶體驗(yàn)產(chǎn)生明顯影響,因?yàn)楫?dāng)用戶導(dǎo)航回該 Activity 時(shí),Activity 會恢復(fù)其所有可見狀態(tài)。
空進(jìn)程
不含任何活動應(yīng)用組件的進(jìn)程。保留這種進(jìn)程的的唯一目的是用作緩存,以縮短下次在其中運(yùn)行組件所需的啟動時(shí)間。 為使總體系統(tǒng)資源在進(jìn)程緩存和底層內(nèi)核緩存之間保持平衡,系統(tǒng)往往會終止這些進(jìn)程。
進(jìn)程被殺死的情況

綜上,可以得出減少進(jìn)程被殺死概率無非就是想辦法提高進(jìn)程優(yōu)先級,減少進(jìn)程在內(nèi)存不足等情況下被殺死的概率。
提升進(jìn)程優(yōu)先級方案
1. 利用Activity提升
方案設(shè)計(jì)思想:監(jiān)控手機(jī)鎖屏解鎖事件,在屏幕鎖屏?xí)r啟動1個(gè)像素的 Activity,在用戶解鎖時(shí)將 Activity 銷毀掉。注意該 Activity 需設(shè)計(jì)成用戶無感知。
通過該方案,可以使進(jìn)程的優(yōu)先級在屏幕鎖屏?xí)r間由4提升為最高優(yōu)先級1。
方案適用范圍:
適用場景:本方案主要解決第三方應(yīng)用及系統(tǒng)管理工具在檢測到鎖屏事件后一段時(shí)間(一般為5分鐘以內(nèi))內(nèi)會殺死后臺進(jìn)程,已達(dá)到省電的目的問題。
適用版本:適用于所有的 Android 版本。
方案具體實(shí)現(xiàn):
首先定義 Activity,并設(shè)置 Activity 的大小為1像素。
其次,從 AndroidManifest 中通過如下屬性,排除 Activity 在 RecentTask 中的顯示。
excludeFromRecents="true"
exported="false"
finishOnTaskLaunch="false"
最后,設(shè)置Activity為透明主題。
2. 利用Notification提升
方案設(shè)計(jì)思想:Android 中 Service 的優(yōu)先級為4,通過 setForeground 接口可以將后臺 Service 設(shè)置為前臺 Service,使進(jìn)程的優(yōu)先級由4提升為2,從而使進(jìn)程的優(yōu)先級僅僅低于用戶當(dāng)前正在交互的進(jìn)程,與可見進(jìn)程優(yōu)先級一致,使進(jìn)程被殺死的概率大大降低。
方案實(shí)現(xiàn)挑戰(zhàn):從 Android2.3 開始調(diào)用 setForeground 將后臺 Service 設(shè)置為前臺 Service 時(shí),必須在系統(tǒng)的通知欄發(fā)送一條通知,也就是前臺 Service 與一條可見的通知時(shí)綁定在一起的。
對于不需要常駐通知欄的應(yīng)用來說,該方案雖好,但卻是用戶感知的,無法直接使用。
方案挑戰(zhàn)應(yīng)對措施:通過實(shí)現(xiàn)一個(gè)內(nèi)部 Service,在 LiveService 和其內(nèi)部 Service 中同時(shí)發(fā)送具有相同 ID 的 Notification,然后將內(nèi)部 Service 結(jié)束掉。隨著內(nèi)部 Service 的結(jié)束,Notification 將會消失,但系統(tǒng)優(yōu)先級依然保持為2。
方案適用范圍:適用于目前已知所有版本。
拉起進(jìn)程
1. 利用系統(tǒng)廣播拉活
方案設(shè)計(jì)思想:在發(fā)生特定系統(tǒng)事件時(shí),系統(tǒng)會發(fā)出響應(yīng)的廣播,通過在 AndroidManifest 中“靜態(tài)”注冊對應(yīng)的廣播監(jiān)聽器,即可在發(fā)生響應(yīng)事件時(shí)拉活。
方案適用范圍:適用于全部 Android 平臺。但存在如下幾個(gè)缺點(diǎn):
- 廣播接收器被管理軟件、系統(tǒng)軟件通過“自啟管理”等功能禁用的場景無法接收到廣播,從而無法自啟。
- 系統(tǒng)廣播事件不可控,只能保證發(fā)生事件時(shí)拉活進(jìn)程,但無法保證進(jìn)程掛掉后立即拉活。
因此,該方案主要作為備用手段。
2. 利用第三方廣播拉活
方案設(shè)計(jì)思想:該方案總的設(shè)計(jì)思想與接收系統(tǒng)廣播類似,不同的是該方案為接收第三方 Top 應(yīng)用廣播。
通過反編譯第三方 Top 應(yīng)用,如:手機(jī)QQ、微信、支付寶、UC瀏覽器等,以及友盟、信鴿、個(gè)推等 SDK,找出它們外發(fā)的廣播,在應(yīng)用中進(jìn)行監(jiān)聽,這樣當(dāng)這些應(yīng)用發(fā)出廣播時(shí),就會將我們的應(yīng)用拉活。
方案適用范圍:該方案的有效程度除與系統(tǒng)廣播一樣的因素外,主要受如下因素限制:
- 反編譯分析過的第三方應(yīng)用的多少
- 第三方應(yīng)用的廣播屬于應(yīng)用私有,當(dāng)前版本中有效的廣播,在后續(xù)版本隨時(shí)就可能被移除或被改為不外發(fā)。
這些因素都影響了拉活的效果。
3. 利用系統(tǒng)Service機(jī)制拉活
方案設(shè)計(jì)思想:將 Service 設(shè)置為 START_STICKY,利用系統(tǒng)機(jī)制在 Service 掛掉后自動拉活。
方案適用范圍:如下兩種情況無法拉活
Service 第一次被異常殺死后會在5秒內(nèi)重啟,第二次被殺死會在10秒內(nèi)重啟,第三次會在20秒內(nèi)重啟,一旦在短時(shí)間內(nèi) Service 被殺死達(dá)到5次,則系統(tǒng)不再拉起。
進(jìn)程被取得 Root 權(quán)限的管理工具或系統(tǒng)工具通過 forestop 停止掉,無法重啟。
4 利用Native進(jìn)程拉活
方案設(shè)計(jì)思想:
主要思想:利用 Linux 中的 fork 機(jī)制創(chuàng)建 Native 進(jìn)程,在 Native 進(jìn)程中監(jiān)控主進(jìn)程的存活,當(dāng)主進(jìn)程掛掉后,在 Native 進(jìn)程中立即對主進(jìn)程進(jìn)行拉活。
主要原理:在 Android 中所有進(jìn)程和系統(tǒng)組件的生命周期受 ActivityManagerService 的統(tǒng)一管理。而且,通過 Linux 的 fork 機(jī)制創(chuàng)建的進(jìn)程為純 Linux 進(jìn)程,其生命周期不受 Android 的管理。
感知進(jìn)程死亡
要在 Native 進(jìn)程中感知主進(jìn)程是否存活有兩種實(shí)現(xiàn)方式:
在 Native 進(jìn)程中通過死循環(huán)或定時(shí)器,輪訓(xùn)判斷主進(jìn)程是否存活,當(dāng)主進(jìn)程不存活時(shí)進(jìn)行拉活。該方案的很大缺點(diǎn)是不停的輪詢執(zhí)行判斷邏輯,非常耗電。
在主進(jìn)程中創(chuàng)建一個(gè)監(jiān)控文件,并且在主進(jìn)程中持有文件鎖。在拉活進(jìn)程啟動后申請文件鎖將會被堵塞,一旦可以成功獲取到鎖,說明主進(jìn)程掛掉,即可進(jìn)行拉活。由于 Android 中的應(yīng)用都運(yùn)行于虛擬機(jī)之上,Java 層的文件鎖與 Linux 層的文件鎖是不同的,要實(shí)現(xiàn)該功能需要封裝 Linux 層的文件鎖供上層調(diào)用。
拉活主進(jìn)程
通過 Native 進(jìn)程拉活主進(jìn)程的部分代碼如下,即通過 am 命令進(jìn)行拉活。通過指定“--include-stopped-packages”參數(shù)來拉活主進(jìn)程處于 forestop 狀態(tài)的情況。
如何保證 Native 進(jìn)程的唯一
從可擴(kuò)展性和進(jìn)程唯一等多方面考慮,將 Native 進(jìn)程設(shè)計(jì)成 C/S 結(jié)構(gòu)模式,主進(jìn)程與 Native 進(jìn)程通過 Localsocket 進(jìn)行通信。在Native進(jìn)程中利用 Localsocket 保證 Native 進(jìn)程的唯一性,不至于出現(xiàn)創(chuàng)建多個(gè) Native 進(jìn)程以及 Native 進(jìn)程變成僵尸進(jìn)程等問題。
方案適用范圍:該方案主要適用于 Android5.0 以下版本手機(jī)。
該方案不受 forcestop 影響,被強(qiáng)制停止的應(yīng)用依然可以被拉活,在 Android5.0 以下版本拉活效果非常好。
對于 Android5.0 以上手機(jī),系統(tǒng)雖然會將native進(jìn)程內(nèi)的所有進(jìn)程都?xì)⑺?,這里其實(shí)就是系統(tǒng)“依次”殺死進(jìn)程時(shí)間與拉活邏輯執(zhí)行時(shí)間賽跑的問題,如果可以跑的比系統(tǒng)邏輯快,依然可以有效拉起。記得網(wǎng)上有人做過實(shí)驗(yàn),該結(jié)論是成立的,在某些 Android 5.0 以上機(jī)型有效。
5. 利用 JobScheduler 機(jī)制拉活
方案設(shè)計(jì)思想:Android5.0 以后系統(tǒng)對 Native 進(jìn)程等加強(qiáng)了管理,Native 拉活方式失效。系統(tǒng)在 Android5.0 以上版本提供了 JobScheduler 接口,系統(tǒng)會定時(shí)調(diào)用該進(jìn)程以使應(yīng)用進(jìn)行一些邏輯操作。
在本項(xiàng)目中,我對 JobScheduler 進(jìn)行了進(jìn)一步封裝,兼容 Android5.0 以下版本。
方案適用范圍:該方案主要適用于 Android5.0 以上版本手機(jī)。
該方案在 Android5.0 以上版本中不受 forcestop 影響,被強(qiáng)制停止的應(yīng)用依然可以被拉活,在 Android5.0 以上版本拉活效果非常好。
僅在小米手機(jī)可能會出現(xiàn)有時(shí)無法拉活的問題。
6. 利用賬號同步機(jī)制拉活
方案設(shè)計(jì)思想:Android 系統(tǒng)的賬號同步機(jī)制會定期同步賬號進(jìn)行,該方案目的在于利用同步機(jī)制進(jìn)行進(jìn)程的拉活。
方案適用范圍:該方案適用于所有的 Android 版本,包括被 forestop 掉的進(jìn)程也可以進(jìn)行拉活。
最新 Android 版本(Android N)中系統(tǒng)好像對賬戶同步這里做了變動,該方法不再有效。
其他
其他還有一些技術(shù)之外的措施,比如說應(yīng)用內(nèi) Push 通道的選擇:
- 國外版應(yīng)用:接入 Google 的 GCM。
- 國內(nèi)版應(yīng)用:根據(jù)終端不同,在小米手機(jī)(包括 MIUI)接入小米推送、華為手機(jī)接入華為推送;其他手機(jī)可以考慮接入騰訊信鴿或極光推送與小米推送做 A/B Test。
4. IntentService與Service的區(qū)別?IntentService的實(shí)現(xiàn)原理。
本人已經(jīng)在另外一篇文章分析過,麻煩移步。[IntentService-你可能需要知道這些](http://www.itdecent.cn/p/6b9358cbfc26)
5. 優(yōu)雅的展示Bitmap大圖
郭霖大神的文章,移步。[優(yōu)雅的展示BitMap大圖](http://blog.csdn.net/guolin_blog/article/details/9316683)
6. Retrofit使用的注解是哪種注解?以及,注解的底層實(shí)現(xiàn)是怎樣的。
Retrofit采用的是運(yùn)行時(shí)注解,下面上實(shí)錘(@GET):
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
/**
* A relative or absolute path, or full URL of the endpoint. This value is optional if the first
* parameter of the method is annotated with {@link Url @Url}.
* <p>
* See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how
* this is resolved against a base URL to create the full endpoint URL.
*/
String value() default "";
}
我們可以看到@GET的@Retention(RUNTIME),為運(yùn)行時(shí)注解無疑。
關(guān)于注解,本人寫過一篇文章分析,麻煩移步。注解-你可能需要知道這些
7. Thread和HandlerThread
簡單的總結(jié)下,兩者的區(qū)別:
- HandlerThread是Thread的子類。
- HandlerThread內(nèi)部持有一個(gè)Looper,可以使用MessageQueue重復(fù)使用當(dāng)前線程,節(jié)省系統(tǒng)開銷。
- HandlerThread一般結(jié)合Handler使用,按順序處理任務(wù)。
HandlerThread的源碼很簡單,我們來簡單分析下。
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason is isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
/**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*
* @see #quitSafely
*/
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
/**
* Quits the handler thread's looper safely.
* <p>
* Causes the handler thread's looper to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* Pending delayed messages with due times in the future will not be delivered.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p>
* If the thread has not been started or has finished (that is if
* {@link #getLooper} returns null), then false is returned.
* Otherwise the looper is asked to quit and true is returned.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*/
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
/**
* Returns the identifier of this thread. See Process.myTid().
*/
public int getThreadId() {
return mTid;
}
}
從源碼中,我們可以看到,HandlerThread繼承于Thread。
重點(diǎn)來看下run方法:
@Override
public void run() {
mTid = Process.myTid();
// 為當(dāng)前線程設(shè)置Looper
Looper.prepare();
synchronized (this) {
// getLooper()返回的就是mLooper
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
// 開始處理消息隊(duì)列
Looper.loop();
mTid = -1;
}
Handler+Thread的方式,在開發(fā)中我們經(jīng)常使用到,我們來對比下HandlerThread+Handler和Handler+Thread的實(shí)現(xiàn)方式。
- Handler+Thread
private MyHandler mHandler;
public void buildHandler() {
new Thread(new Runnable() {
@Override
public void run() {
// 為當(dāng)前線程設(shè)置Looper
Looper.prepare();
// 使用當(dāng)前線程的Looper構(gòu)造Handler
mHandler = new MyHandler(Looper.myLooper());
// 開始處理MessageQueue
Looper.loop();
}
}).start();
}
class MyHandler extends Handler {
MyHandler(Looper looper){
super(looper);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
- HandlerThread+Handler
private MyHandler mHandler;
public void buildHandler() {
// 構(gòu)造HandlerThread
HandlerThread handlerThread = new HandlerThread("WorkThread");
handlerThread.start();
// 直接使用HandlerThread的looper創(chuàng)建Handler
mHandler = new MyHandler(handlerThread.getLooper());
}
class MyHandler extends Handler {
MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
結(jié)合HandlerThread的源碼和兩種方式的對比,驗(yàn)證了我們在開頭的總結(jié)。
- HandlerThread是Thread的子類。
2.HandlerThread內(nèi)部持有一個(gè)Looper,可以使用MessageQueue重復(fù)使用當(dāng)前線程,節(jié)省系統(tǒng)開銷。 - HandlerThread一般結(jié)合Handler使用,按順序處理任務(wù)。
8. Java是值傳遞還是引用傳遞
Java中方法參數(shù)傳遞方式是按值傳遞。
如果參數(shù)是基本類型,傳遞的是基本類型的字面量值的拷貝。
如果參數(shù)是引用類型,傳遞的是該參量所引用的對象在堆中地址值的拷貝。
9. final和static的區(qū)別
static
static變量
按照是否靜態(tài)的對類成員變量進(jìn)行分類可分兩種:
- 一種是被static修飾的變量,叫靜態(tài)變量或類變量。
- 另一種是沒有被static修飾的變量,叫實(shí)例變量。
兩者的區(qū)別是:
靜態(tài)變量在內(nèi)存中只有一個(gè)拷貝(節(jié)省內(nèi)存),JVM只為靜態(tài)變量分配一次內(nèi)存,在加載類的過程中完成靜態(tài)變量的內(nèi)存分配,可用類名直接訪問(方便),當(dāng)然也可以通過對象來訪問(但是這是不推薦的)。
實(shí)例變量屬于某個(gè)確定的實(shí)例,每當(dāng)創(chuàng)建一個(gè)實(shí)例,就會為實(shí)例變量分配一次內(nèi)存,實(shí)例變量可以在內(nèi)存中有多個(gè)拷貝,互不影響(靈活)。
static代碼塊
static代碼塊是類加載時(shí),初始化自動執(zhí)行的。如果static代碼塊有多個(gè),JVM將按照它們在類中出現(xiàn)的先后順序依次執(zhí)行它們,每個(gè)代碼塊只會被執(zhí)行一次。
static方法
static方法是被static修飾的方法,可以通過類名直接調(diào)用,因此static方法不能使用this和super關(guān)鍵字(實(shí)例),不能訪問類實(shí)例變量和方法,只能訪問靜態(tài)變量和靜態(tài)方法;因?yàn)槠鋵儆陬惗?dú)立于任何實(shí)例,所以必須為非抽象。
因?yàn)榉椒▽儆陬?,所以子類和父類可以存在同名的方法,但是不涉及重載(不能被繼承)。
final
final變量
聲明 final 字段有助于編譯器作出更好的優(yōu)化決定,因?yàn)槿绻幾g器知道字段的值不會更改,那么它能安全地在寄存器中高速緩存該值。
final 字段還通過讓編譯器強(qiáng)制該字段為只讀來提供額外的安全級別。
其初始化可以在兩個(gè)地方:
- 一是其定義處,也就是說在final變量定義時(shí)直接給其賦值
- 二是在構(gòu)造函數(shù)中。
這兩個(gè)地方只能選其一,要么在定義時(shí)給值,要么在構(gòu)造函數(shù)中給值,不能同時(shí)既在定義時(shí)給了值,又在構(gòu)造函數(shù)中給另外的值。不能通過調(diào)用方法來賦值。
一旦被初始化便不可改變,這里不可改變的意思對基本類型來說是其值不可變,而對于對象變量來說其引用不可再變。
final方法
- 把方法鎖定,防止任何繼承類修改它的意義和實(shí)現(xiàn)。
- 高效。編譯器在遇到調(diào)用final方法時(shí)候會轉(zhuǎn)入內(nèi)嵌inline機(jī)制,大大提高執(zhí)行效率。
final類
final類不能被繼承,因此final類的成員方法沒有機(jī)會被覆蓋,默認(rèn)都是final的。
在設(shè)計(jì)類時(shí)候,如果這個(gè)類不需要有子類,類的實(shí)現(xiàn)細(xì)節(jié)不允許改變,并且確信這個(gè)類不會載被擴(kuò)展,那么就設(shè)計(jì)為final類
Note:同時(shí)被static和final修飾的變量,必須在聲明時(shí)立即賦值。
可以同時(shí)使用static和final來修飾方法,但是意義不大。因?yàn)楸籹tatic修飾的方法本身就是無
法被繼承的。
10. HashMap的實(shí)現(xiàn)原理
本人已經(jīng)分析了HashMap的實(shí)現(xiàn)原理,請移步。HashMap-你可能需要知道這些
11. HashMap和HashSet的區(qū)別
| HashMap | HashSet |
|---|---|
| HashMap實(shí)現(xiàn)了Map接口 | HashSet實(shí)現(xiàn)了Set接口 |
| HashMap存儲鍵值對 | HashSet存儲對象 |
| 使用put方法添加值 | 使用add方法添加值 |
| HashMap中使用鍵對象來計(jì)算hashcode值 | HashSet使用成員對象來計(jì)算hashcode值,對于兩個(gè)對象來說hashcode可能相同,所以equals()方法用來判斷對象的相等性,如果兩個(gè)對象不同的話,那么返回false |
| HashMap比較快,因?yàn)槭鞘褂梦ㄒ坏逆I來獲取對象 | HashSet較HashMap來說比較慢 |
HashSet取值的時(shí)候部分情況會比HashMap快,因?yàn)镠ashSet不允許重復(fù)值,hash到那個(gè)地方直接取值了,而HashMap有可能有下拉鏈表,這樣就要再遍歷鏈表了;算法兩者用的都是hash算法,而hashMap可能多一步鏈表遍歷,所以肯定是HashSet部分情況比hashMap快。
HashSet不允許存在重復(fù)的值。(hashCode和equals來判斷)
12. 淺拷貝&深拷貝區(qū)別
淺拷貝:使用一個(gè)已知實(shí)例對新創(chuàng)建實(shí)例的成員變量逐個(gè)賦值,這個(gè)方式被稱為淺拷貝。
深拷貝:當(dāng)一個(gè)類的拷貝構(gòu)造方法,不僅要復(fù)制對象的所有非引用成員變量值,還要為引用類型的成員變量創(chuàng)建新的實(shí)例,并且初始化為形式參數(shù)實(shí)例值,這個(gè)方式稱為深拷貝。
也就是說淺拷貝只復(fù)制一個(gè)對象,傳遞引用,不能復(fù)制實(shí)例。而深拷貝對對象內(nèi)部的引用均復(fù)制,它是創(chuàng)建一個(gè)新的實(shí)例,并且復(fù)制實(shí)例。
深拷貝實(shí)現(xiàn)方式:
- 對其引用變量逐個(gè)深拷貝并復(fù)制給新對象。
- 實(shí)現(xiàn)序列化接口,并結(jié)合流進(jìn)行寫入和讀出。(ByteArrayOutputStream&ObjectInputStream)。
public Object deepClone() throws Exception{
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
13. 靜態(tài)代理&動態(tài)代理
移步本人的文章,注解-你可能需要知道這些.
14. LayoutInflater.inflate有幾種使用方式
LayoutInflater的獲?。?/p>
- LayoutInflater.from(context)
- LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
第一種方式是封裝了第二種方式。
inflate方法:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
總結(jié)的話有三種:
- resource!=null,root==null,attachToRott=false,返回解析到的resource的布局,布局參數(shù)無效。
- resource!=null,root!=null,attachToRott=false,返回解析到的resource的布局,布局參數(shù)有效。
- resource!=null,root!=null,attachToRott=true,返回root,resource被添加到root中,布局參數(shù)有效。
15.MVC與MVP的區(qū)別
MVC:
- Activity不僅要顯示UI,還擔(dān)任了一部分Controller的職責(zé)
- 請求的業(yè)務(wù)代碼往往被丟到了Activity里面,布局文件只能提供默認(rèn)的UI設(shè)置,所以開發(fā)中視圖層的變化也被丟到了Activity里面。
- 再加上Activity本身承擔(dān)著控制層的責(zé)任。所以Activity達(dá)成了MVC集合的成就,實(shí)現(xiàn)了代碼的耦合,最終我們的Activity就變得越來越難看,從幾百行變成了幾千行。維護(hù)的成本也越來越高。
MVP:
MVP與MVC最大的不同,其實(shí)是Activity職責(zé)的變化,由原來的C (控制層) 變成了 V(視圖層),不再管控制層的問題,只管如何去顯示。
控制層的角色就由我們的新人 Presenter來擔(dān)當(dāng),這種架構(gòu)就解決了Activity過度耦合控制層和視圖層的問題。
16. Activity有沒有事件分發(fā)機(jī)制?
有,必須有。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
Activity是事件分發(fā)的起點(diǎn),只有Activity不攔截處理事件,事件才會分發(fā)至Window--DecorView--View。
17. 引用的分類和區(qū)別
引用主要分為四種類型:
強(qiáng)引用
Object object = new Object(),object就是一個(gè)強(qiáng)引用了。
當(dāng)內(nèi)存空間不足,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯(cuò)誤,使程序異常終止,也不會靠隨意回收具有強(qiáng)引用的對象來解決內(nèi)存不足問題。軟引用
只有內(nèi)存不夠時(shí)才回收,常用于緩存;當(dāng)內(nèi)存達(dá)到一個(gè)閥值,GC就會去回收它;弱引用
弱引用的對象擁有更短暫的生命周期。
在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當(dāng)前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。虛引用
"虛引用"顧名思義,就是形同虛設(shè),與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個(gè)對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時(shí)候都可能被垃圾回收。
18. Synchronized
作用位置:
- synchronized 方法
- synchronized static 方法
- 代碼塊
synchronized方法:
- synchronized方法控制對類成員變量的訪問.
- 每個(gè)類實(shí)例對應(yīng)一把鎖,每個(gè)synchronized方法都必須獲得調(diào)用該方法的類實(shí)例的鎖方能執(zhí)行,否則所屬線程阻塞,方法一旦執(zhí)行,就獨(dú)占該鎖,直到從該方法返回時(shí)才將鎖釋放,此后被阻塞的線程方能獲得該鎖,重新進(jìn)入可執(zhí)行狀態(tài).
- 同一時(shí)刻對于每一個(gè)類實(shí)例,其所有聲明為 synchronized 的成員方法中至多只有一個(gè)處于可執(zhí)行狀態(tài)(因?yàn)橹炼嘀挥幸粋€(gè)能夠獲得該類實(shí)例對應(yīng)的鎖),從而有效避免了類成員變量的訪問沖突(只要所有可能訪問類成員變量的方法均被聲明為 synchronized.
synchronized static 方法:
某個(gè)類的范圍,synchronized static aStaticMethod{}防止多個(gè)線程同時(shí)訪問這個(gè)類中的synchronized static 方法。它可以對類的所有對象實(shí)例起作用。
Class Foo {
// 同步的static 函數(shù)
public synchronized static void methodAAA() {
//….
}
public void methodBBB() {
synchronized(Foo.class) // class literal(類名稱字面常量)
}
}
代碼中的methodBBB()方法是把class literal作為鎖的情況,它和同步的static函數(shù)產(chǎn)生的效果是一樣的,取得的鎖很特別,是當(dāng)前調(diào)用這個(gè)方法的對象所屬的類(Class,而不再是由這個(gè)Class產(chǎn)生的某個(gè)具體對象了)。
synchronized代碼塊:
- 一個(gè)時(shí)間內(nèi)只能有一個(gè)線程得到執(zhí)行。另一個(gè)線程必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。
- 當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),另一個(gè)線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
- 一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
- 也就是說,當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),它就獲得了這個(gè)object的對象鎖。結(jié)果,其它線程對該object對象所有同步代碼部分的訪問都被暫時(shí)阻塞。
19. 解耦的本質(zhì)
設(shè)計(jì)模式中的幾大原則:
- SRP:單一職責(zé)原則,一個(gè)類應(yīng)該僅有一個(gè)引起它變化的原因。
- OCP:開閉原則,對于擴(kuò)展開發(fā),對于修改封閉。
- 里氏替換原則,所有使用基類的地方必須能透明的使用其子類對象。
- DIP:依賴倒置原則。指代了一種特定的解耦形式,使得高層次的模塊不依賴于低層次模塊的實(shí)現(xiàn)細(xì)節(jié),依賴模塊被顛倒了。
a. 高層模塊不應(yīng)該依賴于低層次模塊,兩者都應(yīng)該依賴于抽象。
b. 抽象不應(yīng)該依賴于細(xì)節(jié),細(xì)節(jié)應(yīng)該依賴于抽象。
總結(jié):模塊間的依賴通過抽象發(fā)生,實(shí)現(xiàn)類不應(yīng)該發(fā)生直接的依賴關(guān)系,依賴關(guān)系是通過接口或者抽象實(shí)現(xiàn)。 - ISP:接口隔離原則,類之間的依賴關(guān)系應(yīng)該建立在最小的接口之上。
- LOD:迪米特原則,一個(gè)對象應(yīng)該對其他對象有最少的了解。
那么很明確了,解耦的本質(zhì)就是DIP原則。
20. Android運(yùn)行時(shí)權(quán)限
權(quán)限分類
危險(xiǎn)權(quán)限
phone
讀寫外部存儲
camera
如果你申請某個(gè)危險(xiǎn)的權(quán)限,假設(shè)你的app早已被用戶授權(quán)了同一組的某個(gè)危險(xiǎn)權(quán)限,那么系統(tǒng)會立即授權(quán),而不需要用戶去點(diǎn)擊授權(quán)。
彈出的權(quán)限請求dialog描述的是一組權(quán)限,無法定制
正常權(quán)限
獲取網(wǎng)絡(luò)狀態(tài)
如何檢查和申請
檢查
checkSlefPermission
deny
grant
請求
requestPermissions
異步方法
處理的回調(diào)方法:onRequestPermissionsResult
解釋
shouldShowRequestPermissionRationale
只有在第一次用戶已經(jīng)拒絕情況下,該方法才會返回true
開源框架
MPermissions
21. Activity-Window-View
- 每個(gè)Activity在創(chuàng)建的時(shí)候,都會產(chǎn)生一個(gè)Window對象,這個(gè)Window對象實(shí)際為PhoneWindow。
- PhoneWindow對象包含一個(gè)根View:DecorView,Activity要顯示的內(nèi)容就包含在DecorView中。
setContentView的顯示流程
Activity setContentView->PhoneWindow setContentView ->mLayoutInflater.inflate(layoutResID, mContentParent) -> onResume時(shí)將DecorView添加至WindowManager ->WindowManagerImpl.addView--ViewRootImpl.setView ->1. ViewRootImpl.requestLayout -> 2. 向WMS發(fā)起顯示W(wǎng)indow的請求
22. Activity與Fragment通信方式
- Handler
- 接口
- EventBus
- 廣播
23. Fragment的坑
getActivity為空
在Fragment基類里設(shè)置一個(gè)Activity mActivity的全局變量,在onAttach(Activity activity)里賦值,使用mActivity代替getActivity(),保證Fragment即使在onDetach后,仍持有Activity的引用(有引起內(nèi)存泄露的風(fēng)險(xiǎn),但是異步任務(wù)沒停止的情況下,本身就可能已內(nèi)存泄漏,相比Crash,這種做法“安全”些)
Can not perform this action after onSaveInstanceState
在重新回到該Activity的時(shí)候(onResumeFragments()或onPostResume()),再執(zhí)行該事務(wù)!
Fragment重疊異常
在類onCreate()的方法加載Fragment,并且沒有判斷saveInstanceState==null或if(findFragmentByTag(mFragmentTag) == null),導(dǎo)致重復(fù)加載了同一個(gè)Fragment導(dǎo)致重疊。
24. 在向Fragment傳遞參數(shù)時(shí),為什么不采用構(gòu)造方法中傳遞?
- 如果通過構(gòu)造參數(shù)進(jìn)行傳遞,那么在Fragment銷毀重建時(shí)參數(shù)就無法保持了。
- 一般通過setArguments傳遞參數(shù),從而在Fragment銷毀重建時(shí)保持?jǐn)?shù)據(jù)。
那么參數(shù)是如何保持的呢?
Activity--onSaveInstanceState
protected void onSaveInstanceState(Bundle outState) {
// 保存Activity的視圖狀態(tài)
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
// 獲取了所有Fragment的狀態(tài)并保存到了outState中
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
Activity--onCreate
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
if (mLastNonConfigurationInstances != null) {
mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
}
if (mActivityInfo.parentActivityName != null) {
if (mActionBar == null) {
mEnableDefaultActionBarUp = true;
} else {
mActionBar.setDefaultDisplayHomeAsUpEnabled(true);
}
}
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
// 恢復(fù)保存的Fragment狀態(tài)
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.fragments : null);
}
mFragments.dispatchCreate();
getApplication().dispatchActivityCreated(this, savedInstanceState);
if (mVoiceInteractor != null) {
mVoiceInteractor.attachActivity(this);
}
mCalled = true;
}
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (mWindow != null) {
// 恢復(fù)視圖狀態(tài)
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if (windowState != null) {
mWindow.restoreHierarchyState(windowState);
}
}
}
從上面兩個(gè)接口我們可以看到,在Activity銷毀重建時(shí)會保存恢復(fù)Fragment的狀態(tài),但是我們還是沒有看到setArguments的參數(shù)保持啊,接著往下看。
public FragmentState(Fragment frag) {
mClassName = frag.getClass().getName();
mIndex = frag.mIndex;
mFromLayout = frag.mFromLayout;
mFragmentId = frag.mFragmentId;
mContainerId = frag.mContainerId;
mTag = frag.mTag;
mRetainInstance = frag.mRetainInstance;
mDetached = frag.mDetached;
mArguments = frag.mArguments;
mHidden = frag.mHidden;
}
Parcelable p = mFragments.saveAllState();
保存Fragment信息:
public Parcelable saveAllState() {
return mHost.mFragmentManager.saveAllState();
}
// FragmentManager->saveAllState
Parcelable saveAllState() {
// Make sure all pending operations have now been executed to get
// our state update-to-date.
execPendingActions();
mStateSaved = true;
if (mActive == null || mActive.size() <= 0) {
return null;
}
// First collect all active fragments.
int N = mActive.size();
FragmentState[] active = new FragmentState[N];
boolean haveFragments = false;
for (int i=0; i<N; i++) {
Fragment f = mActive.get(i);
if (f != null) {
if (f.mIndex < 0) {
throwException(new IllegalStateException(
"Failure saving state: active " + f
+ " has cleared index: " + f.mIndex));
}
haveFragments = true;
// 構(gòu)造待保存Fragment的狀態(tài)信息(變量)
// FragmentState會記錄Fragment的mArguments
FragmentState fs = new FragmentState(f);
active[i] = fs;
if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
// 保存fragment的一些視圖狀態(tài)信息
fs.mSavedFragmentState = saveFragmentBasicState(f);
if (f.mTarget != null) {
if (f.mTarget.mIndex < 0) {
throwException(new IllegalStateException(
"Failure saving state: " + f
+ " has target not in fragment manager: " + f.mTarget));
}
if (fs.mSavedFragmentState == null) {
fs.mSavedFragmentState = new Bundle();
}
putFragment(fs.mSavedFragmentState,
FragmentManagerImpl.TARGET_STATE_TAG, f.mTarget);
if (f.mTargetRequestCode != 0) {
fs.mSavedFragmentState.putInt(
FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG,
f.mTargetRequestCode);
}
}
} else {
fs.mSavedFragmentState = f.mSavedFragmentState;
}
if (DEBUG) Log.v(TAG, "Saved state of " + f + ": "
+ fs.mSavedFragmentState);
}
}
if (!haveFragments) {
if (DEBUG) Log.v(TAG, "saveAllState: no fragments!");
return null;
}
int[] added = null;
BackStackState[] backStack = null;
// Build list of currently added fragments.
if (mAdded != null) {
N = mAdded.size();
if (N > 0) {
added = new int[N];
for (int i=0; i<N; i++) {
added[i] = mAdded.get(i).mIndex;
if (added[i] < 0) {
throwException(new IllegalStateException(
"Failure saving state: active " + mAdded.get(i)
+ " has cleared index: " + added[i]));
}
if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i
+ ": " + mAdded.get(i));
}
}
}
// Now save back stack.
if (mBackStack != null) {
N = mBackStack.size();
if (N > 0) {
backStack = new BackStackState[N];
for (int i=0; i<N; i++) {
backStack[i] = new BackStackState(this, mBackStack.get(i));
if (DEBUG) Log.v(TAG, "saveAllState: adding back stack #" + i
+ ": " + mBackStack.get(i));
}
}
}
FragmentManagerState fms = new FragmentManagerState();
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
return fms;
}
恢復(fù)信息:
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.fragments : null);
void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
// 檢查保存的狀態(tài)信息
if (state == null) return;
FragmentManagerState fms = (FragmentManagerState)state;
if (fms.mActive == null) return;
List<FragmentManagerNonConfig> childNonConfigs = null;
// First re-attach any non-config instances we are retaining back
// to their saved state, so we don't try to instantiate them again.
if (nonConfig != null) {
List<Fragment> nonConfigFragments = nonConfig.getFragments();
childNonConfigs = nonConfig.getChildNonConfigs();
final int count = nonConfigFragments != null ? nonConfigFragments.size() : 0;
for (int i = 0; i < count; i++) {
Fragment f = nonConfigFragments.get(i);
if (DEBUG) Log.v(TAG, "restoreAllState: re-attaching retained " + f);
FragmentState fs = fms.mActive[f.mIndex];
fs.mInstance = f;
f.mSavedViewState = null;
f.mBackStackNesting = 0;
f.mInLayout = false;
f.mAdded = false;
f.mTarget = null;
if (fs.mSavedFragmentState != null) {
fs.mSavedFragmentState.setClassLoader(mHost.getContext().getClassLoader());
f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray(
FragmentManagerImpl.VIEW_STATE_TAG);
f.mSavedFragmentState = fs.mSavedFragmentState;
}
}
}
// Build the full list of active fragments, instantiating them from
// their saved state.
mActive = new ArrayList<>(fms.mActive.length);
if (mAvailIndices != null) {
mAvailIndices.clear();
}
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
FragmentManagerNonConfig childNonConfig = null;
if (childNonConfigs != null && i < childNonConfigs.size()) {
childNonConfig = childNonConfigs.get(i);
}
// 恢復(fù)Fragment
// 通過FragmentState來生成新的Fragment,之前保存到惡狀態(tài)信息得以恢復(fù)
Fragment f = fs.instantiate(mHost, mParent, childNonConfig);
if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
mActive.add(f);
fs.mInstance = null;
} else {
mActive.add(null);
if (mAvailIndices == null) {
mAvailIndices = new ArrayList<>();
}
if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);
mAvailIndices.add(i);
}
}
// Update the target of all retained fragments.
if (nonConfig != null) {
List<Fragment> nonConfigFragments = nonConfig.getFragments();
final int count = nonConfigFragments != null ? nonConfigFragments.size() : 0;
for (int i = 0; i < count; i++) {
Fragment f = nonConfigFragments.get(i);
if (f.mTargetIndex >= 0) {
if (f.mTargetIndex < mActive.size()) {
f.mTarget = mActive.get(f.mTargetIndex);
} else {
Log.w(TAG, "Re-attaching retained fragment " + f
+ " target no longer exists: " + f.mTargetIndex);
f.mTarget = null;
}
}
}
}
// Build the list of currently added fragments.
if (fms.mAdded != null) {
mAdded = new ArrayList<Fragment>(fms.mAdded.length);
for (int i=0; i<fms.mAdded.length; i++) {
Fragment f = mActive.get(fms.mAdded[i]);
if (f == null) {
throwException(new IllegalStateException(
"No instantiated fragment for index #" + fms.mAdded[i]));
}
f.mAdded = true;
if (DEBUG) Log.v(TAG, "restoreAllState: added #" + i + ": " + f);
if (mAdded.contains(f)) {
throw new IllegalStateException("Already added!");
}
mAdded.add(f);
}
} else {
mAdded = null;
}
// Build the back stack.
if (fms.mBackStack != null) {
mBackStack = new ArrayList<BackStackRecord>(fms.mBackStack.length);
for (int i=0; i<fms.mBackStack.length; i++) {
BackStackRecord bse = fms.mBackStack[i].instantiate(this);
if (DEBUG) {
Log.v(TAG, "restoreAllState: back stack #" + i
+ " (index " + bse.mIndex + "): " + bse);
LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
PrintWriter pw = new FastPrintWriter(logw, false, 1024);
bse.dump(" ", pw, false);
pw.flush();
}
mBackStack.add(bse);
if (bse.mIndex >= 0) {
setBackStackIndex(bse.mIndex, bse);
}
}
} else {
mBackStack = null;
}
}
FragmentState.instantiate
public Fragment instantiate(FragmentHostCallback host, Fragment parent,
FragmentManagerNonConfig childNonConfig) {
if (mInstance == null) {
final Context context = host.getContext();
if (mArguments != null) {
mArguments.setClassLoader(context.getClassLoader());
}
mInstance = Fragment.instantiate(context, mClassName, mArguments);
if (mSavedFragmentState != null) {
mSavedFragmentState.setClassLoader(context.getClassLoader());
mInstance.mSavedFragmentState = mSavedFragmentState;
}
mInstance.setIndex(mIndex, parent);
mInstance.mFromLayout = mFromLayout;
mInstance.mRestored = true;
mInstance.mFragmentId = mFragmentId;
mInstance.mContainerId = mContainerId;
mInstance.mTag = mTag;
mInstance.mRetainInstance = mRetainInstance;
mInstance.mDetached = mDetached;
mInstance.mHidden = mHidden;
mInstance.mFragmentManager = host.mFragmentManager;
if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
"Instantiated fragment " + mInstance);
}
mInstance.mChildNonConfig = childNonConfig;
return mInstance;
}
25. RecyclerView
- 為什么要使用RecyclerView?
===提供了插拔式體驗(yàn),高度解耦
===內(nèi)部實(shí)現(xiàn)了ViewHolder機(jī)制,因此使用上更方便
- 你想要控制其顯示的方式,請通過布局管理器LayoutManager
- 你想要控制Item間的間隔(可繪制),請通過ItemDecoration
- 你想要控制Item增刪的動畫,請通過ItemAnimator
- 你想要控制點(diǎn)擊、長按事件,請自己寫(擦,這點(diǎn)尼瑪。)
Adapter的幾個(gè)主要方法:
- public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
- public abstract void onBindViewHolder(VH holder, int position);