標籤:textview illegal move 乾貨 ice button creat use 保留
前言: 我們在開發Android過程中,在處理耗時任務和UI互動的過程中,都會將耗時任務放到子線程處理並重新整理. 下面我提出的兩個問題,相信大多數開發人員都會碰到:
1. 資料經常需要讀取更新,並且比較耗時,需要分步重新整理UI.
2. UI介面切換後,如何停止掉子線程裡面正在讀取的資料而不會將舊資料重新整理到新UI介面上.
目前網上大部分教程主要只是簡單的Handler.postDelayed(), Thread + Handler, Async等方式, 只適用於簡單的一次性重新整理. 或許有人會說我可以採用不斷地new Thread的方式來建立子線程重新整理,然後傳message回去更新UI,但是這樣的不斷地new會有效能消耗大和資料同步的問題.
關於以上這兩個問題的解決, 我在這裡想要介紹的是使用線程池+Future+handler的配合使用.
為了更好理解非同步更新UI的原理,這裡先介紹下Thread + Handler + Looper + Message模型, 如1所示:
圖1 Thread + Handler + Looper + Message模型
圖1清楚給我們展示出了訊息佇列在Looper裡面的處理方式,這裡有兩個重要的要點: (1)子線程也可以通過Looper管理Message, 但是需要加Looper.prepare() 和Looper.loop()才能實現訊息迴圈; (2)UI主線程無需實現prepare()和loop()因為主線程中已經預設實現了.
現在開始介紹線程池+Future+handler的一些基本概念和使用demo執行個體.
線程池
是一種對象池的思想,開闢一塊記憶體空間,裡面存放了眾多(未死亡)的線程,池中線程執行調度由池管理器來處理。當有線程任務時,從池中取一個,執行完成後線程對象歸池,這樣可以避免反覆建立線程對象所帶來的效能開銷,節省了系統的資源.如果在程式中反覆建立和銷毀線程,將會對程式的反應速度造成嚴重影響,有時甚至會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); }
Android 非同步更新UI-線程池-Future-Handler執行個體分析