抽象而言,任何代碼塊執行的業務邏輯均可稱之爲一個任務。最多見的是封裝在Runable
或Callable
或Thread
子類的run
方法中的業務邏輯。數據庫
即將一個任務分派(委託)到一個線程中執行(也可爲當前線程)網絡
設想一下,咱們最但願任務具備什麼特性異步
Android開發不少是基於組件生命週期回調的,如Activity,Fragment,Service等都有提供了一系列on***
回調方法。如當前界面關閉,相關的任務都需取消。如:ide
如不及時取消,有可能出什麼情況:oop
BadTokenException
或NullPointException
異常(TODO:代碼驗證)如有以下需求:獲取用戶信息,先從本地數據庫讀取,有則直接返回,沒有則從網絡獲取。post
同步版本測試
UserInfo getUserInfoFromDb(long uid){ ... } UserInfo getUserInfoFromNet(long uid){ ... } UserInfo getUserInfo(long uid){ UserInfo userInfo = getUserInfoFromDb(uid); if(userInfo!=null){ return userInfo; }else{ return getUserInfoFromNet(uid); } }
異步版ui
public static interface Callback { public void onCallback(UserInfo userInfo); } void getUserInfoFromDb(long uid, Callback callback) { ... } void getUserInfoFromNet(long uid, Callback callback) { ... } void getUserInfo(long uid, final Callback callback) { getUserInfoFromDb(uid, new Callback() { public void onCallback(UserInfo userInfo) { if (userInfo != null) { if (callback != null) { callback.onCallback(userInfo); } } else { getUserInfoFromNet(uid, new Callback() { public void onCallback(UserInfo userInfo) { if (callback != null) { callback.onCallback(userInfo); } } }); } } }); }
很明顯組織同步代碼塊比異步代碼塊簡易許多,可閱讀性高,且測試方便,魯棒性強。而異步代碼,不阻塞當前線程,最典型的異步行爲是:非UI操做分派到子線程去運行,執行結果可經過handler或其餘事件通知機制通知主線程作UI刷新。一個任務若能同時具有同步和異步性,能由調用者選擇,則是最佳的。url
一個UI界面的展現,可能從多個接口獲取數據。如我的資料頁由基本用戶信息和最近動態信息組成。若能將兩個數據接口合併請求,且合併響應,上層處理邏輯將很簡易。若一類任務都能自由組合,勢必快哉。線程
資料頁 = 用戶資料接口+我的動態接口(最近一條) 我的動態頁 = 我的動態接口(多屏分頁)
handler簡潔易用,維持一個任務(消息)隊列,由一個工做線程負責調度。post*(*)
、send*((*)
實現添加任務或發送消息,對應的removeCallbacks(*)
、removeMessages(*)
實現移除任務或消息。使用不當就會致使內存泄漏:
public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mLeakyHandler.postDelayed(new Runnable() { @Override public void run() { /* ... */ } }, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } }
因爲非靜態內部類帶有外部類的引用,故mLeakyHandler
和匿名Runnable
都帶有SmapleActivity
的引用。任務隊列中維持着者兩個內部類的引用,並計劃在10分鐘後執行,故該Activity最快也是在10分鐘後才能被GC掉。必須明確一點,若任務延遲執行的時間改成1ms,那麼就不太算內存泄漏。或者在onDestory
及時remove該任務,也不會內存泄漏。我的認爲的最佳實踐是:
public class TaskExecutor { private static Handler uiHandler = new Handler(Looper.getMainLooper()); private static Handler workHandler = null; private static Looper wordLooper = null; static { HandlerThread workThread = new HandlerThread("workThread"); workThred.start(); workLooper = workThread.getLooper(); workHandler = new Handler(workLooper); } public static Handler uiHandler() { return uiHandler; } public static Handler workHandler() { return workHandler; } }
如何取消(終止)一個運行中的線程?調用目標線程的stop()
方法,這種太直接,可能目標線程還沒作好中止前準備,丟失數據,目前該方法已經廢棄。中斷時實現取消的最合適的方式。以下典型的例子:
public class WorkThread extends Thread { public void run() { try { while (!isInterrupted()) { // 繼續工做 } // 退出工做 } catch (InterruptedException e) { // 退出工做 } } public void cancel() { interrupt(); } }
調用cancle()
時,如當線程爲阻塞狀態(wait,sleep,join或io等待),將馬上拋出InterruptedException
;當線程在非阻塞態,要麼在while判斷中不符合條件而退出,要麼遇到下一個阻塞狀態,拋出InterruptedException
。
ExecutorService.submit
將返回一個Future
來描述任務。Future
有一個cancel
方法。
public class TaskExecutor { // .... 加上上面部分實現 private static ExecutorService executor = Executors.newCachedThreadPool(); public static ExecutorService executor() { return executor; } public static Future<?> runInPoolThread(final Runnable task) { return executor.submit(new Runnable() { @Override public void run() { try { task.run(); } catch (Exception e) { e.printStackTrace(); } } }); } public static <V> Future<V> runInPoolThread(final Callable<V> task) { return executor.submit(new Callable<V>() { @Override public V call() { try { return task.call(); } catch (Exception e) { e.printStackTrace(); return null; } } }); } }
進行取消操做
Future<String> sayHiFuture = TaskExecutor.runInPoolThread(new Callback<String>(){ public String call(){ return "hello word!"; } }); sayHiFuture.cancle(true); //進行取消 cancel(boolean mayInterruptIfRunning)
一個任務的狀態有等待
,執行中
,已完成
,已取消
,其中已完成
包括正常完成的和取消完成的。線程池維持一個任務隊列,按必定的策略進行調度。調用cancle
後,若在對應任務還在等待中,則順利取消任務,如是在執行中
,則更加參數mayInterruptIfRunning
決定是否進行發起中斷請求。那重點來了,如何處理該中斷請求,要看具體的任務,或許被任務直接忽略,繼續執行完任務。
結合上面的小結,不難發現:中斷是一種協商機制。當前線程發起中斷請求,目標線程須要在特定條件(阻塞)或主動去判斷中斷狀態。是否取消,最終決定權在目標線程。
題外話:不要直接new一個線程執行,最佳方案是由線程池來調度
基本思路是,讓任務在非當前線程執行,看須要是否有必要將執行結果進行回調。如
//同步的 UserInfo getUserInfo(long uid){ .... } //改成異步 Future<UserInfo> getUserInfo(final long uid, final Callback<UserInfo> callback){ return TaskExecutor.runInPoolThread(new Callback<UserInfo>(){ public String call(){ UserInfo userInfo = getUserInfo(uid); if(callback!=null){ callback.onCallback(userInfo); } return userInfo; } }); }
基本思路是,調用異步的線程一直等待(或規定時間),直到有有回調結果。以下面典型的例子:
//異步的 void getUserInfo (long uid, Callback<UserInfo> callback) { } //改成同步的 UserInfo getUserInfo (long uid) { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<UserInfo> resultRef = new AtomicReference<UserInfo>(); getUserInfo (uid, new Callback<UserInfo>() { public void onCallback(UserInfo userInfo){ try { resultRef.set(userInfo); } finally { latch.countDown(); } } }); try { latch.await(10, TimeUnit.SECONDS); //最多等待10秒 } catch(){ //等待中斷 } return resultRef.get(); }
是否設置等待時間或設置多少決定具體業務需求。我的建議都設置等待時間,不然若回調沒有調用,將永遠阻塞。上面只是異步代碼同步化的一種實現方案,或許你有更好的方案。
利用線程池的Future.get()
借用 同步變異步的例子
// ... 同步變異步的代碼 // 異步使用方式 getUserInof(1, new Callback<UserInfo>() { public void onCallback(UserInfo userInfo){ // 處理你的業務邏輯 } }); // 同步使用方式 Future<UserInfo> future = getUserInfo(1, null); UserInfo userInfo = future.get();
future.get()
會一直阻塞,直達關聯的任務執行完(多是被取消的)。咱們如今來看OkHttp的封裝方式
OkHttpClient client = new OkHttpClient(); //同步版 String run(String url) throws IOException { Request request = new Request.Builder() .url(url) .build(); Call call = client.newCall(request) Response response = call.execute(); return response.body().string(); } // 異步版 void run(String url) throws IOException { Request request = new Request.Builder() .url(url) .build(); Call call = client.newCall(request) call.enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { } @Override public void onResponse(Response response) throws IOException { String res = response.body().string() // 處理業務邏輯 } }); // call.cancle();//取消任務 }
咱們不細究OkHttp的實現細節,重點是借鑑其封裝任務的方式。調用某個任務,不直接執行,而是返回一箇中間對象Call
(相似Future
),便於對任務進行控制。如同步執行,異步執行,取消,執行狀態判斷等等。
將大的任務,拆分紅粒度小的有依賴關係或獨立的子任務。最近比較火的RxJava就是解決任務串的利器。這裏不細說,從此會分享多包協議設計就是這種思想的實踐。
但願咱們封裝的任務是可取消的,可組合的,同時也兼備同異步。或許很難具有全部特性,可是可取消性是最基本要求。可取消、可取消、可取消。。。。