要做到以上多線程是必不可少的。課本會告訴你什麼時候開辟一個線程,但是很少說的一個很重要的問題是結束。比如,我現在在Activity裡有一個工作需要建立一個線程執行,但是這個Activity在進入背景後不幸遇到系統回收資源被銷毀了。但是這個線程還在漫無目的的遊走,耗費資源。
如何結束?先建立一個:
以上使用kotlin的lambda表達式簡寫了建立<code>Runnable</code>對象部分的代碼。主旨還是建立了一個<code>Runnable</code>對象,并将其作為參數傳入<code>Thread</code>。
如何讓一個<code>Thread</code>能夠退出呢?這就要在<code>Runnable</code>身上下功夫了。首先添加一個是否停止的辨別<code>isCancelled</code>,一旦值為true則停止線程的運作,否則繼續。我們這裡不讨論<code>Thread#interrupt()</code>這個方法,這個方法詭異的地方太多。
首先要給<code>Runnable</code>“添加一個屬性”作為上文的是否停止的辨別。直接添加時不可能的,<code>Runnable</code>隻是一個interface,不是class。是以要實作這個借口為一個抽象類,這樣就可以添加屬性了。
這裡使用抽象類,是因為<code>run()</code>方法的實作留給使用的時候給出。
<code>Thread.sleep(2000)</code>用來模拟一個費時的任務。開始之前檢測是否取消了線程的執行,執行之後在檢測。之後的檢測是有的時候任務執行之後需要有持久化處理結果或者修改任務完成情況的辨別之類的動作,如果已經取消了線程的執行,即使任務執行完成也不持久化結果、不修改完成情況。
最後都檢測完成之後如果沒有取消線程,則發出任務完成執行的消息。
發出和處理這些消息的<code>Handler</code>的定義:
運作在UI線程的<code>Handler</code>檢測從線程發出的消息,如果是<code>THREAD_CANCELLED</code>那就是線程已經取消了,如果是<code>THREAD_FINISHED</code>那就是線程完全運作結束。之後根據message的消息設定<code>TextView</code>的文本内容。
這裡使用了兩個按鈕來啟動和停止線程:
上面用到的<code>Runnable</code>是隻做一件事的,如果是連續不斷的循環很多事的話也可以使用white語句來控制是否一直執行線程的工作。一旦設定為停止線程,則停止線程任務的循環跳出<code>Runnable#run()</code>方法,結束線程。
完整代碼放在附錄中。
是以,如果你在Activity裡開辟了一個線程,在Activity被回收的時候結束線程就可以這麼做:
這樣就再也不用擔心Activity挂了,線程還陰魂不散了。
既然緣起<code>AsyncTask</code>那就肯定需要讀者一起了解一下相關的概念。
比起來使用<code>Handler</code>+<code>Thread</code>+<code>Runnable</code>的多線程異步執行模式來說,使用<code>AsyncTask</code>是簡單了非常的多的。
先簡單了解一下<code>AsyncTask</code>。
<code>AsyncTask</code>是一個抽象泛型類。三個類型Params,Progress,Result分别對應的是輸入參數的類型,精度更新使用的類型,最後是傳回結果的類型。其中任何一個類型如果你不需要的話,可以使用java.lang.Void代替。
繼承<code>AsyncTask</code>給出自己的實作,最少需要實作<code>doInBackground</code>方法。<code>doInBackground</code>方法是在背景線程中運作的。如果要在任務執行之後更新UI線程的話還至少需要給出<code>onPostExecute</code>方法的實作,在這個方法中才可以更新UI。
上述的兩個方法已經構成了一個<code>AsyncTask</code>使用的基本單元。在背景線程處理一些任務,并在處理完成之後更新UI。但是如果一個任務比較長,隻是在最後更新UI是不夠的,還需要不斷的提示使用者已經完成的進度是多少。這就是需要另外實作<code>onProgressUpdate</code>方法。并在<code>doInBackground</code>方法中調用<code>publishProgress</code>方法發出每個任務的處理進度。
這個<code>AsyncTask</code>總體上就是這樣的了:
到這裡各位讀者應該對<code>AsyncTask</code>已經有一個總體的認識了。背景任務在<code>doInBackground</code>處理,處理過程的百分比使用<code>publishProgress</code>方法通知,并在<code>onProgressUpdate</code>方法中更新UI的百分比。最後任務處理全部完成之後在<code>onPostExecute</code>更新UI,顯示全部完成。
怎麼取消一個任務的執行呢?這個機本身還上面的線程的取消基本上一樣。隻是<code>AsyncTask</code>已經提供了足夠的屬性和方法完成取消的工作。直接調用<code>AsyncTask#cancel</code>方法就可以發出取消的信号,但是是否可以取消還要看這個方法的傳回值是什麼。如果是true那就是可以,否則任務不可取消(但是不可取消的原因很可能是任務已經執行完了)。
調用<code>cancel</code>方法發出取消信号,并且可以取消的時候。<code>isCancelled()</code>就會傳回true。同時<code>onPostExecute</code>這個方法就不會再被調用了。而是<code>onCancelled(object)</code>方法被調用。同樣是在<code>doInBackground</code>這個方法執行完之後調用。是以,如果想要在取消任務執行後盡快的調用到<code>onCancelled(object)</code>的話,就需要在<code>onInBackground</code>的時候不斷的檢查<code>isCancelled()</code>是否傳回true。如果傳回的是true就跳出方法的執行。
<code>onCancelled()</code>是API level 3的時候加入的。<code>onCancelled(Result result)</code>是API level 11的時候加入的。這個在相容低版本的時候需要注意。
但是一點需要格外注意:
下面就來看看線程池的概念。顧名思義,線程池就是放線程的池子。把費時費力,或者影響響應使用者操作的代碼放在另外一個線程執行時常有的事。但是如果無顧忌的開辟線程,卻會适得其反,嚴重的浪費系統資源。于是就有了線程池。線程池就是通過某些機制讓線程不要建立那麼多,能複用就複用,實在不行就讓任務排隊等一等。
這個機制線上程池的構造函數裡展現的非常明顯:
corePoolSize 線程池裡閑着也不回收的線程數量。除非<code>allowCoreThreadTimeOut</code>指定可以回收。
** maximumPoolSize** 線程池允許的最大線程數。
** keepAliveTime** 非核心線程(就是如果核心線程數量<code>corePoolSize</code>定義為1的話,第二個就是非核心線程)的逾時時間。
unit <code>keepAliveTime</code>的時間機關,毫秒,秒等。
** workQueue** 存放<code>execute(Runnable cmd)</code>方法送出的<code>Runnable</code>任務。
** threadFactory**線程池用來建立新線程用的一個工廠類。
** handler**線程池達到最大線程數,并且任務隊列也已經滿的時候會拒絕<code>execute(Runnable cmd)</code>方法送出任務。這個時候調用這個handler。
知道以上基本内容以後,就可以探讨線程池管理線程的機制了。概括起來有三點:
如果線程池的線程數量少于<code>corePoolSize</code>的時候,線程池會使用<code>threadFactory</code>這個線程工廠建立新的線程執行<code>Runnable</code>任務。
如果線程池的線程數量大于<code>corePoolSize</code>的時候,線程池會把<code>Runnable</code>任務存放在隊列<code>workQueue</code>中。
線程池的線程數量大于<code>corePoolSize</code>,隊列<code>workQueue</code>已滿,而且小于<code>maximumPoolSize</code>的時候,線程池會建立新的線程執行<code>Runnable</code>任務。否則,任務被拒。
現在回到<code>AsyncTask</code>。被人廣為诟病的<code>AsyncTask</code>是他的任務都是順序執行的。一個AsyncTask的執行個體隻能處理一個任務。但是在<code>AsyncTask</code>後面處理任務的是一個靜态的線程池。在看這個線程池<code>SerialExecutor</code>的<code>execute</code>方法實作:
這個線程池<code>SerialExecutor</code>在處理<code>Runnable</code>的傳入參數的時候對這個任務進行了重新包裝成了一個新的<code>Runnable</code>對象,并且将這個新的對象存入了一個叫做mTasks的隊列。這個新的<code>Runnable</code>對象首先執行傳入的任務,之後不管有無異常調用<code>scheduleNext</code>方法執行下一個。于是整體的就生成了一個傳入的任務都順序執行的邏輯。
這個線性執行的靜态線程池<code>SerialExecutor</code>的實作非常簡單。并不涉及到我們前文所說的那麼多複雜的内容。在實作上,這個線程池隻實作了線程池的最頂層接口<code>Executor</code>。這個接口隻有一個方法就是<code>execute(Runnable r)</code>。另外需要強調一點:mTasks的類型<code>ArrayDeque<T></code>是一個不受大小限制的隊列。可以存放任意多的任務。線上程池的讨論中遇到隊列就需要看看容量概念。
<code>SerialExecutor</code>隻是進行了簡單的隊列排列。但是在<code>scheduleNext</code>方法的實作上又會用到一個複雜一些的線程池來執行任務的具體執行。這線程池叫做
<code>THREAD_POOL_EXECUTOR</code>。我們來具體看看其實作:
這個線程池的實作非常具有現實價值。雖然稍後介紹的系統提供的幾種線程池的實作就夠用。但是難免遇到一些需要自定義線程池的情況。詳細解析如下:
CORE_POOL_SIZE 線程池的核心線程數量為裝置核心數加一。
** MAXIMUM_POOL_SIZE** 線程池的最大線程數量為核心數的兩倍加一。
** KEEP_ALIVE** 線程池中非核心線程的逾時時間為一秒。
** sPoolWorkQueue ** 線程池存放任務的隊列。最大個數為128個。參考上面說的線程池處理機制,會出現任務被拒的情況。排隊的線程池<code>SerialExecutor</code>存放任務的隊列是可以認為無限長的,但是<code>THREAD_POOL_EXECUTOR</code>的隊列最多存放128個任務,加上線程池核心線程的數量,能處理的任務相對有限。出現任務被拒的情況的幾率比較大。是以,往<code>AsyncTask</code>裡直接添加<code>Runnable</code>對象的時候需要三思。
** sThreadFactory** 線程池用來建立線程的工廠對象。<code>ThreadFactory</code>是一個隻有一個方法<code>Thread newThread(Runnable r);</code>的接口。這裡在實作的時候給新建立的線程添加了一個原子計數,并把這個計數作為線程名稱傳遞給了線程的構造函數。
到這裡,我們就已經很清楚<code>AsyncTask</code>是如何用一個極其簡單的線程池<code>SerialExecutor</code>給任務排隊的。又是如何使用一個複雜一些的線程池<code>THREAD_POOL_EXECUTOR</code>來處理具體的任務執行的。尤其是線程池<code>THREAD_POOL_EXECUTOR</code>,在我們實際應用一個自定義的線程池的時候在設定線程池核心線程數量,線程池最大線程數量的時候都依據什麼?明顯就是裝置的CPU核心數。線程分别在不同個CPU核心中做并行的處理。核心數多可以同時處理的線程數就相對較多,相反則會比較少一些。如此設定核心線程數量就會平衡并行處理的任務數量和在處理的過程中耗費的系統資源。
為了讓開發者省時省力,系統預設的提供了四種可以适應不同應用條件的線程池:
** newFixedThreadPool** 顧名思義,線程數量固定的線程池,且其數量等于參數指定值。這一類型的線程池的核心線程數量和最大線程數量是一樣的。存放任務的隊列的容量可以被認為無限大。一旦線程池建立的線程數量等* nThreads*參數值的時候,新增的任務将會被存放在任務隊列中等待核心線程可用的時候執行。
** newSingleThreadExecutor** <code>newFixedThreadPool</code>的一個特殊情況,當mThreads值為1的時候。
** newCachedThreadPool** 這一類型的線程池中建立的線程都有60秒的逾時時間,由于逾時時間比較長等于是線程空閑了以後被緩存了60秒。由于核心線程數量為0,是以建立的線程都是非核心線程。也是以逾時時間才管用。任務隊列<code>SynchronousQueue</code>非常特殊,簡單了解就是一個任務都存放不了。而線程池的最大線程數量又設定為<code>Integer.MAX_VALUE</code>,可以認為是無限大。根據線程池處理任務的機制,可以認為有新任務過來就會建立一個線程去處理這個任務,但是如果存在空閑沒有逾時的線程會優先使用。
** newScheduledThreadPool** 生成一個<code>ScheduledThreadPoolExecutor</code>執行個體。可以通過其提供的接口方法設定延遲一定的時間執行或者隔一定的時間周期執行。
來一個例子:
這裡是上面例子中使用的全部代碼。
歡迎加群互相學習,共同進步。QQ群:iOS: 58099570 | Android: 572064792 | Nodejs:329118122 做人要厚道,轉載請注明出處!
本文轉自張昺華-sky部落格園部落格,原文連結:http://www.cnblogs.com/sunshine-anycall/p/5506154.html,如需轉載請自行聯系原作者