Android進(jìn)程和線程簡述

圍繞Android中的進(jìn)程與線程做了簡要的概述。

按照操作系統(tǒng)中的描述

線程是CPU調(diào)度的最小單元,同時(shí)線程是一種有限的系統(tǒng)資源

而進(jìn)程一般指一個(gè)執(zhí)行單元,在PC和移動設(shè)備上指一個(gè)程序或者一個(gè)應(yīng)用

一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程

操作系統(tǒng)并沒有將多個(gè)線程看做多個(gè)獨(dú)立的應(yīng)用,來實(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ù)模式下不會對其他進(jìn)程產(chǎn)生影響,而線程是一個(gè)進(jìn)程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨(dú)的地址空間,一個(gè)線程死掉就等于整個(gè)進(jìn)程死掉,所以多進(jìn)程的程序要比多線程的健壯,但是多進(jìn)程在切換時(shí),資源耗費(fèi)大,效率要差。

進(jìn)程

進(jìn)程在執(zhí)行過程中擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存,極大的提高了程序的運(yùn)行效率。

進(jìn)程?;畹姆绞剑?a target="_blank" rel="nofollow">這篇文章

Foreground process

  • 有一個(gè)Activity且它
    • 正在交互
  • 有一個(gè)Service且它
    • 綁定到正在交互的Activity
    • "前臺運(yùn)行",startForeground()
    • 正在執(zhí)行生命周期回調(diào) onCreate() onStart() onDestroy()
  • 有一個(gè)BroadcastReceiver且它
    • 正在執(zhí)行onReceive()

Visible process

  • 有一個(gè)Activity且它
    • 不在交互,但仍可見
  • 有一個(gè)Service且它
    • 綁定到可見Activity

Service process

  • 普通Service

故而對于耗時(shí)的比如上傳等,新建一個(gè)Service是比在activity中新建一個(gè)線程好得多的。

Background process

  • 所有Activity都對用戶不可見

會被保存在LRU列表中,即最近查看的最晚被終止

Empty process

系統(tǒng)有時(shí)候會使用空進(jìn)程做為緩存,以縮短下一次在其中運(yùn)行組建所需的啟動時(shí)間。

額外說明

若一個(gè)進(jìn)程A依賴于另一個(gè)進(jìn)程B,則進(jìn)程B的優(yōu)先級可能會被提升并保證B的優(yōu)先級高于A頂優(yōu)先級。

? 舉例 (A優(yōu)先級永遠(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)程,但也可以通過android:process屬性使每個(gè)app有多個(gè)進(jìn)程,或者多個(gè)app共享某個(gè)進(jìn)程。

有時(shí)候通過多進(jìn)程的方式獲取多份內(nèi)存空間。一般是指定android:process屬性,還有一種非常規(guī)的方法,通過JNI在native層去fork一個(gè)新的進(jìn)程。

多進(jìn)程的特性

  • 不同的內(nèi)存空間,數(shù)據(jù)無法共享
  • 需要謹(jǐn)慎處理代碼中的線程同步
  • 需要提防多進(jìn)程并發(fā)導(dǎo)致的文件鎖和數(shù)據(jù)庫鎖時(shí)效的問題

具體問題:

1. 靜態(tài)成員和單例模式完全失效
2. 線程同步機(jī)制失效
3. SharedPreferences可靠性下降
4. Application會多次重建

進(jìn)程間通信方式

摘自任玉剛的《把玩Android多進(jìn)程》ppt

Screen Shot 2017-01-07 at 16.37.37

Parcelable和Serializable

Serializable使用簡單但是開銷很大,序列化和反序列化過程需要大量的IO操作,一般用于將對象序列化到存儲設(shè)備中或者將對象序列化后通過網(wǎng)絡(luò)傳輸。

Parcelable使用麻煩,但效率很高。

Binder

binder是Android中的一種跨進(jìn)程通信方式

可以理解為一種虛擬的物理設(shè)備,它的設(shè)備驅(qū)動是/dev/binder.

從Android Framework角度來說,Binder是ServiceManager連接各種Manager和相應(yīng)ManagerService的橋梁。

從Android應(yīng)用層來說,Binder是客戶端和服務(wù)端進(jìn)行通信的媒介。當(dāng)bindService的時(shí)候,服務(wù)端會返回一個(gè)包含了服務(wù)端業(yè)務(wù)調(diào)用的Binder對象,通過這個(gè)Binder對象,客戶端就可以獲取服務(wù)端提供的服務(wù)或者數(shù)據(jù),這里的服務(wù)包括普通服務(wù)和基于AIDL的服務(wù)。

更安全,比如socket的ip地址可以進(jìn)行偽造,而Binder機(jī)制從協(xié)議本身就支持對通信雙方做身份校驗(yàn),因而大大提升了安全性,這個(gè)也是Android權(quán)限模型的基礎(chǔ)。

Binder通信模型

如下圖,偽裝。即代理模式。對代理對象的操作會通過驅(qū)動最終轉(zhuǎn)發(fā)到Binder本地對象上去完成,當(dāng)然使用者無需關(guān)心這些細(xì)節(jié)。

Binder偽裝

Binder對象是一個(gè)可以跨進(jìn)程引用的對象,它的實(shí)體位于一個(gè)進(jìn)程中,它的引用卻遍布于系統(tǒng)的各個(gè)進(jìn)程中。最誘人的是,這個(gè)引用和java里引用一樣既可以是強(qiáng)類型,也可以是弱類型,而且可以從一個(gè)進(jìn)程傳給另一個(gè)進(jìn)程,讓大家都能訪問同一Server,就像一個(gè)對象或引用賦值給另一個(gè)引用一樣。Binder模糊了進(jìn)程邊界,淡化了進(jìn)程間通信過程,整個(gè)系統(tǒng)仿佛運(yùn)行于同一個(gè)面向?qū)ο蟮某绦蛑小?/p>

線程

線程狀態(tài)轉(zhuǎn)換

Android中的線程

Android系統(tǒng)基于精簡過后的linux內(nèi)核,Linux系統(tǒng)的調(diào)度器在分配time slice的時(shí)候,采用的CFS(Completely fair scheduler)策略,不僅會參考單個(gè)線程的優(yōu)先級,還會追蹤每個(gè)線程已經(jīng)獲取到的time slice數(shù)量。優(yōu)先級高的線程不一定能在爭取timeslice上有絕對的優(yōu)勢。

Android將進(jìn)程分為多個(gè)group,其中有兩種比較重要:

  • default group
    • 能獲得絕大部分的timeslice(UI線程就屬于此列)
  • background group
    • 工作線程,最多被分配10%的timeslice

其中background group需要開發(fā)者顯示的歸位(官方建議

new Thread(new Runnable(){
    @Override
    public void run(){
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        // do sth
    }
}).start();

線程間通信

  1. 共享內(nèi)存
  2. 文件、數(shù)據(jù)庫
  3. Handler
  4. Java里的wait notify notifyAll

UI / Main thread

系統(tǒng)啟動時(shí)創(chuàng)建的線程,用來處理頁面的繪制。

不可以把耗時(shí)操作放在ui線程中,如網(wǎng)絡(luò)請求、數(shù)據(jù)庫的讀寫等等,阻塞超過5s會發(fā)生ANR錯(cuò)誤

因?yàn)锳ndroid UI toolkit不是線程安全的,故而所有的頁面繪制都必須放在UI線程中做。

有個(gè)黑科技是可以在Activity的onResume()前使用非UI線程繪制UI,因?yàn)闄z測線程是否是UI線程是在ViewRootImpl中進(jìn)行檢測的,而ViewRootImpl是在onResume()時(shí)才會進(jìn)行初始化的

僅限了解,請勿在實(shí)際項(xiàng)目中嘗試。

在非UI線程中更新UI

簡單情況

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable,long)
  • handler
  • AsyncTask

Handler介紹

Screen Shot 2017-01-07 at 17.25.19

主要分為三個(gè)部分:

  • Looper
    • 工人,完成MessageQueue里面的任務(wù)
    • 用來執(zhí)行消息隊(duì)列中的消息,本質(zhì)是一個(gè)while循環(huán),通過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)用過Looper.prepare方法,否則沒有l(wèi)ooper來進(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());
}

所以需要在新開的線程中顯示調(diào)用Looper.prepare()方法,否則無法在此線程中新建handler(不然sThreadLocal中是取不到looper來進(jìn)行更新操作的)

Android中有一個(gè)現(xiàn)成的類HandlerThread,可以方便使用

HandlerThread handlerThread = new HandlerThread("thread_name");
handlerThread.start();
//MyHandler extends android.os.Handler
mHandler = new MyHandler(handlerThread.getLooper());

Thread類

  • 啟動了新的線程,沒有任務(wù)的概念,不能做狀態(tài)的管理。
  • start之后,run當(dāng)中的代碼就一定會執(zhí)行到底,中途無法取消。
  • 作為匿名內(nèi)部類持有了外部類的引用,在線程退出之前,會阻礙GC的回收,在一段時(shí)間內(nèi)造成內(nèi)存泄露
  • 沒有線程切換的接口,要傳遞處理結(jié)果到UI線程,需要些額外的線程切換代碼
  • 如果從UI線程啟動,該線程優(yōu)先級默認(rèn)為Default

AsyncTask<Params,Progress,Result>

重寫方法:

  • onPreExecute
    • 在執(zhí)行耗時(shí)線程前被調(diào)用
  • doInBackground(Params...)
    • 在后臺線程執(zhí)行,返回Result,執(zhí)行過程中調(diào)用publicProgress(Progress)來進(jìn)行任務(wù)進(jìn)度的更新
  • onPostExecute(Result)
    • 在UI線程中執(zhí)行
  • onProgressUpdate(Progress...)
    • 在UI線程執(zhí)行,在publishProgress方法調(diào)用后執(zhí)行

在不同的系統(tǒng)版本上串行與并行的執(zhí)行行為不一致

必須遵守的規(guī)則:

  1. Task實(shí)例必須在UI線程中創(chuàng)建
  2. execute方法必須在UI線程中調(diào)用
  3. 該Task只能被執(zhí)行一次,多次調(diào)用時(shí)會出現(xiàn)異常
  4. 不要手動調(diào)用onPreExecute....等生命周期方法,使用publishProgress()更新進(jìn)度

ThreadPoolExecutor

Thread、AsyncTask適合處理單個(gè)任務(wù)的場景,HandlerThread適合串行處理多任務(wù)的場景。當(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)存
  • 線程的切換回帶來額外開銷(switch context)
  • 盡量復(fù)用已有的工作線程

ThreadLocal

很多地方出現(xiàn)這個(gè)東西,其實(shí)它是一個(gè)容器,用來存放線程的靜態(tài)局部變量,保證每一個(gè)線程都擁有單獨(dú)的靜態(tài)成員變量,保證了線程安全。

ThreadLocal<T>可以近似的認(rèn)為是Map<Thread,T>,它的get方法就是以當(dāng)前線程為key去map中取對應(yīng)的T

ThreadLocal為每一個(gè)線程提供了一個(gè)獨(dú)立的副本。

Sample

比如說下面這段代碼為每個(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è)簡易實(shí)現(xiàn)

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

相關(guān)閱讀更多精彩內(nèi)容

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