Android中UI线程(主线程)和子线程间的通讯方式比较

通过上一篇文章我们知道在UI线程中更新UI,在子线程中执行耗时操作。这篇文章记录下那么主线程和子线程之间消息处理的多种方式,更多的是对比它们的区别。

更新UI操作

除了UI线程外,其他线程都不可以对那些UI控件访问和操控

  //写法一
  new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getId());
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    //UI操作
                }
            });
        }
    }).start();
   *****************华丽分割线**********************
   //写法二
    new Thread(new Runnable() {
        @Override
        public void run() {
            //UI操作,效果:延时2000ms后执行
            handler.postDelayed(this, 2000);
        }
    }).start();

上面两个其实原理一样,runOnUiThread这个会调用父类中的方法

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

首先判断是否是UI线程,不是的话就post,如果是的话就正常运行该线程. 只要经过主线程中的Handler.post或者postDelayed处理线程runnable则都可以将其转为UI主线程.本质上就是利用Handler的机制,它就是来处理线程与UI通讯的.

UI线程和子线程间交互方式

以下不同的方式都可以实现主线程和子线程间的通讯,只是使用的场景和使用方式不同,所以根据情况来选择合适的方式。

1)Activity.runOnUIThread(Runnable)

Activity中的方法,运行在主线程

new Thread(new Runnable() {
        @Override
        public void run() {
            //耗时操作
            ...
            //该方法将Runnable中更新UI的代码传送到主线程
            runOnUiThread(new Runnable() {
                @Override
                public void run() {// 运行在UI线程里面的
                    //UI操作 例如: 隐藏进度条,setAdapter等
                }
            });
        }
    }).start();
2)View.Post(Runnable)和View.PostDelayed(Runnabe,long)

View中view.post(Runnable r)源码如下:
方法里,View获得当前线程(即UI线程,因为只有UI线程才能创建view)的Handler,然后将action对象post到Handler里。在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。在Handler再次处理该Message时,有一条分支就是为它所设,直接调用runnable的run方法。因此,我们可以在run方法中更新UI。

 public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }

其原理是将任务添加到消息队列中,保证在UI线程执行。从本质上说,它还是依赖于以Handler、Looper、MessageQueue、Message为基础的异步消息处理机制。相对于新建Handler进行处理更加便捷。

比如在onCreate方法中获取某个view的宽高,而直接View#getWidth获取到的值是0。要知道View显示到界面上需要经历onMeasure、onLayout和onDraw三个过程,而View的宽高是在onLayout阶段才能最终确定的,而在Activity#onCreate中并不能保证View已经执行到了onLayout方法,也就是说Activity的声明周期与View的绘制流程并不是一一绑定的。

那为什么调用post方法就能起作用呢?首先MessageQueue是按顺序处理消息的,而在setContentView()后队列中会包含一条询问是否完成布局的消息,而我们的任务通过View#post方法被添加到队列尾部,保证了在layout结束以后才执行。

再比如对View的隐藏和显示做动画处理时,类似于百度外卖中顶部标题栏弹出的筛选控件View,如果直接通过设置View的Visibility属性,当从隐藏状态切换到显示状态时,动画可以正常实现;但当从显示状态切换到隐藏状态时,动画效果无法展现,而是一瞬间就隐藏了 。

 private void showOrHideLayout() {

        if (layoutView.getVisibility() == View.GONE) {//隐藏-->进行显示
            layoutView.setVisibility(View.VISIBLE);   
            layoutView.animate().translationY(0).setDuration(300);
        } else {//显示-->进行隐藏
            layoutView.setVisibility(View.GONE);
            layoutView.animate().translationY(-250).setDuration(300);
        }
    }

这里写图片描述

 private void showOrHideLayout() {

        if (layoutView.getVisibility() == View.GONE) {//隐藏-->进行显示
            layoutView.setVisibility(View.VISIBLE);   
            layoutView.animate().translationY(0).setDuration(300);
        } else {//显示-->进行隐藏
            layoutView.postDelayed(new Runnable() {
                @Override
                public void run() {
                    layoutView.setVisibility(View.GONE);
                }
            }, 300);
            layoutView.animate().translationY(-250).setDuration(300);
        }
    }

这里写图片描述

原因在于将view的可见性从GONE设置为VISIBLE时会申请requestLayout,而layout是异步执行的,所以setVisibility方法返回了但是该操作还未完成。前面说了post方法可以保证新任务是在layout调用过后执行。因此调用View.post()既方便又可以保证指定的任务在视图操作中顺序执行。

3)AsyncTask

Android 提供了AsyncTask这么一个专门用来处理后台进程与UI线程的工具。通过AsyncTask,我们可以非常方便的进行后台线程和UI线程之间的交流。简单整理下它的基本用法:

AsyncTask拥有3个重要参数

  • 1、Params——后台线程所需的参数

  • 2、Progress——后台线程处理作业的进度

  • 3、Result——后台线程运行的结果,也就是需要提交给UI线程的信息

AsyncTask还拥有4个回调方法

  • 1、onPreExecute——运行在UI线程,主要目的是为后台线程的运行做准备。

  • 2、doInBackground——运行在后台线程,他用来负责运行任务。他拥有参数Params,并且返回Result。

  • 3、onProgressUpdate——运行在UI线程,主要目的是用来更新UI线程中显示进度的UI控件。

  • 4、onPostExecute——运行在UI线程,doInBackground后被调用,在此方法中,就可以将Result更新到UI控件上。

明白了上面的3个参数和4个方法,你要做的就是

1、编写一个继承AsyncTask的类,并声明3个参数的类型,编写4个回调方法的内容。

2、然后在UI线程中创建该类(必须在UI线程中创建)。

3、最后调用AsyncTask的execute方法,传入Parmas参数(同样必须在UI线程中调用)。

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {//在这里声明了Params、Progress、Result参数的类型

        //准备数据
        protected void onPreExecute() {}

        //在后台线程根据URL下载数据
        protected Long doInBackground(URL... urls) {
            int count = urls.length;//urls是数组,不止一个下载链接
            long totalSize = 0;//下载的数据
            for (int i = 0; i < count; i++) {
                //Download是用于下载的一个类
                totalSize += Downloader.downloadFile(urls[i]);
                publishProgress((int) ((i / (float) count) * 100));//更新下载的进度
            }
            return totalSize;
        }

        //更新下载进度
        protected void onProgressUpdate(Integer... progress) {
            setProgressPercent(progress[0]);
        }

        //将下载的数据更新到UI线程
        protected void onPostExecute(Long result) {
            showDialog("Downloaded " + result + " bytes");
        }
    }
4)Handler.Post(Runnabe)和Handler.PostDelayed(Runnabe,long)

Handler.Post(Runnabe)

new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("线程开始运行");//执行耗时操作

            Runnable runnable = new Runnable() {
                @Override
                public void run() {// 运行在UI线程里面的,可进行UI操作
                    System.out.println("执行runnable代码的线程:"+Thread.currentThread().getName());
                }
            };
            //上面代码中的runnable线程体经过post后会直接传送到主线程中执行修改字体的操作。
            //post直接可以把一段代码当做变量一样传递,但是请不要传送耗时操作的代码到主线程中
            handler.post(runnable);
        }
    }).start();

Handler.PostDelayed(Runnabe,long)
long参数用于制定多少时间后运行后台进程
应用:实现一个定时操作

    Handler handler=new Handler();  
    Runnable runnable=new Runnable(){  
       @Override  
       public void run() {  
        // TODO Auto-generated method stub  
        //要做的事情,这里再次调用此Runnable对象,以实现每两秒实现一次的定时器操作  
        handler.postDelayed(this, 2000);  
       }   
    };  
    使用PostDelayed方法,两秒后调用此Runnable对象  
    handler.postDelayed(runnable, 2000);  // 注意: 这种线程间交互的方式,首先开辟的子线程首先实际上也就实现了一个2s的一个定时器  

    如果想要关闭此定时器,可以这样操作  
    handler.removeCallbacks(runnable);        

总结

以上方法各有优点。但是究其根本,其实后面三种方法都是基于handler方法的包装,根据情况选择使用。但是当情形比较复杂时,还是推荐使用handler。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值