圍繞Android中的進(jìn)程與線程做了簡(jiǎn)要的概述。
按照操作系統(tǒng)中的描述
線程是CPU調(diào)度的最小單元,同時(shí)線程是一種有限的系統(tǒng)資源
而進(jìn)程一般指一個(gè)執(zhí)行單元,在PC和移動(dòng)設(shè)備上指一個(gè)程序或者一個(gè)應(yīng)用
一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程
操作系統(tǒng)并沒(méi)有將多個(gè)線程看做多個(gè)獨(dú)立的應(yīng)用,來(lái)實(shí)現(xiàn)進(jìn)程間的調(diào)度和管理以及資源分配,這就是進(jìn)程和線程的重要區(qū)別
差別
進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位。
線程是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分配的基本單位,是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位,線程本身不擁有系統(tǒng)資源(除了必不可少的資源如程序計(jì)數(shù)器、寄存器、棧),但是它可與同屬一個(gè)進(jìn)程的其他的線程共享進(jìn)程所擁有的全部資源。
主要差別在于是不同的操作系統(tǒng)資源管理方式。進(jìn)程有獨(dú)立的地址空間,一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其他進(jìn)程產(chǎn)生影響,而線程是一個(gè)進(jìn)程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程之間沒(méi)有單獨(dú)的地址空間,一個(gè)線程死掉就等于整個(gè)進(jìn)程死掉,所以多進(jìn)程的程序要比多線程的健壯,但是多進(jìn)程在切換時(shí),資源耗費(fèi)大,效率要差。
進(jìn)程
進(jìn)程在執(zhí)行過(guò)程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存,極大的提高了程序的運(yùn)行效率。
進(jìn)程?;畹姆绞剑?a target="_blank" rel="nofollow">這篇文章
Foreground process
- 有一個(gè)Activity且它
- 正在交互
- 有一個(gè)Service且它
- 綁定到正在交互的Activity
- "前臺(tái)運(yùn)行",startForeground()
- 正在執(zhí)行生命周期回調(diào) onCreate() onStart() onDestroy()
- 有一個(gè)BroadcastReceiver且它
- 正在執(zhí)行onReceive()
Visible process
- 有一個(gè)Activity且它
- 不在交互,但仍可見(jiàn)
- 有一個(gè)Service且它
- 綁定到可見(jiàn)Activity
Service process
- 普通Service
故而對(duì)于耗時(shí)的比如上傳等,新建一個(gè)Service是比在activity中新建一個(gè)線程好得多的。
Background process
- 所有Activity都對(duì)用戶(hù)不可見(jiàn)
會(huì)被保存在LRU列表中,即最近查看的最晚被終止
Empty process
系統(tǒng)有時(shí)候會(huì)使用空進(jìn)程做為緩存,以縮短下一次在其中運(yùn)行組建所需的啟動(dòng)時(shí)間。
額外說(shuō)明
若一個(gè)進(jìn)程A依賴(lài)于另一個(gè)進(jìn)程B,則進(jìn)程B的優(yōu)先級(jí)可能會(huì)被提升并保證B的優(yōu)先級(jí)高于A頂優(yōu)先級(jí)。
? 舉例 (A優(yōu)先級(jí)永遠(yuǎn)高于B):
- A中的ContentProvider提供數(shù)據(jù)給B
- A中的某個(gè)service綁定到B的某個(gè)組件
進(jìn)程間通信
IPC InterProcess Communication
RPC Remote Procedure Call
Android默認(rèn)每個(gè)app是一個(gè)進(jìn)程,但也可以通過(guò)android:process屬性使每個(gè)app有多個(gè)進(jìn)程,或者多個(gè)app共享某個(gè)進(jìn)程。
有時(shí)候通過(guò)多進(jìn)程的方式獲取多份內(nèi)存空間。一般是指定android:process屬性,還有一種非常規(guī)的方法,通過(guò)JNI在native層去fork一個(gè)新的進(jìn)程。
多進(jìn)程的特性
- 不同的內(nèi)存空間,數(shù)據(jù)無(wú)法共享
- 需要謹(jǐn)慎處理代碼中的線程同步
- 需要提防多進(jìn)程并發(fā)導(dǎo)致的文件鎖和數(shù)據(jù)庫(kù)鎖時(shí)效的問(wèn)題
具體問(wèn)題:
1. 靜態(tài)成員和單例模式完全失效
2. 線程同步機(jī)制失效
3. SharedPreferences可靠性下降
4. Application會(huì)多次重建
進(jìn)程間通信方式
摘自任玉剛的《把玩Android多進(jìn)程》ppt
Parcelable和Serializable
Serializable使用簡(jiǎn)單但是開(kāi)銷(xiāo)很大,序列化和反序列化過(guò)程需要大量的IO操作,一般用于將對(duì)象序列化到存儲(chǔ)設(shè)備中或者將對(duì)象序列化后通過(guò)網(wǎng)絡(luò)傳輸。
Parcelable使用麻煩,但效率很高。
Binder
binder是Android中的一種跨進(jìn)程通信方式
可以理解為一種虛擬的物理設(shè)備,它的設(shè)備驅(qū)動(dòng)是/dev/binder.
從Android Framework角度來(lái)說(shuō),Binder是ServiceManager連接各種Manager和相應(yīng)ManagerService的橋梁。
從Android應(yīng)用層來(lái)說(shuō),Binder是客戶(hù)端和服務(wù)端進(jìn)行通信的媒介。當(dāng)bindService的時(shí)候,服務(wù)端會(huì)返回一個(gè)包含了服務(wù)端業(yè)務(wù)調(diào)用的Binder對(duì)象,通過(guò)這個(gè)Binder對(duì)象,客戶(hù)端就可以獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù),這里的服務(wù)包括普通服務(wù)和基于AIDL的服務(wù)。
更安全,比如socket的ip地址可以進(jìn)行偽造,而B(niǎo)inder機(jī)制從協(xié)議本身就支持對(duì)通信雙方做身份校驗(yàn),因而大大提升了安全性,這個(gè)也是Android權(quán)限模型的基礎(chǔ)。
如下圖,偽裝。即代理模式。對(duì)代理對(duì)象的操作會(huì)通過(guò)驅(qū)動(dòng)最終轉(zhuǎn)發(fā)到Binder本地對(duì)象上去完成,當(dāng)然使用者無(wú)需關(guān)心這些細(xì)節(jié)。
Binder對(duì)象是一個(gè)可以跨進(jìn)程引用的對(duì)象,它的實(shí)體位于一個(gè)進(jìn)程中,它的引用卻遍布于系統(tǒng)的各個(gè)進(jìn)程中。最誘人的是,這個(gè)引用和java里引用一樣既可以是強(qiáng)類(lèi)型,也可以是弱類(lèi)型,而且可以從一個(gè)進(jìn)程傳給另一個(gè)進(jìn)程,讓大家都能訪問(wèn)同一Server,就像一個(gè)對(duì)象或引用賦值給另一個(gè)引用一樣。Binder模糊了進(jìn)程邊界,淡化了進(jìn)程間通信過(guò)程,整個(gè)系統(tǒng)仿佛運(yùn)行于同一個(gè)面向?qū)ο蟮某绦蛑小?/p>
線程
Android中的線程
Android系統(tǒng)基于精簡(jiǎn)過(guò)后的linux內(nèi)核,Linux系統(tǒng)的調(diào)度器在分配time slice的時(shí)候,采用的CFS(Completely fair scheduler)策略,不僅會(huì)參考單個(gè)線程的優(yōu)先級(jí),還會(huì)追蹤每個(gè)線程已經(jīng)獲取到的time slice數(shù)量。優(yōu)先級(jí)高的線程不一定能在爭(zhēng)取timeslice上有絕對(duì)的優(yōu)勢(shì)。
Android將進(jìn)程分為多個(gè)group,其中有兩種比較重要:
- default group
- 能獲得絕大部分的timeslice(UI線程就屬于此列)
- background group
- 工作線程,最多被分配10%的timeslice
其中background group需要開(kāi)發(fā)者顯示的歸位(官方建議)
new Thread(new Runnable(){
@Override
public void run(){
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// do sth
}
}).start();
線程間通信
- 共享內(nèi)存
- 文件、數(shù)據(jù)庫(kù)
- Handler
- Java里的wait notify notifyAll
UI / Main thread
系統(tǒng)啟動(dòng)時(shí)創(chuàng)建的線程,用來(lái)處理頁(yè)面的繪制。
不可以把耗時(shí)操作放在ui線程中,如網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)庫(kù)的讀寫(xiě)等等,阻塞超過(guò)5s會(huì)發(fā)生ANR錯(cuò)誤。
因?yàn)锳ndroid UI toolkit不是線程安全的,故而所有的頁(yè)面繪制都必須放在UI線程中做。
有個(gè)黑科技是可以在Activity的onResume()前使用非UI線程繪制UI,因?yàn)闄z測(cè)線程是否是UI線程是在ViewRootImpl中進(jìn)行檢測(cè)的,而ViewRootImpl是在onResume()時(shí)才會(huì)進(jìn)行初始化的
僅限了解,請(qǐng)勿在實(shí)際項(xiàng)目中嘗試。
在非UI線程中更新UI
簡(jiǎn)單情況
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable,long)
- handler
- AsyncTask
Handler介紹
主要分為三個(gè)部分:
- Looper
- 工人,完成MessageQueue里面的任務(wù)
- 用來(lái)執(zhí)行消息隊(duì)列中的消息,本質(zhì)是一個(gè)while循環(huán),通過(guò)pipe機(jī)制進(jìn)行同步
- 每個(gè)Thread最多擁有一個(gè)Looper,而Thread只有擁有了Looper,才能初始化Handler
- MessageQueue
- 任務(wù)隊(duì)列,采用FIFS
- Handler
- 將消息放入MessageQueue
注意點(diǎn) :
Can't create handler inside thread that has not called Looper.prepare()
handler所在的線程必須調(diào)用過(guò)Looper.prepare方法,否則沒(méi)有l(wèi)ooper來(lái)進(jìn)行工作
public static final void prepare(){
if(sThreadLocal.get()!=null){
throw new RuntimeException("Only one Looper may be created per thread");
}
//關(guān)于ThreadLocal的介紹,放在文章末尾,此處可以理解為一個(gè)referfence,可以set和get
sThreadLocal.set(new Looper());
}
所以需要在新開(kāi)的線程中顯示調(diào)用Looper.prepare()方法,否則無(wú)法在此線程中新建handler(不然sThreadLocal中是取不到looper來(lái)進(jìn)行更新操作的)
Android中有一個(gè)現(xiàn)成的類(lèi)HandlerThread,可以方便使用
HandlerThread handlerThread = new HandlerThread("thread_name");
handlerThread.start();
//MyHandler extends android.os.Handler
mHandler = new MyHandler(handlerThread.getLooper());
Thread類(lèi)
- 啟動(dòng)了新的線程,沒(méi)有任務(wù)的概念,不能做狀態(tài)的管理。
- start之后,run當(dāng)中的代碼就一定會(huì)執(zhí)行到底,中途無(wú)法取消。
- 作為匿名內(nèi)部類(lèi)持有了外部類(lèi)的引用,在線程退出之前,會(huì)阻礙GC的回收,在一段時(shí)間內(nèi)造成內(nèi)存泄露
- 沒(méi)有線程切換的接口,要傳遞處理結(jié)果到UI線程,需要些額外的線程切換代碼
- 如果從UI線程啟動(dòng),該線程優(yōu)先級(jí)默認(rèn)為Default
AsyncTask<Params,Progress,Result>
重寫(xiě)方法:
- onPreExecute
- 在執(zhí)行耗時(shí)線程前被調(diào)用
- doInBackground(Params...)
- 在后臺(tái)線程執(zhí)行,返回Result,執(zhí)行過(guò)程中調(diào)用publicProgress(Progress)來(lái)進(jìn)行任務(wù)進(jìn)度的更新
- onPostExecute(Result)
- 在UI線程中執(zhí)行
- onProgressUpdate(Progress...)
- 在UI線程執(zhí)行,在publishProgress方法調(diào)用后執(zhí)行
在不同的系統(tǒng)版本上串行與并行的執(zhí)行行為不一致
必須遵守的規(guī)則:
- Task實(shí)例必須在UI線程中創(chuàng)建
- execute方法必須在UI線程中調(diào)用
- 該Task只能被執(zhí)行一次,多次調(diào)用時(shí)會(huì)出現(xiàn)異常
- 不要手動(dòng)調(diào)用onPreExecute....等生命周期方法,使用publishProgress()更新進(jìn)度
ThreadPoolExecutor
Thread、AsyncTask適合處理單個(gè)任務(wù)的場(chǎng)景,HandlerThread適合串行處理多任務(wù)的場(chǎng)景。當(dāng)需要并行的處理多任務(wù)時(shí),ThreadPoolExecutor是更好的選擇。
提供復(fù)用機(jī)制(線程池)
public static Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
//execute
THREAD_POOL_EXECUTOR.execute(Runnable XX);
代價(jià)
- 每一個(gè)新線程至少消耗64kb內(nèi)存
- 線程的切換回帶來(lái)額外開(kāi)銷(xiāo)(switch context)
- 盡量復(fù)用已有的工作線程
ThreadLocal
很多地方出現(xiàn)這個(gè)東西,其實(shí)它是一個(gè)容器,用來(lái)存放線程的靜態(tài)局部變量,保證每一個(gè)線程都擁有單獨(dú)的靜態(tài)成員變量,保證了線程安全。
ThreadLocal<T>可以近似的認(rèn)為是Map<Thread,T>,它的get方法就是以當(dāng)前線程為key去map中取對(duì)應(yīng)的T
ThreadLocal為每一個(gè)線程提供了一個(gè)獨(dú)立的副本。
Sample
比如說(shuō)下面這段代碼為每個(gè)線程創(chuàng)建一個(gè)計(jì)數(shù)器,這時(shí)使用不同的線程獲得的number就不同。
public interface Sequence{
int getNumber();
}
public class SequenceByThreadLocal implements Squence{
private static ThreadLocal<Integer> numberContainer = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 0;
}
};
public int getNumber(){
numberContainer.set(numberContainer.get()+1);
return numberContainer.get();
}
}
一個(gè)簡(jiǎn)易實(shí)現(xiàn)
其實(shí)這個(gè)數(shù)據(jù)結(jié)構(gòu)很簡(jiǎn)單,可以用代碼做一個(gè)簡(jiǎn)易的實(shí)現(xiàn)
public class MyThreadLocal<T>{
private Map<Thread,T> container = Collections.synchronizedMap(new HashMap<Thread,T>());
public void set(T vaule){
container.put(Thread.currentThread(),value);
}
public T get(){
Thread thread = Thread.currentThread();
T value = container.get(thread);
if(value == null && !container.containsKey(key){
valuee = initValue();
container.put(thread,value);
}
return value;
}
public void remove(){
container.put(Thread.currentThread());
}
protected T initialValue(){
return null;
}
}