Android面試點(diǎn)總結(jié)

為了面試,為了高工資,廢話不多說,不定期更新。

1. Activity正常和異常情況下的生命周期分析。

Activity生命周期是一個(gè)老生常談的問題,但是有些刁鉆的問題也會一時(shí)半會的影響大家的思路,我們還是耐著性子來分析下吧。

I.正常生命周期

  1. onCreate

Avtivity啟動時(shí)調(diào)用的第一個(gè)方法,表示Activity正在被創(chuàng)建。應(yīng)該在此初始化Activity的必需組件,最重要的是必需在該方法中調(diào)用setContentView方法來設(shè)置Activity的布局。
不應(yīng)在此接口中執(zhí)行耗時(shí)操作,否則會影響Activity的顯示。

  1. onStart

在Activity即將對用戶可見之前調(diào)用(該接口與onResume對應(yīng),onResume代表已經(jīng)可見了),可以理解為已經(jīng)在后臺準(zhǔn)備就緒了。

  1. onResume

在Activity即將開始與用戶進(jìn)行交互前調(diào)用,此時(shí)Activity處于Activity堆棧的頂層,并具有用戶輸入焦點(diǎn)。

  1. 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。

  1. onStop

在Activity對用戶完全不可見時(shí)調(diào)用,可以認(rèn)為處于后臺。

  1. onDestory

在Activity被銷毀前調(diào)用,這是Activity將收到的最后的調(diào)用。
一般會在此銷毀啟動的線程,或者處理一些可能導(dǎo)致內(nèi)存泄露的問題。

  1. 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.異常生命周期

  1. 資源配置發(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),這是一種典型的委托思想,上層委托下層,父容器委托子容器去處理一些事情。

  1. 資源或者內(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í)候,給面試官舉例說明,更加簡潔明了,省的表述不清。

啟動模式指定

啟動模式的指定存在兩種方式:

  1. launchMode指定:無法指定FLAG_ACTIVITY_CLEAR_TOP.
  2. 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è)方面:

  1. 提升進(jìn)程優(yōu)先級,降低進(jìn)程被殺死的概率。
  2. 進(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)程的優(yōu)先級,進(jìn)程分為以下五類:

  1. 前臺進(jìn)程
  2. 可見進(jìn)程
  3. 服務(wù)進(jìn)程
  4. 后臺進(jìn)程
  5. 空進(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)程被殺死概率無非就是想辦法提高進(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):

  1. 廣播接收器被管理軟件、系統(tǒng)軟件通過“自啟管理”等功能禁用的場景無法接收到廣播,從而無法自啟。
  2. 系統(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)廣播一樣的因素外,主要受如下因素限制:

  1. 反編譯分析過的第三方應(yīng)用的多少
  2. 第三方應(yīng)用的廣播屬于應(yīng)用私有,當(dāng)前版本中有效的廣播,在后續(xù)版本隨時(shí)就可能被移除或被改為不外發(fā)。

這些因素都影響了拉活的效果。

3. 利用系統(tǒng)Service機(jī)制拉活

方案設(shè)計(jì)思想:將 Service 設(shè)置為 START_STICKY,利用系統(tǒng)機(jī)制在 Service 掛掉后自動拉活。

方案適用范圍:如下兩種情況無法拉活

  1. Service 第一次被異常殺死后會在5秒內(nèi)重啟,第二次被殺死會在10秒內(nèi)重啟,第三次會在20秒內(nèi)重啟,一旦在短時(shí)間內(nèi) Service 被殺死達(dá)到5次,則系統(tǒng)不再拉起。

  2. 進(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)方式:

  1. 在 Native 進(jìn)程中通過死循環(huán)或定時(shí)器,輪訓(xùn)判斷主進(jìn)程是否存活,當(dāng)主進(jìn)程不存活時(shí)進(jìn)行拉活。該方案的很大缺點(diǎn)是不停的輪詢執(zhí)行判斷邏輯,非常耗電。

  2. 在主進(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ū)別:

  1. HandlerThread是Thread的子類。
  2. HandlerThread內(nèi)部持有一個(gè)Looper,可以使用MessageQueue重復(fù)使用當(dāng)前線程,節(jié)省系統(tǒng)開銷。
  3. 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)方式。

  1. 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);
        }
    }


  1. 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é)。

  1. HandlerThread是Thread的子類。
    2.HandlerThread內(nèi)部持有一個(gè)Looper,可以使用MessageQueue重復(fù)使用當(dāng)前線程,節(jié)省系統(tǒng)開銷。
  2. HandlerThread一般結(jié)合Handler使用,按順序處理任務(wù)。

8. Java是值傳遞還是引用傳遞

Java中方法參數(shù)傳遞方式是按值傳遞。
如果參數(shù)是基本類型,傳遞的是基本類型的字面量值的拷貝。
如果參數(shù)是引用類型,傳遞的是該參量所引用的對象在堆中地址值的拷貝。


9. final和static的區(qū)別

static

static變量

按照是否靜態(tài)的對類成員變量進(jìn)行分類可分兩種:

  1. 一種是被static修飾的變量,叫靜態(tài)變量或類變量。
  2. 另一種是沒有被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è)地方:

  1. 一是其定義處,也就是說在final變量定義時(shí)直接給其賦值
  2. 二是在構(gòu)造函數(shù)中。

這兩個(gè)地方只能選其一,要么在定義時(shí)給值,要么在構(gòu)造函數(shù)中給值,不能同時(shí)既在定義時(shí)給了值,又在構(gòu)造函數(shù)中給另外的值。不能通過調(diào)用方法來賦值。

一旦被初始化便不可改變,這里不可改變的意思對基本類型來說是其值不可變,而對于對象變量來說其引用不可再變。

final方法

  1. 把方法鎖定,防止任何繼承類修改它的意義和實(shí)現(xiàn)。
  2. 高效。編譯器在遇到調(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)方式:

  1. 對其引用變量逐個(gè)深拷貝并復(fù)制給新對象。
  2. 實(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>

  1. LayoutInflater.from(context)
  2. LayoutInflater LayoutInflater =
    (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

第一種方式是封裝了第二種方式。

inflate方法:

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 

總結(jié)的話有三種:

  1. resource!=null,root==null,attachToRott=false,返回解析到的resource的布局,布局參數(shù)無效。
  2. resource!=null,root!=null,attachToRott=false,返回解析到的resource的布局,布局參數(shù)有效。
  3. resource!=null,root!=null,attachToRott=true,返回root,resource被添加到root中,布局參數(shù)有效。

15.MVC與MVP的區(qū)別

MVC:

  1. Activity不僅要顯示UI,還擔(dān)任了一部分Controller的職責(zé)
  2. 請求的業(yè)務(wù)代碼往往被丟到了Activity里面,布局文件只能提供默認(rèn)的UI設(shè)置,所以開發(fā)中視圖層的變化也被丟到了Activity里面。
  3. 再加上Activity本身承擔(dān)著控制層的責(zé)任。所以Activity達(dá)成了MVC集合的成就,實(shí)現(xiàn)了代碼的耦合,最終我們的Activity就變得越來越難看,從幾百行變成了幾千行。維護(hù)的成本也越來越高。

MVP:

  1. MVP與MVC最大的不同,其實(shí)是Activity職責(zé)的變化,由原來的C (控制層) 變成了 V(視圖層),不再管控制層的問題,只管如何去顯示。

  2. 控制層的角色就由我們的新人 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ū)別

引用主要分為四種類型:

  1. 強(qiáng)引用
    Object object = new Object(),object就是一個(gè)強(qiáng)引用了。
    當(dāng)內(nèi)存空間不足,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯(cuò)誤,使程序異常終止,也不會靠隨意回收具有強(qiáng)引用的對象來解決內(nèi)存不足問題。

  2. 軟引用
    只有內(nèi)存不夠時(shí)才回收,常用于緩存;當(dāng)內(nèi)存達(dá)到一個(gè)閥值,GC就會去回收它;

  3. 弱引用
    弱引用的對象擁有更短暫的生命周期。
    在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當(dāng)前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。

  4. 虛引用
    "虛引用"顧名思義,就是形同虛設(shè),與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個(gè)對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時(shí)候都可能被垃圾回收。


18. Synchronized

作用位置:

  1. synchronized 方法
  2. synchronized static 方法
  3. 代碼塊

synchronized方法:

  1. synchronized方法控制對類成員變量的訪問.
  2. 每個(gè)類實(shí)例對應(yīng)一把鎖,每個(gè)synchronized方法都必須獲得調(diào)用該方法的類實(shí)例的鎖方能執(zhí)行,否則所屬線程阻塞,方法一旦執(zhí)行,就獨(dú)占該鎖,直到從該方法返回時(shí)才將鎖釋放,此后被阻塞的線程方能獲得該鎖,重新進(jìn)入可執(zhí)行狀態(tài).
  3. 同一時(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代碼塊:

  1. 一個(gè)時(shí)間內(nèi)只能有一個(gè)線程得到執(zhí)行。另一個(gè)線程必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。
  2. 當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),另一個(gè)線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
  3. 一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
  4. 也就是說,當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),它就獲得了這個(gè)object的對象鎖。結(jié)果,其它線程對該object對象所有同步代碼部分的訪問都被暫時(shí)阻塞。

19. 解耦的本質(zhì)

設(shè)計(jì)模式中的幾大原則:

  1. SRP:單一職責(zé)原則,一個(gè)類應(yīng)該僅有一個(gè)引起它變化的原因。
  2. OCP:開閉原則,對于擴(kuò)展開發(fā),對于修改封閉。
  3. 里氏替換原則,所有使用基類的地方必須能透明的使用其子類對象。
  4. 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)。
  5. ISP:接口隔離原則,類之間的依賴關(guān)系應(yīng)該建立在最小的接口之上。
  6. 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

  1. 每個(gè)Activity在創(chuàng)建的時(shí)候,都會產(chǎn)生一個(gè)Window對象,這個(gè)Window對象實(shí)際為PhoneWindow。
  2. 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通信方式

  1. Handler
  2. 接口
  3. EventBus
  4. 廣播

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)造方法中傳遞?

  1. 如果通過構(gòu)造參數(shù)進(jìn)行傳遞,那么在Fragment銷毀重建時(shí)參數(shù)就無法保持了。
  2. 一般通過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

  1. 為什么要使用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);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容