Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android應用線程詳解

Android應用線程詳解

編輯:關於Android編程

Android應用三種線程 UI Thread Binder Thread Background Thread之間的區別與聯系

UI Thread

我們先來看一下App與Linux進程之間的關系,一般情況下,Android應用組件運行在同一個進程之中,app與Dalvik和Linux系統進程之間的關系如圖:

這裡寫圖片描述
UI Thread又稱作Main Thread,它的生命周期和Android應用生命周期完全一致,它的主要功能是處理用戶輸入事件與展現相關UI界面。並且只可以在UI Thread中操作View,否則會出CalledFromWrongThreadException。
這是為了保證線程安全,因此對UI元素的操作必須在主線程中進行。

Binder Thread

Binder Thread是用作在不同進程間進行交互,每一個進程維持著一系列的線程,即線程池。該線程不會被中斷和重建。Binder Thread可以處理其他進程中發過來的請求,例如系統服務,intent,content providers 和 service。
當有需要時,可以創建一個新的Binder Thread來處理相關請求。大部分情況下,應用不需要關心Binder Thread,UI Thread會優先處理這些請求。例外情況為當應用提供了一個Service可以被其他進程中的組件綁定並通過AIDL進行通信時。下面我們通過adb命令看一下一個app中的線程信息,
首先在模擬器上跑一個demo,然後adb shell ps | grep “packagename”
如下圖:
這裡寫圖片描述
這是我們的demo進程相關信息,然後再adb shell ps -t 25559
這裡寫圖片描述
這裡我們需要關注的一個應用最重要的三個線程
com.letv.downloaddemo
Binder_1
Binder_2
第1個即應用的主線程,也就是UI Thread。一般情況下其名字和包名一致。
下面的Binder_1和Binder_2即為Binder線程,為了更好的說明這三個線程,下面摘用Android內核剖析中的一段話來說明:
首先,很明確的講,包含有Activity的客戶端程序至少包含3個線程,每個Binder對象都對應一個線程,Activity啟動後會創建一個ViewRoot.W對象,同時ActivityThread會創建一個ApplicationThread對象,兩個對象都繼承於Binder,因此會啟動兩個線程,負責接收Linux Binder驅動發送IPC調用。最後一個主要線程也就是程序本身所在的線程,也叫做用戶交互(UI)線程,因為所有的處理用戶消息,以及繪制界面的工作都在該線程中完成。

Background Threads

所有在應用程序中創建的線程都可以被稱作為後台線程,後台線程可以處理很多任務,例如下載,加載數據等等。後台線程在UI 線程中取出,因此可以視為是UI Thread的後台,它繼承UI 線程的優先級(priority默認為5,後續章節會講到優先級如何劃分以及作用)。默認情況下,一個新的進程不包含任何後台線程,後台線程由應用決定在需要的時候創建它。
在上圖中,我們可以看到,除了UI線程,其他線程的PPID(即父線程ID為25559),也就是說所有其它線程的父線程為UI線程,即Binder_1,Binder_2,和其他一些堆監控和GCDemon(內存回收守護線程)均為UI線程創建,其PPID為25559。如果在demo的MainActivity的onCreate方法中new了一個新的Thread newThread(“AutoThread-1”),並start,我們在執行adb shell ps -t 25559 會發現線程中多了一個名為AutoThread-1的線程,其父線程ID也為25559。

總結

對於應用來說UI Thread 和其他線程會略有不同,但是對於Linux來說,它們都是普通的本地線程並且平等的對待,UI線程用來處理UI的相關更新操作,是通過Android Framework層的WindowManager來控制,而不是Linux來決定的。
因此下一節我們會講到Linux進程和線程的關系,怎樣提高線程優先級或者設置合適的優先級以防止自己new的線程和UI線程搶占資源。

Linux進程與線程

在Android應用中,我們通常將一些耗時操作放到子線程中去執行,後台線程(子線程)有多種實現方式,但不管是如何實現,其對於Linux內核來說它們都是相同的優先級(正常情況下)。由於Android平台是以Linux為內核的系統,因此每個應用其實相當於Linux應用,所有的Android應用和線程遵守Linux執行環境。因此,我們了解Linux環境不僅可以幫助我們剛好的掌握Android應用及其線程是如何執行的,還可以更好的優化我們的應用。
正常情況下,每個的運行中的應用都依賴於一個Linux進程下,這個進程由Zygote進程孵化出來,並且有一下幾個屬性:

User ID(UID)
每一個Linux進程有一個唯一的用戶標示符,例如root 當前user,對於Android系統來說,每一個應用代表了一個用戶,當應用安轉的時候,會被分配一個user id。

Process identifier(PID)
進程唯一ID

Parent process identifier (PPID)
系統啟動以後,每個進程都應該由其他進程創建,運行中的系統其所有進程可以看作以一顆樹的形式存在,因此,每一個應用進程由一個父進程。對於Android系統來說,所有的應用的父進程為Zygote。 Stack
存儲本地函數指針和變量 Heap
進程內存,每個進程的內存都是私有的,不會被其他進程共享。Android提供了進程間通信方法。
下面我們adb shell ps一下,
這裡寫圖片描述
我們看到Android自帶應用例如Media,systemUI,exchang等其PPID均為1151即Zygote 的PID,並且每個應用都有自己唯一的User ID。

Linux線程調度程序

在Linux系統下,線程是最小的執行單元。線程調度程序決定了如何分配線程執行時間。對於一個應用中的線程來說,它要和本應用中的其它所有線程競爭。線程調度程序決定了線程的執行順序與時間。在決定選擇哪一個線程執行時,線程優先級是最重要的決定元素。在Android系統上,應用線程調度由Linux內核統一的調度器而不是Dalvik虛擬機來調度。事實上,這意味著我們應用中的線程要和所有應用的線程進行競爭。
Linux kernel調度器被稱作為完全公平的調度器(completely fair scheduler (CFS))。它的公平性在與調度不僅僅依賴於線程優先級,而且還會追蹤已分配的線程執行時間。如果一個線程原先被拒絕執行,調度器會允許它比高優先級的線程提前執行。如果一個線程在分配的時間內沒有執行,CFS會認為它的優先級比較低,將來分配的執行時間會比較少。
有兩種方法影響調度器:
- 優先級
改變Linux線程的優先級
- 控制組
改變Android特定的控制組

優先級

所有線程都由優先級來決定了調度器什麼時候去執行該線程。在Linux上,線程優先級的范圍為-20(最高優先級)到 19(最低優先級),默認為0。
一個線程的優先級和創建它的線程優先級相同。在應用程序中改變優先級的兩個方法
java.lang.Thread
setPriority(int priority);
1-10 1優先級最低 10 優先級最高 5默認優先級
10 (most prioritized).
android.os.Process
Process.setThreadPriority(int priority); // Calling thread. Process.setThreadPriority(int threadId, int priority); // Thread with
// specific id.
下面是Linux和Thread優先級對比圖
這裡寫圖片描述

控制組

Android提供了5種進程,具體可參考文章。
Android進程和線程

這五種進程又分為兩大類,前台組和後台組,如下圖所示
這裡寫圖片描述

前台進程和可見進程意味著會分配更多的內存去執行,Android通過這個保證了用戶交互界面可以有更多的內存,從而保證應用程序的流暢性。盡管控制組保證了後台程序相對於前台程序占用更少的執行內存,但一個應用仍然可以創建多個線程和UI線程競爭,而且通過前文我們知道,創建的線程會有和UI線程同樣的優先級。因此,一個應用創建大量的子線程有可能會造成UI線程阻塞,因為子線程會占用系統資源。為了避免這種情況我們在創建子線程的時候最好給它設置低的優先級,將控制組設置為後台控制組。我們看下HandlerThread源碼

public HandlerThread(String name) {
    super(name);
    mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
    @Override
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}
其給自己設置了一個默認的優先級,有些時候我們可以通過Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)給自己的線程設置低優先級。

總結

對於Android應用來說 UI Binder 和 Background線程都是Linux Posix線程。當應用運行的時候,必要線程為UI線程和Binder線程,我們可以在UI線程中創建後台線程。默認情況下,所有的Android組件運行在UI線程中,例如Service BroadcastReceiver。但是一些耗時操作我們要放到後台線程中,例如從文件中讀取數據,解析大圖片,下載等,否則就可能造成ANR。
但是對於調度器來說,UI線程和其他線程並沒有什麼不同,調度器並不知道哪一個線程是UI線程。因此我們盡可能的將我們創建的線程優先級降低,或者設定一個低的控制組。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved