Android的UI更新只能在UI線程中,即主線程。子線程中若是要進行UI更新,都是要通知主線程來進行。java
幾種實現方式總結以下,歡迎補充。android
一、runOnUiThread()設計模式
子線程中持有當前Activity引用(假如爲Activity mActivity;),便可以調用mActivity的runOnUiThread(Runnable r)方法。框架
二、post()和postDelay()ide
子線程若是持有某個View的引用,要對該View進行更新,則可調用該View對象的post(Runnable r)或postDelay(Runnable r)方法函數
Handler對象也有post()方法。其實在Android的源碼中,這些post()方法都是藉助下面的第3種方法:Handler + Message來實現的。oop
三、Handler + Message或者Handler + Thread + Message佈局
主線程創建時,默認狀況下是有Looper的,能夠處理消息隊列。post
在主線程創建一個Handler對象,複寫其handleMessage()方法,在該方法中實現UI更新。this
示例:
private static final int MSG_CODE = 1001; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { //接收並處理消息 if(msg.what == MSG_CODE) { //UI更新 } } }; public void doSomething() { new Thread() { @Override public void run() { //子線程發送信息 Message msg = mHandler.obtainMessage(MSG_CODE); msg.sendToTarget(); } }.start(); }
四、Broadcast
子線程中發送廣播,主線程中接收廣播並更新UI
五、AsyncTask
AsyncTask可方便地實現新開一個線程,並將結果返回給UI線程,而不須要開發者手動去新開一個線程,也無須開發者使用Handler,很是方便。
應當注意的是AsyncTask是一個抽象類,其三個泛型參數的意義以下:
AsyncTask<Param, Progress, Result>
Param:發送給新開的線程的參數類型
Progress:表徵任務處理進度的類型。
Result:線程任務處理完以後,返回給UI線程的值的類型。
該類中有四個抽象函數,onPreExecute(), doInBackground(Params... param),
onProgressUpdate(Progress... progress), onPostExecute(Result result)。
除了,doInBackground(Params...)方法,其它三個方法都運行在UI線程。
自定義一個類繼承AsyncTask並至少實現 doInBackground()函數。在該函數中執行的過程當中,能夠隨時調用publishProgress(Progress...)報告其執行進 度。此時會觸發另外一個方法onProgressUpdate(Progress... progress),以便在UI線程中的某些控件(如ProgressBar)上更新任務處理的進度。
也能夠等doInBackground()執行完,進入onPostExecute()方法後,再進行UI控件的更新。
可在任意時間,任意線程中,取消AsyncTask開啓的任務(調用自定義的AsynTask子類的cancel(boolean mayInterruptIfRunning)方法)
使用示例以下:
//若是沒記錯的話,這個例子應該是以前總結的時候從官網剪下來的
public void onClick(View v) { new DownloadImageTask().execute("http://example.com/image.png"); } private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> { /** The system calls this to perform work in a worker thread and * delivers it the parameters given to AsyncTask.execute() */ protected Bitmap doInBackground(String... urls) { return loadImageFromNetwork(urls[0]); } /** The system calls this to perform work in the UI thread and delivers * the result from doInBackground() */ protected void onPostExecute(Bitmap result) { mImageView.setImageBitmap(result); } }
六、EventBus
EventBus是Android下高效的發佈/訂閱事件總線機制。做用是能夠代替傳統的Intent,Handler,Broadcast或接口函數在Fragment,Activity,Service,線程之間傳遞數據,執行方法。特色是代碼簡潔,是一種發佈訂閱設計模式(Publish/Subsribe),或稱做觀察者設計模式。
1. 下載EventBus庫:
2. EventBus-2.4.0.jar放入libs便可
1. 定義事件, 定義一個類,繼承默認的Object便可,用於區分事件和傳輸數據。 本例爲MsgEvent1和MsgEvent2
2. 添加訂閱者:EventBus.getDefault().register(this); 將所在類做爲訂閱者,框架會經過反射機制獲取全部方法及其參數。
訂閱者所在類能夠定義如下一個或多個方法用以接收事件:
public void onEvent(MsgEvent1 msg) public void onEventMainThread(MsgEvent1 msg) public void onEventBackgroundThread(MsgEvent1 msg) public void onEventAsync(MsgEvent1 msg)
3.發佈者發佈事件:EventBus.getDefault().post(new MsgEvent1("主線程發的消息1"));
一旦執行了此方法, 全部訂閱者都會執行第二步定義的方法。
4. 取消訂閱:EventBus.getDefault().unregister(this); 當訂閱者再也不被使用,或者被關閉時,最好進行取消訂閱,再也不接受事件消息。
5. 注意事項:發佈者post方法參數是Object類型,也就是能夠發佈任何事件。訂閱者接受消息時,只要定義的是第二步四個方法任意一個,而且參數和發佈者發佈的一致,便可被執行。發佈者也能夠經過第二步接收消息,訂閱者也能夠做爲發佈者發消息給本身。
1.主界面搭建:
java
public class MainActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } }
xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:divider="?android:attr/dividerHorizontal" android:orientation="horizontal" android:showDividers="middle" android:baselineAligned="false" tools:context="com.itheima.eventbusdemo.MainActivity" > <fragment android:id="@+id/left_fragment" android:name="com.itheima.eventbusdemo.LeftFragment" android:layout_width="0dip" android:layout_height="match_parent" android:layout_weight="1" /> <fragment android:id="@+id/right_fragment" android:name="com.itheima.eventbusdemo.RightFragment" android:layout_width="0dip" android:layout_height="match_parent" android:layout_weight="3" /> </LinearLayout>
2. 定一個事件類MsgEvent1 (MsgEvent2與此一致):
public class MsgEvent1 { private String msg; public MsgEvent1(String msg) { super(); this.msg = msg; } public String getMsg() { return msg; } }
3. 將右面板做爲訂閱者, 執行方法並接收數據:
public class RightFragment extends Fragment { private TextView tv; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 界面建立時,訂閱事件, 接受消息 EventBus.getDefault().register(this); } @Override public void onDestroy() { super.onDestroy(); // 界面銷燬時,取消訂閱 EventBus.getDefault().unregister(this); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // 佈局只有一個TextView,再也不貼代碼 View view = inflater.inflate(R.layout.fragment_right, null); tv = (TextView) view.findViewById(R.id.tv); return view; } /** * 與發佈者在同一個線程 * @param msg 事件1 */ public void onEvent(MsgEvent1 msg){ String content = msg.getMsg() + "\n ThreadName: " + Thread.currentThread().getName() + "\n ThreadId: " + Thread.currentThread().getId(); System.out.println("onEvent(MsgEvent1 msg)收到" + content); } /** * 執行在主線程。 * 很是實用,能夠在這裏將子線程加載到的數據直接設置到界面中。 * @param msg 事件1 */ public void onEventMainThread(MsgEvent1 msg){ String content = msg.getMsg() + "\n ThreadName: " + Thread.currentThread().getName() + "\n ThreadId: " + Thread.currentThread().getId(); System.out.println("onEventMainThread(MsgEvent1 msg)收到" + content); tv.setText(content); } /** * 執行在子線程,若是發佈者是子線程則直接執行,若是發佈者不是子線程,則建立一個再執行 * 此處可能會有線程阻塞問題。 * @param msg 事件1 */ public void onEventBackgroundThread(MsgEvent1 msg){ String content = msg.getMsg() + "\n ThreadName: " + Thread.currentThread().getName() + "\n ThreadId: " + Thread.currentThread().getId(); System.out.println("onEventBackgroundThread(MsgEvent1 msg)收到" + content); } /** * 執行在在一個新的子線程 * 適用於多個線程任務處理, 內部有線程池管理。 * @param msg 事件1 */ public void onEventAsync(MsgEvent1 msg){ String content = msg.getMsg() + "\n ThreadName: " + Thread.currentThread().getName() + "\n ThreadId: " + Thread.currentThread().getId(); System.out.println("onEventAsync(MsgEvent1 msg)收到" + content); } /** * 與發佈者在同一個線程 * @param msg 事件2 */ public void onEvent(MsgEvent2 msg){ String content = msg.getMsg() + "\n ThreadName: " + Thread.currentThread().getName() + "\n ThreadId: " + Thread.currentThread().getId(); System.out.println("onEvent(MsgEvent2 msg)收到" + content); tv.setText(content); } }
4. 在左面板發佈消息。(任意類均可以發佈消息)
public class LeftFragment extends ListFragment { @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); String[] strs = new String[]{"主線程消息1", "子線程消息1", "主線程消息2"}; setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, strs)); } @Override public void onListItemClick(ListView l, View v, int position, long id) { switch (position) { case 0: // 主線程 System.out.println( "----------------------主線程發的消息1" + " threadName: "+ Thread.currentThread().getName() + " threadId: " + Thread.currentThread().getId()); EventBus.getDefault().post(new MsgEvent1("主線程發的消息1")); break; case 1: // 子線程 new Thread(){ public void run() { System.out.println( "----------------------子線程發的消息1" + " threadName: "+ Thread.currentThread().getName() + " threadId: " + Thread.currentThread().getId()); EventBus.getDefault().post(new MsgEvent1("子線程發的消息1")); }; }.start(); break; case 2: // 主線程 System.out.println( "----------------------主線程發的消息2" + " threadName: "+ Thread.currentThread().getName() + " threadId: " + Thread.currentThread().getId()); EventBus.getDefault().post(new MsgEvent2("主線程發的消息2")); break; } } }
分別點擊左邊條目, Log輸出分析
EventBus框架原理流程圖
1. Publisher是發佈者, 經過post()方法將消息事件Event發佈到事件總線
2. EventBus是事件總線, 遍歷全部已經註冊事件的訂閱者們,找到裏邊的onEvent等4個方法,分發Event
3. Subscriber是訂閱者, 收到事件總線發下來的消息。即onEvent方法被執行。注意參數類型必須和發佈者發佈的參數一致。