前言: 咱們在開發Android過程當中,在處理耗時任務和UI交互的過程當中,都會將耗時任務放到子線程處理並刷新. 下面我提出的兩個問題,相信大多數開發者都會碰到:java
1. 數據常常須要讀取更新,而且比較耗時,須要分步刷新UI.異步
2. UI界面切換後,如何中止掉子線程裏面正在讀取的數據而不會將舊數據刷新到新UI界面上.ide
目前網上大部分教程主要只是簡單的Handler.postDelayed(), Thread + Handler, Async等方式, 只適用於簡單的一次性刷新. 或許有人會說我能夠採用不斷地new Thread的方式來建立子線程刷新,而後傳message回去更新UI,可是這樣的不斷地new會有性能消耗大和數據同步的問題.oop
關於以上這兩個問題的解決, 我在這裏想要介紹的是使用線程池+Future+handler的配合使用.post
爲了更好理解異步更新UI的原理,這裏先介紹下Thread + Handler + Looper + Message模型, 以下圖1所示:性能
圖1 Thread + Handler + Looper + Message模型spa
圖1清楚給咱們展現出了消息隊列在Looper裏面的處理方式,這裏有兩個重要的要點: (1)子線程也能夠經過Looper管理Message, 可是須要加Looper.prepare() 和Looper.loop()才能實現消息循環; (2)UI主線程無需實現prepare()和loop()由於主線程中已經默認實現了.線程
如今開始介紹線程池+Future+handler的一些基本概念和使用demo實例.code
線程池對象
是一種對象池的思想,開闢一塊內存空間,裏面存放了衆多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣能夠避免反覆建立線程對象所帶來的性能開銷,節省了系統的資源.若是在程序中反覆建立和銷燬線程,將會對程序的反應速度形成嚴重影響,有時甚至會Crash掉程序.這裏咱們使用簡單的ExecutorService類.
Future
Future模式能夠這樣來描述:我有一個任務,提交給了Future,Future替我完成這個任務。期間我本身能夠去作任何想作的事情。一段時間以後,我就即可以從Future那兒取出結果。就至關於下了一張定貨單,一段時間後能夠拿着提訂單來提貨,這期間能夠幹別的任何事情。其中Future 接口就是定貨單,真正處理訂單的是Executor類,它根據Future接口的要求來生產產品。
Handler
鏈接子線程和主線程的橋樑,能夠經過sendmessage或者post的方式跟主線程通訊.
說了這麼多,若是還有對基本概念不太熟悉的童鞋能夠先移步到最後的參考文章裏看下再回來看本文章,此處直接上代碼乾貨.
方法一:利用sendMessage實現
public class MyActivity extends Activity { private final String TAG = DemoExecutorService; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initFindView(); setListener(); } private TextView mTitle; private Button mBtn; private void initFindView() { mTitle = (TextView) findViewById(R.id.title); mBtn = (Button) findViewById(R.id.btn); } private void setListener() { mBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { TestCallBack testCallBack = new TestCallBack(); testCallBack.loadToHandler(); } }); } private class TestCallBack { public TestCallBack() { Log.d(TAG, #####TestCallBack===Constructor); } public void loadToHandler() { Handler myHandler = new Handler(getMainLooper()) { @Override public void handleMessage(Message msg) { Log.d(TAG, #######receive the msg?? what = + msg.what); int num = msg.what; switch(num){ case 1: mTitle.setText(######Yeah, we receive the first msg 1); break; case 2: mTitle.setText(######Yeah, we receive the second msg 2); break; default: break; } } }; testExecutorHandler(myHandler); } } private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); Future<!--?--> mTask; boolean mSendMsg; public void testExecutorHandler(final Handler handler) { Log.d(TAG, ########testExecutorHandler, mTask = + mTask); if(mTask != null) { // 經過取消mTask,來實現以前排隊但未運行的submit的task的目的,經過標誌位不讓其發msg給UI主線程更新. mTask.cancel(false); Log.d(TAG, ########mTask.isCannel? === + mTask.isCancelled()); mSendMsg = false; } Runnable r = new Runnable() { @Override public void run() { mSendMsg = true; try { Log.d(TAG, ###step 1####start to sleep 6s.); Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } Message msg; Log.d(TAG, #######1111 mSendMsg === + mSendMsg); if(mSendMsg) { msg = handler.obtainMessage(); msg.what = 1; handler.sendMessage(msg); } else { return ; } Log.d(TAG, ####step 2####start to sleep 4s.); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } // 若沒有從新obtainMessage的話,就會出現如下錯誤,由於已經被回收, 因此報錯. 須要從新 obtainMessage(). // E/AndroidRuntime( 1606): java.lang.IllegalStateException: The specified message queue synchronization barrier token has not been posted or has already been removed. Log.d(TAG, #######22222 mSendMsg === + mSendMsg); if(mSendMsg) { msg = handler.obtainMessage(); msg.what = 2; handler.sendMessage(msg); } else { return ; } } }; // mExecutor.submit(r); // 若只是這樣子就不會進入Future任務裏面,那樣每個submit提交的都會被依次執行. mTask = mExecutor.submit(r); } }
結果和打印以下圖2和3所示:
圖4用屢次點擊來模擬UI不停調用刷新的狀況,後臺的執行任務會只是保留當前task和最後一次提交的task,中間的task都被Futurecancel掉了.並且當前舊的task也會受到標誌位的控制,不會將更新內容sendMessage出來,從而不會影響最後一次UI的刷新.
方法二:利用runnable實現更新:
因爲部分方法跟上面同樣,因此要看完整代碼能夠在下面下載,如下只是核心代碼.
public void loadToRunnable() { Runnable runable = new Runnable(){ @Override public void run() { Log.d(TAG, #########Ok..1111 let update callback1...); mTitle.setText(####Yeah, Refresh in runnable, callback1); } }; Runnable runable2 = new Runnable() { @Override public void run() { Log.d(TAG, ######Ok, let update callback2, ...); mTitle.setText(####Callback2 update success!!!); } }; testExecutorRunnable(runable, runable2, getMainLooper()); } }
public void testExecutorRunnable(final Runnable callback, final Runnable callback2, final Looper looper) { Log.d(TAG, #####testExecutor##mTask..#### = + mTask); if(mTask != null) {www.2cto.com mTask.cancel(false); // true 表示強制取消,會發生異常; false表示等待任務結束後取消. mSendMsg = false; } Runnable r = new Runnable(){ @Override public void run() { mSendMsg = true; try { Log.d(TAG, #####Step 1####Async run after submit...###sleep 6s); Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } if(callback != null && mSendMsg){ // Handler handler = new Handler(); // Not use it, should use the Looper. Handler handler = new Handler(looper); handler.post(callback); } try { Log.d(TAG, #####Step 2####Async run after submit...###sleep 4s); Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } if(callback2 != null && mSendMsg) { Handler handler = new Handler(looper); handler.post(callback2); } } }; mTask = mExecutor.submit(r); }