zoukankan      html  css  js  c++  java
  • 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() 
                                    + "
     ThreadName: " + Thread.currentThread().getName() 
                                    + "
     ThreadId: " + Thread.currentThread().getId();
                    System.out.println("onEvent(MsgEvent1 msg)收到" + content);
            }
            
            /**
             * 执行在主线程。
             * 非常实用,可以在这里将子线程加载到的数据直接设置到界面中。
             * @param msg 事件1
             */
            public void onEventMainThread(MsgEvent1 msg){
                    String content = msg.getMsg() 
                                    + "
     ThreadName: " + Thread.currentThread().getName() 
                                    + "
     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() 
                                    + "
     ThreadName: " + Thread.currentThread().getName() 
                                    + "
     ThreadId: " + Thread.currentThread().getId();
                    System.out.println("onEventBackgroundThread(MsgEvent1 msg)收到" + content);
            }
            
            /**
             * 执行在在一个新的子线程
             * 适用于多个线程任务处理, 内部有线程池管理。
             * @param msg 事件1
             */
            public void onEventAsync(MsgEvent1 msg){
                    String content = msg.getMsg() 
                                    + "
     ThreadName: " + Thread.currentThread().getName() 
                                    + "
     ThreadId: " + Thread.currentThread().getId();
                    System.out.println("onEventAsync(MsgEvent1 msg)收到" + content);
            }
            
            /**
             * 与发布者在同一个线程
             * @param msg 事件2
             */
            public void onEvent(MsgEvent2 msg){
                    String content = msg.getMsg() 
                                    + "
     ThreadName: " + Thread.currentThread().getName() 
                                    + "
     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

  • 相关阅读:
    darknet实时识别无法显示在窗口解决
    C# 获取当前打开的文件夹2
    C# 如何调试安装包
    C# 自定义文件格式并即时刷新注册表 非关闭explorer
    C# 获取当前打开的文件夹
    SQL Server里面导出SQL脚本(表数据的insert语句)
    windows平台安装redis服务
    C# 默认参数/可选参数需要注意
    webstrom使用
    office密匙
  • 原文地址:https://www.cnblogs.com/scarecrow-blog/p/6095094.html
Copyright © 2011-2022 走看看