1 Activity的生命周期和啟動模式
1.1 Activity的生命周期全面分析
用戶正常使用情況下的生命周期 & 由於Activity被系統回收或者設備配置改變導致Activity被銷毀重建情況下的生命周期。
1.1.1 典型情況下的生命周期分析
Activity的生命周期和啟動模式

- Activity第一次啟動:onCreate->onStart->onResume。
- Activity切換到后台( 用戶打開新的Activity或者切換到桌面) ,onPause->onStop(如果新Activity采用了透明主題,則當前Activity不會回調onstop)。
- Activity從后台到前台,重新可見,onRestart->onStart->onResume。
- 用戶退出Activity,onPause->onStop->onDestroy。
- onStart開始到onStop之前,Activity可見。onResume到onPause之前,Activity可以接受用戶交互。
- 在新Activity啟動之前,棧頂的Activity需要先onPause后,新Activity才能啟動。所以不能在onPause執行耗時操作。
- onstop中也不可以太耗時,資源回收和釋放可以放在onDestroy中。
1.1.2 異常情況下的生命周期分析
1 系統配置變化導致Activity銷毀重建
例如Activity處於豎屏狀態,如果突然旋轉屏幕,由於系統配置發生了改變,Activity就會被銷
毀並重新創建。
在異常情況下系統會在onStop之前調用onSaveInstanceState來保存狀態。Activity重新創建后,會在onStart之后調用onRestoreInstanceState來恢復之前保存的數據。

保存數據的流程: Activity被意外終止,調用onSaveIntanceState保存數據-> Activity委托Window,Window委托它上面的頂級容器一個ViewGroup( 可能是DecorView) 。然后頂層容器在通知所有子元素來保存數據。
這是一種委托思想,Android中類似的還有:View繪制過程、事件分發等。
系統只在Activity異常終止的時候才會調用 onSaveInstanceState 和onRestoreInstanceState 方法。其他情況不會觸發。
2 資源內存不足導致低優先級的Activity被回收
三種Activity優先級:前台- 可見非前台 -后台,從高到低。
如果一個進程沒有四大組件,那么將很快被系統殺死。因此,后台工作最好放入service中。
android:configChanges="orientation" 在manifest中指定 configChanges 在系統配置變化后不重新創建Activity,也不會執行 onSaveInstanceState 和onRestoreInstanceState 方法,而是調用 onConfigurationChnaged 方法。
附:系統配置變化項目
configChanges 一般常用三個選項:
- locale 系統語言變化
- keyborardHidden 鍵盤的可訪問性發生了變化,比如用戶調出了鍵盤
- orientation 屏幕方向變化
1.2 Activity的啟動模式
1.2.1 Activity的LaunchMode
Android使用棧來管理Activity。
- standard
每次啟動都會重新創建一個實例,不管這個Activity在棧中是否已經存在。誰啟動了這個Activity,那么Activity就運行在啟動它的那個Activity所在的棧中。
用Application去啟動Activity時會報錯,原因是非Activity的Context沒有任務棧。解決辦法是為待啟動Activity制定FLAG_ACTIVITY_NEW_TASH標志位,這樣就會為它創建一個新的任務棧。 - singleTop
如果新Activity位於任務棧的棧頂,那么此Activity不會被重新創建,同時回調 onNewIntent 方法。onCreate和onStart方法不會被執行。 - singleTask
這是一種單實例模式。如果不存在activity所需要的任務棧,則創建一個新任務棧和新Activity實例;如果存在所需要的任務棧,不存在實例,則新創建一個Activity實例;如果存在所需要的任務棧和實例,則不創建,調用onNewIntent方法。同時使該Activity實例之上的所有Activity出棧。
參考:taskAffinity標識Activity所需要的任務棧 - singleIntance
單實例模式。具有singleTask模式的所有特性,同時具有此模式的Activity只能獨自位於一個任務棧中。
假設兩個任務棧,前台任務棧為12,后台任務棧為XY。Y的啟動模式是singleTask。現在請求Y,整個后台任務棧會被切換到前台。如圖所示:

設置啟動模式
- manifest中 設置下的 android:launchMode 屬性。
- 啟動Activity的 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 。
- 兩種同時存在時,以第二種為准。第一種方式無法直接為Activity添加FLAG_ACTIVITY_CLEAR_TOP標識,第二種方式無法指定singleInstance模式。
- 可以通過命令行 adb shell dumpsys activity 命令查看棧中的Activity信息。
1.2.2 Activity的Flags
這些FLAG可以設定啟動模式、可以影響Activity的運行狀態。
- FLAG_ACTIVITY_NEW_TASK
為Activity指定“singleTask”啟動模式。 - FLAG_ACTIVITY_SINGLE_TOP
為Activity指定“singleTop"啟動模式。 - FLAG_ACTIVITY_CLEAR_TOP
具有此標記位的Activity啟動時,同一個任務棧中位於它上面的Activity都要出棧,一般和FLAG_ACTIVITY_NEW_TASK配合使用。 - FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
如果設置,新的Activity不會在最近啟動的Activity的列表(就是安卓手機里顯示最近打開的Activity那個系統級的UI)中保存。等同於在xml中指定android:exludeFromRecents="true"屬性。
1.3 IntentFilter的匹配規則
Activity調用方式
- 顯示調用 明確指定被啟動對象的組件信息,包括包名和類名
- 隱式調用 不需要明確指定組件信息,需要Intent能夠匹配目標組件中的IntentFilter中所設置的過濾信息。
匹配規則
- IntentFilter中的過濾信息有action、category、data。
- 只有一個Intent同時匹配action類別、category類別、data類別才能成功啟動目標Activity。
- 一個Activity可以有多個intent-filter,一個Intent只要能匹配任何一組intent-filter即可成功啟動對應的Activity。
** action**
action是一個字符串,匹配是指與action的字符串完全一樣,區分大小寫。
一個intent-filter可以有多個aciton,只要Intent中的action能夠和任何一個action相同即可成功匹配。
Intent中如果沒有指定action,那么匹配失敗。
** category**
category是一個字符串。
Intent可以沒有category,但是如果你一旦有category,不管有幾個,每個都必須與intent-filter中的其中一個category相同。
系統在 startActivity 和 startActivityForResult 的時候,會默認為Intent加上 android.intent.category.DEFAULT 這個category,所以為了我們的activity能夠接收隱式調用,就必須在intent-filter中加上 android.intent.category.DEFAULT 這個category。
** data**
data的匹配規則與action一樣,如果intent-filter中定義了data,那么Intent中必須要定義可匹配的data。
intent-filter中data的語法:
<data android:scheme="string" android:host="string" android:port="string" android:path="string" android:pathPattern="string" android:pathPrefix="string" android:mimeType="string"/>
Intent中的data有兩部分組成:mimeType和URI。mimeType是指媒體類型,比如
image/jpeg、audio/mpeg4-generic和video/等,可以表示圖片、文本、視頻等不同的媒
體格式。
URI的結構:
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
實際例子
content://com.example.project:200/folder/subfolder/etc http://www.baidu.com:80/search/info
scheme:URI的模式,比如http、file、content等,默認值是 file 。
host:URI的主機名
port:URI的端口號
path、pathPattern和pathPrefix:這三個參數描述路徑信息。
path、pathPattern可以表示完整的路徑信息,其中pathPattern可以包含通配符 * ,表示0個或者多個任意字符。
pathPrefix只表示路徑的前綴信息。
過濾規則的uri為空時,有默認值content和file,因此intent設置uri的scheme部分必須為content或file。
Intent指定data時,必須調用 setDataAndType 方法, setData 和 setType 會清除另一方的值。
對於service和BroadcastReceiver也是同樣的匹配規則,不過對於service最好使用顯式調用。
隱式調用需注意
-
當通過隱式調用啟動Activity時,沒找到對應的Activity系統就會拋出 android.content.ActivityNotFoundException 異常,所以需要判斷是否有Activity能夠匹配我們的隱式Intent。
-
采用 PackageManager 的 resloveActivity 方法或Intent 的 resloveActivity 方法
public abstract List<ResolveInfo> queryIntentActivityies(Intent intent,int flags);
public abstract ResolveInfo resloveActivity(Intent intent,int flags);以上的第二個參數使用 MATCH_DEFAULT_ONLY ,這個標志位的含義是僅僅匹配那些在
intent-filter中聲明了 android.intent.category.DEFAULT 這個category的Activity。因為如果把不含這個category的Activity匹配出來了,由於不含DEFAULT這個category的Activity是無法接受隱式Intent的從而導致startActivity失敗。 -
下面的action和category用來表明這是一個入口Activity,並且會出現在系統的應用列表中,二者缺一不可。
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
2 IPC機制
2.1 Android IPC 簡介
- IPC即Inter-Process Communication,含義為進程間通信或者跨進程通信,是指兩個進程之間進行數據交換的過程。
- 線程是CPU調度的最小單元,是一種有限的系統資源。進程一般指一個執行單元,在PC和移動設備上是指一個程序或者應用。進程與線程是包含與被包含的關系。一個進程可以包含多個線程。最簡單的情況下一個進程只有一個線程,即主線程( 例如Android的UI線程) 。
- 任何操作系統都需要有相應的IPC機制。如Windows上的剪貼板、管道和郵槽;Linux上命名管道、共享內容、信號量等。Android中最有特色的進程間通信方式就是binder,另外還支持socket。contentProvider是Android底層實現的進程間通信。
- 在Android中,IPC的使用場景大概有以下:
- 有些模塊由於特殊原因需要運行在單獨的進程中。
- 通過多進程來獲取多份內存空間。
- 當前應用需要向其他應用獲取數據。
2.2 Android中的多進程模式
2.2.1 開啟多進程模式
在Android中使用多線程只有一種方法:給四大組件在Manifest中指定 android:process 屬性。這個屬性的值就是進程名。這意味着不能在運行時指定一個線程所在的進程。
tips:使用 adb shell ps 或 adb shell ps|grep 包名 查看當前所存在的進程信息。
兩種進程命名方式的區別
- “:remote”
“:”的含義是指在當前的進程名前面附加上當前的包名,完整的進程名為“com.example.c2.remote"。這種進程屬於當前應用的私有進程,其他應用的組件不可以和它跑在同一個進程中。 - "com.example.c2.remote"
這是一種完整的命名方式。這種進程屬於全局進程,其他應用可以通過ShareUID方式和它跑在同一個進程中。
2.2.2 多線程模式的運行機制
Android為每個進程都分配了一個獨立的虛擬機,不同虛擬機在內存分配上有不同的地址空間,導致不同的虛擬機訪問同一個類的對象會產生多份副本。例如不同進程的Activity對靜態變量的修改,對其他進程不會造成任何影響。所有運行在不同進程的四大組件,只要它們之間需要通過內存在共享數據,都會共享失敗。四大組件之間不可能不通過中間層來共享數據。
多進程會帶來以下問題:
- 靜態成員和單例模式完全失效。
- 線程同步鎖機制完全失效。
這兩點都是因為不同進程不在同一個內存空間下,鎖的對象也不是同一個對象。 - SharedPreferences的可靠性下降。
SharedPreferences底層是 通過讀/寫XML文件實現的,並發讀/寫會導致一定幾率的數據丟失。 - Application會多次創建。
由於系統創建新的進程的同時分配獨立虛擬機,其實這就是啟動一個應用的過程。在多進程模式中,不同進程的組件擁有獨立的虛擬機、Application以及內存空間。
多進程相當於兩個不同的應用采用了SharedUID的模式
實現跨進程的方式有很多:
- Intent傳遞數據。
- 共享文件和SharedPreferences。
- 基於Binder的Messenger和AIDL。
- Socket等
2.3 IPC基礎概念介紹
主要介紹 Serializable 、 Parcelable 、 Binder 。Serializable和Parcelable接口可以完成對象的序列化過程,我們通過Intent和Binder傳輸數據時就需要Parcelabel和Serializable。還有的時候我們需要對象持久化到存儲設備上或者通過網絡傳輸到其他客戶端,也需要Serializable完成對象持久化。
2.3.1 Serializable接口
Serializable 是Java提供的一個序列化接口( 空接口) ,為對象提供標准的序列化和反序列化操作。只需要一個類去實現 Serializable 接口並聲明一個 serialVersionUID 即可實現序列化。
private static final long serialVersionUID = 8711368828010083044L
serialVersionUID也可以不聲明。如果不手動指定 serialVersionUID 的值,反序列化時如果當前類有所改變( 比如增刪了某些成員變量) ,那么系統就會重新計算當前類的hash值並更新 serialVersionUID 。這個時候當前類的 serialVersionUID 就和序列化數據中的serialVersionUID 不一致,導致反序列化失敗,程序就出現crash。
靜態成員變量屬於類不屬於對象,不參與序列化過程,其次 transient 關鍵字標記的成員變量也不參與序列化過程。
通過重寫writeObject和readObject方法可以改變系統默認的序列化過程。
2.3.2 Parcelable接口
Parcel內部包裝了可序列化的數據,可以在Binder中自由傳輸。序列化過程中需要實現的功能有序列化、反序列化和內容描述。
序列化功能由 writeToParcel 方法完成,最終是通過 Parcel 的一系列writer方法來完成。
@Override public void writeToParcel(Parcel out, int flags) { out.writeInt(code); out.writeString(name); }
反序列化功能由 CREATOR 來完成,其內部表明了如何創建序列化對象和數組,通過 Parcel 的一系列read方法來完成。
public static final Creator<Book> CREATOR = new Creator<Book>() { @Override public Book createFromParcel(Parcel in) { return new Book(in); } @Override public Book[] newArray(int size) { return new Book[size]; } }; protected Book(Parcel in) { code = in.readInt(); name = in.readString(); }
在Book(Parcel in)方法中,如果有一個成員變量是另一個可序列化對象,在反序列化過程中需要傳遞當前線程的上下文類加載器,否則會報無法找到類的錯誤。
book = in.readParcelable(Thread.currentThread().getContextClassLoader());
內容描述功能由 describeContents 方法完成,幾乎所有情況下都應該返回0,僅當當前對象中存在文件描述符時返回1。
public int describeContents() { return 0; }
Serializable 是Java的序列化接口,使用簡單但開銷大,序列化和反序列化過程需要大量I/O操作。而 Parcelable 是Android中的序列化方式,適合在Android平台使用,效率高但是使用麻煩。 Parcelable 主要在內存序列化上,Parcelable 也可以將對象序列化到存儲設備中或者將對象序列化后通過網絡傳輸,但是稍顯復雜,推薦使用 Serializable 。
2.3.3 Binder
Binder是Android中的一個類,實現了 IBinder 接口。從IPC角度說,
Binder是Andoird的一種跨進程通訊方式,Binder還可以理解為一種虛擬物理設備,它的設備驅動是/dev/binder。從Android Framework角度來說,Binder是 ServiceManager 連接各種Manager( ActivityManager· 、 WindowManager ) 和相應 ManagerService 的橋梁。從Android應用層來說,Binder是客戶端和服務端進行通信的媒介,當bindService時,服務端返回一個包含服務端業務調用的Binder對象,通過這個Binder對象,客戶端就可以獲取服務器端提供的服務或者數據( 包括普通服務和基於AIDL的服務)。

Binder通信采用C/S架構,從組件視角來說,包含Client、Server、ServiceManager以及binder驅動,其中ServiceManager用於管理系統中的各種服務。
圖中的Client,Server,Service Manager之間交互都是虛線表示,是由於它們彼此之間不是直接交互的,而是都通過與Binder驅動進行交互的,從而實現IPC通信方式。其中Binder驅動位於內核空間,Client,Server,Service Manager位於用戶空間。Binder驅動和Service Manager可以看做是Android平台的基礎架構,而Client和Server是Android的應用層,開發人員只需自定義實現client、Server端,借助Android的基本平台架構便可以直接進行IPC通信。
http://gityuan.com/2015/10/31/binder-prepare/
Android中Binder主要用於 Service ,包括AIDL和Messenger。普通Service的Binder不涉及進程間通信,Messenger的底層其實是AIDL,所以下面通過AIDL分析Binder的工作機制。
由系統根據AIDL文件自動生成.java文件
- Book.java
表示圖書信息的實體類,實現了Parcelable接口。 - Book.aidl
Book類在AIDL中的聲明。 - IBookManager.aidl
定義的管理Book實體的一個接口,包含 getBookList 和 addBook 兩個方法。盡管Book類和IBookManager位於相同的包中,但是在IBookManager仍然要導入Book類。 - IBookManager.java
系統為IBookManager.aidl生產的Binder類,在 gen 目錄下。
IBookManager繼承了 IInterface 接口,所有在Binder中傳輸的接口都需要繼IInterface接口。結構如下:- 聲明了 getBookList 和 addBook 方法,還聲明了兩個整型id分別標識這兩個方法,用於標識在 transact 過程中客戶端請求的到底是哪個方法。
- 聲明了一個內部類 Stub ,這個 Stub 就是一個Binder類,當客戶端和服務端位於同一進程時,方法調用不會走跨進程的 transact 。當二者位於不同進程時,方法調用需要走 transact 過程,這個邏輯有 Stub 的內部代理類 Proxy 來完成。
- 這個接口的核心實現就是它的內部類 Stub 和 Stub 的內部代理類 Proxy 。
Stub和Proxy類的內部方法和定義

- DESCRIPTOR
Binder的唯一標識,一般用Binder的類名表示。 - asInterface(android.os.IBinder obj)
將服務端的Binder對象轉換為客戶端所需的AIDL接口類型的對象,如果C/S位於同一進
程,此方法返回就是服務端的Stub對象本身,否則返回的就是系統封裝后的Stub.proxy對
象。 - asBinder
返回當前Binder對象。 - onTransact
這個方法運行在服務端的Binder線程池中,由客戶端發起跨進程請求時,遠程請求會通過
系統底層封裝后交由此方法來處理。該方法的原型是
java public Boolean onTransact(int code,Parcelable data,Parcelable reply,int flags)- 服務端通過code確定客戶端請求的目標方法是什么,
- 接着從data取出目標方法所需的參數,然后執行目標方法。
- 執行完畢后向reply寫入返回值( 如果有返回值) 。
- 如果這個方法返回值為false,那么服務端的請求會失敗,利用這個特性我們可以來做權限驗證。
- Proxy#getBookList 和Proxy#addBook
這兩個方法運行在客戶端,內部實現過程如下:- 首先創建該方法所需要的輸入型對象Parcel對象_data,輸出型Parcel對象_reply和返回值對象List。
- 然后把該方法的參數信息寫入_data( 如果有參數)
- 接着調用transact方法發起RPC( 遠程過程調用) ,同時當前線程掛起
- 然后服務端的onTransact方法會被調用知道RPC過程返回后,當前線程繼續執行,並從_reply中取出RPC過程的返回結果,最后返回_reply中的數據。
AIDL文件不是必須的,之所以提供AIDL文件,是為了方便系統為我們生成IBookManager.java,但我們完全可以自己寫一個。
linkToDeath和unlinkToDeath
如果服務端進程異常終止,我們到服務端的Binder連接斷裂。但是,如果我們不知道Binder連接已經斷裂,那么客戶端功能會受影響。通過linkTODeath我們可以給Binder設置一個死亡代理,當Binder死亡時,我們就會收到通知。
- 聲明一個 DeathRecipient 對象。 DeathRecipient 是一個接口,只有一個方法 binderDied ,當Binder死亡的時候,系統就會回調 binderDied 方法,然后我們就可以重新綁定遠程服務。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied(){
if(mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
// TODO:這里重新綁定遠程Service
}
} - 在客戶端綁定遠程服務成功后,給binder設置死亡代理:
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0); - 另外,可以通過Binder的 isBinderAlive 判斷Binder是否死亡。
2.4 Android中的IPC方式
主要有以下方式:
- Intent中附加extras
- 共享文件
- Binder
- ContentProvider
- Socket
2.4.1 使用Bundle
四大組件中的三大組件( Activity、Service、Receiver) 都支持在Intent中傳遞 Bundle 數據。
Bundle實現了Parcelable接口,因此可以方便的在不同進程間傳輸。當我們在一個進程中啟動了另一個進程的Activity、Service、Receiver,可以再Bundle中附加我們需要傳輸給遠程進程的消息並通過Intent發送出去。被傳輸的數據必須能夠被序列化。
2.4.2 使用文件共享
我們可以序列化一個對象到文件系統中的同時從另一個進程中恢復這個對象。
- 通過 ObjectOutputStream / ObjectInputStream 序列化一個對象到文件中,或者在另一個進程從文件中反序列這個對象。注意:反序列化得到的對象只是內容上和序列化之前的對象一樣,本質是兩個對象。
- 文件並發讀寫會導致讀出的對象可能不是最新的,並發寫的話那就更嚴重了 。所以文件共享方式適合對數據同步要求不高的進程之間進行通信,並且要妥善處理並發讀寫問題。
- SharedPreferences 底層實現采用XML文件來存儲鍵值對。系統對它的讀/寫有一定的緩存策略,即在內存中會有一份 SharedPreferences 文件的緩存,因此在多進程模式下,系統對它的讀/寫變得不可靠,面對高並發讀/寫時 SharedPreferences 有很大幾率丟失數據,因此不建議在IPC中使用 SharedPreferences 。
2.4.3 使用Messenger
Messenger可以在不同進程間傳遞Message對象。是一種輕量級的IPC方案,底層實現是AIDL。它對AIDL進行了封裝,使得我們可以更簡便的進行IPC。

具體使用時,分為服務端和客戶端:
- 服務端:創建一個Service來處理客戶端請求,同時創建一個Handler並通過它來創建一個
Messenger,然后再Service的onBind中返回Messenger對象底層的Binder即可。
private final Messenger mMessenger = new Messenger (new xxxHandler()); - 客戶端:綁定服務端的Sevice,利用服務端返回的IBinder對象來創建一個Messenger,通過這個Messenger就可以向服務端發送消息了,消息類型是 Message 。如果需要服務端響應,則需要創建一個Handler並通過它來創建一個Messenger( 和服務端一樣) ,並通過 Message 的 replyTo 參數傳遞給服務端。服務端通過Message的 replyTo 參數就可以回應客戶端了。
總而言之,就是客戶端和服務端 拿到對方的Messenger來發送 Message 。只不過客戶端通過bindService 而服務端通過 message.replyTo 來獲得對方的Messenger。
Messenger中有一個 Hanlder 以串行的方式處理隊列中的消息。不存在並發執行,因此我們不用考慮線程同步的問題。
2.4.4 使用AIDL
如果有大量的並發請求,使用Messenger就不太適合,同時如果需要跨進程調用服務端的方法,Messenger就無法做到了。這時我們可以使用AIDL。
流程如下:
- 服務端需要創建Service來監聽客戶端請求,然后創建一個AIDL文件,將暴露給客戶端的接口在AIDL文件中聲明,最后在Service中實現這個AIDL接口即可。
- 客戶端首先綁定服務端的Service,綁定成功后,將服務端返回的Binder對象轉成AIDL接口所屬的類型,接着就可以調用AIDL中的方法了。
AIDL支持的數據類型:
- 基本數據類型、String、CharSequence
- List:只支持ArrayList,里面的每個元素必須被AIDL支持
- Map:只支持HashMap,里面的每個元素必須被AIDL支持
- Parcelable
- 所有的AIDL接口本身也可以在AIDL文件中使用
自定義的Parcelable對象和AIDL對象,不管它們與當前的AIDL文件是否位於同一個包,都必須顯式import進來。
如果AIDL文件中使用了自定義的Parcelable對象,就必須新建一個和它同名的AIDL文件,並在其中聲明它為Parcelable類型。
package com.ryg.chapter_2.aidl; parcelable Book;
AIDL接口中的參數除了基本類型以外都必須表明方向in/out。AIDL接口文件中只支持方法,不支持聲明靜態常量。建議把所有和AIDL相關的類和文件放在同一個包中,方便管理。
void addBook(in Book book);
AIDL方法是在服務端的Binder線程池中執行的,因此當多個客戶端同時連接時,管理數據的集合直接采用 CopyOnWriteArrayList 來進行自動線程同步。類似的還有 ConcurrentHashMap 。
因為客戶端的listener和服務端的listener不是同一個對象,所以 RecmoteCallbackList 是系統專門提供用於刪除跨進程listener的接口,支持管理任意的AIDL接口,因為所有AIDL接口都繼承自 IInterface 接口。
public class RemoteCallbackList<E extends IInterface>
它內部通過一個Map接口來保存所有的AIDL回調,這個Map的key是 IBinder 類型,value是 Callback 類型。當客戶端解除注冊時,遍歷服務端所有listener,找到和客戶端listener具有相同Binder對象的服務端listenr並把它刪掉。
==客戶端RPC的時候線程會被掛起,由於被調用的方法運行在服務端的Binder線程池中,可能很耗時,不能在主線程中去調用服務端的方法。==
權限驗證
默認情況下,我們的遠程服務任何人都可以連接,我們必須加入權限驗證功能,權限驗證失敗則無法調用服務中的方法。通常有兩種驗證方法:
- 在onBind中驗證,驗證不通過返回null
驗證方式比如permission驗證,在AndroidManifest聲明:
<permission
android:name="com.rgy.chapter_2.permisson.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
Android自定義權限和使用權限
public IBinder onBind(Intent intent){
int check = checkCallingOrSelefPermission("com.ryq.chapter_2.permission.ACCESS_BOOK_SERVICE");
if(check == PackageManager.PERMISSION_DENIED){
return null;
}
return mBinder;
}
這種方法也適用於Messager。 - 在onTransact中驗證,驗證不通過返回false
可以permission驗證,還可以采用Uid和Pid驗證。
2.4.5 使用ContentProvider
==ContentProvider是四大組件之一,天生就是用來進程間通信。和Messenger一樣,其底層實現是用Binder。==
系統預置了許多ContentProvider,比如通訊錄、日程表等。要RPC訪問這些信息,只需要通過ContentResolver的query、update、insert和delete方法即可。
創建自定義的ContentProvider,只需繼承ContentProvider類並實現 onCreate 、 query 、 update 、 insert 、 getType 六個抽象方法即可。getType用來返回一個Uri請求所對應的MIME類型,剩下四個方法對應於CRUD操作。這六個方法都運行在ContentProvider進程中,除了 onCreate 由系統回調並運行在主線程里,其他五個方法都由外界調用並運行在Binder線程池中。
ContentProvider是通過Uri來區分外界要訪問的數據集合,例如外界訪問ContentProvider中的表,我們需要為它們定義單獨的Uri和Uri_Code。根據Uri_Code,我們就知道要訪問哪個表了。
==query、update、insert、delete四大方法存在多線程並發訪問,因此方法內部要做好線程同步。==若采用SQLite並且只有一個SQLiteDatabase,SQLiteDatabase內部已經做了同步處理。若是多個SQLiteDatabase或是采用List作為底層數據集,就必須做線程同步。
2.4.6 使用Socket
Socket也稱為“套接字”,分為流式套接字和用戶數據報套接字兩種,分別對應於TCP和UDP協議。Socket可以實現計算機網絡中的兩個進程間的通信,當然也可以在本地實現進程間的通信。我們以一個跨進程的聊天程序來演示。
在遠程Service建立一個TCP服務,然后在主界面中連接TCP服務。服務端Service監聽本地端口,客戶端連接指定的端口,建立連接成功后,拿到 Socket 對象就可以向服務端發送消息或者接受服務端發送的消息。
本例的客戶端和服務端源代碼
除了采用TCP套接字,也可以用UDP套接字。實際上socket不僅能實現進程間的通信,還可以實現設備間的通信(只要設備之間的IP地址互相可見)。
2.5 Binder連接池
前面提到AIDL的流程是:首先創建一個service和AIDL接口,接着創建一個類繼承自AIDL接口中的Stub類並實現Stub中的抽象方法,客戶端在Service的onBind方法中拿到這個類的對象,然后綁定這個service,建立連接后就可以通過這個Stub對象進行RPC。
那么如果項目龐大,有多個業務模塊都需要使用AIDL進行IPC,隨着AIDL數量的增加,我們不能無限制地增加Service,我們需要把所有AIDL放在同一個Service中去管理。

- 服務端只有一個Service,把所有AIDL放在一個Service中,不同業務模塊之間不能有耦合
- 服務端提供一個 queryBinder 接口,這個接口能夠根據業務模塊的特征來返回響應的Binder對象給客戶端
- 不同的業務模塊拿到所需的Binder對象就可以進行RPC了
2.6 選用合適的IPC方式

/////////----------------
一 Activity
1 Activity 生命周期
1.1 Activity 的四種狀態
running 當前Activity正在運行,獲取焦點
paused 當前Activity處於暫停狀態,可見,沒有焦點
stopped 當前Activity處於暫停狀態,完全不可見,內存里的成員變量和狀態信息仍在。
killed 當前Activity被銷毀后的狀態,成員變量和狀態信息被一並回收。
1.2 Activity的生命周期
Activity啟動 →onCreate()→onStart()→onResume();
點擊home鍵返回桌面→onPause()→onStop();
再次回到原Activity→ onRestart()→onStart()→onResume();
按返回鍵退出當前Activity→onPause()→onStop()→onDestroy();
2 Android任務棧
優先級:前台>可見>服務>后台>空
前台:正在與用戶進行交互的Activity所在的進程
可見:Activity可見但沒有在前台所在的進程
服務:Activity在后台開啟了服務所在的進程
后台:Activity完全處於后台所在的進程
空:沒有任何Activity存在的進程
3. Activity的啟動模式
3.1 為什么需要啟動模式?
每次啟動一個Activity都會把對應的要啟動的Activity的實例放入任務棧中,加入這個Activity被頻繁啟動,會產生很多的這個Activity的實例,為了杜絕這種內存浪費的行為,Activity的啟動模式被創造出來。
3.2 Activity的啟動模式
- 系統模式模式:standard
標准模式,也是系統的默認模式,啟動一個activity就創建一個activity實例,不管這個實例是否存在,誰啟動了這個Activity,那么這個Activity就運行在啟動它的那個Activity的任務棧中。 - 棧頂復用模式:singleTop
在這種模式下,如果新的Activity已經位於棧頂,那么此Activity不會被重新創建,同時它的onNewIntent方法被回調,通過此方法的參數我們可以取出當前的請求信息。需要注意,此Activity的onCreate,onStart方法不會被系統調用。如果新Activity不在棧頂,那么新Activity任然會被重新重建。 - 棧內復用模式:singleTask
這是一種單實例模式,只要Activity在一個棧中存在,那么多次啟動此Activity都不會重新創建實例,系統也會回調onNewIntent方法。
例如:當前棧內情況為ABC,此時D被以singleTask的模式被啟動,當前棧變為ABCD。
如果當前棧內情況為ADBC,此時D被以singleTask的模式被啟動,當前棧變為AD。 - 單實例模式:singleInstance
這是一種加強的單實例模式,它除了具有singleTask模式的所有特性外,還加強了一點,那就是具有此種模式的Activity只能單獨位於一個任務棧中,比如Activity A是singleInstance模式,A被啟動時系統會為它創建一個新的任務棧,A運行在這個單獨的任務棧中,后續的請求均不會再創建A,除非這個單獨的任務棧被系統銷毀了。
二 Fragment
1. 為什么Fragment被稱為第五大組件?
Android中的四大組件為Activity,service,ContentProvider,Broadcast。
Fragment因為有生命周期,使用頻率不輸於四大組件,可靈活加載到Activity中。
1.1 Fragment加載到Activity的兩種方式
- 靜態加載:直接在Activity布局文件中指定Fragment。代碼如下
<fragment android:name="com.example.myfragment.MyFragment" android:id="@+id/myfragment_1" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
- 動態加載:動態加載需要使用到FragmentManager,這種加載方式在開發中是非常常見的,示例代碼如下:
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction(); //將FragmentA從容器中移除掉,減少內存的消耗 fragmentTransaction.remove(fragmentA); fragmentTransaction.add(R.id.fragment_layout,new FragmentB()); fragmentTransaction.commit();
1.2 Fragment 與ViewPager搭配使用
通常情況下我們開發應用最常見的使用情況是TabLayout+ViewPager+Fragment的使用方式,這就涉及到兩個常用的適配器的使用,一個是FragmentPagerAdapter,另外一個是FragmentStatePagerAdapter,那么它們之間有什么區別呢?其實很簡單,FragmentPagerAdapter適用於頁面較少的情況,而FragmentStatePagerAdapter適用於頁面較多的情況。
2. Fragment的生命周期
Fragment
界面打開
onCreate() 方法執行!
onCreateView() 方法執行!
onActivityCreated() 方法執行!
onStart() 方法執行!
onResume() 方法執行!
按下主屏幕鍵/鎖屏
onPause() 方法執行!
onStop() 方法執行!
重新打開
onStart() 方法執行!
onResume() 方法執行!
按下后退鍵
onPause() 方法執行!
onStop() 方法執行!
onDestroyView() 方法執行!
onDestroy() 方法執行!
onDetach() 方法執行!
Activity
打開應用
onCreate() 方法執行!
onStart() 方法執行!
onResume() 方法執行!
按下主屏幕鍵/鎖屏
onPause() 方法執行!
onStop() 方法執行!
重新打開應用
onRestart() 方法執行!
onStart() 方法執行!
onResume() 方法執行!
按下后退鍵
onPause() 方法執行!
onStop() 方法執行!
onDestroy() 方法執行!
在Activity中加入Fragment,對應的生命周期
打開
Fragment onAttach()方法執行
Fragment onCreate() 方法執行!
Fragment onCreateView() 方法執行!
Fragment onViewCreated()方法執行
Activity onCreate() 方法執行!
Fragment onActivityCreated() 方法執行!
Activity onStart() 方法執行!
Fragment onStart() 方法執行!
Activity onResume() 方法執行!
Fragment onResume() 方法執行!
按下主屏幕鍵/鎖屏
Fragment onPause() 方法執行!
Activity onPause() 方法執行!
Fragment onStop() 方法執行!
Activity onStop() 方法執行!
再次打開
Activity onRestart() 方法執行!
Activity onStart() 方法執行!
Fragment onStart() 方法執行!
Activity onResume() 方法執行!
Fragment onResume() 方法執行!
按下后退鍵
Fragment onPause() 方法執行!
Activity onPause() 方法執行!
Fragment onStop() 方法執行!
Activity onStop() 方法執行!
Fragment onDestroyView() 方法執行!
Fragment onDestroy() 方法執行!
Fragment onDetach() 方法執行!
Activity onDestroy() 方法執行!
3. Fragment的通信
3.1 在Fragment中調用Activity中的方法
在Fragment中調用Activity的方法很簡單,Fragment有個getActivity()的方法,比如,在MainActivity中的一個Fragment中獲取MainActivity的引用,並調用MainActivity的某個方法methodA()方法你可以這么寫:
MainActivity mainActivity = (MainActivity) getActivity();
mainActivity.methodA();
3.2 在Activity中調用Fragment的方法
在Activity中調用Fragment中的方法是最簡單的,我想這里我不用多說吧!直接接口回調即可調用Fragment的任何可訪問的方法。
3.3 在Fragment中調用另外一個Fragment的方法
這個可就需要一定的思維性了,首先要想調用Fragment A的方法,除了這個Fragment A自身可以調用外,這個Fragment A所屬的Activity也可以調用,要想另外一個Fragment B調用此Fragment A的方法,Fragment B可以間接通過Activity來進行調用,也就是3.1 和 3.2 的結合。
三 Service
1. Service基礎知識
1.1 Service是什么?
Service(服務)是一個一種可以在后台執行長時間運行操作而沒有用戶界面的組件。它運行於UI線程,因此不能進行耗時的操作。
1.2 Service和Thread的區別
Service的運行是在UI線程當中的,是絕對絕對不能進行耗時操作的,而Thread開啟的子線程則可以進行耗時操作,但是Thread開啟的子線程是不能直接對UI進行操作的,否則極有可能發生直接讓程序崩掉,這就是它們的區別。
2. 啟動Service的2種方式
2.1 startService()方法開啟Service
步驟:
a.定義一個類繼承Service。
b.在AndroidManifest.xml文件中配置該Service。
c.使用Context的startService(Intent)方法啟動該Service。
d.不再使用該Service時,調用Context的stopService(Intent)方法停止該Service。
2.2 bindService方法開啟Service(Activity與Service綁定)
步驟:
a.創建BinderService服務端,繼承自Service並在類中創建一個實現IBinder接口的實現實例對象並提供公共方法給客戶端調用。
b.從onBind()回調方法返回此Binder實例。
c.在客戶端中,從onServiceConnected回調方法接收Binder,並使用提供的方法調用綁定服務。
3. Service的生命周期
服務的生命周期有兩種,因為服務可以跟Activity綁定起來,也可以不綁定,Activity和服務進行通信的話,是需要把服務和Activity進行綁定的。因此服務的生命周期分為未綁定Activity的和綁定Activity的。
沒有綁定Activity的服務生命周期:
啟動服務>onCreate()>onStartCommand()>服務運行>onDestory()>服務銷毀
綁定Activity的服務生命周期
綁定服務>onCreate()>onBind()>服務運行>onUnBind()>onDestory()>服務被銷毀
-
通過Intent和startService()方法啟動了一個服務,接下來執行onCreate()方法,首次創建服務時,系統將調用此方法來執行一次性設置程序(在調用 onStartCommand() 或 onBind() 之前)。如果服務已在運行,則不會調用此方法。
-
當另一個組件(如 Activity)通過調用 startService() 請求啟動服務時,系統將調用此方法。一旦執行此方法,服務即會啟動並可在后台無限期運行。 如果您實現此方法,則在服務工作完成后,需要由您通過調用 stopSelf() 或 stopService() 來停止服務。(如果您只想提供綁定,則無需實現此方法。)
-
服務開始處於運行狀態。
-
某個操作導致服務停止,比如執行了方法stopService(),那么服務接下來會執行onDestory()銷毀。服務應該實現此方法來清理所有資源,如線程、注冊的偵聽器、接收器等。 這是服務接收的最后一個調用。
-
服務被完全銷毀,下一步就是等待被垃圾回收器回收了。
-
通過Intent和bindService()方法啟動了一個服務,接下來會執行onCreate()方法,首次創建服務時,系統將調用此方法來執行一次性設置程序(在調用 onStartCommand() 或 onBind() 之前)。如果服務已在運行,則不會調用此方法。
-
當另一個組件想通過調用 bindService() 與服務綁定(例如執行 RPC)時,系統將調用此方法。在此方法的實現中,您必須通過返回 IBinder 提供一個接口,供客戶端用來與服務進行通信。請務必實現此方法,但如果您並不希望允許綁定,則應返回 null。
-
服務開始處於運行狀態。成功與Activity綁定。
-
某個操作導致服務解除綁定,比如執行了方法unbindService(),那么服務接下來會解除與當前Activity的綁定。接下來服務將面臨銷毀。
-
服務執行onDestory()方法被銷毀。服務應該實現此方法來清理所有資源,如線程、注冊的偵聽器、接收器等。 這是服務接收的最后一個調用。
-
服務被完全銷毀,下一步就是等待被垃圾回收器回收了。
Service總結:
-
被啟動的服務的生命周期:如果一個Service被某個Activity 調用 Context.startService 方法啟動,那么不管是否有Activity使用bindService綁定或unbindService解除綁定到該Service,該Service都在后台運行。如果一個Service被startService 方法多次啟動,那么onCreate方法只會調用一次,onStart將會被調用多次(對應調用startService的次數),並且系統只會創建Service的一個實例(因此你應該知道只需要一次stopService調用)。該Service將會一直在后台運行,而不管對應程序的Activity是否在運行,直到被調用stopService,或自身的stopSelf方法。當然如果系統資源不足,android系統也可能結束服務。
-
被綁定的服務的生命周期:如果一個Service被某個Activity 調用 Context.bindService 方法綁定啟動,不管調用 bindService 調用幾次,onCreate方法都只會調用一次,同時onStart方法始終不會被調用。當連接建立之后,Service將會一直運行,除非調用Context.unbindService 斷開連接或者之前調用bindService 的 Context 不存在了(如Activity被finish的時候),系統將會自動停止Service,對應onDestroy將被調用。
-
被啟動又被綁定的服務的生命周期:如果一個Service又被啟動又被綁定,則該Service將會一直在后台運行。並且不管如何調用,onCreate始終只會調用一次,對應startService調用多少次,Service的onStart便會調用多少次。調用unbindService將不會停止Service,而必須調用 stopService 或 Service的 stopSelf 來停止服務。
-
當服務被停止時清除服務:當一個Service被終止(1、調用stopService;2、調用stopSelf;3、不再有綁定的連接(沒有被啟動))時,onDestroy方法將會被調用,在這里你應當做一些清除工作,如停止在Service中創建並運行的線程。
四 Broadcast
1. 廣播的概念
1.1 定義
在Android中,它是一種廣泛運用在應用程序之間傳輸信息的機制,Android中我們發送廣播內容是一個Intent,這個Intent中可以攜帶我們要發送的數據。
1.2 廣播的使用場景
a.同一app內有多個進程的不同組件之間的消息通信。
b.不同app之間的組件之間消息的通信。
1.3 廣播的種類
標准廣播:context.sendBroadcast(Intent)方法發送的廣播,不可被攔截
有序廣播:context.sendOrderBroadcast(Intent)方法發送的廣播,可被攔截
本地廣播:localBroadcastManager.sendBroadcast(Intent),只在app內傳播
2. 廣播接收器
廣播接收器是專門用來接收廣播信息的,它可分為靜態注冊和動態注冊:
- 靜態注冊:注冊完成一直在運行。
首先你要創建一個廣播接收器類,實例代碼如下:
public class BootCompleteReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show(); } }
另外,靜態的廣播接收器一定要在AndroidManifest.xml文件中注冊才可以使用,AndroidManifest.xml文件中注冊靜態廣播代碼如下:
<receiver android:name=".BootCompleteReceiver" > <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver>
- 動態注冊:跟隨Activity的生命周期。
新建一個類,讓它繼承自BroadcastReceiver,並重寫父類的onReceive()方法就行了。這樣有廣播到來時,onReceive()方法就會得到執行,具體的邏輯就可以在這個方法中處理。 - 動態注冊廣播接收器的優點以及缺點:
動態注冊的廣播接收器可以自由地控制注冊與注銷,在靈活性方面有很大優勢,但是它也存在着一個缺點,即必須要在程序啟動之后才能接收到廣播,因為注冊的邏輯是寫在onCreate()方法中的。那么有沒有廣播能在程序未啟動的情況下就能接收到廣播呢?靜態注冊的廣播接收器就可以做到。
3. 廣播內部實現機制
-
自定義廣播接收者BroadcastReceiver,並且重寫onReceiver()方法。
-
通過Binder機制向AMS(Activity Manager Service)進行注冊。
-
廣播發送者通過Binder機制向AMS發送廣播。
-
AMS查找符合條件(IntentFilter/Permission等)的BroadcastReceiver,將廣播發送到相應的BroadcastReceiver(一般情況下是Activity)的消息隊列中。
-
消息循環執行拿到此廣播,回調BroadcastReceiver中的onReceiver()方法。
4. 本地廣播
本地廣播的發送和注冊廣播接收器都需要使用到LocalBroadcastManager類,如下所示為本地廣播的發送和本地廣播接收器注冊的代碼:
本地廣播的發送:
public static void sendLocalBroadcast(Context context,String action){ Intent intent = new Intent(action); LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); localBroadcastManager.sendBroadcast(intent); }
本地廣播的接收器的注冊:
IntentFilter intentFilter = new IntentFilter(); LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context); intentFilter.addAction(new BroadcastUtil().action_next); nasbr = new NextAndStartBroadcastReceiver(); localBroadcastManager.registerReceiver(nasbr, intentFilter);//注冊本地廣播接收器
特點:
1. 使用它發送的廣播將只在自身app內傳播,因此你不必擔心泄漏隱私的數據。
2. 其他app無法對你的app發送該廣播,因此你的app根本不可能收到非自身app發送的該廣播,因此你不必擔心有安全漏洞可以利用。
3. 比系統廣播更加高效。
內部實現機制:
1. LocalBroadcast高效的原因:因為它內部是通過Handler實現的,它的sendBroadcast()方法含義並非和系統的sendBroadcast()一樣,它的sendBroadcast()方法其實就是通過Handler發送了一個Message而已。
2. LocalBroadcast安全的原因:既然它是通過Handler實現廣播發送的,那么相比系統廣播通過Binder機制實現那肯定更加高效,同時使用Handler來實現,別的app無法向我們應用發送該廣播,而我們app內部發送的廣播也不會離開我們的app。
LocalBroadcast內部協作主要是靠兩個Map集合:mReceivers和mActions,當然還有一個List集合mPendingBroadcasts,這個主要存儲待接收的廣播對象。
六 Bainder機制
-
通常情況下,Binder是一種通信機制。
-
對於Server來說,Binder指的是Binder本地對象/對於Client來說,Binder指的是Binder的代理對象。
-
對於傳輸過程而言,Binder是可以進行跨進程傳遞的對象。
-
AIDL是Binder機制的一個實例。
七 Handler機制
1. 定義
Handler是可以通過發送和處理Message和Runnable對象來關聯相應線程的MessageQueue。通常我們認為它是一種異步機制。
- 可以讓對應的Message和Runnable在未來的某個時間點進行相應的處理。
- 讓自己想要的耗時操作在子線程中完成,讓更新UI的操作在主線程中完成,而子線程與主線程之間的通信就是靠Handler來完成。
2. Handler的使用方法
- post(Runnable)
- sendMessage(Message)
3. Handler內部實現機制
Handler機制也可叫異步消息機制,它主要由4個部分組成:Message,Handler,MessageQueue,Looper,在上面我們已經接觸到了Message和Handler,接下來我們對4個成員進行着重的了解:
-
Message
Message是在線程之間傳遞的消息,它可以在內部攜帶少量的信息,用於在不同線程之間交換數據。使用Message的arg1和arg2便可攜帶int數據,使用obj便可攜帶Object類型數據。 -
Handler
Handler顧名思義就是處理者的意思,它只要用於在子線程發送消息對象Message,在UI線程處理消息對象Message,在子線程調用sendMessage方法發送消息對象Message,而發送的消息經過一系列地輾轉之后最終會被傳遞到Handler的handleMessage方法中,最終在handleMessage方法中消息對象Message被處理。 -
MessageQueue
MessageQueue就是消息隊列的意思,它只要用於存放所有通過Handler發送過來的消息。這部分消息會一直存放於消息隊列當中,等待被處理。每個線程中只會有一個MessageQueue對象,請牢記這句話。其實從字面上就可以看出,MessageQueue底層數據結構是隊列,而且這個隊列只存放Message對象。 -
Looper
Looper是每個線程中的MessageQueue的管家,調用Looper的loop()方法后,就會進入到一個無限循環當中,然后每當MesssageQueue中存在一條消息,Looper就會將這條消息取出,並將它傳遞到Handler的handleMessage()方法中。每個線程只有一個Looper對象。
Handler機制流程圖如下:

4. Handler引起的內存泄漏以及解決方法
原因:靜態內部類持有外部類的匿名引用,導致外部activity無法得到釋放。
解決方法:handler內部持有外部的弱引用,並把handler改為靜態內部類,在activity的onDestory()中調用handler的removeCallback()方法。
八 IntentService機制
1.IntentService是什么?
它的優先級高於Service。
IntentService是繼承處理異步請求的一個類,在IntentService內有一個工作線程來處理耗時操作,啟動IntentServiced的方式和啟動傳統的Service一樣,同時,當任務執行完成后,IntentService會自動停止,而不需要我們手動去控制或stopSelf()。另外,可以啟動IntentService多次,而每一個耗時操作會以工作隊列的方式在IntentService的onHandlerIntent回調方法中執行,並且,每次只執行一個工作線程,執行完第一個在執行第二個。
- 它本質是一種特殊的Service,繼承自Service並且本身就是一個抽象類。
- 它內部是由HandlerThread和Handler實現異步操作。
2.IntentService的使用方法
創建IntentService時,只需要實現onHandlerIntent和構造方法,onHandlerIntent為異步方法,可以執行耗時操作。
十 HandlerThread機制
1.HandlerThread的產生背景
開啟子線程進行耗時操作,多次創建和銷毀子線程是很耗費資源的,但是木有關系,谷歌考慮了這點為我們專門開發出了HandlerThread機制。
2.HandlerThread是什么?
本質:Handler + Thread + Looper,是一個Thread內部有Looper。
-
HandlerThread本質上是一個線程類,它繼承了Thread。
-
HandlerThread有自己內部的Looper對象,可以進行Looper循環。
-
通過獲取HandlerThread的Looper對象傳遞給Handler對象,可以在handlerMessage方法中執行異步任務。
-
優點是不會有堵塞,減少對性能的消耗,缺點是不能進行多任務的處理,需要等待進行處理,處理效率較低。
-
與線程池注重並發不同,HandlerThread是一個串行隊列,HandlerThread背后只有一個線程。
十一 IntentService機制
1.IntentService是什么?
它的優先級高於Service。
IntentService是繼承處理異步請求的一個類,在IntentService內有一個工作線程來處理耗時操作,啟動IntentServiced的方式和啟動傳統的Service一樣,同時,當任務執行完成后,IntentService會自動停止,而不需要我們手動去控制或stopSelf()。另外,可以啟動IntentService多次,而每一個耗時操作會以工作隊列的方式在IntentService的onHandlerIntent回調方法中執行,並且,每次只執行一個工作線程,執行完第一個在執行第二個。
-
它本質是一種特殊的Service,繼承自Service並且本身就是一個抽象類。
-
它內部是由HandlerThread和Handler實現異步操作。
2.IntentService的使用方法
創建IntentService時,只需要實現onHandlerIntent和構造方法,onHandlerIntent為異步方法,可以執行耗時操作。
十二 View繪制機制
1. View樹的繪制流程
measure(測量)→layout(布局)→draw(繪制)
2. Measure過程

measure過程主要就是從頂層父View向子View遞歸調用view.measure方法(measure中又回調onMeasure方法)的過程。具體measure核心主要有如下幾點:
MeasureSpec(View的內部類)測量規格為int型,值由高2位規格模式specMode和低30位具體尺寸specSize組成。其中specMode只有三種值:
- MeasureSpec.EXACTLY //確定模式,父View希望子View的大小是確定的,由specSize決定;
- MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值;
- MeasureSpec.UNSPECIFIED //未指定模式,父View完全依據子View的設計值來決定;
View的measure方法是final的,不允許重載,View子類只能重載onMeasure來完成自己的測量邏輯。
最頂層DecorView測量時的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法確定的(LayoutParams寬高參數均為MATCH_PARENT,specMode是EXACTLY,specSize為物理屏幕大小)。
ViewGroup類提供了measureChild,measureChild和measureChildWithMargins方法,簡化了父子View的尺寸計算。
只要是ViewGroup的子類就必須要求LayoutParams繼承子MarginLayoutParams,否則無法使用layout_margin參數。
View的布局大小由父View和子View共同決定。
使用View的getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onMeasure流程之后被調用才能返回有效值。
3. Layout過程
整個layout過程比較容易理解,從上面分析可以看出layout也是從頂層父View向子View的遞歸調用view.layout方法的過程,即父View根據上一步measure子View所得到的布局大小和布局參數,將子View放在合適的位置上。具體layout核心主要有以下幾點:
-
View.layout方法可被重載,ViewGroup.layout為final的不可重載,ViewGroup.onLayout為abstract的,子類必須重載實現自己的位置邏輯。
-
measure操作完成后得到的是對每個View經測量過的measuredWidth和measuredHeight,layout操作完成之后得到的是對每個View進行位置分配后的mLeft、mTop、mRight、mBottom,這些值都是相對於父View來說的。
-
凡是layout_XXX的布局屬性基本都針對的是包含子View的ViewGroup的,當對一個沒有父容器的View設置相關layout_XXX屬性是沒有任何意義的(前面《Android應用setContentView與LayoutInflater加載解析機制源碼分析》也有提到過)。
-
使用View的getWidth()和getHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onLayout流程之后被調用才能返回有效值。
4.Draw過程
繪制過程就是把View對象繪制到屏幕上,整個draw過程需要注意如下細節:
-
如果該View是一個ViewGroup,則需要遞歸繪制其所包含的所有子View。
-
View默認不會繪制任何內容,真正的繪制都需要自己在子類中實現。
-
View的繪制是借助onDraw方法傳入的Canvas類來進行的。
-
區分View動畫和ViewGroup布局動畫,前者指的是View自身的動畫,可以通過setAnimation添加,后者是專門針對ViewGroup顯示內部子視圖時設置的動畫,可以在xml布局文件中對ViewGroup設置layoutAnimation屬性(譬如對LinearLayout設置子View在顯示時出現逐行、隨機、下等顯示等不同動畫效果)。
-
在獲取畫布剪切區(每個View的draw中傳入的Canvas)時會自動處理掉padding,子View獲取Canvas不用關注這些邏輯,只用關心如何繪制即可。
-
默認情況下子View的ViewGroup.drawChild繪制順序和子View被添加的順序一致,但是你也可以重載ViewGroup.getChildDrawingOrder()方法提供不同順序。
十三 Android部分事件分發機制
1. 為什么有事件分發機制
Android上面的View是樹形結構,View可能會重疊在一起,當我們點擊的地方有多個View都可以響應的時候,這個點擊事件應該給誰呢?為了解決這個問題,就有了事件分發機制。
2. 3個重要的有關事件分發的方法
- dispatch TouchEvent
用來進行事件的分發。如果事件能夠傳遞給當前View,那么此方法一定會被調用,返回結果受當前View的onTouchEvent和下級的dispatchTouchEvent方法影響,表示是否消耗此事件。 - onInterceptTouchEvent
在上述方法dispatchTouchEvent內部調用,用來判斷是否攔截某個事件,如果當前View攔截了某個事件,那么同一個事件序列當中,此方法不會被再次調用,返回結果表示是否攔截當前事件。 - onTouchEvent
同樣也會在dispatchTouchEvent內部調用,用來處理點擊事件,返回結果表示是否消耗當前事件,如果不消耗,則在同一個事件序列中,當前View無法再次接收到事件。
偽代碼
public boolean dispatchTouchEvent(MotionEvent ev){ boolean consume = false;//記錄返回值 if(onInterceptTouchEvent(ev)){//判斷是否攔截此事件 consume = onTouchEvent(ev);//如果當前確認攔截此事件,那么就處理這個事件 }else{ consume = child.dispatchToucnEvent(ev);//如果當前確認不攔截此事件,那么就將事件分發給下一級 } return consume; }
通過上述偽代碼,我們可以得知點擊事件的傳遞規則:對於一個根ViewGroup而言,點擊事件產生后,首先會傳遞給它,這時它的dispatchTouch就會被調用,如果這個ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當前的事件,接着事件就會交給這個ViewGroup處理,即它的onTouch方法就會被調用;如果這個ViewGroup的onInterceptTouchEvent方法返回false就表示它不攔截當前事件,這時當前事件就會繼續傳遞給它的子元素,接着子元素的dispatchTouchEvent方法就會被調用,如此直到事件被最終處理。
當一個View需要處理事件時,如果它設置了OnTouchListener,那么OnTouchListener中的onTouch方法會被回調。這時事件處理還要看onTouch的返回值,如果返回false,則當前View的onTouchEvent方法會被調用;如果返回true,那么當前View的onTouchEvent方法不會被調用。由此可見,給View設置的onTouchListener的優先級比onTouchEvent要高。在onTouchEvent方法中,如果當前設置的有onClickListener,那么它的onClick方法會被調用。可以看出,平時我們常用的OnClickListener,其優先級最低,即處於事件傳遞的尾端。
當一個點擊事件產生后,它的傳遞過程遵循如下順序:Activity–>Window–>View,即事件總數先傳遞給Activity,Activity再傳遞給Window,最后Window再傳遞給頂級View,頂級View接收到事件后,就會按照事件分發機制去分發事件。考慮一種情況,如果一個View的onTouchEvent返回false,那么它的父容器的onTouchEvent將會被調用,依次類推。如果所有的元素都不處理這個事件,那么這個事件將會最終傳遞給Activity處理, 即Activity的onTouchEvent方法會被調用。這個過程其實很好理解,我們可以換一種思路,假設點擊事件是一個難題,這個難題最終被上級領導分給了一個程序員去處理(這是事件分發過程),結果這個程序員搞不定(onTouchEvent返回了false),現在該怎么辦呢?難題必須要解決,那就只能交給水平更高的上級解決(上級的onTouchEvent被調用),如果上級再搞不定,那就只能交給上級的上級去解決,就這樣難題一層層地向上拋,這是公司內部一種常見的處理問題的過程。
關於事件傳遞機制還需要注意以下:
-
同一見事件序列是從手指接觸屏幕的那一刻起,到手指離開屏幕的那一刻結束,在這個過程中所產生的一系列事件,這個事件的序列以down開始,中間含有數量不定的move事件,最終以up事件結束。
-
正常情況下,一個事件序列只能被一個View攔截且消耗。這一條的原因可以參考(3),因為一旦一個元素攔截了某個事件,那么同一個事件序列的所有事件都會直接交給它處理,因此同一個事件序列中的事件不能分別由兩個View同時處理,但是通過特殊手段可以做到,比如
一個View將本該自己處理的事件通過onTouchEvent強行傳遞給其他View處理。 -
某個View一旦決定攔截,那么這個事件序列都只能由它來處理(如果事件序列能夠傳遞給它的話),並且它的onInterceptTouchEvent不會被調用。這條也很好理解,就是說當一個View決定攔截一個事件后,那么系統會把同一個事件序列內的其他方法都直接交給它來處理,因此就不用再調用這個View的onInterceptTouchEvent去詢問它是否攔截了。
-
某個View一旦開始處理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一件序列中的其他事件都不會再交給它處理,並且事件 將重新交由它的父元素去處理,即父元素的onTouchEvent會被調用。意思就是事件一旦交給一個View處理,那么它就必須消耗掉,否則同一事件序列中剩下的事件就不再交給它處理了,這就好比上級交給程序員一件事,如果這件事沒有處理好,短時間內上級就不敢再把事件交給這個程序員做了,二者是類似的道理。
-
如果View不消耗ACTION_DOWN以外的事件,那么這個點擊事件會消失,此時父元素的onTouchEvent並不會調用,並且當前View可以持續收到后續的事件,最終這些消失的點擊事件會傳遞給Activity處理。
-
ViewGroup默認不攔截任何事件。Android源碼中ViewGroup的onInterceptTouchEvent方法默認返回false。
-
View沒有onInterceptTouchEvent方法,一旦點擊事件傳遞給它,那么它的onTouchEvent方法就會被調用。
-
View的onTouchEvent默認都會消耗事件(返回true),除非它是不可點擊的(clickable和longClickable同時為false)。View的longClickable屬性默認為false,clickable屬性要分情況,比如Button的clickable屬性默認為true,而TextView的clickable屬性默認為false。
-
View的enable屬性不影響onTouchEvent的默認返回值。哪怕一個View是disable狀態的,只要它的clickable或者longClickable有一個為true,那么它的onTouchEvent就返回true。
-
onClick會發生的前提是當前View是可點擊的,並且它接收到了down和up事件。
-
事件傳遞過程是由外向內的,即事件總是先傳遞給父元素,然后再由父元素分發給子View,通過requestDisallowInterTouchEvent方法可以在子元素中干預父元素的事件分發過程,但是ACTION_DOWN事件除外。
3. 事件分發的流程
Activity–>PhoneWindow–>DecorView–>ViewGroup–>…–>View(View樹最底部的View)

十四 動畫
1.Android動畫的分類
1.1 補間動畫
a.漸變動畫支持四種類型:平移(Translate)、旋轉(Rotate)、縮放(Scale)、不透明度
b. 只是顯示的位置變動,View的實際位置未改變,表現為View移動到其他地方,點擊事件仍在原處才能響應。
c. 組合使用步驟較復雜。
d. View Animation 也是指此動畫。
1.2 幀動畫
a. 用於生成連續的Gif效果圖。
b. DrawableAnimation也是指此動畫
1.3 屬性動畫
a.支持對所有View能更新的屬性的動畫(需要屬性的setXxx()和getXxx())。
b. 更改的是View實際的屬性,所以不會影響其在動畫執行后所在位置的正常使用。
c. Android3.0(API11)及以后出現的功能,3.0之前的版本可使用github第三方開源庫nineoldandroids.jar進行支持。
2.補間動畫,幀動畫,屬性動畫優缺點
2.1 補間動畫優缺點
缺點:當平移動畫執行完停在最后的位置,結果焦點還在原來的位置(控件的屬性沒有真的被改變)
優點:相對於逐幀動畫來說,補間動畫更為連貫自然
2.2 幀動畫優缺點
缺點:效果單一,逐幀播放需要很多圖片,占用控件較大
優點:制作簡單
2.3 屬性動畫優缺點
缺點:(3.0+API出現)向下兼容問題。
優點:易定制,效果強。
十五 自定義View
1. 自定義View的幾種方式
-
對原View進行擴展方式
-
多個View的組合方式
-
重寫View的方式
2.自定義View需要重寫的方法。
- onMesure(測量)
- onLayout(布局)
- onDraw(繪制)
十六 Context
Context的中文翻譯為:語境; 上下文; 背景; 環境,在開發中我們經常說稱之為“上下文”,那么這個“上下文”到底是指什么意思呢?在語文中,我們可以理解為語境,在程序中,我們可以理解為當前對象在程序中所處的一個環境,一個與系統交互的過程。比如微信聊天,此時的“環境”是指聊天的界面以及相關的數據請求與傳輸,Context在加載資源、啟動Activity、獲取系統服務、創建View等操作都要參與。
Context提供了關於應用環境全局信息的接口。它是一個抽象類,它的執行被Android系統所提供。它允許獲取以應用為特征的資源和類型,是一個統領一些資源(應用程序環境變量等)的上下文。就是說,它描述一個應用程序環境的信息(即上下文);是一個抽象類,Android提供了該抽象類的具體實現類;通過它我們可以獲取應用程序的資源和類(包括應用級別操作,如啟動Activity,發廣播,接受Intent等)。

Context的作用域:

十七 ContentProvider
1. 簡介
內容提供者(Content Provider)主要用於在不同的應用程序之間實現數據共享的功能,它提供了一套完整的機制,允許一個程序訪問另一個程序中的數據,同時還能保證被訪數據的安全性。目前,使用內容提供者是Android實現跨程序共享數據的標准方式。
不同於文件存儲和SharedPreferences存儲中的兩種全局可讀可寫操作模式,內容提供者可以選擇只對哪一部分數據進行共享,從而保證我們程序中的隱私數據不會泄露的風險。
2.通過內容提供者訪問其他程序的數據
內容提供者的用法一般有兩種,一種是使用現有的內容提供器來讀取和操作相應程序中的數據,另一種是創建自己的內容提供者來給我們的程序提供外部訪問接口。如果一個程序通過內容提供者對其數據提供外部訪問接口,那么任何其他的應用程序就都可以對這部分數據進行訪問。Android系統中自帶的電話簿,短信,媒體庫等程序都提供了類似的訪問接口,這就使得第三方應用程序可以充分利用這部分數據來實現更好的功能。下面我們就來看看如何通過內容提供者訪問其他程序的數據:
2.1 ContentResolver的基本用法
想要訪問內容提供者中共享的數據,就一定要借助CotentResolver類,可以通過Context中的getContentResolver()方法獲取該類的實例。ContentResolver中提供了一系列的方法用於對數據進行CRUD(增刪改查)操作,其中insert()方法用於添加數據,update()方法用於數據的更新,delete()方法用於數據的刪除,query()方法用於數據的查詢。這好像SQLite數據庫操作有木有?
不同於SQLiteDatabase,ContentResolver中的增刪改查方法都是不接收表名參數的,而是使用一個Uri的參數代替,這個參數被稱作內容URI。內容URI給內容提供者中的數據建立了唯一的標識符,它主要由兩部分組成:authority和path。authority是用於對不同的應用程序做區分的,一般為了避免沖突,都會采用程序包名的方式來進行命名。比如某個程序的包名為com.example.app,那么該程序對應的authority就可以命名為com.example.app.provider。path則是用於對同一應用程序中不同的表做區分的,通常都會添加到authority的后面。比如某個程序的數據庫里存在兩張表:table1和table2,這時就可以將path分別命名為/table1和/table2,然后把authority和path進行組合,內容的URI就變成了com.example.app.provider/table1和com.example.app.provider/table2。不過目前還是很難辨認出這兩個字符串就是兩個內容URI,我們還需要在字符串的頭部加上協議聲明。因此,內容URI最標准的格式寫法如下:
content://com.example.app.provider/table1 content://com.example.app.provider/table2
在得到內容URI字符串之后,我們還需要將它解析成Uri對象才可以作為參數傳入。解析的方法也相當簡單,代碼如下所示:
Uri uri = new Uri.parse("content://com.example.app.provider/table1");
只需要調用Uri的靜態方法parse()就可以把內容URI字符串解析成URI對象。
現在,我們可以通過這個Uri對象來查詢table1表中的數據了。代碼如下所示:
Cursor cursor = getContentResolver()
.query(
uri,projection,selection,selectionArgs,sortOrder
);
query()方法接收的參數跟SQLiteDatabase中的query()方法接收的參數很像,但總體來說這個稍微簡單一些,畢竟這是在訪問其他程序中的數據,沒必要構建復雜的查詢語句。下標對內容提供者中的query的接收的參數進行了詳細的解釋:
查詢完成仍然會返回一個Cursor對象,這時我們就可以將數據從Cursor對象中逐個讀取出來了。讀取的思路仍然是對這個Cursor對象進行遍歷,然后一條一條的取出數據即可,代碼如下:
if(cursor != null){//注意這里一定要進行一次判空,因為有可能你要查詢的表根本不存在 while(cursor.moveToNext()){ String column1 = cursor.getString(cursor.getColumnIndex("column1")); int column2 = cursor.getInt(cursor.getColumnIndex("column2")); } }
增加,刪除,修改
//增加數據 ContentValues values = new ContentValues(); values.put("Column1","text"); values.put("Column2","1"); getContextResolver.insert(uri,values); //刪除數據 getContextResolver.delete(uri,"column2 = ?",new String[]{ "1" }); //更新數據 ContentValues values = new ContentValues(); values.put("Column1","改數據"); getContextResolver.update(uri,values,"column1 = ? and column2 = ?",new String[]{"text","1"});
3. 創建自己的內容提供者
前面已經提到過,如果要想實現跨程序共享數據的功能,官方推薦的方式就是使用內容提供器,可以新建一個類去繼承ContentProvider類的方式來創建一個自己的內容提供器。ContentProvider類有6個抽象方法,我們在使用子類繼承它的時候,需要將這6個方法全部重寫。新建MyProvider繼承字ContentProvider類,代碼如下所示:
public class MyProvider extends ContentProvider {
-
onCreate()方法:
初始化內容提供器的時候調用。通常會在這里完成對數據庫的創建和升級等操作。返回true表示內容提供器初始化成功,返回false則表示失敗。注意,只有當存在ContentResolver嘗試訪問我們的程序中的數據時,內容提供器才會被初始化。 -
query()方法:
從內容提供器中查詢數據。使用uri參數來確定查詢的哪張表,projection參數用於確定查詢的哪一列,selection和selectionArgs參數用於約束查詢哪些行,sortOrder參數用於對結果進行排序,查詢的結果存放在Cursor對象中返回。 -
insert()方法:
向內容提供器中添加一條數據。使用uri參數來確定要添加的表,待添加的數據保存在values參數中。添加完成后,返回一個用於表示這條新紀錄的URI。 -
update()方法:
更新內容提供器中已有的數據。使用uri參數來確定更新哪一張表中的數據,新數據保存着values參數當中,selection和selectionArgs參數用於約束更新哪些行,受影響的行數將作為返回值返回。 -
delete()方法:
從內容提供器中刪除數據。使用uri參數來確定刪除哪一張表中的數據,selection和selectionArgs參數用於約束刪除哪些行,被刪除的行數將作為返回值返回。 -
getType()方法:
根據傳入的內容URI來返回相應的MIME類型。