編輯:Android資訊
本文系2015 北京 GDG Devfest分享內容整理。
在Android中,我們或多或少使用了工作者線程,比如Thread,AsyncTask,HandlerThread,甚至是自己創建的線程池,使用工作者線程我們可以將耗時的操作從主線程中移走。然而在Android系統中為什麼存在工作者線程呢,常用的工作者線程有哪些不易察覺的問題呢,關於工作者線程有哪些優化的方面呢,本文將一一解答這些問題。
因而,在Android中使用工作者線程顯得勢在必行,如一開始提到那樣,在Android中工作者線程有很多,接下來我們將圍繞AsyncTask,HandlerThread等深入研究。
AsyncTask是Android框架提供給開發者的一個輔助類,使用該類我們可以輕松的處理異步線程與主線程的交互,由於其便捷性,在Android工程中,AsyncTask被廣泛使用。然而AsyncTask並非一個完美的方案,使用它往往會存在一些問題。接下來將逐一列舉AsyncTask不容易被開發者察覺的問題。
內存洩露是Android開發中常見的問題,只要開發者稍有不慎就有可能導致程序產生內存洩露,嚴重時甚至可能導致OOM(OutOfMemory,即內存溢出錯誤)。AsyncTask也不例外,也有可能造成內存洩露。
以一個簡單的場景為例:
在Activity中,通常我們這樣使用AsyncTask
//In Activity new AsyncTask<String, Void, Void>() { @Override protected Void doInBackground(String... params) { //some code return null; } }.execute("hello world");
上述代碼使用的匿名內存類創建AsyncTask實例,然而在Java中,非靜態內存類會隱式持有外部類的實例引用
,上面例子AsyncTask創建於Activity中,因而會隱式持有Activity的實例引用。
而在AsyncTask內部實現中,mFuture同樣使用匿名內部類創建對象,而mFuture會作為執行任務加入到任務執行器中。
private final WorkerRunnable<Params, Result> mWorker; public AsyncTask() { mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { //some code } }; }
而mFuture加入任務執行器,實際上是放入了一個靜態成員變量SERIAL_Executor指向的對象SerialExecutor的一個ArrayDeque類型的集合中。
public static final Executor SERIAL_EXECUTOR = new SerialExecutor(); private static class SerialExecutor implements Executor { final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { //fake code r.run(); } }); } }
當任務處於排隊狀態,則Activity實例引用被靜態常量SERIAL_EXECUTOR 間接持有。
在通常情況下,當設備發生屏幕旋轉事件,當前的Activity被銷毀,新的Activity被創建,以此完成對布局的重新加載。
而本例中,當屏幕旋轉時,處於排隊的AsyncTask由於其對Activity實例的引用關系,導致這個Activity不能被銷毀,其對應的內存不能被GC回收,因而就出現了內存洩露問題。
關於如何避免內存洩露,我們可以使用靜態內部類 + 弱引用的形式解決。
AsyncTask作為任務,是支持調用者取消任務的,即允許我們使用AsyncTask.canncel()方法取消提交的任務。然而其實cancel並非真正的起作用。
首先,我們看一下cancel方法:
public final boolean cancel(boolean mayInterruptIfRunning) { mCancelled.set(true); return mFuture.cancel(mayInterruptIfRunning); }
cancel方法接受一個boolean類型的參數,名稱為mayInterruptIfRunning
,意思是是否可以打斷正在執行的任務。
當我們調用cancel(false),不打斷正在執行的任務,對應的結果是
onPostExecute
方法,而是執行onCancelled
方法當我們調用cancel(true),表示打斷正在執行的任務,會出現如下情況:
如下,就是一個cancel方法無法打斷正在執行的任務的例子
AsyncTask<String,Void,Void> task = new AsyncTask<String, Void, Void>() { @Override protected Void doInBackground(String... params) { boolean loop = true; while(loop) { Log.i(LOGTAG, "doInBackground after interrupting the loop"); } return null; } } task.execute("hello world"); try { Thread.sleep(2000);//確保AsyncTask任務執行 task.cancel(true); } catch (InterruptedException e) { e.printStackTrace(); }
上面的例子,如果想要使cancel正常工作需要在循環中,需要在循環條件裡面同時檢測isCancelled()
才可以。
Android團隊關於AsyncTask執行策略進行了多次修改,修改大致如下:
executeOnExecutor()
實現並行執行。然而AsyncTask的串行實際執行起來是這樣的邏輯
在AsyncTask中,並發執行器實際為ThreadPoolExecutor的實例,其CORE_POOL_SIZE為當前設備CPU數量+1,MAXIMUM_POOL_SIZE值為CPU數量的2倍 + 1。
以一個四核手機為例,當我們持續調用AsyncTask任務過程中
但是由於AsyncTask屬於默認線性執行任務,導致並發執行器總是處於某一個線程工作的狀態,因而造成了ThreadPool中其他線程的浪費。同時由於AsyncTask中並不存在allowCoreThreadTimeOut(boolean)的調用,所以ThreadPool中的核心線程即使處於空閒狀態也不會銷毀掉。
Executors是Java API中一個快速創建線程池的工具類,然而在它裡面也是存在問題的。
以Executors中獲取一個固定大小的線程池方法為例
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()); }
在上面代碼實現中,CORE_POOL_SIZE和MAXIMUM_POOL_SIZE都是同樣的值,如果把nThreads當成核心線程數,則無法保證最大並發,而如果當做最大並發線程數,則會造成線程的浪費。因而Executors這樣的API導致了我們無法在最大並發數和線程節省上做到平衡。
為了達到最大並發數和線程節省的平衡,建議自行創建ThreadPoolExecutor,根據業務和設備信息確定CORE_POOL_SIZE和MAXIMUM_POOL_SIZE的合理值。
HandlerThread是Android中提供特殊的線程類,使用這個類我們可以輕松創建一個帶有Looper的線程,同時利用Looper我們可以結合Handler實現任務的控制與調度。以Handler的post方法為例,我們可以封裝一個輕量級的任務處理器
private Handler mHandler; private LightTaskManager() { HandlerThread workerThread = new HandlerThread("LightTaskThread"); workerThread.start(); mHandler = new Handler(workerThread.getLooper()); } public void post(Runnable run) { mHandler.post(run); } public void postAtFrontOfQueue(Runnable runnable) { mHandler.postAtFrontOfQueue(runnable); } public void postDelayed(Runnable runnable, long delay) { mHandler.postDelayed(runnable, delay); } public void postAtTime(Runnable runnable, long time) { mHandler.postAtTime(runnable, time); }
在本例中,我們可以按照如下規則提交任務
上面的輕量級任務處理器利用HandlerThread的單一線程 + 任務隊列的形式,可以處理類似本地IO(文件或數據庫讀取)的輕量級任務。在具體的處理場景下,可以參考如下做法:
在Android應用中,將耗時任務放入異步線程是一個不錯的選擇,那麼為異步線程調整應有的優先級則是一件錦上添花的事情。眾所周知,線程的並行通過CPU的時間片切換實現,對線程優先級調整,最主要的策略就是降低異步線程的優先級,從而使得主線程獲得更多的CPU資源。
Android中的線程優先級和Linux系統進程優先級有些類似,其值都是從-20至19。其中Android中,開發者可以控制的優先級有:
THREAD_PRIORITY_DEFAULT
,默認的線程優先級,值為0THREAD_PRIORITY_LOWEST
,最低的線程級別,值為19THREAD_PRIORITY_BACKGROUND
後台線程建議設置這個優先級,值為10THREAD_PRIORITY_MORE_FAVORABLE
相對THREAD_PRIORITY_DEFAULT
稍微優先,值為-1THREAD_PRIORITY_LESS_FAVORABLE
相對THREAD_PRIORITY_DEFAULT
稍微落後一些,值為1為線程設置優先級也比較簡單,通用的做法是在run方法體的開始部分加入下列代碼
android.os.Process.setThreadPriority(priority);
通常設置優先級的規則如下:
THREAD_PRIORITY_BACKGROUND
THREAD_PRIORITY_LOWEST
在Android中工作者線程如此普遍,然而潛在的問題也不可避免,建議在開發者使用工作者線程時,從工作者線程的數量和優先級等方面進行審視,做到較為合理的使用。
前言 谷歌在2015年8月份時候,發布了Android 6.0版本,代號叫做“棉花糖”(Marshmallow ),其中的很大的一部分變化,是在用戶權限授權上,或
創建Window 在Activity的attach方法中通過調用PolicyManager.makeNewWindo創建Window,將一個View add到Wi
ImageView有scaleType屬性可以縮放圖片,讓圖片鋪滿屏幕寬度,但是會出現壓縮或裁剪的情況。 ImageView的scaleType的屬性分別是mat
Fragment是Android中的重要組件,在Android 3.0的時候添加進來。 關於Fragment的生命周期,我相信了解過的開發人員都應該把以下方法脫口