1. 程式人生 > >android 快速建立一個新的執行緒 & android執行緒的正確使用

android 快速建立一個新的執行緒 & android執行緒的正確使用

下面是快速建立一個新執行緒的方法:

第一種:直接建立子執行緒並啟動
      new Thread() {
@Override
public void run() {
     //這裡寫入子執行緒需要做的工作
        }
   }.start();

第二種:先建立子執行緒,然後啟動
        private Thread newThread; //宣告一個子執行緒
newThread = new Thread(new Runnable() {
    @Override
            public void run() {
            //這裡寫入子執行緒需要做的工作
            }


        });

    newThread.start(); //啟動執行緒

-------------------------------------------------------------------------------------------------------------------------------------------分     割    線------------------------------------------------------------------------

下面是android中執行緒的正確使用:

執行緒是程式設計師進階的一道重要門檻。對於移動開發者來說,“將耗時的任務放到子執行緒去執行,以保證UI執行緒的流暢性”是執行緒程式設計的第一金科玉律,但這條鐵則往往也是UI執行緒不怎麼流暢的主因。我們在督促自己更多的使用執行緒的同時,還需要時刻提醒自己怎麼避免執行緒失控。除了瞭解各類開執行緒的API之外,更需要理解執行緒本身到底是個什麼樣的存在,並行是否真的高效?系統是怎麼樣去排程執行緒的?開執行緒的方式那麼多,什麼樣的姿勢才正確?

多執行緒程式設計之所以複雜原因之一在於其並行的特性,人腦的工作方式更符合單執行緒序列的特點。一個接著一個的處理任務是大腦最舒服的狀態,頻繁的在任務之間切換會產生“頭痛”這類系統異常。人腦的多工和計算機的多工效能差異太大導致我們在設計並行的業務邏輯之時,很容易犯錯。

另一個複雜點在於執行緒所帶來的副作用,這些副作用包括但不限於:多執行緒資料安全,死鎖,記憶體消耗,物件的生命週期管理,UI的卡頓等。每一個新開的執行緒就像扔進湖面的石子,在你忽視的遠處產生漣漪。

把抽象的東西具像化是我們認知世界的主要方式。執行緒作為作業系統世界的“公民”之一,是如何被排程獲取到CPU和記憶體資源的,又怎麼樣去和其他“公民”互通有無進而實現效益最大化?把這些實體和行為具像到大腦,像作業系統一樣開“上帝視角”,才能正確掌控執行緒這頭強大的野獸。

程序優先順序(Process Priority)

執行緒寄宿在程序當中,執行緒的生命週期直接被程序所影響,而程序的存活又和其優先順序直接相關。在處理程序優先順序的時候,大部分人靠直覺都能知道前臺程序(Foreground Process)優先順序要高於後臺程序(Background Process)。但這種粗糙的劃分無法滿足作業系統高精度排程的需求。無論Android還是iOS,系統對於Foreground,Background程序有進一步的細化。

Foreground Process

Foreground一般意味著使用者雙眼可見,可見卻不一定是active。在Android的世界裡,一個Activity處於前臺之時,如果能採集使用者的input事件,就可以判定為active,如果中途彈出一個Dialog,Dialog變成新的active實體,直接面對使用者的操作。被部分遮擋的activity儘管依然可見,但狀態卻變為inactive。不能正確的區分visible和active是很多初級程式設計師會犯的錯誤。

Background Process

後臺程序同樣有更細的劃分。所謂的Background可以理解為不可見(invisible)。對於不可見的任務,Android也有重要性的區分。重要的後臺任務定義為Service,如果一個程序包含Service(稱為Service Process),那麼在“重要性”上就會被系統區別對待,其優先順序自然會高於不包含Service的程序(稱為Background Process),最後還剩一類空程序(Empty Process)。Empty Process初看有些費解,一個Process如果什麼都不做,還有什麼存在的必要。其實Empty Process並不Empty,還存在不少的記憶體佔用。

在iOS的世界裡,Memory被分為Clean Memory和Dirty Memory,Clean Memory是App啟動被載入到記憶體之後原始佔用的那一部分記憶體,一般包括初始的stack, heap, text, data等segment,Dirty Memory是由於使用者操作所改變的那部分記憶體,也就是App的狀態值。系統在出現Low Memory Warning的時候會首先清掉Dirty Memory,對於使用者來說,操作的進度就全部丟失了,即使再次點選App圖示,也是一切從頭開始。但由於Clean Memory沒有被清除,避免了從磁碟重新讀取app資料的io損耗,啟動會變快。這也是為什麼很多人會感覺手機重啟後,app開啟的速度都比較慢。

同理Android世界當中的Empty Process還儲存有App相關的Clean Memory,這部分Memory對於提升App的啟動速度大有幫助。顯而易見Empty Process的優先順序是最低的。

綜上所述,我們可以把Android世界的Process按優先順序分為如下幾類:

      

程序的優先順序從高到低依次分為五類,越往下,在記憶體緊張的時候越有可能被系統殺掉。簡而言之,越是容易被使用者感知到的程序,其優先順序必定更高。

執行緒排程(Thread Scheduling)

Android系統基於精簡過後的linux核心,其執行緒的排程受時間片輪轉和優先順序控制等諸多因素影響。不少初學者會認為某個執行緒分配到的time slice多少是按照其優先順序與其它執行緒優先順序對比所決定的,這並不完全正確。

Linux系統的排程器在分配time slice的時候,採用的CFS(completely fair scheduler)策略。這種策略不但會參考單個執行緒的優先順序,還會追蹤每個執行緒已經獲取到的time slice數量,如果高優先順序的執行緒已經執行了很長時間,但低優先順序的執行緒一直在等待,後續系統會保證低優先順序的執行緒也能獲取更多的CPU時間。顯然使用這種排程策略的話,優先順序高的執行緒並不一定能在爭取time slice上有絕對的優勢,所以Android系統線上程排程上使用了cgroups的概念,cgroups能更好的凸顯某些執行緒的重要性,使得優先順序更高的執行緒明確的獲取到更多的time slice。

Android將執行緒分為多個group,其中兩類group尤其重要。一類是default group,UI執行緒屬於這一類。另一類是background group,工作執行緒應該歸屬到這一類。background group當中所有的執行緒加起來總共也只能分配到5~10%的time slice,剩下的全部分配給default group,這樣設計顯然能保證UI執行緒繪製UI的流暢性。

      

有不少人吐槽Android系統之所以不如iOS流暢,是因為UI執行緒的優先順序和普通工作執行緒一致導致的。這其實是個誤會,Android的設計者實際上提供了background group的概念來降低工作執行緒的CPU資源消耗,只不過與iOS不同的是,Android開發者需要顯式的將工作執行緒歸於background group

  1. new Thread(new Runnable() {  
  2.   @Override
  3.   publicvoid run() {  
  4.     Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
  5.   }  
  6. }).start();  

所以在我們決定新啟一個執行緒執行任務的時候,首先要問自己這個任務在完成時間上是否重要到要和UI執行緒爭奪CPU資源。如果不是,降低執行緒優先順序將其歸於background group,如果是,則需要進一步的profile看這個執行緒是否造成UI執行緒的卡頓。

雖說Android系統在任務排程上是以執行緒為基礎單位,設定單個thread的優先順序也可以改變其所屬的control groups,從而影響CPU time slice的分配。但程序的屬性變化也會影響到執行緒的排程,當一個App進入後臺的時候,該App所屬的整個程序都將進入background group,以確保處於foreground,使用者可見的新程序能獲取到儘可能多的CPU資源。用adb可以檢視不同程序的當前排程策略。

  1. $ adb shell ps -P  

當你的App重新被使用者切換到前臺的時候,程序當中所屬的執行緒又會迴歸的原來的group。在這些使用者頻繁切換的過程當中,thread的優先順序並不會發生變化,但系統在time slice的分配上卻在不停的調整。

是否真的需要新執行緒?

開執行緒並不是提升App效能,解決UI卡頓的萬金油。每一個新啟的執行緒會消耗至少64KB的記憶體,系統在不同的執行緒之間switch context也會帶來額外的開銷。如果隨意開啟新執行緒,隨著業務的膨脹,很容易在App執行的某個時間點發現幾十個執行緒同時在執行。後果是原本想解決UI流暢性,卻反而導致了偶現的不可控的卡頓。

移動端App新啟執行緒一般都是為了保證UI的流暢性,增加App使用者操作的響應度。但是否需要將任務放入工作執行緒需要先了解任務的瓶頸在哪,是i/o,gpu還是cpu?UI出現卡頓並不一定是UI執行緒出現了費時的計算,有可能是其它原因,比如layout層級太深。

儘量重用已有的工作執行緒(使用執行緒池)可以避免出現大量同時活躍的執行緒,比如對HTTP請求設定最大併發數。或者將任務放入某個序列的佇列(HandlerThread)按順序執行,工作執行緒任務佇列適合處理大量耗時較短的任務,避免出現單個任務阻塞整個佇列的情況。

用什麼姿勢開執行緒?

new Thread()

這是Android系統裡開執行緒最簡單的方式,也只能應用於最簡單的場景,簡單的好處卻伴隨不少的隱患。

  1. new Thread(new Runnable() {  
  2.            @Override
  3.            publicvoid run() {  
  4.            }  
  5.        }).start();  

這種方式僅僅是起動了一個新的執行緒,沒有任務的概念,不能做狀態的管理。start之後,run當中的程式碼就一定會執行到底,無法中途取消

Runnable作為匿名內部類還持有了外部類的引用,線上程退出之前,該引用會一直存在,阻礙外部類物件被GC回收,在一段時間內造成記憶體洩漏。

沒有執行緒切換的介面,要傳遞處理結果到UI執行緒的話,需要寫額外的執行緒切換程式碼。

如果從UI執行緒啟動,則該執行緒優先順序預設為Default,歸於default cgroup,會平等的和UI執行緒爭奪CPU資源。這一點尤其需要注意,在對UI效能要求高的場景下要記得:

  1. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  

雖說處於background group的執行緒總共只能爭取到5~10%的CPU資源,但這對絕大部分的後臺任務處理都綽綽有餘了,1ms和10ms對使用者來說,都是快到無法感知,所以我們一般都偏向於在background group當中執行工作執行緒任務。

AsyncTask

一個典型的AsyncTask實現如下:

  1. publicclass MyAsyncTask extends AsyncTask {  
  2.     @Override
  3.     protected Object doInBackground(Object[] params) {  
  4.         returnnull;  
  5.     }  
  6.     @Override
  7.     protectedvoid onPreExecute() {  
  8.         super.onPreExecute();  
  9.     }  
  10.     @Override
  11.     protectedvoid onPostExecute(Object o) {  
  12.         super.onPostExecute(o);  
  13.     }  
  14. }  

和使用Thread()不同的是,多了幾處API回撥來嚴格規範工作執行緒與UI執行緒之間的互動。我們大部分的業務場景幾乎都符合這種規範,比如去磁碟讀取圖片,縮放處理需要在工作執行緒執行,最後繪製到ImageView控制元件需要切換到UI執行緒。

AsyncTask的幾處回撥都給了我們機會去中斷任務,在任務狀態的管理上較之Thread()方式更為靈活。值得注意的是AsyncTask的cancel()方法並不會終止任務的執行,開發者需要自己去檢查cancel的狀態值來決定是否中止任務。

AsyncTask也有隱式的持有外部類物件引用的問題,需要特別注意防止出現意外的記憶體洩漏。

AsyncTask由於在不同的系統版本上序列與並行的執行行為不一致,被不少開發者所詬病,這確實是硬傷,絕大部分的多執行緒場景都需要明確任務是序列還是並行。

執行緒優先順序為background,對UI執行緒的執行影響極小

HandlerThread

在需要對多工做更精細控制,執行緒切換更頻繁的場景之下,Thread()和AsyncTask都會顯得力不從心。HandlerThread卻能勝任這些需求甚至更多

HandlerThread將Handler,Thread,Looper,MessageQueue幾個概念相結合。Handler是執行緒對外的介面,所有新的message或者runnable都通過handler post到工作執行緒。Looper在MessageQueue取到新的任務就切換到工作執行緒去執行。不同的post方法可以讓我們對任務做精細的控制,什麼時候執行,執行的順序都可以控制。HandlerThread最大的優勢在於引入MessageQueue概念,可以進行多工佇列管理。

HandlerThread背後只有一個執行緒,所以任務是序列執行的。序列相對於並行來說更安全,各任務之間不會存在多執行緒安全問題。

HandlerThread所產生的執行緒會一直存活,Looper會在該執行緒中持續的檢查MessageQueue。這一點和Thread(),AsyncTask都不同,thread例項的重用可以避免執行緒相關的物件的頻繁重建和銷燬。

HandlerThread較之Thread(),AsyncTask需要寫更多的程式碼,但在實用性,靈活度,安全性上都有更好的表現。

ThreadPoolExecutor

Thread(), AsyncTask適合處理單個任務的場景,HandlerThread適合序列處理多工的場景。當需要並行的處理多工之時,ThreadPoolExecutor是更好的選擇。

  1. publicstatic Executor THREAD_POOL_EXECUTOR  
  2.        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,  
  3.        TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);  

執行緒池可以避免執行緒的頻繁建立和銷燬,顯然效能更好,但執行緒池併發的特性往往也是疑難雜症的源頭,是程式碼降級和失控的開始。多執行緒並行導致的bug往往是偶現的,不方便除錯,一旦出現就會耗掉大量的開發精力。

ThreadPool較之HandlerThread在處理多工上有更高的靈活性,但也帶來了更大的複雜度和不確定性。

IntentService

不得不說Android在API設計上粒度很細,同一樣工作可以通過各種不同的類來完成。IntentService又是另一種開工作執行緒的方式,從名字就可以看出這個工作執行緒會帶有service的屬性。和AsyncTask不同,沒有和UI執行緒的互動,也不像HandlerThread的工作執行緒會一直存活。IntentService背後其實也有一個HandlerThread來序列的處理Message Queue,從IntentService的onCreate方法可以看出:

  1. @Override
  2. publicvoid onCreate() {  
  3.     // TODO: It would be nice to have an option to hold a partial wakelock
  4.     // during processing, and to have a static startService(Context, Intent)
  5.     // method that would launch the service & hand off a wakelock.
  6.     super.onCreate();  
  7.     HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");  
  8.     thread.start();  
  9.     mServiceLooper = thread.getLooper();  
  10.     mServiceHandler = new ServiceHandler(mServiceLooper);  
  11. }  

只不過在所有的Message處理完畢之後,工作執行緒會自動結束。所以可以把IntentService看做是Service和HandlerThread的結合體,適合需要在工作執行緒處理UI無關任務的場景

結束語

Android開執行緒的方式雖然五花八門,但歸根到底最後還是對映到linux下的pthread,業務的設計還是脫不了和執行緒相關的基礎概念範疇:執行緒的執行順序,排程策略,生命週期,序列還是並行,同步還是非同步等等。摸清楚各類API下執行緒的行為特點,在設計具體業務的執行緒模型的時候自然輕車熟路了,執行緒模型的設計要有整個app視角的廣度,切忌各業務模組各玩各的。


相關推薦

android 快速建立一個執行 & android執行正確使用

下面是快速建立一個新執行緒的方法:第一種:直接建立子執行緒並啟動      new Thread() {@Overridepublic void run() {     //這裡寫入子執行緒需要做的工作        }   }.start();第二種:先建立子執行緒,然後啟

Android集成一個產品時,lunch的product name和device name註意事項

相關 oca end col 全部 article cut 返回 開發 Android系統lunch一個當前的Product大概流程包括下面幾個部分:1. lunch確定TARGET_PRODUCT。一般位於vendor/device/build/target/produ

Godot Engine 學習筆記 建立一個專案

建立一個新專案 新建專案 編輯器 新建專案 開啟Godot會出現專案管理器 點選"新建"按鈕,會彈出一個新建視窗,輸入專案名和專案路徑即可。 編輯器 新建完專案,Godot了就會開啟編輯器。 現在打算在場景上顯

Eclipse下建立一個的Maven專案

首先在電腦上配置好Maven環境 第一步:在Eclipse中選擇建立Maven Project Next Next Finish 建立好後項目結構如下: 第二步:講專案轉為Web專案,右鍵專案點選properties 進行如下操作: 選擇OK後項目結構變為

如何在CAD中快速增加一個窗口?

ces cde 了解 兩個 分享 搜索 ges 方便 電腦 如何在CAD中快速增加一個新窗口?每天的日常就是編輯CAD圖紙,如果每編輯一個都要反回界面中去進行在鍵一個窗口,這樣非常的南非時間,那麽如何在CAD中快速增加一個新窗口?具體要怎麽來操作呢?下面小編就利用迅捷CAD

windows程式設計 建立一個的視窗

#include <windows.h> LRESULT CALLBACK myProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR

C# Tuple 建立一個二元集合

List<string> list1=new List<string>(); List<string> list2=new List<string>(); //Tuple<Lis

Java中如何建立一個的物件的/Creating Objects/Java/New方法/原文

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later rele

HierarchyId通過父節點建立一個的子節點

--HierarchyId通過父節點建立一個新的子節點 CREATE TABLE #temp( node HierarchyID ); insert into #temp select '/' union all select '/1/' union all select '/2/' union al

從頭基於空映象scratch建立一個的Docker映象

我們在使用Dockerfile構建docker映象時,一種方式是使用官方預先配置好的容器映象。優點是我們不用從頭開始構建,節省了很多工作量,但付出的代價是需要下載很大的映象包。 比如我機器上docker images返回的這些基於nginx的映象,每個都超過了100MB,而一個簡單的Ubuntu

最簡單的Docker映象教程:從頭基於空映象scratch建立一個的Docker映象

我們在使用Dockerfile構建docker映象時,一種方式是使用官方預先配置好的容器映象。優點是我們不用從頭開始構建,節省了很多工作量,但付出的代價是需要下載很大的映象包。 比如我機器上docker images返回的這些基於nginx的映象,每個都超過了100MB,而一個簡單的

【SpringBoot】手把手使用IDEA快速建立一個SpringBoot專案

微信公眾號: 關注可獲得更多幹貨。問題或建議,請公眾號留言; 關注小編微信公眾號獲取更多資源 手把手使用IDEA快速建立一個SpringBoot專案 目錄 1.New Project

快速建立一個pandas Data_Frame ,方便測試

import numpy as np import pandas as pd data = np.random.randn(6, 4) df = pd.DataFrame(data,columns=['a','b','c','d']) print(df) a

在python中使用openpyxl和xlrd建立一個Excel並把原表格資料複製到表中

在Python中使用openpyxl和xlrd建立一個新Excel並把原表格資料複製到新表中 新的開始 文科出生,經濟學專業,年近四旬,純粹是把這個當成一個興趣在學。很早之前就打算學Python,但是一直停留在安裝好Python,這幾天比較空閒,開始從最最基礎自

如何快速建立一個VUE專案(介紹專案結構)

建立vue專案流程 1.安裝node.js 首先檢視一下你的node.js是否安裝成功,如果安裝成功第一步可以略過。 驗證是否安裝成功 node -v #檢視nodejs版本號 比如:v8.4.0 如果未安裝,windows 在官網下載安裝包,直接安裝即可

去掉MFC多文件程式中開啟程式就建立一個文件的功能

當我們建立一個MFC多文件程式後,直接編譯執行會發現程式會自動建立一個空白的文件,如果我們不想要這個功能的話,可以這樣做: 在App類的InitInstance()中加上下面的程式碼: cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothi

Android在開啟一個的Activity瀏覽大圖時使用ImageView/PhotoView的時候圖片顯示在螢幕上部的問題

如題:昨天再做這個需求時,先在xml裡面放入的是一個ImageView來載入圖片,在載入完圖片時使用 PhotoViewAttacher attacher = new PhotoViewAttacher(ivBigImageView); 達到的效果是當點開瀏覽大圖的Activity頁面時圖片

建立一個的optix工程及其他

首先,本文的基礎配置參考了新的optix一文,但是依然遇到了巨多問題,截至現在才算基本弄清楚了邊邊角角。 推薦先不要把以上資訊配置完,而是優先配置其他的部分,以免兩邊的配置混在一起完全無法找到原因所在。理論上來說,比著sample中的一個簡單樣例配置完成後完全不需要配置cuda即可執行,cu

docker 建立一個映象

在原映象上,修改: [[email protected] Desktop]# docker run -ti ubuntu /bin/bash [email protected]:/

[Swift通天遁地]二、表格表單-(9)快速建立一個美觀強大的表單

本文將演示如何利用第三方庫,快速建立一個美觀強大的表單 首先確保在專案中已經安裝了所需的第三方庫。 點選【Podfile】,檢視安裝配置檔案。 1 platform :ios, '12.0' 2 use_frameworks! 3 4 target 'DemoApp' do 5