首页 > 其他分享 >Handler和HandlerThread

Handler和HandlerThread

时间:2023-04-21 11:41:34浏览次数:30  
标签:HandlerThread Handler 线程 Looper UI Message 消息


1.什么是Handler?

SDK中关于Handler的说明如下:

A Handler allows you to sendand process Messageand Runnable objects associated with a thread's MessageQueue.Each Handler instance is associated with a single thread and that thread'smessage queue. When you create a new Handler, it is bound to the thread /message queue of the thread that is creating it -- from that point on, it willdeliver messages and runnables to that message queue and execute them as theycome out of the message queue.

1.1Handler的作用

There are two main uses for aHandler: (1) to schedule messages and runnables to be executed as some point inthe future; and (2) to enqueue an action to be performed on a different threadthan your own.

1.1.1发送和处理消息

下面的代码对于我们来说又是多么的常见:

首先在Activity中创建一个继承自Handler的匿名内部类以及这个类的一个对象

Private MainHandler mMainHandler = new MainHandler();
private class MainHandler extends Handler {
    public voidhandleMessage(android.os.Message msg) {
        switch (msg.what) {
        case MSG_MAIN_HANDLER_TEST:
            Log.d(TAG, "MainHandler-->handleMessage-->thread id =" +     Thread.currentThread().getId());
            break;
        }
    }
 };

这样在Activity的其他地方就可以通过mMainHandler对象发送消息给Handler处理了

Message msg = mMainHandler.obtainMessage(MSG_MAIN_HANDLER_TEST);

mMainHandler.sendMessage(msg);

【疑问】这里的消息是怎样交给handler处理的,另外当有多个消息的时候处理的先后顺序呢?(下面会进行解答)

1.1.2处理runnables方法中的代码

除了上述的使用Handler发送以及处理消息外,handler还有一个作用就是处理传递给它的action对象,具体使用步骤示例:

1、在主线程中定义Handler对象

2、构造一个runnable对象,为该对象实现runnable方法。

3、在子线程中使用Handler对象post(runnable)对象.

 

handler.post这个函数的作用是把Runnable里面的run方法里面的这段代码发送到消息队列中,等待运行。
如果handler是以UI线程消息队列为参数构造的,那么是把run里面的代码发送到UI线程中,等待UI线程运行这段代码。
如果handler是以子线程线程消息队列为参数构造的,那么是把run里面的代码发送到子线程中,等待子线程运行这段代码。

 

Runnable 并不一定是新开一个线程,比如下面的代码中就是运行在UI主线程中的:

 

public class TestActivity extends Activity implementsOnClickListener {
/** Calledwhen the activity is first created. */
   
    private Button mBtnTest=null;
    private Handler myHandler=null;
@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
       
mBtnTest=(Button)findViewById(R.id.btn_test);
mBtnTest.setOnClickListener(this);
       
myHandler=new Handler();
    }
@Override
    public void onClick(View v) {
        //注意:此处UI线程被阻塞,因为myHandler是在UI线程中创建的
myHandler.post(new Runnable() {
           public void run() {
                long i=0;
                while(true){
                i++;
                }
              }
           });
    }
}

官方对这个方法的解释如下,注意其中的:“The runnable will be run on the userinterface thread. ”

 

booleanandroid.view.View .post(Runnable action)

Causes the Runnableto be added to the message queue. The runnable will be run on the userinterface thread.

Parameters:

action The Runnablethat will be executed.

Returns:

Returns true if theRunnable was successfully placed in to the message queue. Returns false onfailure, usually because the looper processing the message queue is exiting.

 

这里我们看代码 mHandler.post(new Runnable(){  好像是new 了一个 interface, 其实是new的一个实现Runnable的匿名内部类(Inner Anonymous Class),这是很简练的写法。

上面的代码可以看成是: new anonymousClass() implement interface{ [改写interface method]}

Runnable是一个接口,不是一个线程,一般线程会实现Runnable。所以如果我们使用匿名内部类是运行在UI主线程的,如果我们使用实现这个Runnable接口的线程类,则是运行在对应线程的。

【疑问】handler的消息发送与Post方法有什么区别呢?

     Handler对象消息发送sendXXXX相关方法如下,同时还有postXXXX相关方法, 这些和Win32中的道理基本一致,一个为发送后直接返回,一个为处理后才返回。

 

1.1.3线程间通信

    下面代码中红色标注处即为在MyThread这个线程中,通过UI线程中的handler向UI线程发送消息。

  定义一个线程

class MyThread extends Thread{
               Handler   mHandler;
               Boolean  boo;
               publicMyThread(Handler handler){
               mHandler = handler;
               }
               publicvoid setBoo(boolean b) {boo = b; }
               publidvoid run(){
                                          if(boo){
                                                         getWeatherInfo();//耗时操作
                                                                                   analyzing();//耗时操作
                                                                                   mHandler.post(newRunnable() {
                                                                                                              publicvoid run() {
                                       setWeather();//更新UI
                                        });//更新UI
                   boo = true;
              }
       }
}

 

在处理单击事件时:

sureButton.setOnClickListener(newButton.OnClickListener(){
                                          publicvoid onClick(View view){
                      setBoo(true);
                                                    myThread.start();
                                          }
                            });

在activity中:

   MyThread myThread = new MyThread(mHandler);

其中mHandler为在UI线程中创建的。

 

1.2职责与关系

熟悉Windows编程的朋友可能知道Windows程序是消息驱动的,并且有全局的消息循环系统。而Android应用程序也是消息驱动的,按道理来说也应该提供消息循环机制。实际上谷歌参考了Windows的消息循环机制,也在Android系统中实现了消息循环机制。 Android通过Looper、Handler来实现消息循环机制,Android消息循环是针对线程的(每个线程都可以有自己的消息队列和消息循环)。

什么是looper?下面就简单的介绍下各个对象的职责:

Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。

Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Messagemsg)方法来对特定的Message进行处理,例如更新UI等。

MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。

Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。

Thread:线程,负责调度整个消息循环,即消息循环的执行场所。

Handler,Looper和MessageQueue就是简单的三角关系。Looper和MessageQueue一一对应,创建一个 Looper的同时,会创建一个MessageQueue。而Handler与它们的关系,只是简单的聚集关系,即Handler里会引用当前线程里的特定Looper和MessageQueue。

这样说来,多个Handler都可以共享同一Looper和MessageQueue了。当然,这些Handler也就运行在同一个线程里。

【疑问】我们写的Activity中没有看见looper,也没有看见什么Thread去调度整个消息循环?

Activity是一个UI线程,运行于主线程中,Android系统在启动的时候会为Activity创建一个消息队列和消息循环(Looper),详细实现请参考ActivityThread.java文件。

1.3如何实现消息的发送与处理

接下来,我们简单地看一下消息的循环过程:

1.3.1消息的生成

 

Message msg =mHandler.obtainMessage();
       msg.what = what;
       msg.sendToTarget();

1.3.2消息的发送

   

MessageQueue queue= mQueue;
        if (queue != null){
            msg.target =this;
            sent =queue.enqueueMessage(msg, uptimeMillis);
        }

在Handler.java的sendMessageAtTime(Messagemsg, long uptimeMillis)方法中,我们看到,它找到它所引用的MessageQueue,然后将Message的target设定成自己(目的是为了在处理消息环节,Message能找到正确的Handler),再将这个Message纳入到消息队列中。

1.3.3消息的抽取

     

Looper me =myLooper();
        MessageQueue queue= me.mQueue;
        while (true) {
            Message msg =queue.next(); // might block
            if (msg !=null) {
                if(msg.target == null) {
                    // Notarget is a magic identifier for the quit message.
                   return;
                }
                msg.target.dispatchMessage(msg);
               msg.recycle();
            }
        }

在Looper.java的loop()函数里,我们看到,这里有一个死循环,不断地从MessageQueue中获取下一个(next方法)Message,然后通过Message中携带的target信息,交由正确的Handler处理(dispatchMessage方法)。

1.3.4消息的处理

  

if (msg.callback!= null) {
           handleCallback(msg);
        } else {
            if (mCallback!= null) {
                if(mCallback.handleMessage(msg)) {
                    return;
                }
            }
           handleMessage(msg);
        }

在Handler.java的dispatchMessage(Messagemsg)方法里,其中的一个分支就是调用handleMessage方法来处理这条Message,而这也正是我们在职责处描述使用Handler时需要实现handleMessage(Messagemsg)的原因。

至于dispatchMessage方法中的另外一个分支,我将会在后面的内容中说明。

至此,我们看到,一个Message经由Handler的发送,MessageQueue的入队,Looper的抽取,又再一次地回到Handler的怀抱。而绕的这一圈,也正好帮助我们将同步操作变成了异步操作。

1.4在工作线程(WorkerThread)中使用Handler

【疑问】上面介绍了如何在主线程中使用handler以及handler发送和处理消息的具体原理,并且SDK中已经提到可以在自定义的线程中使用相应的handler来处理消息,这个如何实现呢?

通用的作法是:

     

class LooperThreadextends Thread {
                publicHandler mHandler;
                   publicvoid run() {
                      Looper.prepare();
                       mHandler= new Handler() {
                           public void handleMessage(Message msg) {
                           // process incoming messages here
                            }
                        };
                       Looper.loop();   //不能在这个后面添加代码,程序是无法运行到这行之后的
                   }
      }

在创建Handler之前,为该线程准备好一个Looper(Looper.prepare),然后让这个Looper跑起来(Looper.loop),抽取Message,这样,Handler才能正常工作。

1.5不是所有的Handler都能更新UI

    Handler处理消息总是在创建Handler的线程里运行。而我们的消息处理中,不乏更新UI的操作,不正确的线程直接更新UI将引发异常。因此,需要时刻关心Handler在哪个线程里创建的。如何更新UI才能不出异常呢?SDK告诉我们,有以下4种方式可以从其它线程访问UI线程(也即线程间通信):

·      Activity.runOnUiThread(Runnable)

·      View.post(Runnable)

·      View.postDelayed(Runnable, long)

·      在UI线程中创建的Handler

其中,重点说一下的是View.post(Runnable)方法。在post(Runnableaction)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里。在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。在Handler再次处理该Message时,有一条分支(未解释的那条)就是为它所设,直接调用runnable的run方法。而此时,已经路由到UI线程里,因此,我们可以毫无顾虑的来更新UI。

几点小结

·      Handler的处理过程运行在创建Handler的线程里

·      一个Looper对应一个MessageQueue,一个线程对应一个Looper,一个Looper可以对应多个Handler

·      不确定当前线程时,更新UI时尽量调用View.post方法

·      handler应该由处理消息的线程创建。

·      handler与创建它的线程相关联,而且也只与创建它的线程相关联。handler运行在创建它的线程中,所以,如果在handler中进行耗时的操作,会阻塞创建它的线程。

·      Android的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper。主线程(UI线程)就是一个消息循环的线程。

·      Looper.myLooper();      //获得当前的Looper

        Looper.getMainLooper() //获得UI线程的Lopper

·      Handle的初始化函数(构造函数),如果没有参数,那么他就默认使用的是当前的Looper,如果有Looper参数,就是用对应的线程的Looper。

·      如果一个线程中调用Looper.prepare(),那么系统就会自动的为该线程建立一个消息队列,然后调用Looper.loop();之后就进入了消息循环,这个之后就可以发消息、取消息、和处理消息。

2.HandlerThread

在上面的总结中指出,Android的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper。事实上Android提供了一个封装好的带有looper的线程类,即为HandlerThread,具体可参见下面的代码:

public class HandlerThreadActivity extends Activity {
    private static final String TAG = "HandlerThreadActivity";
    private HandlerThreadmHandlerThread;
    private MyHandler mMyHandler;
@Override
    protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generatedmethod stub
       super.onCreate(savedInstanceState);
       TextView text = new TextView(this);
"HandlerThreadActivity");
       setContentView(text);
      
       Log.d(TAG, "The mainthread id = " +      Thread.currentThread().getId());
      
//生成一个HandlerThread对象,实现了使用Looper来处理消息队列的功能,
//这个类由Android应用程序框架提供
mHandlerThread = new HandlerThread("handler_thread");
      
//在使用HandlerThread的getLooper()方法之前,必须先调用该类的start();
mHandlerThread.start();
//即这个Handler是运行在mHandlerThread这个线程中
mMyHandler = new MyHandler(mHandlerThread.getLooper());
      
mMyHandler.sendEmptyMessage(1);
    }
   
    private class MyHandler extends Handler {
      
       public MyHandler(Looper looper) {
           super(looper);
       }
 
@Override
       public void handleMessage(Message msg) {
           Log.d(TAG, "MyHandler-->handleMessage-->threadid = " + Thread.currentThread().getId());
           super.handleMessage(msg);
       }
    }
   
}

标签:HandlerThread,Handler,线程,Looper,UI,Message,消息
From: https://blog.51cto.com/u_2198048/6212318

相关文章

  • Android Handler 知识点
    Android面试必问的Handler知识点一叶飘舟于 2020-12-0312:03:10 发布547 收藏 9 版权前言在Android中,Handler是贯穿于整个应用的消息机制,在面试中出现的概率为:100%在这篇文章里,我将带你梳理Handler的使用攻略&设计原理。追求简单易懂又不失深度......
  • @RestControllerAdvice注解 @ExceptionHandler注解
    RestControllerAdvice+ExceptionHandler这两个注解的组合,被用作项目的全局异常处理。一旦项目中发生了异常,就会进入使用了RestControllerAdvice注解类中使用了ExceptionHandler注解的方法。下面是一些项目全局异常的处理@ControllerAdvice(annotations={RestController.class,......
  • Revit二次开发的IExternalEventHandler
    看了revit二次开发书籍中关于IExternalEventHandler的用法,个人认为过于麻烦,且在实现外部事件并改变winform或wpf表格的内容时,代码的功能直接过于纠缠,作者根据wpf+prism框架重新写了一个外部事件的demoxaml语言如下<Windowx:Class="RevitDevFrame.Views.TestView"xmln......
  • 2023-04-14 uni-popup 报错:Error in config.errorHandler: "RangeError: Maximum call
    问题描述:首次导入uniapp的uni-popup,在项目中使用时报错,业务场景为:页面渲染完成后显示弹窗。报错:Errorinconfig.errorHandler:"RangeError:Maximumcallstacksizeexceeded"config.errorHandler中的错误:“RangeError:超出了最大调用堆栈大小”页面如下:<uni-popupref="l......
  • playbook核心元素之handlers、notify
    playbook核心元素之handlers、notifyCloud研习社 Cloud研习社 2023-04-1307:31 发表于山东收录于合集#一站式教程221个#linux211个#计算机186个#云计算198个#ansible23个教程每周二、四、六更新 我们上面的integration.yml写的有点随意,现在把它改一下:......
  • SetConsoleCtrlHandler 处理控制台消息
    SetConsoleCtrlHandler处理控制台消息一、如何处理所有的控制台消息。 第一步,首先要安装一个事件钩子,也就是说要建立一个回调函数。调用Win32API,原型如下:BOOLSetConsoleCtrlHandler(PHANDLER_ROUTINEHandlerRoutine,//回调函数BOOLAdd//表示添加还是删除);参......
  • 【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandl
    ExceptionHandler的作用ExceptionHandler是Spring框架提供的一个注解,用于处理应用程序中的异常。当应用程序中发生异常时,ExceptionHandler将优先地拦截异常并处理它,然后将处理结果返回到前端。该注解可用于类级别和方法级别,以捕获不同级别的异常。在Spring中使用ExceptionHandler非......
  • 【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandl
    ExceptionHandler的作用ExceptionHandler是Spring框架提供的一个注解,用于处理应用程序中的异常。当应用程序中发生异常时,ExceptionHandler将优先地拦截异常并处理它,然后将处理结果返回到前端。该注解可用于类级别和方法级别,以捕获不同级别的异常。在Spring中使用ExceptionHandle......
  • typeHandler
    一、什么是类型处理器1、类型处理器(TypeHandler)MyBatis在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都会用类型处理器将获取到的值以合适的方式转换成Java类型。也就是java类型与jdbc类型之间的处换器,诸如最常见StringTypeHandler。2、Mybatis内置了......
  • IIS 配置错误定义了重复的“system.web.extensions/scripting/scriptResourceHandler
    https://www.cnblogs.com/skylaugh/p/6376426.html我运行在iis中配置的那个网站后,报错:错误代码0x800700b7配置错误定义了重复的“system.web.extensions/scripting/scriptResourceHandler”节 这个问题原因在于window7的IIS默认用的是ASP.NETv4.0应用程序池。解决方法:把这......