1 Activity的生命周期和啟動(dòng)模式
1.1 Activity的生命周期全面分析
用戶(hù)正常使用情況下的生命周期 & 由于Activity被系統(tǒng)回收或者設(shè)備配置改變導(dǎo)致Activity被銷(xiāo)毀重建情況下的生命周期逸爵。
1.1.1 典型情況下的生命周期分析
Activity的生命周期和啟動(dòng)模式
Activity第一次啟動(dòng):onCreate->onStart->onResume馅巷。
Activity切換到后臺(tái)( 用戶(hù)打開(kāi)新的Activity或者切換到桌面) ,onPause->onStop(如果新Activity采用了透明主題言沐,則當(dāng)前Activity不會(huì)回調(diào)onstop)爆侣。
Activity從后臺(tái)到前臺(tái)熄云,重新可見(jiàn)膜赃,onRestart->onStart->onResume。
用戶(hù)退出Activity赶熟,onPause->onStop->onDestroy最疆。
onStart開(kāi)始到onStop之前,Activity可見(jiàn)每聪。onResume到onPause之前,Activity可以接受用戶(hù)交互。
在新Activity啟動(dòng)之前药薯,棧頂?shù)腁ctivity需要先onPause后绑洛,新Activity才能啟動(dòng)。所以不能在onPause執(zhí)行耗時(shí)操作童本。
onstop中也不可以太耗時(shí)真屯,資源回收和釋放可以放在onDestroy中。
1.1.2 異常情況下的生命周期分析
1 系統(tǒng)配置變化導(dǎo)致Activity銷(xiāo)毀重建
例如Activity處于豎屏狀態(tài)穷娱,如果突然旋轉(zhuǎn)屏幕绑蔫,由于系統(tǒng)配置發(fā)生了改變,Activity就會(huì)被銷(xiāo)
毀并重新創(chuàng)建泵额。
在異常情況下系統(tǒng)會(huì)在onStop之前調(diào)用onSaveInstanceState來(lái)保存狀態(tài)晾匠。Activity重新創(chuàng)建后,會(huì)在onStart之后調(diào)用onRestoreInstanceState來(lái)恢復(fù)之前保存的數(shù)據(jù)梯刚。
保存數(shù)據(jù)的流程: Activity被意外終止凉馆,調(diào)用onSaveIntanceState保存數(shù)據(jù)-> Activity委托Window,Window委托它上面的頂級(jí)容器一個(gè)ViewGroup( 可能是DecorView) 亡资。然后頂層容器在通知所有子元素來(lái)保存數(shù)據(jù)澜共。
這是一種委托思想,Android中類(lèi)似的還有:View繪制過(guò)程锥腻、事件分發(fā)等嗦董。
系統(tǒng)只在Activity異常終止的時(shí)候才會(huì)調(diào)用 onSaveInstanceState 和onRestoreInstanceState 方法。其他情況不會(huì)觸發(fā)瘦黑。
2 資源內(nèi)存不足導(dǎo)致低優(yōu)先級(jí)的Activity被回收
三種Activity優(yōu)先級(jí):前臺(tái)- 可見(jiàn)非前臺(tái) -后臺(tái)京革,從高到低。
如果一個(gè)進(jìn)程沒(méi)有四大組件幸斥,那么將很快被系統(tǒng)殺死匹摇。因此,后臺(tái)工作最好放入service中甲葬。
android:configChanges="orientation" 在manifest中指定 configChanges 在系統(tǒng)配置變化后不重新創(chuàng)建Activity廊勃,也不會(huì)執(zhí)行 onSaveInstanceState 和onRestoreInstanceState 方法,而是調(diào)用 onConfigurationChnaged 方法经窖。
configChanges 一般常用三個(gè)選項(xiàng):
locale 系統(tǒng)語(yǔ)言變化
keyborardHidden 鍵盤(pán)的可訪(fǎng)問(wèn)性發(fā)生了變化坡垫,比如用戶(hù)調(diào)出了鍵盤(pán)
orientation 屏幕方向變化
1.2 Activity的啟動(dòng)模式
1.2.1 Activity的LaunchMode
Android使用棧來(lái)管理Activity。
standard
每次啟動(dòng)都會(huì)重新創(chuàng)建一個(gè)實(shí)例画侣,不管這個(gè)Activity在棧中是否已經(jīng)存在冰悠。誰(shuí)啟動(dòng)了這個(gè)Activity,那么Activity就運(yùn)行在啟動(dòng)它的那個(gè)Activity所在的棧中配乱。
用Application去啟動(dòng)Activity時(shí)會(huì)報(bào)錯(cuò)溉卓,原因是非Activity的Context沒(méi)有任務(wù)棧皮迟。解決辦法是為待啟動(dòng)Activity制定FLAG_ACTIVITY_NEW_TASH標(biāo)志位,這樣就會(huì)為它創(chuàng)建一個(gè)新的任務(wù)棧的诵。
singleTop
如果新Activity位于任務(wù)棧的棧頂万栅,那么此Activity不會(huì)被重新創(chuàng)建佑钾,同時(shí)回調(diào) onNewIntent 方法西疤。onCreate和onStart方法不會(huì)被執(zhí)行。
singleTask
這是一種單實(shí)例模式休溶。如果不存在activity所需要的任務(wù)棧代赁,則創(chuàng)建一個(gè)新任務(wù)棧和新Activity實(shí)例;如果存在所需要的任務(wù)棧兽掰,不存在實(shí)例芭碍,則新創(chuàng)建一個(gè)Activity實(shí)例;如果存在所需要的任務(wù)棧和實(shí)例孽尽,則不創(chuàng)建窖壕,調(diào)用onNewIntent方法。同時(shí)使該Activity實(shí)例之上的所有Activity出棧杉女。
參考:taskAffinity標(biāo)識(shí)Activity所需要的任務(wù)棧
singleIntance
單實(shí)例模式瞻讽。具有singleTask模式的所有特性,同時(shí)具有此模式的Activity只能獨(dú)自位于一個(gè)任務(wù)棧中熏挎。
假設(shè)兩個(gè)任務(wù)棧速勇,前臺(tái)任務(wù)棧為12,后臺(tái)任務(wù)棧為XY坎拐。Y的啟動(dòng)模式是singleTask》炒牛現(xiàn)在請(qǐng)求Y,整個(gè)后臺(tái)任務(wù)棧會(huì)被切換到前臺(tái)哼勇。如圖所示:
設(shè)置啟動(dòng)模式
manifest中 設(shè)置下的 android:launchMode 屬性都伪。
啟動(dòng)Activity的 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 。
兩種同時(shí)存在時(shí)积担,以第二種為準(zhǔn)院溺。第一種方式無(wú)法直接為Activity添加FLAG_ACTIVITY_CLEAR_TOP標(biāo)識(shí),第二種方式無(wú)法指定singleInstance模式磅轻。
可以通過(guò)命令行 adb shell dumpsys activity 命令查看棧中的Activity信息珍逸。
1.2.2 Activity的Flags
這些FLAG可以設(shè)定啟動(dòng)模式、可以影響Activity的運(yùn)行狀態(tài)聋溜。
FLAG_ACTIVITY_NEW_TASK
為Activity指定“singleTask”啟動(dòng)模式谆膳。
FLAG_ACTIVITY_SINGLE_TOP
為Activity指定“singleTop"啟動(dòng)模式。
FLAG_ACTIVITY_CLEAR_TOP
具有此標(biāo)記位的Activity啟動(dòng)時(shí)撮躁,同一個(gè)任務(wù)棧中位于它上面的Activity都要出棧漱病,一般和FLAG_ACTIVITY_NEW_TASK配合使用。
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
如果設(shè)置,新的Activity不會(huì)在最近啟動(dòng)的Activity的列表(就是安卓手機(jī)里顯示最近打開(kāi)的Activity那個(gè)系統(tǒng)級(jí)的UI)中保存杨帽。等同于在xml中指定android:exludeFromRecents="true"屬性漓穿。
1.3 IntentFilter的匹配規(guī)則
Activity調(diào)用方式
顯示調(diào)用 明確指定被啟動(dòng)對(duì)象的組件信息,包括包名和類(lèi)名
隱式調(diào)用 不需要明確指定組件信息注盈,需要Intent能夠匹配目標(biāo)組件中的IntentFilter中所設(shè)置的過(guò)濾信息晃危。
匹配規(guī)則
IntentFilter中的過(guò)濾信息有action、category老客、data僚饭。
只有一個(gè)Intent同時(shí)匹配action類(lèi)別、category類(lèi)別胧砰、data類(lèi)別才能成功啟動(dòng)目標(biāo)Activity鳍鸵。
一個(gè)Activity可以有多個(gè)intent-filter,一個(gè)Intent只要能匹配任何一組intent-filter即可成功啟動(dòng)對(duì)應(yīng)的Activity尉间。
** action**
action是一個(gè)字符串,匹配是指與action的字符串完全一樣,區(qū)分大小寫(xiě)偿乖。
一個(gè)intent-filter可以有多個(gè)aciton,只要Intent中的action能夠和任何一個(gè)action相同即可成功匹配哲嘲。
Intent中如果沒(méi)有指定action贪薪,那么匹配失敗。
** category**
category是一個(gè)字符串撤蚊。
Intent可以沒(méi)有category古掏,但是如果你一旦有category,不管有幾個(gè)侦啸,每個(gè)都必須與intent-filter中的其中一個(gè)category相同槽唾。
系統(tǒng)在 startActivity 和 startActivityForResult 的時(shí)候,會(huì)默認(rèn)為Intent加上 android.intent.category.DEFAULT 這個(gè)category光涂,所以為了我們的activity能夠接收隱式調(diào)用庞萍,就必須在intent-filter中加上 android.intent.category.DEFAULT 這個(gè)category。
** data**
data的匹配規(guī)則與action一樣忘闻,如果intent-filter中定義了data钝计,那么Intent中必須要定義可匹配的data。
intent-filter中data的語(yǔ)法:
? ? ? ? <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是指媒體類(lèi)型私恬,比如
image/jpeg、audio/mpeg4-generic和video/等炼吴,可以表示圖片本鸣、文本、視頻等不同的媒
體格式硅蹦。
URI的結(jié)構(gòu):
? ? <scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
實(shí)際例子
? ? content://com.example.project:200/folder/subfolder/etc? ? http://www.baidu.com:80/search/info
scheme:URI的模式荣德,比如http闷煤、file、content等涮瞻,默認(rèn)值是 file 鲤拿。
host:URI的主機(jī)名
port:URI的端口號(hào)
path、pathPattern和pathPrefix:這三個(gè)參數(shù)描述路徑信息署咽。
path近顷、pathPattern可以表示完整的路徑信息,其中pathPattern可以包含通配符 * 艇抠,表示0個(gè)或者多個(gè)任意字符幕庐。
pathPrefix只表示路徑的前綴信息久锥。
過(guò)濾規(guī)則的uri為空時(shí)家淤,有默認(rèn)值content和file,因此intent設(shè)置uri的scheme部分必須為content或file瑟由。
Intent指定data時(shí)絮重,必須調(diào)用 setDataAndType 方法, setData 和 setType 會(huì)清除另一方的值歹苦。
對(duì)于service和BroadcastReceiver也是同樣的匹配規(guī)則青伤,不過(guò)對(duì)于service最好使用顯式調(diào)用。
隱式調(diào)用需注意
當(dāng)通過(guò)隱式調(diào)用啟動(dòng)Activity時(shí)殴瘦,沒(méi)找到對(duì)應(yīng)的Activity系統(tǒng)就會(huì)拋出 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);
以上的第二個(gè)參數(shù)使用 MATCH_DEFAULT_ONLY 蚪腋,這個(gè)標(biāo)志位的含義是僅僅匹配那些在
intent-filter中聲明了 android.intent.category.DEFAULT 這個(gè)category的Activity丰歌。因?yàn)槿绻巡缓@個(gè)category的Activity匹配出來(lái)了,由于不含DEFAULT這個(gè)category的Activity是無(wú)法接受隱式Intent的從而導(dǎo)致startActivity失敗屉凯。
下面的action和category用來(lái)表明這是一個(gè)入口Activity立帖,并且會(huì)出現(xiàn)在系統(tǒng)的應(yīng)用列表中,二者缺一不可悠砚。
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
2 IPC機(jī)制
2.1 Android IPC 簡(jiǎn)介
IPC即Inter-Process Communication晓勇,含義為進(jìn)程間通信或者跨進(jìn)程通信,是指兩個(gè)進(jìn)程之間進(jìn)行數(shù)據(jù)交換的過(guò)程灌旧。
線(xiàn)程是CPU調(diào)度的最小單元绑咱,是一種有限的系統(tǒng)資源。進(jìn)程一般指一個(gè)執(zhí)行單元枢泰,在PC和移動(dòng)設(shè)備上是指一個(gè)程序或者應(yīng)用描融。進(jìn)程與線(xiàn)程是包含與被包含的關(guān)系。一個(gè)進(jìn)程可以包含多個(gè)線(xiàn)程宗苍。最簡(jiǎn)單的情況下一個(gè)進(jìn)程只有一個(gè)線(xiàn)程稼稿,即主線(xiàn)程( 例如Android的UI線(xiàn)程) 薄榛。
任何操作系統(tǒng)都需要有相應(yīng)的IPC機(jī)制。如Windows上的剪貼板让歼、管道和郵槽敞恋;Linux上命名管道、共享內(nèi)容谋右、信號(hào)量等硬猫。Android中最有特色的進(jìn)程間通信方式就是binder,另外還支持socket改执。contentProvider是Android底層實(shí)現(xiàn)的進(jìn)程間通信啸蜜。
在Android中,IPC的使用場(chǎng)景大概有以下:
有些模塊由于特殊原因需要運(yùn)行在單獨(dú)的進(jìn)程中辈挂。
通過(guò)多進(jìn)程來(lái)獲取多份內(nèi)存空間衬横。
當(dāng)前應(yīng)用需要向其他應(yīng)用獲取數(shù)據(jù)。
2.2 Android中的多進(jìn)程模式
2.2.1 開(kāi)啟多進(jìn)程模式
在Android中使用多線(xiàn)程只有一種方法:給四大組件在Manifest中指定 android:process 屬性终蒂。這個(gè)屬性的值就是進(jìn)程名蜂林。這意味著不能在運(yùn)行時(shí)指定一個(gè)線(xiàn)程所在的進(jìn)程。
tips:使用 adb shell ps 或 adb shell ps|grep 包名 查看當(dāng)前所存在的進(jìn)程信息拇泣。
兩種進(jìn)程命名方式的區(qū)別
“:remote”
“:”的含義是指在當(dāng)前的進(jìn)程名前面附加上當(dāng)前的包名噪叙,完整的進(jìn)程名為“com.example.c2.remote"。這種進(jìn)程屬于當(dāng)前應(yīng)用的私有進(jìn)程霉翔,其他應(yīng)用的組件不可以和它跑在同一個(gè)進(jìn)程中睁蕾。
"com.example.c2.remote"
這是一種完整的命名方式。這種進(jìn)程屬于全局進(jìn)程债朵,其他應(yīng)用可以通過(guò)ShareUID方式和它跑在同一個(gè)進(jìn)程中子眶。
2.2.2 多線(xiàn)程模式的運(yùn)行機(jī)制
Android為每個(gè)進(jìn)程都分配了一個(gè)獨(dú)立的虛擬機(jī),不同虛擬機(jī)在內(nèi)存分配上有不同的地址空間葱弟,導(dǎo)致不同的虛擬機(jī)訪(fǎng)問(wèn)同一個(gè)類(lèi)的對(duì)象會(huì)產(chǎn)生多份副本壹店。例如不同進(jìn)程的Activity對(duì)靜態(tài)變量的修改,對(duì)其他進(jìn)程不會(huì)造成任何影響芝加。所有運(yùn)行在不同進(jìn)程的四大組件硅卢,只要它們之間需要通過(guò)內(nèi)存在共享數(shù)據(jù),都會(huì)共享失敗藏杖。四大組件之間不可能不通過(guò)中間層來(lái)共享數(shù)據(jù)将塑。
多進(jìn)程會(huì)帶來(lái)以下問(wèn)題:
靜態(tài)成員和單例模式完全失效。
線(xiàn)程同步鎖機(jī)制完全失效蝌麸。
這兩點(diǎn)都是因?yàn)椴煌M(jìn)程不在同一個(gè)內(nèi)存空間下点寥,鎖的對(duì)象也不是同一個(gè)對(duì)象。
SharedPreferences的可靠性下降来吩。
SharedPreferences底層是 通過(guò)讀/寫(xiě)XML文件實(shí)現(xiàn)的敢辩,并發(fā)讀/寫(xiě)會(huì)導(dǎo)致一定幾率的數(shù)據(jù)丟失蔽莱。
Application會(huì)多次創(chuàng)建。
由于系統(tǒng)創(chuàng)建新的進(jìn)程的同時(shí)分配獨(dú)立虛擬機(jī)戚长,其實(shí)這就是啟動(dòng)一個(gè)應(yīng)用的過(guò)程盗冷。在多進(jìn)程模式中,不同進(jìn)程的組件擁有獨(dú)立的虛擬機(jī)同廉、Application以及內(nèi)存空間仪糖。
多進(jìn)程相當(dāng)于兩個(gè)不同的應(yīng)用采用了SharedUID的模式
實(shí)現(xiàn)跨進(jìn)程的方式有很多:
Intent傳遞數(shù)據(jù)。
共享文件和SharedPreferences迫肖。
基于Binder的Messenger和AIDL锅劝。
Socket等
2.3 IPC基礎(chǔ)概念介紹
主要介紹 Serializable 、 Parcelable 蟆湖、 Binder 故爵。Serializable和Parcelable接口可以完成對(duì)象的序列化過(guò)程,我們通過(guò)Intent和Binder傳輸數(shù)據(jù)時(shí)就需要Parcelabel和Serializable帐姻。還有的時(shí)候我們需要對(duì)象持久化到存儲(chǔ)設(shè)備上或者通過(guò)網(wǎng)絡(luò)傳輸?shù)狡渌蛻?hù)端稠集,也需要Serializable完成對(duì)象持久化奶段。
2.3.1 Serializable接口
Serializable 是Java提供的一個(gè)序列化接口( 空接口) 饥瓷,為對(duì)象提供標(biāo)準(zhǔn)的序列化和反序列化操作。只需要一個(gè)類(lèi)去實(shí)現(xiàn) Serializable 接口并聲明一個(gè) serialVersionUID 即可實(shí)現(xiàn)序列化痹籍。
private static final long serialVersionUID = 8711368828010083044L
serialVersionUID也可以不聲明呢铆。如果不手動(dòng)指定 serialVersionUID 的值,反序列化時(shí)如果當(dāng)前類(lèi)有所改變( 比如增刪了某些成員變量) 蹲缠,那么系統(tǒng)就會(huì)重新計(jì)算當(dāng)前類(lèi)的hash值并更新 serialVersionUID 棺克。這個(gè)時(shí)候當(dāng)前類(lèi)的 serialVersionUID 就和序列化數(shù)據(jù)中的serialVersionUID 不一致,導(dǎo)致反序列化失敗线定,程序就出現(xiàn)crash娜谊。
靜態(tài)成員變量屬于類(lèi)不屬于對(duì)象,不參與序列化過(guò)程斤讥,其次 transient 關(guān)鍵字標(biāo)記的成員變量也不參與序列化過(guò)程纱皆。
通過(guò)重寫(xiě)writeObject和readObject方法可以改變系統(tǒng)默認(rèn)的序列化過(guò)程。
2.3.2 Parcelable接口
Parcel內(nèi)部包裝了可序列化的數(shù)據(jù)芭商,可以在Binder中自由傳輸派草。序列化過(guò)程中需要實(shí)現(xiàn)的功能有序列化、反序列化和內(nèi)容描述铛楣。
序列化功能由 writeToParcel 方法完成,最終是通過(guò) Parcel 的一系列writer方法來(lái)完成近迁。
? @Override
? ? public void writeToParcel(Parcel out, int flags) {
? ? out.writeInt(code);
? ? out.writeString(name);
? ? }
反序列化功能由 CREATOR 來(lái)完成,其內(nèi)部表明了如何創(chuàng)建序列化對(duì)象和數(shù)組簸州,通過(guò) Parcel 的一系列read方法來(lái)完成鉴竭。
public static final Creator<Book> CREATOR = new Creator<Book>() {@Overridepublic Book createFromParcel(Parcel in) {return new Book(in);
} @Overridepublic Book[] newArray(int size) {return new Book[size];
}
};protected Book(Parcel in) {
code = in.readInt();
name = in.readString();
}
在Book(Parcel in)方法中歧譬,如果有一個(gè)成員變量是另一個(gè)可序列化對(duì)象,在反序列化過(guò)程中需要傳遞當(dāng)前線(xiàn)程的上下文類(lèi)加載器搏存,否則會(huì)報(bào)無(wú)法找到類(lèi)的錯(cuò)誤缴罗。
? book = in.readParcelable(Thread.currentThread().getContextClassLoader());
內(nèi)容描述功能由 describeContents 方法完成,幾乎所有情況下都應(yīng)該返回0祭埂,僅當(dāng)當(dāng)前對(duì)象中存在文件描述符時(shí)返回1面氓。
public int describeContents() {return 0;
}
Serializable 是Java的序列化接口,使用簡(jiǎn)單但開(kāi)銷(xiāo)大蛆橡,序列化和反序列化過(guò)程需要大量I/O操作舌界。而 Parcelable 是Android中的序列化方式,適合在Android平臺(tái)使用泰演,效率高但是使用麻煩呻拌。 Parcelable 主要在內(nèi)存序列化上,Parcelable 也可以將對(duì)象序列化到存儲(chǔ)設(shè)備中或者將對(duì)象序列化后通過(guò)網(wǎng)絡(luò)傳輸睦焕,但是稍顯復(fù)雜藐握,推薦使用 Serializable 。
2.3.3 Binder
? ? ? ?Binder是Android中的一個(gè)類(lèi)垃喊,實(shí)現(xiàn)了 IBinder 接口猾普。從IPC角度說(shuō),
Binder是Andoird的一種跨進(jìn)程通訊方式本谜,Binder還可以理解為一種虛擬物理設(shè)備初家,它的設(shè)備驅(qū)動(dòng)是/dev/binder。從Android Framework角度來(lái)說(shuō)乌助,Binder是 ServiceManager 連接各種Manager( ActivityManager· 溜在、 WindowManager ) 和相應(yīng) ManagerService 的橋梁。從Android應(yīng)用層來(lái)說(shuō)他托,Binder是客戶(hù)端和服務(wù)端進(jìn)行通信的媒介掖肋,當(dāng)bindService時(shí),服務(wù)端返回一個(gè)包含服務(wù)端業(yè)務(wù)調(diào)用的Binder對(duì)象赏参,通過(guò)這個(gè)Binder對(duì)象志笼,客戶(hù)端就可以獲取服務(wù)器端提供的服務(wù)或者數(shù)據(jù)( 包括普通服務(wù)和基于AIDL的服務(wù))。
Binder通信采用C/S架構(gòu)登刺,從組件視角來(lái)說(shuō)澳窑,包含Client铭污、Server、ServiceManager以及binder驅(qū)動(dòng),其中ServiceManager用于管理系統(tǒng)中的各種服務(wù)评腺。
圖中的Client,Server,Service Manager之間交互都是虛線(xiàn)表示耙厚,是由于它們彼此之間不是直接交互的,而是都通過(guò)與Binder驅(qū)動(dòng)進(jìn)行交互的,從而實(shí)現(xiàn)IPC通信方式万伤。其中Binder驅(qū)動(dòng)位于內(nèi)核空間,Client,Server,Service Manager位于用戶(hù)空間呜袁。Binder驅(qū)動(dòng)和Service Manager可以看做是Android平臺(tái)的基礎(chǔ)架構(gòu)敌买,而Client和Server是Android的應(yīng)用層,開(kāi)發(fā)人員只需自定義實(shí)現(xiàn)client阶界、Server端虹钮,借助Android的基本平臺(tái)架構(gòu)便可以直接進(jìn)行IPC通信。
http://gityuan.com/2015/10/31/binder-prepare/
Android中Binder主要用于 Service 膘融,包括AIDL和Messenger芙粱。普通Service的Binder不涉及進(jìn)程間通信,Messenger的底層其實(shí)是AIDL氧映,所以下面通過(guò)AIDL分析Binder的工作機(jī)制春畔。
由系統(tǒng)根據(jù)AIDL文件自動(dòng)生成.java文件
Book.java
表示圖書(shū)信息的實(shí)體類(lèi),實(shí)現(xiàn)了Parcelable接口岛都。
Book.aidl
Book類(lèi)在AIDL中的聲明律姨。
IBookManager.aidl
定義的管理Book實(shí)體的一個(gè)接口,包含 getBookList 和 addBook 兩個(gè)方法臼疫。盡管Book類(lèi)和IBookManager位于相同的包中择份,但是在IBookManager仍然要導(dǎo)入Book類(lèi)。
IBookManager.java
系統(tǒng)為IBookManager.aidl生產(chǎn)的Binder類(lèi)多矮,在 gen 目錄下缓淹。
IBookManager繼承了 IInterface 接口,所有在Binder中傳輸?shù)慕涌诙夹枰^IInterface接口塔逃。結(jié)構(gòu)如下:
聲明了 getBookList 和 addBook 方法,還聲明了兩個(gè)整型id分別標(biāo)識(shí)這兩個(gè)方法料仗,用于標(biāo)識(shí)在 transact 過(guò)程中客戶(hù)端請(qǐng)求的到底是哪個(gè)方法湾盗。
聲明了一個(gè)內(nèi)部類(lèi) Stub ,這個(gè) Stub 就是一個(gè)Binder類(lèi)立轧,當(dāng)客戶(hù)端和服務(wù)端位于同一進(jìn)程時(shí)格粪,方法調(diào)用不會(huì)走跨進(jìn)程的 transact 。當(dāng)二者位于不同進(jìn)程時(shí)氛改,方法調(diào)用需要走 transact 過(guò)程帐萎,這個(gè)邏輯有 Stub 的內(nèi)部代理類(lèi) Proxy 來(lái)完成。
這個(gè)接口的核心實(shí)現(xiàn)就是它的內(nèi)部類(lèi) Stub 和 Stub 的內(nèi)部代理類(lèi) Proxy 胜卤。
Stub和Proxy類(lèi)的內(nèi)部方法和定義
DESCRIPTOR
Binder的唯一標(biāo)識(shí)疆导,一般用Binder的類(lèi)名表示。
asInterface(android.os.IBinder obj)
將服務(wù)端的Binder對(duì)象轉(zhuǎn)換為客戶(hù)端所需的AIDL接口類(lèi)型的對(duì)象葛躏,如果C/S位于同一進(jìn)
程澈段,此方法返回就是服務(wù)端的Stub對(duì)象本身悠菜,否則返回的就是系統(tǒng)封裝后的Stub.proxy對(duì)
象。
asBinder
返回當(dāng)前Binder對(duì)象败富。
onTransact
這個(gè)方法運(yùn)行在服務(wù)端的Binder線(xiàn)程池中悔醋,由客戶(hù)端發(fā)起跨進(jìn)程請(qǐng)求時(shí),遠(yuǎn)程請(qǐng)求會(huì)通過(guò)
系統(tǒng)底層封裝后交由此方法來(lái)處理兽叮。該方法的原型是
java public Boolean onTransact(int code,Parcelable data,Parcelable reply,int flags)
服務(wù)端通過(guò)code確定客戶(hù)端請(qǐng)求的目標(biāo)方法是什么芬骄,
接著從data取出目標(biāo)方法所需的參數(shù),然后執(zhí)行目標(biāo)方法鹦聪。
執(zhí)行完畢后向reply寫(xiě)入返回值( 如果有返回值) 穴墅。
如果這個(gè)方法返回值為false,那么服務(wù)端的請(qǐng)求會(huì)失敗提岔,利用這個(gè)特性我們可以來(lái)做權(quán)限驗(yàn)證匪傍。
Proxy#getBookList 和Proxy#addBook
這兩個(gè)方法運(yùn)行在客戶(hù)端,內(nèi)部實(shí)現(xiàn)過(guò)程如下:
首先創(chuàng)建該方法所需要的輸入型對(duì)象Parcel對(duì)象_data观挎,輸出型Parcel對(duì)象_reply和返回值對(duì)象List琴儿。
然后把該方法的參數(shù)信息寫(xiě)入_data( 如果有參數(shù))
接著調(diào)用transact方法發(fā)起RPC( 遠(yuǎn)程過(guò)程調(diào)用) ,同時(shí)當(dāng)前線(xiàn)程掛起
然后服務(wù)端的onTransact方法會(huì)被調(diào)用知道RPC過(guò)程返回后嘁捷,當(dāng)前線(xiàn)程繼續(xù)執(zhí)行造成,并從_reply中取出RPC過(guò)程的返回結(jié)果,最后返回_reply中的數(shù)據(jù)雄嚣。
AIDL文件不是必須的晒屎,之所以提供AIDL文件,是為了方便系統(tǒng)為我們生成IBookManager.java缓升,但我們完全可以自己寫(xiě)一個(gè)鼓鲁。
linkToDeath和unlinkToDeath
如果服務(wù)端進(jìn)程異常終止,我們到服務(wù)端的Binder連接斷裂港谊。但是骇吭,如果我們不知道Binder連接已經(jīng)斷裂,那么客戶(hù)端功能會(huì)受影響歧寺。通過(guò)linkTODeath我們可以給Binder設(shè)置一個(gè)死亡代理燥狰,當(dāng)Binder死亡時(shí),我們就會(huì)收到通知斜筐。
聲明一個(gè) DeathRecipient 對(duì)象龙致。 DeathRecipient 是一個(gè)接口,只有一個(gè)方法 binderDied 顷链,當(dāng)Binder死亡的時(shí)候目代,系統(tǒng)就會(huì)回調(diào) binderDied 方法,然后我們就可以重新綁定遠(yuǎn)程服務(wù)。
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient(){
@Override
public void binderDied(){
if(mBookManager == null){
return;
}
mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
mBookManager = null;
// TODO:這里重新綁定遠(yuǎn)程Service
}
}
在客戶(hù)端綁定遠(yuǎn)程服務(wù)成功后像啼,給binder設(shè)置死亡代理:
mService = IBookManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);
另外俘闯,可以通過(guò)Binder的 isBinderAlive 判斷Binder是否死亡。
2.4 Android中的IPC方式
主要有以下方式:
Intent中附加extras
共享文件
Binder
ContentProvider
Socket
2.4.1 使用Bundle
四大組件中的三大組件( Activity忽冻、Service真朗、Receiver) 都支持在Intent中傳遞 Bundle 數(shù)據(jù)。
Bundle實(shí)現(xiàn)了Parcelable接口僧诚,因此可以方便的在不同進(jìn)程間傳輸遮婶。當(dāng)我們?cè)谝粋€(gè)進(jìn)程中啟動(dòng)了另一個(gè)進(jìn)程的Activity、Service湖笨、Receiver旗扑,可以再Bundle中附加我們需要傳輸給遠(yuǎn)程進(jìn)程的消息并通過(guò)Intent發(fā)送出去。被傳輸?shù)臄?shù)據(jù)必須能夠被序列化慈省。
2.4.2 使用文件共享
我們可以序列化一個(gè)對(duì)象到文件系統(tǒng)中的同時(shí)從另一個(gè)進(jìn)程中恢復(fù)這個(gè)對(duì)象臀防。
通過(guò) ObjectOutputStream / ObjectInputStream 序列化一個(gè)對(duì)象到文件中,或者在另一個(gè)進(jìn)程從文件中反序列這個(gè)對(duì)象边败。注意:反序列化得到的對(duì)象只是內(nèi)容上和序列化之前的對(duì)象一樣袱衷,本質(zhì)是兩個(gè)對(duì)象。
文件并發(fā)讀寫(xiě)會(huì)導(dǎo)致讀出的對(duì)象可能不是最新的笑窜,并發(fā)寫(xiě)的話(huà)那就更嚴(yán)重了 致燥。所以文件共享方式適合對(duì)數(shù)據(jù)同步要求不高的進(jìn)程之間進(jìn)行通信,并且要妥善處理并發(fā)讀寫(xiě)問(wèn)題排截。
SharedPreferences 底層實(shí)現(xiàn)采用XML文件來(lái)存儲(chǔ)鍵值對(duì)嫌蚤。系統(tǒng)對(duì)它的讀/寫(xiě)有一定的緩存策略,即在內(nèi)存中會(huì)有一份 SharedPreferences 文件的緩存断傲,因此在多進(jìn)程模式下脱吱,系統(tǒng)對(duì)它的讀/寫(xiě)變得不可靠,面對(duì)高并發(fā)讀/寫(xiě)時(shí) SharedPreferences 有很大幾率丟失數(shù)據(jù)艳悔,因此不建議在IPC中使用 SharedPreferences 急凰。
2.4.3 使用Messenger
Messenger可以在不同進(jìn)程間傳遞Message對(duì)象。是一種輕量級(jí)的IPC方案猜年,底層實(shí)現(xiàn)是AIDL。它對(duì)AIDL進(jìn)行了封裝疾忍,使得我們可以更簡(jiǎn)便的進(jìn)行IPC乔外。
具體使用時(shí),分為服務(wù)端和客戶(hù)端:
服務(wù)端:創(chuàng)建一個(gè)Service來(lái)處理客戶(hù)端請(qǐng)求一罩,同時(shí)創(chuàng)建一個(gè)Handler并通過(guò)它來(lái)創(chuàng)建一個(gè)
Messenger杨幼,然后再Service的onBind中返回Messenger對(duì)象底層的Binder即可。
private final Messenger mMessenger = new Messenger (new xxxHandler());
客戶(hù)端:綁定服務(wù)端的Sevice,利用服務(wù)端返回的IBinder對(duì)象來(lái)創(chuàng)建一個(gè)Messenger差购,通過(guò)這個(gè)Messenger就可以向服務(wù)端發(fā)送消息了四瘫,消息類(lèi)型是 Message 。如果需要服務(wù)端響應(yīng)欲逃,則需要?jiǎng)?chuàng)建一個(gè)Handler并通過(guò)它來(lái)創(chuàng)建一個(gè)Messenger( 和服務(wù)端一樣) 找蜜,并通過(guò) Message 的 replyTo 參數(shù)傳遞給服務(wù)端。服務(wù)端通過(guò)Message的 replyTo 參數(shù)就可以回應(yīng)客戶(hù)端了稳析。
總而言之洗做,就是客戶(hù)端和服務(wù)端 拿到對(duì)方的Messenger來(lái)發(fā)送 Message 。只不過(guò)客戶(hù)端通過(guò)bindService 而服務(wù)端通過(guò) message.replyTo 來(lái)獲得對(duì)方的Messenger彰居。
Messenger中有一個(gè) Hanlder 以串行的方式處理隊(duì)列中的消息诚纸。不存在并發(fā)執(zhí)行,因此我們不用考慮線(xiàn)程同步的問(wèn)題陈惰。
2.4.4 使用AIDL
如果有大量的并發(fā)請(qǐng)求畦徘,使用Messenger就不太適合,同時(shí)如果需要跨進(jìn)程調(diào)用服務(wù)端的方法抬闯,Messenger就無(wú)法做到了井辆。這時(shí)我們可以使用AIDL。
流程如下:
服務(wù)端需要?jiǎng)?chuàng)建Service來(lái)監(jiān)聽(tīng)客戶(hù)端請(qǐng)求画髓,然后創(chuàng)建一個(gè)AIDL文件掘剪,將暴露給客戶(hù)端的接口在AIDL文件中聲明,最后在Service中實(shí)現(xiàn)這個(gè)AIDL接口即可奈虾。
客戶(hù)端首先綁定服務(wù)端的Service夺谁,綁定成功后,將服務(wù)端返回的Binder對(duì)象轉(zhuǎn)成AIDL接口所屬的類(lèi)型肉微,接著就可以調(diào)用AIDL中的方法了匾鸥。
AIDL支持的數(shù)據(jù)類(lèi)型:
基本數(shù)據(jù)類(lèi)型、String碉纳、CharSequence
List:只支持ArrayList勿负,里面的每個(gè)元素必須被AIDL支持
Map:只支持HashMap,里面的每個(gè)元素必須被AIDL支持
Parcelable
所有的AIDL接口本身也可以在AIDL文件中使用
自定義的Parcelable對(duì)象和AIDL對(duì)象劳曹,不管它們與當(dāng)前的AIDL文件是否位于同一個(gè)包奴愉,都必須顯式import進(jìn)來(lái)。
如果AIDL文件中使用了自定義的Parcelable對(duì)象铁孵,就必須新建一個(gè)和它同名的AIDL文件锭硼,并在其中聲明它為Parcelable類(lèi)型。
? ? package com.ryg.chapter_2.aidl;
? ? parcelable Book;
AIDL接口中的參數(shù)除了基本類(lèi)型以外都必須表明方向in/out蜕劝。AIDL接口文件中只支持方法檀头,不支持聲明靜態(tài)常量轰异。建議把所有和AIDL相關(guān)的類(lèi)和文件放在同一個(gè)包中,方便管理暑始。
? ? void addBook(in Book book);
AIDL方法是在服務(wù)端的Binder線(xiàn)程池中執(zhí)行的搭独,因此當(dāng)多個(gè)客戶(hù)端同時(shí)連接時(shí),管理數(shù)據(jù)的集合直接采用 CopyOnWriteArrayList 來(lái)進(jìn)行自動(dòng)線(xiàn)程同步廊镜。類(lèi)似的還有 ConcurrentHashMap 牙肝。
因?yàn)榭蛻?hù)端的listener和服務(wù)端的listener不是同一個(gè)對(duì)象,所以 RecmoteCallbackList 是系統(tǒng)專(zhuān)門(mén)提供用于刪除跨進(jìn)程listener的接口期升,支持管理任意的AIDL接口惊奇,因?yàn)樗蠥IDL接口都繼承自 IInterface 接口。
? ? public class RemoteCallbackList<E extends IInterface>
它內(nèi)部通過(guò)一個(gè)Map接口來(lái)保存所有的AIDL回調(diào)播赁,這個(gè)Map的key是 IBinder 類(lèi)型颂郎,value是 Callback 類(lèi)型。當(dāng)客戶(hù)端解除注冊(cè)時(shí)容为,遍歷服務(wù)端所有l(wèi)istener乓序,找到和客戶(hù)端listener具有相同Binder對(duì)象的服務(wù)端listenr并把它刪掉。
==客戶(hù)端RPC的時(shí)候線(xiàn)程會(huì)被掛起坎背,由于被調(diào)用的方法運(yùn)行在服務(wù)端的Binder線(xiàn)程池中替劈,可能很耗時(shí),不能在主線(xiàn)程中去調(diào)用服務(wù)端的方法得滤。==
權(quán)限驗(yàn)證
默認(rèn)情況下陨献,我們的遠(yuǎn)程服務(wù)任何人都可以連接,我們必須加入權(quán)限驗(yàn)證功能懂更,權(quán)限驗(yàn)證失敗則無(wú)法調(diào)用服務(wù)中的方法眨业。通常有兩種驗(yàn)證方法:
在onBind中驗(yàn)證,驗(yàn)證不通過(guò)返回null
驗(yàn)證方式比如permission驗(yàn)證沮协,在AndroidManifest聲明:
<permission
android:name="com.rgy.chapter_2.permisson.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
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中驗(yàn)證,驗(yàn)證不通過(guò)返回false
可以permission驗(yàn)證慷暂,還可以采用Uid和Pid驗(yàn)證聘殖。
2.4.5 使用ContentProvider
==ContentProvider是四大組件之一,天生就是用來(lái)進(jìn)程間通信行瑞。和Messenger一樣奸腺,其底層實(shí)現(xiàn)是用Binder。==
系統(tǒng)預(yù)置了許多ContentProvider血久,比如通訊錄洋机、日程表等。要RPC訪(fǎng)問(wèn)這些信息洋魂,只需要通過(guò)ContentResolver的query、update、insert和delete方法即可副砍。
創(chuàng)建自定義的ContentProvider衔肢,只需繼承ContentProvider類(lèi)并實(shí)現(xiàn) onCreate 、 query 豁翎、 update 角骤、 insert 、 getType 六個(gè)抽象方法即可心剥。getType用來(lái)返回一個(gè)Uri請(qǐng)求所對(duì)應(yīng)的MIME類(lèi)型邦尊,剩下四個(gè)方法對(duì)應(yīng)于CRUD操作。這六個(gè)方法都運(yùn)行在ContentProvider進(jìn)程中优烧,除了 onCreate 由系統(tǒng)回調(diào)并運(yùn)行在主線(xiàn)程里蝉揍,其他五個(gè)方法都由外界調(diào)用并運(yùn)行在Binder線(xiàn)程池中。
ContentProvider是通過(guò)Uri來(lái)區(qū)分外界要訪(fǎng)問(wèn)的數(shù)據(jù)集合畦娄,例如外界訪(fǎng)問(wèn)ContentProvider中的表又沾,我們需要為它們定義單獨(dú)的Uri和Uri_Code。根據(jù)Uri_Code熙卡,我們就知道要訪(fǎng)問(wèn)哪個(gè)表了杖刷。
==query、update驳癌、insert滑燃、delete四大方法存在多線(xiàn)程并發(fā)訪(fǎng)問(wèn),因此方法內(nèi)部要做好線(xiàn)程同步颓鲜。==若采用SQLite并且只有一個(gè)SQLiteDatabase表窘,SQLiteDatabase內(nèi)部已經(jīng)做了同步處理。若是多個(gè)SQLiteDatabase或是采用List作為底層數(shù)據(jù)集灾杰,就必須做線(xiàn)程同步蚊丐。
2.4.6 使用Socket
Socket也稱(chēng)為“套接字”,分為流式套接字和用戶(hù)數(shù)據(jù)報(bào)套接字兩種艳吠,分別對(duì)應(yīng)于TCP和UDP協(xié)議麦备。Socket可以實(shí)現(xiàn)計(jì)算機(jī)網(wǎng)絡(luò)中的兩個(gè)進(jìn)程間的通信,當(dāng)然也可以在本地實(shí)現(xiàn)進(jìn)程間的通信昭娩。我們以一個(gè)跨進(jìn)程的聊天程序來(lái)演示凛篙。
在遠(yuǎn)程Service建立一個(gè)TCP服務(wù),然后在主界面中連接TCP服務(wù)栏渺。服務(wù)端Service監(jiān)聽(tīng)本地端口呛梆,客戶(hù)端連接指定的端口,建立連接成功后磕诊,拿到 Socket 對(duì)象就可以向服務(wù)端發(fā)送消息或者接受服務(wù)端發(fā)送的消息填物。
除了采用TCP套接字纹腌,也可以用UDP套接字。實(shí)際上socket不僅能實(shí)現(xiàn)進(jìn)程間的通信滞磺,還可以實(shí)現(xiàn)設(shè)備間的通信(只要設(shè)備之間的IP地址互相可見(jiàn))升薯。
2.5 Binder連接池
前面提到AIDL的流程是:首先創(chuàng)建一個(gè)service和AIDL接口,接著創(chuàng)建一個(gè)類(lèi)繼承自AIDL接口中的Stub類(lèi)并實(shí)現(xiàn)Stub中的抽象方法击困,客戶(hù)端在Service的onBind方法中拿到這個(gè)類(lèi)的對(duì)象涎劈,然后綁定這個(gè)service,建立連接后就可以通過(guò)這個(gè)Stub對(duì)象進(jìn)行RPC阅茶。
那么如果項(xiàng)目龐大蛛枚,有多個(gè)業(yè)務(wù)模塊都需要使用AIDL進(jìn)行IPC,隨著AIDL數(shù)量的增加脸哀,我們不能無(wú)限制地增加Service蹦浦,我們需要把所有AIDL放在同一個(gè)Service中去管理。
服務(wù)端只有一個(gè)Service企蹭,把所有AIDL放在一個(gè)Service中白筹,不同業(yè)務(wù)模塊之間不能有耦合
服務(wù)端提供一個(gè) queryBinder 接口,這個(gè)接口能夠根據(jù)業(yè)務(wù)模塊的特征來(lái)返回響應(yīng)的Binder對(duì)象給客戶(hù)端
不同的業(yè)務(wù)模塊拿到所需的Binder對(duì)象就可以進(jìn)行RPC了
2.6 選用合適的IPC方式
/////////----------------
一 Activity
1 Activity 生命周期
1.1 Activity 的四種狀態(tài)
running?當(dāng)前Activity正在運(yùn)行谅摄,獲取焦點(diǎn)
paused?當(dāng)前Activity處于暫停狀態(tài)徒河,可見(jiàn),沒(méi)有焦點(diǎn)
stopped?當(dāng)前Activity處于暫停狀態(tài)送漠,完全不可見(jiàn)顽照,內(nèi)存里的成員變量和狀態(tài)信息仍在。
killed?當(dāng)前Activity被銷(xiāo)毀后的狀態(tài)闽寡,成員變量和狀態(tài)信息被一并回收代兵。
1.2 Activity的生命周期
Activity啟動(dòng) →onCreate()→onStart()→onResume();
點(diǎn)擊home鍵返回桌面→onPause()→onStop()爷狈;
再次回到原Activity→ onRestart()→onStart()→onResume()植影;
按返回鍵退出當(dāng)前Activity→onPause()→onStop()→onDestroy();
2 Android任務(wù)棧
優(yōu)先級(jí):前臺(tái)>可見(jiàn)>服務(wù)>后臺(tái)>空
前臺(tái):正在與用戶(hù)進(jìn)行交互的Activity所在的進(jìn)程
可見(jiàn):Activity可見(jiàn)但沒(méi)有在前臺(tái)所在的進(jìn)程
服務(wù):Activity在后臺(tái)開(kāi)啟了服務(wù)所在的進(jìn)程
后臺(tái):Activity完全處于后臺(tái)所在的進(jìn)程
空:沒(méi)有任何Activity存在的進(jìn)程
3. Activity的啟動(dòng)模式
3.1 為什么需要啟動(dòng)模式涎永?
? ? ? ?每次啟動(dòng)一個(gè)Activity都會(huì)把對(duì)應(yīng)的要啟動(dòng)的Activity的實(shí)例放入任務(wù)棧中思币,加入這個(gè)Activity被頻繁啟動(dòng),會(huì)產(chǎn)生很多的這個(gè)Activity的實(shí)例羡微,為了杜絕這種內(nèi)存浪費(fèi)的行為谷饿,Activity的啟動(dòng)模式被創(chuàng)造出來(lái)。
3.2 Activity的啟動(dòng)模式
系統(tǒng)模式模式:standard
? ? ? ?標(biāo)準(zhǔn)模式妈倔,也是系統(tǒng)的默認(rèn)模式博投,啟動(dòng)一個(gè)activity就創(chuàng)建一個(gè)activity實(shí)例,不管這個(gè)實(shí)例是否存在盯蝴,誰(shuí)啟動(dòng)了這個(gè)Activity毅哗,那么這個(gè)Activity就運(yùn)行在啟動(dòng)它的那個(gè)Activity的任務(wù)棧中听怕。
棧頂復(fù)用模式:singleTop
? ? ? ?在這種模式下,如果新的Activity已經(jīng)位于棧頂黎做,那么此Activity不會(huì)被重新創(chuàng)建叉跛,同時(shí)它的onNewIntent方法被回調(diào),通過(guò)此方法的參數(shù)我們可以取出當(dāng)前的請(qǐng)求信息蒸殿。需要注意,此Activity的onCreate鸣峭,onStart方法不會(huì)被系統(tǒng)調(diào)用宏所。如果新Activity不在棧頂,那么新Activity任然會(huì)被重新重建摊溶。
棧內(nèi)復(fù)用模式:singleTask
? ? ? ?這是一種單實(shí)例模式爬骤,只要Activity在一個(gè)棧中存在,那么多次啟動(dòng)此Activity都不會(huì)重新創(chuàng)建實(shí)例莫换,系統(tǒng)也會(huì)回調(diào)onNewIntent方法霞玄。
例如:當(dāng)前棧內(nèi)情況為ABC,此時(shí)D被以singleTask的模式被啟動(dòng)拉岁,當(dāng)前棧變?yōu)锳BCD坷剧。
如果當(dāng)前棧內(nèi)情況為ADBC,此時(shí)D被以singleTask的模式被啟動(dòng)喊暖,當(dāng)前棧變?yōu)锳D惫企。
單實(shí)例模式:singleInstance
? ? ? ?這是一種加強(qiáng)的單實(shí)例模式,它除了具有singleTask模式的所有特性外陵叽,還加強(qiáng)了一點(diǎn)狞尔,那就是具有此種模式的Activity只能單獨(dú)位于一個(gè)任務(wù)棧中,比如Activity A是singleInstance模式巩掺,A被啟動(dòng)時(shí)系統(tǒng)會(huì)為它創(chuàng)建一個(gè)新的任務(wù)棧偏序,A運(yùn)行在這個(gè)單獨(dú)的任務(wù)棧中,后續(xù)的請(qǐng)求均不會(huì)再創(chuàng)建A胖替,除非這個(gè)單獨(dú)的任務(wù)棧被系統(tǒng)銷(xiāo)毀了研儒。
二 Fragment
1. 為什么Fragment被稱(chēng)為第五大組件?
Android中的四大組件為Activity刊殉,service殉摔,ContentProvider,Broadcast记焊。
Fragment因?yàn)橛猩芷谝菰拢褂妙l率不輸于四大組件,可靈活加載到Activity中遍膜。
1.1 Fragment加載到Activity的兩種方式
靜態(tài)加載:直接在Activity布局文件中指定Fragment碗硬。代碼如下
<fragment?
? ? android:name="com.example.myfragment.MyFragment"?
? ? android:id="@+id/myfragment_1"?
? ? android:layout_width="wrap_content"?
? ? android:layout_height="wrap_content"/>
動(dòng)態(tài)加載:動(dòng)態(tài)加載需要使用到FragmentManager瓤湘,這種加載方式在開(kāi)發(fā)中是非常常見(jiàn)的,示例代碼如下:
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();//將FragmentA從容器中移除掉恩尾,減少內(nèi)存的消耗fragmentTransaction.remove(fragmentA);
fragmentTransaction.add(R.id.fragment_layout,new FragmentB());
fragmentTransaction.commit();
1.2 Fragment 與ViewPager搭配使用
? ? ? ?通常情況下我們開(kāi)發(fā)應(yīng)用最常見(jiàn)的使用情況是TabLayout+ViewPager+Fragment的使用方式弛说,這就涉及到兩個(gè)常用的適配器的使用,一個(gè)是FragmentPagerAdapter翰意,另外一個(gè)是FragmentStatePagerAdapter,那么它們之間有什么區(qū)別呢木人?其實(shí)很簡(jiǎn)單,F(xiàn)ragmentPagerAdapter適用于頁(yè)面較少的情況冀偶,而FragmentStatePagerAdapter適用于頁(yè)面較多的情況醒第。
2. Fragment的生命周期
Fragment
界面打開(kāi)
onCreate() 方法執(zhí)行!
onCreateView() 方法執(zhí)行进鸠!
onActivityCreated() 方法執(zhí)行稠曼!
onStart() 方法執(zhí)行!
onResume() 方法執(zhí)行客年!
按下主屏幕鍵/鎖屏
onPause() 方法執(zhí)行霞幅!
onStop() 方法執(zhí)行!
重新打開(kāi)
onStart() 方法執(zhí)行量瓜!
onResume() 方法執(zhí)行司恳!
按下后退鍵
onPause() 方法執(zhí)行!
onStop() 方法執(zhí)行榔至!
onDestroyView() 方法執(zhí)行抵赢!
onDestroy() 方法執(zhí)行!
onDetach() 方法執(zhí)行唧取!
Activity
打開(kāi)應(yīng)用
onCreate() 方法執(zhí)行铅鲤!
onStart() 方法執(zhí)行!
onResume() 方法執(zhí)行枫弟!
按下主屏幕鍵/鎖屏
onPause() 方法執(zhí)行邢享!
onStop() 方法執(zhí)行!
重新打開(kāi)應(yīng)用
onRestart() 方法執(zhí)行淡诗!
onStart() 方法執(zhí)行骇塘!
onResume() 方法執(zhí)行!
按下后退鍵
onPause() 方法執(zhí)行韩容!
onStop() 方法執(zhí)行款违!
onDestroy() 方法執(zhí)行!
在Activity中加入Fragment,對(duì)應(yīng)的生命周期
打開(kāi)
Fragment onAttach()方法執(zhí)行
Fragment onCreate() 方法執(zhí)行群凶!
Fragment onCreateView() 方法執(zhí)行池充!
Fragment onViewCreated()方法執(zhí)行
Activity onCreate() 方法執(zhí)行螺捐!
Fragment onActivityCreated() 方法執(zhí)行赔蒲!
Activity onStart() 方法執(zhí)行!
Fragment onStart() 方法執(zhí)行力穗!
Activity onResume() 方法執(zhí)行!
Fragment onResume() 方法執(zhí)行气嫁!
按下主屏幕鍵/鎖屏
Fragment onPause() 方法執(zhí)行当窗!
Activity onPause() 方法執(zhí)行!
Fragment onStop() 方法執(zhí)行寸宵!
Activity onStop() 方法執(zhí)行崖面!
再次打開(kāi)
Activity onRestart() 方法執(zhí)行!
Activity onStart() 方法執(zhí)行邓馒!
Fragment onStart() 方法執(zhí)行嘶朱!
Activity onResume() 方法執(zhí)行!
Fragment onResume() 方法執(zhí)行光酣!
按下后退鍵
Fragment onPause() 方法執(zhí)行!
Activity onPause() 方法執(zhí)行脉课!
Fragment onStop() 方法執(zhí)行救军!
Activity onStop() 方法執(zhí)行!
Fragment onDestroyView() 方法執(zhí)行倘零!
Fragment onDestroy() 方法執(zhí)行唱遭!
Fragment onDetach() 方法執(zhí)行!
Activity onDestroy() 方法執(zhí)行呈驶!
3. Fragment的通信
3.1 在Fragment中調(diào)用Activity中的方法
? ? ? ?在Fragment中調(diào)用Activity的方法很簡(jiǎn)單拷泽,F(xiàn)ragment有個(gè)getActivity()的方法,比如袖瞻,在MainActivity中的一個(gè)Fragment中獲取MainActivity的引用司致,并調(diào)用MainActivity的某個(gè)方法methodA()方法你可以這么寫(xiě):
MainActivity mainActivity = (MainActivity) getActivity();
mainActivity.methodA();
3.2 在Activity中調(diào)用Fragment的方法
? ? ? ?在Activity中調(diào)用Fragment中的方法是最簡(jiǎn)單的,我想這里我不用多說(shuō)吧聋迎!直接接口回調(diào)即可調(diào)用Fragment的任何可訪(fǎng)問(wèn)的方法脂矫。
3.3 在Fragment中調(diào)用另外一個(gè)Fragment的方法
? ? ? ?這個(gè)可就需要一定的思維性了,首先要想調(diào)用Fragment A的方法霉晕,除了這個(gè)Fragment A自身可以調(diào)用外庭再,這個(gè)Fragment A所屬的Activity也可以調(diào)用,要想另外一個(gè)Fragment B調(diào)用此Fragment A的方法牺堰,F(xiàn)ragment B可以間接通過(guò)Activity來(lái)進(jìn)行調(diào)用拄轻,也就是3.1 和 3.2 的結(jié)合。
三 Service
1. Service基礎(chǔ)知識(shí)
1.1 Service是什么伟葫?
? ? ? ?Service(服務(wù))是一個(gè)一種可以在后臺(tái)執(zhí)行長(zhǎng)時(shí)間運(yùn)行操作而沒(méi)有用戶(hù)界面的組件恨搓。它運(yùn)行于UI線(xiàn)程,因此不能進(jìn)行耗時(shí)的操作扒俯。
1.2 Service和Thread的區(qū)別
? ? ? ?Service的運(yùn)行是在UI線(xiàn)程當(dāng)中的奶卓,是絕對(duì)絕對(duì)不能進(jìn)行耗時(shí)操作的一疯,而Thread開(kāi)啟的子線(xiàn)程則可以進(jìn)行耗時(shí)操作,但是Thread開(kāi)啟的子線(xiàn)程是不能直接對(duì)UI進(jìn)行操作的夺姑,否則極有可能發(fā)生直接讓程序崩掉墩邀,這就是它們的區(qū)別。
2. 啟動(dòng)Service的2種方式
2.1 startService()方法開(kāi)啟Service
步驟:
??a.定義一個(gè)類(lèi)繼承Service盏浙。
??b.在AndroidManifest.xml文件中配置該Service眉睹。
??c.使用Context的startService(Intent)方法啟動(dòng)該Service。
??d.不再使用該Service時(shí)废膘,調(diào)用Context的stopService(Intent)方法停止該Service竹海。
2.2 bindService方法開(kāi)啟Service(Activity與Service綁定)
步驟:
??a.創(chuàng)建BinderService服務(wù)端,繼承自Service并在類(lèi)中創(chuàng)建一個(gè)實(shí)現(xiàn)IBinder接口的實(shí)現(xiàn)實(shí)例對(duì)象并提供公共方法給客戶(hù)端調(diào)用丐黄。
??b.從onBind()回調(diào)方法返回此Binder實(shí)例斋配。
??c.在客戶(hù)端中,從onServiceConnected回調(diào)方法接收Binder,并使用提供的方法調(diào)用綁定服務(wù)灌闺。
3. Service的生命周期
? ? ? ?服務(wù)的生命周期有兩種艰争,因?yàn)榉?wù)可以跟Activity綁定起來(lái),也可以不綁定桂对,Activity和服務(wù)進(jìn)行通信的話(huà)甩卓,是需要把服務(wù)和Activity進(jìn)行綁定的。因此服務(wù)的生命周期分為未綁定Activity的和綁定Activity的蕉斜。
沒(méi)有綁定Activity的服務(wù)生命周期:
啟動(dòng)服務(wù)>onCreate()>onStartCommand()>服務(wù)運(yùn)行>onDestory()>服務(wù)銷(xiāo)毀
綁定Activity的服務(wù)生命周期
綁定服務(wù)>onCreate()>onBind()>服務(wù)運(yùn)行>onUnBind()>onDestory()>服務(wù)被銷(xiāo)毀
通過(guò)Intent和startService()方法啟動(dòng)了一個(gè)服務(wù)逾柿,接下來(lái)執(zhí)行onCreate()方法,首次創(chuàng)建服務(wù)時(shí),系統(tǒng)將調(diào)用此方法來(lái)執(zhí)行一次性設(shè)置程序(在調(diào)用 onStartCommand() 或 onBind() 之前)宅此。如果服務(wù)已在運(yùn)行机错,則不會(huì)調(diào)用此方法。
當(dāng)另一個(gè)組件(如 Activity)通過(guò)調(diào)用 startService() 請(qǐng)求啟動(dòng)服務(wù)時(shí)诽凌,系統(tǒng)將調(diào)用此方法毡熏。一旦執(zhí)行此方法,服務(wù)即會(huì)啟動(dòng)并可在后臺(tái)無(wú)限期運(yùn)行侣诵。 如果您實(shí)現(xiàn)此方法痢法,則在服務(wù)工作完成后,需要由您通過(guò)調(diào)用 stopSelf() 或 stopService() 來(lái)停止服務(wù)杜顺。(如果您只想提供綁定财搁,則無(wú)需實(shí)現(xiàn)此方法。)
服務(wù)開(kāi)始處于運(yùn)行狀態(tài)躬络。
某個(gè)操作導(dǎo)致服務(wù)停止尖奔,比如執(zhí)行了方法stopService(),那么服務(wù)接下來(lái)會(huì)執(zhí)行onDestory()銷(xiāo)毀。服務(wù)應(yīng)該實(shí)現(xiàn)此方法來(lái)清理所有資源提茁,如線(xiàn)程淹禾、注冊(cè)的偵聽(tīng)器、接收器等茴扁。 這是服務(wù)接收的最后一個(gè)調(diào)用铃岔。
服務(wù)被完全銷(xiāo)毀,下一步就是等待被垃圾回收器回收了峭火。
通過(guò)Intent和bindService()方法啟動(dòng)了一個(gè)服務(wù),接下來(lái)會(huì)執(zhí)行onCreate()方法毁习,首次創(chuàng)建服務(wù)時(shí),系統(tǒng)將調(diào)用此方法來(lái)執(zhí)行一次性設(shè)置程序(在調(diào)用 onStartCommand() 或 onBind() 之前)卖丸。如果服務(wù)已在運(yùn)行纺且,則不會(huì)調(diào)用此方法。
當(dāng)另一個(gè)組件想通過(guò)調(diào)用 bindService() 與服務(wù)綁定(例如執(zhí)行 RPC)時(shí)稍浆,系統(tǒng)將調(diào)用此方法载碌。在此方法的實(shí)現(xiàn)中,您必須通過(guò)返回 IBinder 提供一個(gè)接口衅枫,供客戶(hù)端用來(lái)與服務(wù)進(jìn)行通信恐仑。請(qǐng)務(wù)必實(shí)現(xiàn)此方法,但如果您并不希望允許綁定为鳄,則應(yīng)返回 null。
服務(wù)開(kāi)始處于運(yùn)行狀態(tài)腕让。成功與Activity綁定孤钦。
某個(gè)操作導(dǎo)致服務(wù)解除綁定,比如執(zhí)行了方法unbindService()纯丸,那么服務(wù)接下來(lái)會(huì)解除與當(dāng)前Activity的綁定偏形。接下來(lái)服務(wù)將面臨銷(xiāo)毀。
服務(wù)執(zhí)行onDestory()方法被銷(xiāo)毀觉鼻。服務(wù)應(yīng)該實(shí)現(xiàn)此方法來(lái)清理所有資源俊扭,如線(xiàn)程、注冊(cè)的偵聽(tīng)器坠陈、接收器等萨惑。 這是服務(wù)接收的最后一個(gè)調(diào)用。
服務(wù)被完全銷(xiāo)毀仇矾,下一步就是等待被垃圾回收器回收了庸蔼。
Service總結(jié):
被啟動(dòng)的服務(wù)的生命周期:如果一個(gè)Service被某個(gè)Activity 調(diào)用 Context.startService 方法啟動(dòng),那么不管是否有Activity使用bindService綁定或unbindService解除綁定到該Service贮匕,該Service都在后臺(tái)運(yùn)行姐仅。如果一個(gè)Service被startService 方法多次啟動(dòng),那么onCreate方法只會(huì)調(diào)用一次,onStart將會(huì)被調(diào)用多次(對(duì)應(yīng)調(diào)用startService的次數(shù))掏膏,并且系統(tǒng)只會(huì)創(chuàng)建Service的一個(gè)實(shí)例(因此你應(yīng)該知道只需要一次stopService調(diào)用)劳翰。該Service將會(huì)一直在后臺(tái)運(yùn)行,而不管對(duì)應(yīng)程序的Activity是否在運(yùn)行馒疹,直到被調(diào)用stopService佳簸,或自身的stopSelf方法。當(dāng)然如果系統(tǒng)資源不足行冰,android系統(tǒng)也可能結(jié)束服務(wù)溺蕉。
被綁定的服務(wù)的生命周期:如果一個(gè)Service被某個(gè)Activity 調(diào)用 Context.bindService 方法綁定啟動(dòng),不管調(diào)用 bindService 調(diào)用幾次悼做,onCreate方法都只會(huì)調(diào)用一次疯特,同時(shí)onStart方法始終不會(huì)被調(diào)用。當(dāng)連接建立之后肛走,Service將會(huì)一直運(yùn)行漓雅,除非調(diào)用Context.unbindService 斷開(kāi)連接或者之前調(diào)用bindService 的 Context 不存在了(如Activity被finish的時(shí)候),系統(tǒng)將會(huì)自動(dòng)停止Service朽色,對(duì)應(yīng)onDestroy將被調(diào)用邻吞。
被啟動(dòng)又被綁定的服務(wù)的生命周期:如果一個(gè)Service又被啟動(dòng)又被綁定,則該Service將會(huì)一直在后臺(tái)運(yùn)行葫男。并且不管如何調(diào)用抱冷,onCreate始終只會(huì)調(diào)用一次,對(duì)應(yīng)startService調(diào)用多少次梢褐,Service的onStart便會(huì)調(diào)用多少次旺遮。調(diào)用unbindService將不會(huì)停止Service,而必須調(diào)用 stopService 或 Service的 stopSelf 來(lái)停止服務(wù)盈咳。
當(dāng)服務(wù)被停止時(shí)清除服務(wù):當(dāng)一個(gè)Service被終止(1耿眉、調(diào)用stopService;2鱼响、調(diào)用stopSelf鸣剪;3、不再有綁定的連接(沒(méi)有被啟動(dòng)))時(shí)丈积,onDestroy方法將會(huì)被調(diào)用筐骇,在這里你應(yīng)當(dāng)做一些清除工作,如停止在Service中創(chuàng)建并運(yùn)行的線(xiàn)程桶癣。
四 Broadcast
1. 廣播的概念
1.1 定義
在Android中拥褂,它是一種廣泛運(yùn)用在應(yīng)用程序之間傳輸信息的機(jī)制,Android中我們發(fā)送廣播內(nèi)容是一個(gè)Intent,這個(gè)Intent中可以攜帶我們要發(fā)送的數(shù)據(jù)牙寞。
1.2 廣播的使用場(chǎng)景
a.同一app內(nèi)有多個(gè)進(jìn)程的不同組件之間的消息通信饺鹃。
b.不同app之間的組件之間消息的通信莫秆。
1.3 廣播的種類(lèi)
標(biāo)準(zhǔn)廣播:context.sendBroadcast(Intent)方法發(fā)送的廣播,不可被攔截
有序廣播:context.sendOrderBroadcast(Intent)方法發(fā)送的廣播悔详,可被攔截
本地廣播:localBroadcastManager.sendBroadcast(Intent)镊屎,只在app內(nèi)傳播
2. 廣播接收器
廣播接收器是專(zhuān)門(mén)用來(lái)接收廣播信息的,它可分為靜態(tài)注冊(cè)和動(dòng)態(tài)注冊(cè):
靜態(tài)注冊(cè):注冊(cè)完成一直在運(yùn)行茄螃。
首先你要?jiǎng)?chuàng)建一個(gè)廣播接收器類(lèi)缝驳,實(shí)例代碼如下:
public class BootCompleteReceiver extends BroadcastReceiver {
? ? @Override? ? public void onReceive(Context context, Intent intent) {
? ? ? ? Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();
? ? }
}
另外,靜態(tài)的廣播接收器一定要在AndroidManifest.xml文件中注冊(cè)才可以使用归苍,AndroidManifest.xml文件中注冊(cè)靜態(tài)廣播代碼如下:
? ? ? ? ? <receiver
? ? ? ? ? ? ? android:name=".BootCompleteReceiver" >? ? ? ? ? ? ? <intent-filter>? ? ? ? ? ? ? ? ? <action android:name="android.intent.action.BOOT_COMPLETED" />? ? ? ? ? ? ? </intent-filter>? ? ? ? ? </receiver>
動(dòng)態(tài)注冊(cè):跟隨Activity的生命周期用狱。
新建一個(gè)類(lèi),讓它繼承自BroadcastReceiver,并重寫(xiě)父類(lèi)的onReceive()方法就行了拼弃。這樣有廣播到來(lái)時(shí)夏伊,onReceive()方法就會(huì)得到執(zhí)行,具體的邏輯就可以在這個(gè)方法中處理吻氧。
動(dòng)態(tài)注冊(cè)廣播接收器的優(yōu)點(diǎn)以及缺點(diǎn):
動(dòng)態(tài)注冊(cè)的廣播接收器可以自由地控制注冊(cè)與注銷(xiāo)溺忧,在靈活性方面有很大優(yōu)勢(shì),但是它也存在著一個(gè)缺點(diǎn)盯孙,即必須要在程序啟動(dòng)之后才能接收到廣播鲁森,因?yàn)樽?cè)的邏輯是寫(xiě)在onCreate()方法中的。那么有沒(méi)有廣播能在程序未啟動(dòng)的情況下就能接收到廣播呢振惰?靜態(tài)注冊(cè)的廣播接收器就可以做到歌溉。
3. 廣播內(nèi)部實(shí)現(xiàn)機(jī)制
自定義廣播接收者BroadcastReceiver,并且重寫(xiě)onReceiver()方法。
通過(guò)Binder機(jī)制向AMS(Activity Manager Service)進(jìn)行注冊(cè)骑晶。
廣播發(fā)送者通過(guò)Binder機(jī)制向AMS發(fā)送廣播研底。
AMS查找符合條件(IntentFilter/Permission等)的BroadcastReceiver,將廣播發(fā)送到相應(yīng)的BroadcastReceiver(一般情況下是Activity)的消息隊(duì)列中透罢。
消息循環(huán)執(zhí)行拿到此廣播,回調(diào)BroadcastReceiver中的onReceiver()方法冠蒋。
4. 本地廣播
本地廣播的發(fā)送和注冊(cè)廣播接收器都需要使用到LocalBroadcastManager類(lèi)羽圃,如下所示為本地廣播的發(fā)送和本地廣播接收器注冊(cè)的代碼:
??本地廣播的發(fā)送:
public static void sendLocalBroadcast(Context context,String action){
? ? Intent intent = new Intent(action);
? ? LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
? ? localBroadcastManager.sendBroadcast(intent);
}
本地廣播的接收器的注冊(cè):
IntentFilter intentFilter = new IntentFilter();
? ? LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(context);
? ? intentFilter.addAction(new BroadcastUtil().action_next);
? ? nasbr = new NextAndStartBroadcastReceiver();
? ? localBroadcastManager.registerReceiver(nasbr, intentFilter);//注冊(cè)本地廣播接收器
特點(diǎn):
1. 使用它發(fā)送的廣播將只在自身app內(nèi)傳播,因此你不必?fù)?dān)心泄漏隱私的數(shù)據(jù)抖剿。
2. 其他app無(wú)法對(duì)你的app發(fā)送該廣播朽寞,因此你的app根本不可能收到非自身app發(fā)送的該廣播,因此你不必?fù)?dān)心有安全漏洞可以利用斩郎。
3. 比系統(tǒng)廣播更加高效脑融。
內(nèi)部實(shí)現(xiàn)機(jī)制:
1. LocalBroadcast高效的原因:因?yàn)樗鼉?nèi)部是通過(guò)Handler實(shí)現(xiàn)的,它的sendBroadcast()方法含義并非和系統(tǒng)的sendBroadcast()一樣缩宜,它的sendBroadcast()方法其實(shí)就是通過(guò)Handler發(fā)送了一個(gè)Message而已肘迎。
2. LocalBroadcast安全的原因:既然它是通過(guò)Handler實(shí)現(xiàn)廣播發(fā)送的甥温,那么相比系統(tǒng)廣播通過(guò)Binder機(jī)制實(shí)現(xiàn)那肯定更加高效,同時(shí)使用Handler來(lái)實(shí)現(xiàn)妓布,別的app無(wú)法向我們應(yīng)用發(fā)送該廣播姻蚓,而我們app內(nèi)部發(fā)送的廣播也不會(huì)離開(kāi)我們的app。
LocalBroadcast內(nèi)部協(xié)作主要是靠?jī)蓚€(gè)Map集合:mReceivers和mActions,當(dāng)然還有一個(gè)List集合mPendingBroadcasts,這個(gè)主要存儲(chǔ)待接收的廣播對(duì)象匣沼。
六 Bainder機(jī)制
通常情況下狰挡,Binder是一種通信機(jī)制。
對(duì)于Server來(lái)說(shuō),Binder指的是Binder本地對(duì)象/對(duì)于Client來(lái)說(shuō)释涛,Binder指的是Binder的代理對(duì)象加叁。
對(duì)于傳輸過(guò)程而言,Binder是可以進(jìn)行跨進(jìn)程傳遞的對(duì)象唇撬。
AIDL是Binder機(jī)制的一個(gè)實(shí)例它匕。
七 Handler機(jī)制
1. 定義
Handler是可以通過(guò)發(fā)送和處理Message和Runnable對(duì)象來(lái)關(guān)聯(lián)相應(yīng)線(xiàn)程的MessageQueue。通常我們認(rèn)為它是一種異步機(jī)制局荚。
可以讓對(duì)應(yīng)的Message和Runnable在未來(lái)的某個(gè)時(shí)間點(diǎn)進(jìn)行相應(yīng)的處理超凳。
讓自己想要的耗時(shí)操作在子線(xiàn)程中完成,讓更新UI的操作在主線(xiàn)程中完成耀态,而子線(xiàn)程與主線(xiàn)程之間的通信就是靠Handler來(lái)完成轮傍。
2. Handler的使用方法
post(Runnable)
sendMessage(Message)
3. Handler內(nèi)部實(shí)現(xiàn)機(jī)制
Handler機(jī)制也可叫異步消息機(jī)制,它主要由4個(gè)部分組成:Message,Handler,MessageQueue,Looper,在上面我們已經(jīng)接觸到了Message和Handler,接下來(lái)我們對(duì)4個(gè)成員進(jìn)行著重的了解:
Message
??Message是在線(xiàn)程之間傳遞的消息首装,它可以在內(nèi)部攜帶少量的信息创夜,用于在不同線(xiàn)程之間交換數(shù)據(jù)。使用Message的arg1和arg2便可攜帶int數(shù)據(jù)仙逻,使用obj便可攜帶Object類(lèi)型數(shù)據(jù)驰吓。
Handler
??Handler顧名思義就是處理者的意思,它只要用于在子線(xiàn)程發(fā)送消息對(duì)象Message,在UI線(xiàn)程處理消息對(duì)象Message系奉,在子線(xiàn)程調(diào)用sendMessage方法發(fā)送消息對(duì)象Message檬贰,而發(fā)送的消息經(jīng)過(guò)一系列地輾轉(zhuǎn)之后最終會(huì)被傳遞到Handler的handleMessage方法中,最終在handleMessage方法中消息對(duì)象Message被處理。
MessageQueue
??MessageQueue就是消息隊(duì)列的意思,它只要用于存放所有通過(guò)Handler發(fā)送過(guò)來(lái)的消息缺亮。這部分消息會(huì)一直存放于消息隊(duì)列當(dāng)中翁涤,等待被處理。每個(gè)線(xiàn)程中只會(huì)有一個(gè)MessageQueue對(duì)象萌踱,請(qǐng)牢記這句話(huà)葵礼。其實(shí)從字面上就可以看出,MessageQueue底層數(shù)據(jù)結(jié)構(gòu)是隊(duì)列并鸵,而且這個(gè)隊(duì)列只存放Message對(duì)象鸳粉。
Looper
??Looper是每個(gè)線(xiàn)程中的MessageQueue的管家,調(diào)用Looper的loop()方法后园担,就會(huì)進(jìn)入到一個(gè)無(wú)限循環(huán)當(dāng)中届谈,然后每當(dāng)MesssageQueue中存在一條消息枯夜,Looper就會(huì)將這條消息取出,并將它傳遞到Handler的handleMessage()方法中疼约。每個(gè)線(xiàn)程只有一個(gè)Looper對(duì)象卤档。
Handler機(jī)制流程圖如下:
Handler機(jī)制流程圖
4. Handler引起的內(nèi)存泄漏以及解決方法
原因:靜態(tài)內(nèi)部類(lèi)持有外部類(lèi)的匿名引用,導(dǎo)致外部activity無(wú)法得到釋放程剥。
解決方法:handler內(nèi)部持有外部的弱引用劝枣,并把handler改為靜態(tài)內(nèi)部類(lèi),在activity的onDestory()中調(diào)用handler的removeCallback()方法织鲸。
八 IntentService機(jī)制
1.IntentService是什么舔腾?
它的優(yōu)先級(jí)高于Service。
??IntentService是繼承處理異步請(qǐng)求的一個(gè)類(lèi)搂擦,在IntentService內(nèi)有一個(gè)工作線(xiàn)程來(lái)處理耗時(shí)操作稳诚,啟動(dòng)IntentServiced的方式和啟動(dòng)傳統(tǒng)的Service一樣,同時(shí)瀑踢,當(dāng)任務(wù)執(zhí)行完成后扳还,IntentService會(huì)自動(dòng)停止,而不需要我們手動(dòng)去控制或stopSelf()橱夭。另外氨距,可以啟動(dòng)IntentService多次,而每一個(gè)耗時(shí)操作會(huì)以工作隊(duì)列的方式在IntentService的onHandlerIntent回調(diào)方法中執(zhí)行棘劣,并且俏让,每次只執(zhí)行一個(gè)工作線(xiàn)程,執(zhí)行完第一個(gè)在執(zhí)行第二個(gè)茬暇。
它本質(zhì)是一種特殊的Service,繼承自Service并且本身就是一個(gè)抽象類(lèi)首昔。
它內(nèi)部是由HandlerThread和Handler實(shí)現(xiàn)異步操作。
2.IntentService的使用方法
創(chuàng)建IntentService時(shí)糙俗,只需要實(shí)現(xiàn)onHandlerIntent和構(gòu)造方法勒奇,onHandlerIntent為異步方法,可以執(zhí)行耗時(shí)操作巧骚。
十 HandlerThread機(jī)制
1.HandlerThread的產(chǎn)生背景
開(kāi)啟子線(xiàn)程進(jìn)行耗時(shí)操作撬陵,多次創(chuàng)建和銷(xiāo)毀子線(xiàn)程是很耗費(fèi)資源的,但是木有關(guān)系网缝,谷歌考慮了這點(diǎn)為我們專(zhuān)門(mén)開(kāi)發(fā)出了HandlerThread機(jī)制。
2.HandlerThread是什么蟋定?
本質(zhì):Handler + Thread + Looper粉臊,是一個(gè)Thread內(nèi)部有Looper。
HandlerThread本質(zhì)上是一個(gè)線(xiàn)程類(lèi)驶兜,它繼承了Thread扼仲。
HandlerThread有自己內(nèi)部的Looper對(duì)象远寸,可以進(jìn)行Looper循環(huán)。
通過(guò)獲取HandlerThread的Looper對(duì)象傳遞給Handler對(duì)象屠凶,可以在handlerMessage方法中執(zhí)行異步任務(wù)驰后。
優(yōu)點(diǎn)是不會(huì)有堵塞,減少對(duì)性能的消耗矗愧,缺點(diǎn)是不能進(jìn)行多任務(wù)的處理灶芝,需要等待進(jìn)行處理,處理效率較低唉韭。
與線(xiàn)程池注重并發(fā)不同夜涕,HandlerThread是一個(gè)串行隊(duì)列,HandlerThread背后只有一個(gè)線(xiàn)程属愤。
十一 IntentService機(jī)制
1.IntentService是什么女器?
它的優(yōu)先級(jí)高于Service。
??IntentService是繼承處理異步請(qǐng)求的一個(gè)類(lèi)住诸,在IntentService內(nèi)有一個(gè)工作線(xiàn)程來(lái)處理耗時(shí)操作驾胆,啟動(dòng)IntentServiced的方式和啟動(dòng)傳統(tǒng)的Service一樣,同時(shí)贱呐,當(dāng)任務(wù)執(zhí)行完成后丧诺,IntentService會(huì)自動(dòng)停止,而不需要我們手動(dòng)去控制或stopSelf()吼句。另外锅必,可以啟動(dòng)IntentService多次,而每一個(gè)耗時(shí)操作會(huì)以工作隊(duì)列的方式在IntentService的onHandlerIntent回調(diào)方法中執(zhí)行惕艳,并且搞隐,每次只執(zhí)行一個(gè)工作線(xiàn)程,執(zhí)行完第一個(gè)在執(zhí)行第二個(gè)远搪。
它本質(zhì)是一種特殊的Service,繼承自Service并且本身就是一個(gè)抽象類(lèi)劣纲。
它內(nèi)部是由HandlerThread和Handler實(shí)現(xiàn)異步操作。
2.IntentService的使用方法
創(chuàng)建IntentService時(shí)谁鳍,只需要實(shí)現(xiàn)onHandlerIntent和構(gòu)造方法癞季,onHandlerIntent為異步方法,可以執(zhí)行耗時(shí)操作倘潜。
十二 View繪制機(jī)制
1. View樹(shù)的繪制流程
measure(測(cè)量)→layout(布局)→draw(繪制)
2. Measure過(guò)程?
Measure過(guò)程
measure過(guò)程主要就是從頂層父View向子View遞歸調(diào)用view.measure方法(measure中又回調(diào)onMeasure方法)的過(guò)程绷柒。具體measure核心主要有如下幾點(diǎn):
MeasureSpec(View的內(nèi)部類(lèi))測(cè)量規(guī)格為int型,值由高2位規(guī)格模式specMode和低30位具體尺寸specSize組成涮因。其中specMode只有三種值:
MeasureSpec.EXACTLY //確定模式废睦,父View希望子View的大小是確定的,由specSize決定养泡;
MeasureSpec.AT_MOST //最多模式嗜湃,父View希望子View的大小最多是specSize指定的值奈应;
MeasureSpec.UNSPECIFIED //未指定模式,父View完全依據(jù)子View的設(shè)計(jì)值來(lái)決定购披;
View的measure方法是final的杖挣,不允許重載,View子類(lèi)只能重載onMeasure來(lái)完成自己的測(cè)量邏輯刚陡。
最頂層DecorView測(cè)量時(shí)的MeasureSpec是由ViewRootImpl中g(shù)etRootMeasureSpec方法確定的(LayoutParams寬高參數(shù)均為MATCH_PARENT惩妇,specMode是EXACTLY,specSize為物理屏幕大虚佘)屿附。
ViewGroup類(lèi)提供了measureChild,measureChild和measureChildWithMargins方法哥童,簡(jiǎn)化了父子View的尺寸計(jì)算挺份。
只要是ViewGroup的子類(lèi)就必須要求LayoutParams繼承子MarginLayoutParams,否則無(wú)法使用layout_margin參數(shù)贮懈。
View的布局大小由父View和子View共同決定匀泊。
使用View的getMeasuredWidth()和getMeasuredHeight()方法來(lái)獲取View測(cè)量的寬高,必須保證這兩個(gè)方法在onMeasure流程之后被調(diào)用才能返回有效值朵你。
3. Layout過(guò)程
整個(gè)layout過(guò)程比較容易理解各聘,從上面分析可以看出layout也是從頂層父View向子View的遞歸調(diào)用view.layout方法的過(guò)程,即父View根據(jù)上一步measure子View所得到的布局大小和布局參數(shù)抡医,將子View放在合適的位置上躲因。具體layout核心主要有以下幾點(diǎn):
View.layout方法可被重載逗旁,ViewGroup.layout為final的不可重載断楷,ViewGroup.onLayout為abstract的赏寇,子類(lèi)必須重載實(shí)現(xiàn)自己的位置邏輯劲妙。
measure操作完成后得到的是對(duì)每個(gè)View經(jīng)測(cè)量過(guò)的measuredWidth和measuredHeight,layout操作完成之后得到的是對(duì)每個(gè)View進(jìn)行位置分配后的mLeft痕鳍、mTop阱穗、mRight罢屈、mBottom俘种,這些值都是相對(duì)于父View來(lái)說(shuō)的秤标。
凡是layout_XXX的布局屬性基本都針對(duì)的是包含子View的ViewGroup的,當(dāng)對(duì)一個(gè)沒(méi)有父容器的View設(shè)置相關(guān)layout_XXX屬性是沒(méi)有任何意義的(前面《Android應(yīng)用setContentView與LayoutInflater加載解析機(jī)制源碼分析》也有提到過(guò))宙刘。
使用View的getWidth()和getHeight()方法來(lái)獲取View測(cè)量的寬高苍姜,必須保證這兩個(gè)方法在onLayout流程之后被調(diào)用才能返回有效值。
4.Draw過(guò)程
繪制過(guò)程就是把View對(duì)象繪制到屏幕上悬包,整個(gè)draw過(guò)程需要注意如下細(xì)節(jié):
如果該View是一個(gè)ViewGroup衙猪,則需要遞歸繪制其所包含的所有子View。
View默認(rèn)不會(huì)繪制任何內(nèi)容,真正的繪制都需要自己在子類(lèi)中實(shí)現(xiàn)屈嗤。
View的繪制是借助onDraw方法傳入的Canvas類(lèi)來(lái)進(jìn)行的。
區(qū)分View動(dòng)畫(huà)和ViewGroup布局動(dòng)畫(huà)吊输,前者指的是View自身的動(dòng)畫(huà)饶号,可以通過(guò)setAnimation添加,后者是專(zhuān)門(mén)針對(duì)ViewGroup顯示內(nèi)部子視圖時(shí)設(shè)置的動(dòng)畫(huà)季蚂,可以在xml布局文件中對(duì)ViewGroup設(shè)置layoutAnimation屬性(譬如對(duì)LinearLayout設(shè)置子View在顯示時(shí)出現(xiàn)逐行茫船、隨機(jī)、下等顯示等不同動(dòng)畫(huà)效果)扭屁。
在獲取畫(huà)布剪切區(qū)(每個(gè)View的draw中傳入的Canvas)時(shí)會(huì)自動(dòng)處理掉padding算谈,子View獲取Canvas不用關(guān)注這些邏輯,只用關(guān)心如何繪制即可料滥。
默認(rèn)情況下子View的ViewGroup.drawChild繪制順序和子View被添加的順序一致然眼,但是你也可以重載ViewGroup.getChildDrawingOrder()方法提供不同順序。
十三 Android部分事件分發(fā)機(jī)制
1. 為什么有事件分發(fā)機(jī)制
Android上面的View是樹(shù)形結(jié)構(gòu)葵腹,View可能會(huì)重疊在一起高每,當(dāng)我們點(diǎn)擊的地方有多個(gè)View都可以響應(yīng)的時(shí)候,這個(gè)點(diǎn)擊事件應(yīng)該給誰(shuí)呢践宴?為了解決這個(gè)問(wèn)題鲸匿,就有了事件分發(fā)機(jī)制。
2. 3個(gè)重要的有關(guān)事件分發(fā)的方法
dispatch TouchEvent
用來(lái)進(jìn)行事件的分發(fā)阻肩。如果事件能夠傳遞給當(dāng)前View,那么此方法一定會(huì)被調(diào)用带欢,返回結(jié)果受當(dāng)前View的onTouchEvent和下級(jí)的dispatchTouchEvent方法影響,表示是否消耗此事件烤惊。
onInterceptTouchEvent
在上述方法dispatchTouchEvent內(nèi)部調(diào)用乔煞,用來(lái)判斷是否攔截某個(gè)事件,如果當(dāng)前View攔截了某個(gè)事件撕氧,那么同一個(gè)事件序列當(dāng)中瘤缩,此方法不會(huì)被再次調(diào)用,返回結(jié)果表示是否攔截當(dāng)前事件伦泥。
onTouchEvent
同樣也會(huì)在dispatchTouchEvent內(nèi)部調(diào)用剥啤,用來(lái)處理點(diǎn)擊事件,返回結(jié)果表示是否消耗當(dāng)前事件不脯,如果不消耗府怯,則在同一個(gè)事件序列中,當(dāng)前View無(wú)法再次接收到事件防楷。
偽代碼
public boolean dispatchTouchEvent(MotionEvent ev){
? ? boolean consume = false;//記錄返回值? ? if(onInterceptTouchEvent(ev)){//判斷是否攔截此事件? ? ? ? consume = onTouchEvent(ev);//如果當(dāng)前確認(rèn)攔截此事件牺丙,那么就處理這個(gè)事件? ? }else{
? ? ? ? consume = child.dispatchToucnEvent(ev);//如果當(dāng)前確認(rèn)不攔截此事件,那么就將事件分發(fā)給下一級(jí)? ? }
? ? return consume;
}
通過(guò)上述偽代碼,我們可以得知點(diǎn)擊事件的傳遞規(guī)則:對(duì)于一個(gè)根ViewGroup而言冲簿,點(diǎn)擊事件產(chǎn)生后粟判,首先會(huì)傳遞給它,這時(shí)它的dispatchTouch就會(huì)被調(diào)用峦剔,如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回true就表示它要攔截當(dāng)前的事件档礁,接著事件就會(huì)交給這個(gè)ViewGroup處理,即它的onTouch方法就會(huì)被調(diào)用吝沫;如果這個(gè)ViewGroup的onInterceptTouchEvent方法返回false就表示它不攔截當(dāng)前事件呻澜,這時(shí)當(dāng)前事件就會(huì)繼續(xù)傳遞給它的子元素,接著子元素的dispatchTouchEvent方法就會(huì)被調(diào)用惨险,如此直到事件被最終處理羹幸。
當(dāng)一個(gè)View需要處理事件時(shí),如果它設(shè)置了OnTouchListener,那么OnTouchListener中的onTouch方法會(huì)被回調(diào)辫愉。這時(shí)事件處理還要看onTouch的返回值栅受,如果返回false,則當(dāng)前View的onTouchEvent方法會(huì)被調(diào)用;如果返回true,那么當(dāng)前View的onTouchEvent方法不會(huì)被調(diào)用。由此可見(jiàn)一屋,給View設(shè)置的onTouchListener的優(yōu)先級(jí)比onTouchEvent要高窘疮。在onTouchEvent方法中,如果當(dāng)前設(shè)置的有onClickListener冀墨,那么它的onClick方法會(huì)被調(diào)用闸衫。可以看出诽嘉,平時(shí)我們常用的OnClickListener,其優(yōu)先級(jí)最低蔚出,即處于事件傳遞的尾端。
當(dāng)一個(gè)點(diǎn)擊事件產(chǎn)生后虫腋,它的傳遞過(guò)程遵循如下順序:Activity–>Window–>View,即事件總數(shù)先傳遞給Activity,Activity再傳遞給Window,最后Window再傳遞給頂級(jí)View,頂級(jí)View接收到事件后骄酗,就會(huì)按照事件分發(fā)機(jī)制去分發(fā)事件≡眉剑考慮一種情況趋翻,如果一個(gè)View的onTouchEvent返回false,那么它的父容器的onTouchEvent將會(huì)被調(diào)用,依次類(lèi)推盒蟆。如果所有的元素都不處理這個(gè)事件踏烙,那么這個(gè)事件將會(huì)最終傳遞給Activity處理, 即Activity的onTouchEvent方法會(huì)被調(diào)用历等。這個(gè)過(guò)程其實(shí)很好理解讨惩,我們可以換一種思路,假設(shè)點(diǎn)擊事件是一個(gè)難題寒屯,這個(gè)難題最終被上級(jí)領(lǐng)導(dǎo)分給了一個(gè)程序員去處理(這是事件分發(fā)過(guò)程)荐捻,結(jié)果這個(gè)程序員搞不定(onTouchEvent返回了false),現(xiàn)在該怎么辦呢?難題必須要解決,那就只能交給水平更高的上級(jí)解決(上級(jí)的onTouchEvent被調(diào)用)处面,如果上級(jí)再搞不定厂置,那就只能交給上級(jí)的上級(jí)去解決,就這樣難題一層層地向上拋魂角,這是公司內(nèi)部一種常見(jiàn)的處理問(wèn)題的過(guò)程农渊。
關(guān)于事件傳遞機(jī)制還需要注意以下:
同一見(jiàn)事件序列是從手指接觸屏幕的那一刻起,到手指離開(kāi)屏幕的那一刻結(jié)束或颊,在這個(gè)過(guò)程中所產(chǎn)生的一系列事件,這個(gè)事件的序列以down開(kāi)始传于,中間含有數(shù)量不定的move事件囱挑,最終以u(píng)p事件結(jié)束。
正常情況下沼溜,一個(gè)事件序列只能被一個(gè)View攔截且消耗平挑。這一條的原因可以參考(3),因?yàn)橐坏┮粋€(gè)元素?cái)r截了某個(gè)事件系草,那么同一個(gè)事件序列的所有事件都會(huì)直接交給它處理通熄,因此同一個(gè)事件序列中的事件不能分別由兩個(gè)View同時(shí)處理,但是通過(guò)特殊手段可以做到找都,比如
一個(gè)View將本該自己處理的事件通過(guò)onTouchEvent強(qiáng)行傳遞給其他View處理唇辨。
某個(gè)View一旦決定攔截,那么這個(gè)事件序列都只能由它來(lái)處理(如果事件序列能夠傳遞給它的話(huà))能耻,并且它的onInterceptTouchEvent不會(huì)被調(diào)用赏枚。這條也很好理解,就是說(shuō)當(dāng)一個(gè)View決定攔截一個(gè)事件后晓猛,那么系統(tǒng)會(huì)把同一個(gè)事件序列內(nèi)的其他方法都直接交給它來(lái)處理饿幅,因此就不用再調(diào)用這個(gè)View的onInterceptTouchEvent去詢(xún)問(wèn)它是否攔截了。
某個(gè)View一旦開(kāi)始處理事件戒职,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false)栗恩,那么同一件序列中的其他事件都不會(huì)再交給它處理,并且事件 將重新交由它的父元素去處理洪燥,即父元素的onTouchEvent會(huì)被調(diào)用磕秤。意思就是事件一旦交給一個(gè)View處理,那么它就必須消耗掉蚓曼,否則同一事件序列中剩下的事件就不再交給它處理了亲澡,這就好比上級(jí)交給程序員一件事,如果這件事沒(méi)有處理好纫版,短時(shí)間內(nèi)上級(jí)就不敢再把事件交給這個(gè)程序員做了床绪,二者是類(lèi)似的道理。
如果View不消耗ACTION_DOWN以外的事件,那么這個(gè)點(diǎn)擊事件會(huì)消失癞己,此時(shí)父元素的onTouchEvent并不會(huì)調(diào)用膀斋,并且當(dāng)前View可以持續(xù)收到后續(xù)的事件,最終這些消失的點(diǎn)擊事件會(huì)傳遞給Activity處理痹雅。
ViewGroup默認(rèn)不攔截任何事件仰担。Android源碼中ViewGroup的onInterceptTouchEvent方法默認(rèn)返回false。
View沒(méi)有onInterceptTouchEvent方法绩社,一旦點(diǎn)擊事件傳遞給它摔蓝,那么它的onTouchEvent方法就會(huì)被調(diào)用。
View的onTouchEvent默認(rèn)都會(huì)消耗事件(返回true),除非它是不可點(diǎn)擊的(clickable和longClickable同時(shí)為false)愉耙。View的longClickable屬性默認(rèn)為false贮尉,clickable屬性要分情況,比如Button的clickable屬性默認(rèn)為true,而TextView的clickable屬性默認(rèn)為false朴沿。
View的enable屬性不影響onTouchEvent的默認(rèn)返回值猜谚。哪怕一個(gè)View是disable狀態(tài)的,只要它的clickable或者longClickable有一個(gè)為true,那么它的onTouchEvent就返回true赌渣。
onClick會(huì)發(fā)生的前提是當(dāng)前View是可點(diǎn)擊的魏铅,并且它接收到了down和up事件。
事件傳遞過(guò)程是由外向內(nèi)的坚芜,即事件總是先傳遞給父元素览芳,然后再由父元素分發(fā)給子View,通過(guò)requestDisallowInterTouchEvent方法可以在子元素中干預(yù)父元素的事件分發(fā)過(guò)程,但是ACTION_DOWN事件除外鸿竖。
3. 事件分發(fā)的流程
Activity–>PhoneWindow–>DecorView–>ViewGroup–>…–>View(View樹(shù)最底部的View)
事件分發(fā)
十四 動(dòng)畫(huà)
1.Android動(dòng)畫(huà)的分類(lèi)
1.1 補(bǔ)間動(dòng)畫(huà)
a.漸變動(dòng)畫(huà)支持四種類(lèi)型:平移(Translate)路操、旋轉(zhuǎn)(Rotate)、縮放(Scale)千贯、不透明度
b. 只是顯示的位置變動(dòng)屯仗,View的實(shí)際位置未改變,表現(xiàn)為View移動(dòng)到其他地方搔谴,點(diǎn)擊事件仍在原處才能響應(yīng)魁袜。
c. 組合使用步驟較復(fù)雜。
d. View Animation 也是指此動(dòng)畫(huà)敦第。
1.2 幀動(dòng)畫(huà)
a. 用于生成連續(xù)的Gif效果圖峰弹。
b. DrawableAnimation也是指此動(dòng)畫(huà)
1.3 屬性動(dòng)畫(huà)
a.支持對(duì)所有View能更新的屬性的動(dòng)畫(huà)(需要屬性的setXxx()和getXxx())。
b. 更改的是View實(shí)際的屬性芜果,所以不會(huì)影響其在動(dòng)畫(huà)執(zhí)行后所在位置的正常使用鞠呈。
c. Android3.0(API11)及以后出現(xiàn)的功能,3.0之前的版本可使用github第三方開(kāi)源庫(kù)nineoldandroids.jar進(jìn)行支持右钾。
2.補(bǔ)間動(dòng)畫(huà)蚁吝,幀動(dòng)畫(huà)旱爆,屬性動(dòng)畫(huà)優(yōu)缺點(diǎn)
2.1 補(bǔ)間動(dòng)畫(huà)優(yōu)缺點(diǎn)
缺點(diǎn):當(dāng)平移動(dòng)畫(huà)執(zhí)行完停在最后的位置,結(jié)果焦點(diǎn)還在原來(lái)的位置(控件的屬性沒(méi)有真的被改變)
優(yōu)點(diǎn):相對(duì)于逐幀動(dòng)畫(huà)來(lái)說(shuō)窘茁,補(bǔ)間動(dòng)畫(huà)更為連貫自然
2.2 幀動(dòng)畫(huà)優(yōu)缺點(diǎn)
缺點(diǎn):效果單一怀伦,逐幀播放需要很多圖片,占用控件較大
優(yōu)點(diǎn):制作簡(jiǎn)單
2.3 屬性動(dòng)畫(huà)優(yōu)缺點(diǎn)
缺點(diǎn):(3.0+API出現(xiàn))向下兼容問(wèn)題山林。
優(yōu)點(diǎn):易定制房待,效果強(qiáng)。
十五 自定義View
1. 自定義View的幾種方式
對(duì)原View進(jìn)行擴(kuò)展方式
多個(gè)View的組合方式
重寫(xiě)View的方式
2.自定義View需要重寫(xiě)的方法驼抹。
onMesure(測(cè)量)
onLayout(布局)
onDraw(繪制)
十六 Context
Context的中文翻譯為:語(yǔ)境; 上下文; 背景; 環(huán)境桑孩,在開(kāi)發(fā)中我們經(jīng)常說(shuō)稱(chēng)之為“上下文”,那么這個(gè)“上下文”到底是指什么意思呢框冀?在語(yǔ)文中洼怔,我們可以理解為語(yǔ)境,在程序中左驾,我們可以理解為當(dāng)前對(duì)象在程序中所處的一個(gè)環(huán)境,一個(gè)與系統(tǒng)交互的過(guò)程极谊。比如微信聊天诡右,此時(shí)的“環(huán)境”是指聊天的界面以及相關(guān)的數(shù)據(jù)請(qǐng)求與傳輸,Context在加載資源轻猖、啟動(dòng)Activity帆吻、獲取系統(tǒng)服務(wù)、創(chuàng)建View等操作都要參與咙边。
Context提供了關(guān)于應(yīng)用環(huán)境全局信息的接口猜煮。它是一個(gè)抽象類(lèi),它的執(zhí)行被Android系統(tǒng)所提供败许。它允許獲取以應(yīng)用為特征的資源和類(lèi)型王带,是一個(gè)統(tǒng)領(lǐng)一些資源(應(yīng)用程序環(huán)境變量等)的上下文。就是說(shuō)市殷,它描述一個(gè)應(yīng)用程序環(huán)境的信息(即上下文)愕撰;是一個(gè)抽象類(lèi),Android提供了該抽象類(lèi)的具體實(shí)現(xiàn)類(lèi)醋寝;通過(guò)它我們可以獲取應(yīng)用程序的資源和類(lèi)(包括應(yīng)用級(jí)別操作搞挣,如啟動(dòng)Activity,發(fā)廣播音羞,接受Intent等)囱桨。
Context.png
Context的作用域:
Context的作用域.png
十七 ContentProvider
1. 簡(jiǎn)介
內(nèi)容提供者(Content Provider)主要用于在不同的應(yīng)用程序之間實(shí)現(xiàn)數(shù)據(jù)共享的功能,它提供了一套完整的機(jī)制嗅绰,允許一個(gè)程序訪(fǎng)問(wèn)另一個(gè)程序中的數(shù)據(jù)舍肠,同時(shí)還能保證被訪(fǎng)數(shù)據(jù)的安全性搀继。目前,使用內(nèi)容提供者是Android實(shí)現(xiàn)跨程序共享數(shù)據(jù)的標(biāo)準(zhǔn)方式貌夕。
不同于文件存儲(chǔ)和SharedPreferences存儲(chǔ)中的兩種全局可讀可寫(xiě)操作模式律歼,內(nèi)容提供者可以選擇只對(duì)哪一部分?jǐn)?shù)據(jù)進(jìn)行共享,從而保證我們程序中的隱私數(shù)據(jù)不會(huì)泄露的風(fēng)險(xiǎn)啡专。
2.通過(guò)內(nèi)容提供者訪(fǎng)問(wèn)其他程序的數(shù)據(jù)
內(nèi)容提供者的用法一般有兩種险毁,一種是使用現(xiàn)有的內(nèi)容提供器來(lái)讀取和操作相應(yīng)程序中的數(shù)據(jù),另一種是創(chuàng)建自己的內(nèi)容提供者來(lái)給我們的程序提供外部訪(fǎng)問(wèn)接口们童。如果一個(gè)程序通過(guò)內(nèi)容提供者對(duì)其數(shù)據(jù)提供外部訪(fǎng)問(wèn)接口畔况,那么任何其他的應(yīng)用程序就都可以對(duì)這部分?jǐn)?shù)據(jù)進(jìn)行訪(fǎng)問(wèn)。Android系統(tǒng)中自帶的電話(huà)簿慧库,短信跷跪,媒體庫(kù)等程序都提供了類(lèi)似的訪(fǎng)問(wèn)接口,這就使得第三方應(yīng)用程序可以充分利用這部分?jǐn)?shù)據(jù)來(lái)實(shí)現(xiàn)更好的功能齐板。下面我們就來(lái)看看如何通過(guò)內(nèi)容提供者訪(fǎng)問(wèn)其他程序的數(shù)據(jù):
2.1 ContentResolver的基本用法
想要訪(fǎng)問(wèn)內(nèi)容提供者中共享的數(shù)據(jù)吵瞻,就一定要借助CotentResolver類(lèi),可以通過(guò)Context中的getContentResolver()方法獲取該類(lèi)的實(shí)例甘磨。ContentResolver中提供了一系列的方法用于對(duì)數(shù)據(jù)進(jìn)行CRUD(增刪改查)操作橡羞,其中insert()方法用于添加數(shù)據(jù),update()方法用于數(shù)據(jù)的更新济舆,delete()方法用于數(shù)據(jù)的刪除卿泽,query()方法用于數(shù)據(jù)的查詢(xún)。這好像SQLite數(shù)據(jù)庫(kù)操作有木有滋觉?
??不同于SQLiteDatabase,ContentResolver中的增刪改查方法都是不接收表名參數(shù)的签夭,而是使用一個(gè)Uri的參數(shù)代替,這個(gè)參數(shù)被稱(chēng)作內(nèi)容URI椎侠。內(nèi)容URI給內(nèi)容提供者中的數(shù)據(jù)建立了唯一的標(biāo)識(shí)符第租,它主要由兩部分組成:authority和path。authority是用于對(duì)不同的應(yīng)用程序做區(qū)分的我纪,一般為了避免沖突煌妈,都會(huì)采用程序包名的方式來(lái)進(jìn)行命名。比如某個(gè)程序的包名為com.example.app,那么該程序?qū)?yīng)的authority就可以命名為com.example.app.provider宣羊。path則是用于對(duì)同一應(yīng)用程序中不同的表做區(qū)分的璧诵,通常都會(huì)添加到authority的后面。比如某個(gè)程序的數(shù)據(jù)庫(kù)里存在兩張表:table1和table2,這時(shí)就可以將path分別命名為/table1和/table2仇冯,然后把a(bǔ)uthority和path進(jìn)行組合之宿,內(nèi)容的URI就變成了com.example.app.provider/table1和com.example.app.provider/table2。不過(guò)目前還是很難辨認(rèn)出這兩個(gè)字符串就是兩個(gè)內(nèi)容URI,我們還需要在字符串的頭部加上協(xié)議聲明苛坚。因此比被,內(nèi)容URI最標(biāo)準(zhǔn)的格式寫(xiě)法如下:
content://com.example.app.provider/table1content://com.example.app.provider/table2
在得到內(nèi)容URI字符串之后色难,我們還需要將它解析成Uri對(duì)象才可以作為參數(shù)傳入。解析的方法也相當(dāng)簡(jiǎn)單等缀,代碼如下所示:
Uri uri = new Uri.parse("content://com.example.app.provider/table1");
只需要調(diào)用Uri的靜態(tài)方法parse()就可以把內(nèi)容URI字符串解析成URI對(duì)象枷莉。
??現(xiàn)在,我們可以通過(guò)這個(gè)Uri對(duì)象來(lái)查詢(xún)table1表中的數(shù)據(jù)了尺迂。代碼如下所示:
Cursor cursor = getContentResolver()
? ? ? ? ? ? ? ? .query(
? ? ? ? ? ? ? ? ? ? ? uri,projection,selection,selectionArgs,sortOrder
? ? ? ? ? ? ? ? );
query()方法接收的參數(shù)跟SQLiteDatabase中的query()方法接收的參數(shù)很像笤妙,但總體來(lái)說(shuō)這個(gè)稍微簡(jiǎn)單一些,畢竟這是在訪(fǎng)問(wèn)其他程序中的數(shù)據(jù)噪裕,沒(méi)必要構(gòu)建復(fù)雜的查詢(xún)語(yǔ)句蹲盘。下標(biāo)對(duì)內(nèi)容提供者中的query的接收的參數(shù)進(jìn)行了詳細(xì)的解釋?zhuān)?/p>
查詢(xún)完成仍然會(huì)返回一個(gè)Cursor對(duì)象,這時(shí)我們就可以將數(shù)據(jù)從Cursor對(duì)象中逐個(gè)讀取出來(lái)了膳音。讀取的思路仍然是對(duì)這個(gè)Cursor對(duì)象進(jìn)行遍歷召衔,然后一條一條的取出數(shù)據(jù)即可,代碼如下:
if(cursor != null){//注意這里一定要進(jìn)行一次判空祭陷,因?yàn)橛锌赡苣阋樵?xún)的表根本不存在? ? while(cursor.moveToNext()){
? ? ? ? String column1 = cursor.getString(cursor.getColumnIndex("column1"));
? ? ? ? int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
? ? }
}
增加苍凛,刪除,修改
//增加數(shù)據(jù)ContentValues values = new ContentValues();
values.put("Column1","text");
values.put("Column2","1");
getContextResolver.insert(uri,values);//刪除數(shù)據(jù)getContextResolver.delete(uri,"column2 = ?",new String[]{ "1" });//更新數(shù)據(jù)ContentValues values = new ContentValues();
values.put("Column1","改數(shù)據(jù)");
getContextResolver.update(uri,values,"column1 =? 兵志? and column2 = ?",new String[]{"text","1"});
3. 創(chuàng)建自己的內(nèi)容提供者
前面已經(jīng)提到過(guò)醇蝴,如果要想實(shí)現(xiàn)跨程序共享數(shù)據(jù)的功能,官方推薦的方式就是使用內(nèi)容提供器毒姨,可以新建一個(gè)類(lèi)去繼承ContentProvider類(lèi)的方式來(lái)創(chuàng)建一個(gè)自己的內(nèi)容提供器。ContentProvider類(lèi)有6個(gè)抽象方法钉寝,我們?cè)谑褂米宇?lèi)繼承它的時(shí)候弧呐,需要將這6個(gè)方法全部重寫(xiě)。新建MyProvider繼承字ContentProvider類(lèi)嵌纲,代碼如下所示:
public class MyProvider extends ContentProvider {
? ? @Override? ? public boolean onCreate() {
? ? ? ? return false;
? ? }
? ? @Override? ? public Cursor query(Uri uri, String[] projection, String selection,
? ? ? ? ? ? ? ? ? ? ? ? String[] selectionArgs, String sortOrder) {
? ? ? ? return null;
? ? }//查詢(xún)? ? @Override? ? public Uri insert(Uri uri, ContentValues values) {
? ? ? ? return null;
? ? }//添加? ? @Override? ? public int update(Uri uri, ContentValues values, String selection,
? ? ? ? ? ? ? ? ? ? ? String[] selectionArgs) {
? ? ? ? return 0;
? ? }//更新? ? @Override? ? public int delete(Uri uri, String selection, String[] selectionArgs) {
? ? ? ? return 0;
? ? }//刪除? ? @Override? ? public String getType(Uri uri) {
? ? ? ? return null;
? ? }
}
onCreate()方法:
??初始化內(nèi)容提供器的時(shí)候調(diào)用俘枫。通常會(huì)在這里完成對(duì)數(shù)據(jù)庫(kù)的創(chuàng)建和升級(jí)等操作。返回true表示內(nèi)容提供器初始化成功逮走,返回false則表示失敗鸠蚪。注意,只有當(dāng)存在ContentResolver嘗試訪(fǎng)問(wèn)我們的程序中的數(shù)據(jù)時(shí)师溅,內(nèi)容提供器才會(huì)被初始化茅信。
query()方法:
??從內(nèi)容提供器中查詢(xún)數(shù)據(jù)。使用uri參數(shù)來(lái)確定查詢(xún)的哪張表墓臭,projection參數(shù)用于確定查詢(xún)的哪一列蘸鲸,selection和selectionArgs參數(shù)用于約束查詢(xún)哪些行,sortOrder參數(shù)用于對(duì)結(jié)果進(jìn)行排序窿锉,查詢(xún)的結(jié)果存放在Cursor對(duì)象中返回酌摇。
insert()方法:
??向內(nèi)容提供器中添加一條數(shù)據(jù)膝舅。使用uri參數(shù)來(lái)確定要添加的表,待添加的數(shù)據(jù)保存在values參數(shù)中窑多。添加完成后仍稀,返回一個(gè)用于表示這條新紀(jì)錄的URI。
update()方法:
??更新內(nèi)容提供器中已有的數(shù)據(jù)埂息。使用uri參數(shù)來(lái)確定更新哪一張表中的數(shù)據(jù)技潘,新數(shù)據(jù)保存著values參數(shù)當(dāng)中,selection和selectionArgs參數(shù)用于約束更新哪些行耿芹,受影響的行數(shù)將作為返回值返回崭篡。
delete()方法:
??從內(nèi)容提供器中刪除數(shù)據(jù)。使用uri參數(shù)來(lái)確定刪除哪一張表中的數(shù)據(jù)吧秕,selection和selectionArgs參數(shù)用于約束刪除哪些行琉闪,被刪除的行數(shù)將作為返回值返回。
getType()方法:
??根據(jù)傳入的內(nèi)容URI來(lái)返回相應(yīng)的MIME類(lèi)型砸彬。