Android線程間通信更新UI的方法(重點分析EventBus)


Android的UI更新只能在UI線程中,即主線程。子線程中如果要進行UI更新,都是要通知主線程來進行。

幾種實現方式總結如下,歡迎補充。

1、runOnUiThread()

子線程中持有當前Activity引用(假如為Activity mActivity;),即可以調用mActivity的runOnUiThread(Runnable r)方法。

2、post()和postDelay()

子線程如果持有某個View的引用,要對該View進行更新,則可調用該View對象的post(Runnable r)或postDelay(Runnable r)方法

Handler對象也有post()方法。其實在Android的源碼中,這些post()方法都是借助下面的第3種方法:Handler + Message來實現的。

3、Handler + Message或者Handler + Thread + Message

主線程建立時,默認情況下是有Looper的,可以處理消息隊列。

在主線程建立一個Handler對象,復寫其handleMessage()方法,在該方法中實現UI更新。

示例:

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();

}

4、Broadcast

子線程中發送廣播,主線程中接收廣播並更新UI

5、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);

   }

}

6、EventBus

  • 什么是EventBus

    EventBus是Android下高效的發布/訂閱事件總線機制。作用是可以代替傳統的Intent,Handler,Broadcast或接口函數在Fragment,Activity,Service,線程之間傳遞數據,執行方法。特點是代碼簡潔,是一種發布訂閱設計模式(Publish/Subsribe),或稱作觀察者設計模式。

 

  • 下載EventBus

    1. 下載EventBus庫:

    2. EventBus-2.4.0.jar放入libs即可

 

  • 如何使用EventBus

    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類型,也就是可以發布任何事件。訂閱者接受消息時,只要定義的是第二步四個方法任意一個,並且參數和發布者發布的一致,即可被執行。發布者也可以通過第二步接收消息,訂閱者也可以作為發布者發消息給自己。

    • 代碼實現 (本例是兩個Fragment交互, 也可以是Service,Activity,Fragment以及任意類之間交互)
    • 點擊左邊面板的條目, 可以發送事件,右面板(另一個Fragment)接收到事件,顯示界面,打印日志。
    • 代碼下載 http://yunpan.cn/cctFTVuWtyIgK  訪問密碼 66ed

 

      

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方法被執行。注意參數類型必須和發布者發布的參數一致。

EventBus 部分源碼解析 參考http://blog.csdn.net/superjimmy/article/details/45601515


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM