編輯:關於Android編程
前言: 我們在開發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所示:
圖2 圖3
圖4用多次點擊來模擬UI不停調用刷新的情況,後台的執行任務會只是保留當前task和最後一次提交的task,中間的task都被Futurecancel掉了.而且當前舊的task也會受到標志位的控制,不會將更新內容sendMessage出來,從而不會影響最後一次UI的刷新.
圖4
方法二:利用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); }
這個效果的完成主要分為兩個部分自定義view作為listview的列表項 一個view裡面包括 顯示頭像,名字,消息內容等的contentView和滑動才能顯示出來的刪除
用到的知識點:1.Http 協議字段"Range", "bytes="+start+"-"+end2.Rand
紅米手機官方微博正式公布了7月27日新品發布會主角之一——紅米Pro。除此之外,還有一款神秘新品露面,不出意外的話,應該就是小米筆記
當Android系統捕獲到用戶的各種輸入事件後,如何准確的傳遞給真正的需要這個事件的控件?Android提供了一整套完善的事件傳遞、處理機制,來幫助開發者完成准確的事件分