美文网首页
Handler机制

Handler机制

作者: 程序农猿 | 来源:发表于2022-04-22 10:17 被阅读0次

    Android消息循环流程图如下所示: 

    主要涉及的角色如下所示:

    message:消息体,用于装载需要发送的对象。

    MessageQueue:消息队列,负责消息的存储与管理,负责管理由 Handler 发送过来的 Message。读取会自动删除消息,单链表维护,在插入和删除上有优势。无限循环判断next()方法中是否有消息,有就返回这条消息并移除。

    Looper:消息循环器,负责关联线程以及消息的分发,在该线程下从 MessageQueue获取 Message,分发给Handler,Looper创建的时候会创建一个MessageQueue,调用loop()方法的时候消息循环开始,其中会不断调用messageQueue的next()方法,当有消息就处理,否则阻塞在messageQueue的next()方法中。当Looper的quit()被调用的时候会调用messageQueue的quit(),此时next()会返回null,然后loop()方法也就跟着退出。

    Handler:消息处理器,负责发送并处理消息,面向开发者,提供 API,并隐藏背后实现的细节。

    整个消息的循环流程还是比较清晰的,具体说来:

    1、Handler通过sendMessage()发送消息Message到消息队列MessageQueue。

    2、Looper通过loop()不断提取触发条件的Message,并将Message交给对应的target handler来处理。

    3、target handler调用自身的handleMessage()方法来处理Message。

    事实上,在整个消息循环的流程中,并不只有Java层参与,很多重要的工作都是在C++层来完成的。我们来看下这些类的调用关系。

    注:虚线表示关联关系,实线表示调用关系。

    在这些类中MessageQueue是Java层与C++层维系的桥梁,MessageQueue与Looper相关功能都通过MessageQueue的Native方法来完成,而其他虚线连接的类只有关联关系,并没有直接调用的关系,它们发生关联的桥梁是MessageQueue。

    总结

    Handler 发送的消息由 MessageQueue 存储管理,并由 Looper 负责回调消息到 handleMessage()。

    线程的转换由 Looper 完成,handleMessage() 所在线程由 Looper.loop() 调用者所在线程决定。

    Handler 引起的内存泄露原因以及最佳解决方案

    Handler 允许我们发送延时消息,如果在延时期间用户关闭了 Activity,那么该 Activity 会泄露。 这个泄露是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露。

    解决:将 Handler 定义成静态的内部类,在内部持有 Activity 的弱引用,并在Acitivity的onDestroy()中调用handler.removeCallbacksAndMessages(null)及时移除所有消息。

    为什么我们能在主线程直接使用 Handler,而不需要创建 Looper ?

    通常我们认为 ActivityThread 就是主线程。事实上它并不是一个线程,而是主线程操作的管理者。在 ActivityThread.main() 方法中调用了 Looper.prepareMainLooper() 方法创建了 主线程的 Looper ,并且调用了 loop() 方法,所以我们就可以直接使用 Handler 了。

    因此我们可以利用 Callback 这个拦截机制来拦截 Handler 的消息。如大部分插件化框架中Hook ActivityThread.mH 的处理。

    主线程的 Looper 不允许退出

    主线程不允许退出,退出就意味 APP 要挂。

    Handler 里藏着的 Callback 能干什么?

    Handler.Callback 有优先处理消息的权利 ,当一条消息被 Callback 处理并拦截(返回 true),那么 Handler 的 handleMessage(msg) 方法就不会被调用了;如果 Callback 处理了消息,但是并没有拦截,那么就意味着一个消息可以同时被 Callback 以及 Handler 处理。

    创建 Message 实例的最佳方式

    为了节省开销,Android 给 Message 设计了回收机制,所以我们在使用的时候尽量复用 Message ,减少内存消耗:

    通过 Message 的静态方法 Message.obtain();

    通过 Handler 的公有方法 handler.obtainMessage()。

    子线程里弹 Toast 的正确姿势

    本质上是因为 Toast 的实现依赖于 Handler,按子线程使用 Handler 的要求修改即可,同理的还有 Dialog。

    妙用 Looper 机制

    将 Runnable post 到主线程执行;

    利用 Looper 判断当前线程是否是主线程。

    主线程的死循环一直运行是不是特别消耗CPU资源呢?

    并不是,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质是同步I/O,即读写是阻塞的。所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

    handler postDelay这个延迟是怎么实现的?

    handler.postDelay并不是先等待一定的时间再放入到MessageQueue中,而是直接进入MessageQueue,以MessageQueue的时间顺序排列和唤醒的方式结合实现的。

    一、handler的引入:

    我们都知道,Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃。相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一个Message对象,然后借助Handler发送出去,之后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作就不会再出现崩溃了。具体实现代码如下:

    package com.example.androidthreadtest; 

    import android.app.Activity; 

    import android.os.Bundle; 

    import android.os.Handler; 

    import android.os.Message; 

    import android.view.View; 

    import android.view.View.OnClickListener; 

    import android.widget.Button;

    import android.widget.TextView;

    public class MainActivity extends Activity implements OnClickListener {

    public static final int UPDATE_TEXT = 1;

    private TextView text;

    private Button changeText;

    private Handler handler =new Handler() {

    public void handleMessage(Message msg) {

    switch (msg.what) {

    case UPDATE_TEXT:

    text.setText("Nice to meet you");

    break;

    default:

    break;

     }

            }

        };

        @Override

    protectedvoid onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

            setContentView(R.layout.activity_main);

    text = (TextView) findViewById(R.id.text);

    changeText = (Button) findViewById(R.id.change_text);

    changeText.setOnClickListener(this);

        }

       @Override

    publicvoid onClick(View v) {

    switch (v.getId()) {

    case R.id.change_text:

    newThread(new Runnable() {

                  @Override

    publicvoid run() {

    Message message =new Message();

    message.what = UPDATE_TEXT;

                        handler.sendMessage(message);

                   }

               }).start();

    break;

    default:

    break;

            }

        }

    }

    上方第45行代码,官方建议我们写成:(这样的话,可以由系统自己负责message的创建和销毁)

    Message msg = handler.obtainMessage();

    或者写成:

    Message msg = Message.obtain();

    上面的代码中,我们并没有在子线程中直接进行UI操作,而是创建了一个Message对象,并将它的what字段的值指定为了一个整形常量UPDATE_TEXT,用于表示更新TextView这个动作。然后调用Handler的sendMessage()方法将这条Message发送出去。很快,Handler就会收到这条Message,并在handleMessage()方法,在这里对具体的Message进行处理(需要注意的是,此时handleMessage()方法中的代码是在主线程中运行的)。如果发现Message的what字段的值等于UPDATE_TEXT,就将TextView显示的内容更新。运行程序后,点击按钮,TextView就会显示出更新的内容。

     注:如果从源码的角度理解,粗略的描述是这样的:

    先是调用了handler的obtainMessage()方法得到Message对象。在obtainMessage()方法里做的事情是:调用了Message.obtain(this)方法,把handler作为对象传进来。在Message.obtain(this)方法里做的事情是:生成message对象,把handler作为参数赋值给message的target属性。总的来说,一个Handler对应一个Looper对象,一个Looper对应一个MessageQueue对象,使用Handler生成Message,所生成的Message对象的Target属性,就是该对象。而一个Handler可以生成多个Message,所以说,Handler和Message是一对多的关系。

    二、异步消息处理机制:

    Handler是Android类库提供的用于接受、传递和处理消息或Runnable对象的处理类,它结合Message、MessageQueue和Looper类以及当前线程实现了一个消息循环机制,用于实现任务的异步加载和处理。整个异步消息处理流程的示意图如下图所示:

    根据上面的图片,我们现在来解析一下异步消息处理机制

    Message:消息体,用于装载需要发送的对象。

    handler:它直接继承自Object。作用是:在子线程中发送Message或者Runnable对象到MessageQueue中;在UI线程中接收、处理从MessageQueue分发出来的Message或者Runnable对象。发送消息一般使用Handler的sendMessage()方法,而发出去的消息经过处理后最终会传递到Handler的handlerMessage()方法中。

    MessageQueue:用于存放Message或Runnable对象的消息队列。它由对应的Looper对象创建,并由Looper对象管理。每个线程中都只会有一个MessageQueue对象。

    Looper:是每个线程中的MessageQueue的管家,循环不断地管理MessageQueue接收和分发Message或Runnable的工作。调用Looper的loop()方法后,就会进入到一个无限循环中然后每当发现MessageQueue中存在一条消息,就会将它取出,并调用Handler的handlerMessage()方法。每个线程中也只会有一个Looper对象。

    了解这些之后,我们在来看一下他们之间的联系

    首先要明白的是,Handler和Looper对象是属于线程内部的数据,不过也提供与外部线程的访问接口,Handler就是公开给外部线程的接口,用于线程间的通信。Looper是由系统支持的用于创建和管理MessageQueue的依附于一个线程的循环处理对象,而Handler是用于操作线程内部的消息队列的,所以Handler也必须依附一个线程,而且只能是一个线程。

    我们再来对异步消息处理的整个流程梳理一遍:

    当应用程序开启时,系统会自动为UI线程创建一个MessageQueue(消息队列)和Looper循环处理对象。首先需要在主线程中创建一个Handler对象,并重写handlerMessage()方法。然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handler将这条消息发送出去。之后这条消息就会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,并找到与消息对象对应的Handler对象,然后调用Handler的handleMessage()方法。由于Handler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行UI操作了。

    通俗地来讲,一般我们在实际的开发过程中用的比较多一种情况的就是主线程的Handler将子线程中处理过的耗时操作的结果封装成Message(消息),并将该Message(利用主线程里的MessageQueue和Looper)传递到主线程中,最后主线程再根据传递过来的结果进行相关的UI元素的更新,从而实现任务的异步加载和处理,并达到线程间的通信。

    通过上一小节对Handler的一个初步认识后,我们可以很容易总结出Handler的主要用途,下面是Android官网总结的关于Handler类的两个主要用途:

    (1)执行定时任务:

    指定任务时间,在某个具体时间或某个时间段后执行特定的任务操作,例如使用Handler提供的postDelayed(Runnable r,long delayMillis)方法指定在多久后执行某项操作,比如当当、淘宝、京东和微信等手机客户端的开启界面功能,都是通过Handler定时任务来完成的。

    我们接下来讲一下post。 

    (2)线程间的通信:

    在执行较为耗时的操作时,Handler负责将子线程中执行的操作的结果传递到UI线程,然后UI线程再根据传递过来的结果进行相关UI元素的更新。(上面已有说明)

    三、post:

    对于Handler的Post方式来说,它会传递一个Runnable对象到消息队列中(这句话稍后会进行详细解释),在这个Runnable对象中,重写run()方法。一般在这个run()方法中写入需要在UI线程上的操作。

    Post允许把一个Runnable对象入队到消息队列中。它的方法有:post(Runnable)、postAtTime(Runnable,long)、postDelayed(Runnable,long)。详细解释如下:

    boolean post(Runnable r):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,立即执行。

    boolean postAtTime(Runnable r,long uptimeMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,在特定的时间执行。

    boolean postDelayed(Runnable r,long delayMillis):把一个Runnable入队到消息队列中,UI线程从消息队列中取出这个对象后,延迟delayMills秒执行

    void removeCallbacks(Runnable r):从消息队列中移除一个Runnable对象。

    下面通过一个Demo,讲解如何通过Handler的post方式在新启动的线程中修改UI组件的属性

    package com.example.m03_threadtest01;

    import android.app.Activity;

    import android.os.Bundle; 

    import android.os.Handler;

    import android.view.View;

    import android.widget.Button;

    import android.widget.TextView;

    public class MainActivity extends Activity {

    private Button btnMes1,btnMes2;

    private TextView tvMessage;

    // 声明一个Handler对象

    private static Handler handler=new Handler(); 

    @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

            setContentView(R.layout.activity_main);   

    btnMes1=(Button)findViewById(R.id.button1);

    btnMes2=(Button)findViewById(R.id.button2);

    tvMessage=(TextView)findViewById(R.id.TextView1);

    btnMes1.setOnClickListener(new View.OnClickListener() {

    @Override

    public void onClick(View v) {

    // 新启动一个子线程

    new Thread(new Runnable() {                   

    @Override

    public void run() {

    // tvMessage.setText("...");

    // 以上操作会报错,无法再子线程中访问UI组件,UI组件的属性必须在UI线程中访问

    // 使用post方式修改UI组件tvMessage的Text属性

    handler.post(new Runnable() {                    3

    @Override

    public void run() {

    tvMessage.setText("使用Handler.post在工作线程中发送一段执行到消息队列中,在主线程中执行。");                       

    }

    });                               

    }

    }).start();

    }

    });

    btnMes2.setOnClickListener(new View.OnClickListener() {

    @Override

    public void onClick(View v) {

    new Thread(new Runnable() {                   

    @Override

    public void run() {

    // 使用postDelayed方式修改UI组件tvMessage的Text属性值

    // 并且延迟3S执行

    handler.postDelayed(new Runnable() {

    @Override

    public void run() {

    tvMessage.setText("使用Handler.postDelayed在工作线程中发送一段执行到消息队列中,在主线程中延迟3S执行。");   

    }

    }, 3000);

    }).start();

    }

    });

    }}

    点击按钮,运行结果如下:

    有一点值得注意的是:对于Post方式而言,它其中Runnable对象的run()方法的代码(37行至39行或者58至61行),均运行在主线程上(虽然看上去是写在子线程当中的),如果我们在这段代码里打印日志输出线程的名字,会发现输出的是Main Thread的名字。所以对于这段代码而言,不能执行在UI线程上的操作,一样无法使用post方式执行,比如说访问网络。

    我们现在来解释一下上面蓝色字体的那句话:

    这个Runnable对象被放到了消息队列当中去了,然后主线程中的Looper(因为Handler是在主线程中生成的,所以Looper也在主线程中)将这个Runnable对象从消息队列中取出来,取出来之后,做了些什么呢?为什么在执行Post的Runnable对象的run()方法时,不是重新开启一个线程呢?要了解这个过程,只能求助Android的源代码:

    打开源码的目录sdk\sources\android-19\android\os,并找到Handler.java这个文件。找到post方法:

    public final boolean post(Runnable r)

        {

          return sendMessageDelayed(getPostMessage(r), 0);

        }

    上方的代码中, 可以看到,post方法其实就一行代码(326行),里面调用了sendMessageDelayed()这个方法,里面有两个参数。先看一下第一个参数getPostMessage(r):(719行)

    privatestatic Message getPostMessage(Runnable r) {

            Message m = Message.obtain();

            m.callback = r;

            return m;

        }

    上方的代码中,将Runnable对象赋值给Message的callback属性。注:通过查看Message.java文件的源代码发现,callback属性是一个Runnable对象:(91行)

    /*package*/Runnable callback;

    我们再来分析一下上方getPostMessage()这个方法,该方法完成了两个操作:

    一是生成了一个Message对象,二是将r对象复制给Message对象的callback属性。返回的是一个Message对象。

    再回到326行:

    publicfinalboolean post(Runnable r)

        {

          returnsendMessageDelayed(getPostMessage(r), 0);

        } 

    这行代码相当于:

    publicfinalboolean post(Runnable r)

    {

        Message msg = getPostMessage(r);

        return sendMessage(msg);////如果需要延时的话,这一行可以改为return sendMessageDelayed(msg,0);其中第二个参数改为具体的延时时间}

    现在应该好理解了:

    第一个问题,如何把一个Runnable对象放到消息队列中:实际上是生成了一个Message对象,并将r赋值给Message对象的callback属性,然后再将Message对象放置到消息队列当中。

    我们再看看一下Looper做了什么。打开Looper.java的dispatchMessage的方法:(136行)

    //一个Handler对应一个Looper对象,一个Looper对应一个MessageQueue对象,

                //使用Handler生成Message,所生成的Message对象的Target属性,就是该对象   

                //Message msg = handler.obtainMessage();

                //发送一个message对象

                //handler.sendMessage(msg);

    msg.target.dispatchMessage(msg); 

    这里面调用了dispatchMessage()方法,打开Handler.java的dispatchMessage()方法:(93至104行)

     Handle system messages here. 

    public void dispatchMessage(Message msg) { 

    if(msg.callback !=null) { 

                handleCallback(msg); 

    }else { 

            if(mCallback !=null) { 

                    if (mCallback.handleMessage(msg)) {

                            return;

                     }

              }

               handleMessage(msg);

       }

    上方第5行代码:因为这次已经给Message的callback属性赋值了,所以就不为空,直接执行这行代码。即执行handleCallBack()这个方法。打开handleCallBack()方法的源码:(732至734行) 

    privatestaticvoid handleCallback(Message message) {

            message.callback.run();

        } 

    看到这个方法,就真相大白了:message的callback属性直接调用了run()方法,而不是开启一个新的子线程。

    现在可以明白了:

    第二个问题: Looper取出了携带有r对象的Message对象以后,做的事情是:取出Message对象之后,调用了dispatchMessage()方法,然后判断Message的callback属性是否为空,此时的callback属性是有值的,所以执行了handleCallback(Message message),在该方法中执行了 message.callback.run()。根据Java的线程知识,我们可以知道,如果直接调用Thread对象或者Runnable对象的run()方法,是不会开辟新线程的,而是在原有的线程中执行。

    因为Looper是在主线程当中的,所以dispatchMessage()方法和handleMessage()方法也都是在主线程当中运行。所以post()里面的run方法也自然是在主线程当中运行的。 使用Post()方法的好处在于:避免了在主线程和子线程中将数据传来传去的麻烦。

    四、Message:

    Handler如果使用sendMessage的方式把消息入队到消息队列中,需要传递一个Message对象,而在Handler中,需要重写handleMessage()方法,用于获取工作线程传递过来的消息,此方法运行在UI线程上。

    对于Message对象,一般并不推荐直接使用它的构造方法得到,而是建议通过使用Message.obtain()这个静态的方法或者Handler.obtainMessage()获取。Message.obtain()会从消息池中获取一个Message对象,如果消息池中是空的,才会使用构造方法实例化一个新Message,这样有利于消息资源的利用。并不需要担心消息池中的消息过多,它是有上限的,上限为10个。Handler.obtainMessage()具有多个重载方法,如果查看源码,会发现其实Handler.obtainMessage()在内部也是调用的Message.obtain()。

    Handler中,与Message发送消息相关的方法有:

    Message obtainMessage():获取一个Message对象。

    boolean sendMessage():发送一个Message对象到消息队列中,并在UI线程取到消息后,立即执行。

    boolean sendMessageDelayed():发送一个Message对象到消息队列中,在UI线程取到消息后,延迟执行。

    boolean sendEmptyMessage(int what):发送一个空的Message对象到队列中,并在UI线程取到消息后,立即执行。

    boolean sendEmptyMessageDelayed(int what,long delayMillis):发送一个空Message对象到消息队列中,在UI线程取到消息后,延迟执行。

    void removeMessage():从消息队列中移除一个未响应的消息。

    五、通过Handler实现线程间通信:

    1、在Worker Thread发送消息,在MainThread中接收消息:

    【实例】点击按扭,将下方的TextView的内容修改为“从网络中获取的数据”

    need-to-insert-img

    【实际意义】点击按钮时,程序访问服务器,服务器接到请求之后,会返回字符串结果,然后更新到程序。

    完整版代码如下:

    XML布局文件代码如下:

    <RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"

       android:paddingLeft="@dimen/activity_horizontal_margin"  

      android:paddingRight="@dimen/activity_horizontal_margin"  

      android:paddingTop="@dimen/activity_vertical_margin"   

     tools:context=".MainActivity">

    <TextView

          android:id="@+id/TextViewId"    

      android:layout_width="match_parent"   

       android:layout_height="wrap_content"    

      android:text="数据"/>

    <Button

            android:id="@+id/ButtonId"   

         android:layout_width="match_parent"  

          android:layout_height="wrap_content" 

           android:text="发送消息"

            android:layout_below="@id/TextViewId"/>

    </RelativeLayout> 

    MainActivity.java代码如下:

    package com.example.test0207_handler; 

    import android.app.Activity; 

    import android.os.Bundle; 

    import android.os.Handler; 

    import android.os.Message; 

    import android.view.Menu; 

    import android.view.View; 

    import android.view.View.OnClickListener;

    import android.widget.Button;

    import android.widget.TextView;

    public class MainActivity extends Activity { 

    private TextView textView ; private Button button ;

    private Handler handler ;

        @Override

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

           setContentView(R.layout.activity_main)

    textView = (TextView)findViewById(R.id.TextViewId) ;

    button = (Button)findViewById(R.id.ButtonId) ;    

    handler =new MyHandler() ;

    button.setOnClickListener(new ButtonListener()) ;

        }

    //在MainAthread线程中接收数据,从而修改TextView的值

    class MyHandler extends Handler {

           @Override

    public void handleMessage(Message msg) {

    System.out.println("handleMessage--->"+Thread.currentThread().getName()) ;//得到当前线程的名字

    String s = (String)msg.obj ;

                textView.setText(s) ;

            }

        }

    //生成线程对象,让NetworkThread线程启动

    class ButtonListener implements OnClickListener {

            @Override        

    public void onClick(View arg0) {

    Thread t =new NetworkThread() ;

                t.start();

            }

        }

    //在Worker Thread线程中发送数据

    class NetworkThread extends Thread {

            @Override 

    public void run(){

    System.out.println("network--->"+Thread.currentThread().getName()) ;//得到当前线程的名字

    //模拟访问网络:当线程运行时,首先休眠2秒钟

    try {

    Thread.sleep(2*1000) ;

    }catch (InterruptedException e) {

                    e.printStackTrace();

                }//变量s的值,模拟从网络当中获取的数据

    String s = "从网络中获取的数据" ;

    //textView.setText(s) ; //这种做法是错误的,只有在Mainthread中才能操作UI            

    //开始发送消息

    Message msg = handler.obtainMessage() ;    

    msg.obj = s ;

    handler.sendMessage(msg) ;//sendMessage()方法,在主线程或者Worker Thread线程中发送,都是可以的,都可以被取到

           }

        }    

        @Override

    public boolean onCreateOptionsMenu(Menu menu) {

    // Inflate the menu; this adds items to the action bar if it is present.

           getMenuInflater().inflate(R.menu.main, menu);

    return true;

        }

    }

     这段代码的结构,和最上面的第一章节是一样的。

    上方代码中,我们在子线程中休眠2秒来模拟访问网络的操作。

    65行:用字符串s表示从网络中获取的数据;70行:然后我们把这个字符串放在Message的obj属性当中发送出去,并在主线程中接收(36行)。

    运行后结果如下:

    点击按钮后结果如下:

    need-to-insert-img

    点击按钮后,后台输出结果如下:

    need-to-insert-img

    可以看到,子线程的名字是:Thread-1118,主线程的名字是:main。

    2、在MainThread中发送消息,在Worker Thread中接收消息:

    【实例】点击按钮,在在MainThread中发送消息,在Worker Thread中接收消息,并在后台打印输出。

    【代码】完整版代码如下:

    XML布局文件代码如下:

    <RelativeLayout 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:paddingBottom="@dimen/activity_vertical_margin"    android:paddingLeft="@dimen/activity_horizontal_margin"    android:paddingRight="@dimen/activity_horizontal_margin"    android:paddingTop="@dimen/activity_vertical_margin"    tools:context=".MainActivity"><Button

            android:id="@+id/ButtonId"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="在主线程中发送消息"/></RelativeLayout> 

    MainActivity.java代码如下:

    1package com.example.m03_handle01; 2 3import android.app.Activity; 4import android.os.Bundle; 5import android.os.Handler; 6import android.os.Looper; 7import android.os.Message; 8import android.util.Log; 9import android.view.Menu;10import android.view.View;11import android.view.View.OnClickListener;12import android.widget.Button;13publicclassMainActivityextends Activity {14private Button button ;15private Handler handler ;16    @Override17protectedvoid onCreate(Bundle savedInstanceState) {18super.onCreate(savedInstanceState);19        setContentView(R.layout.activity_main);2021button = (Button)findViewById(R.id.ButtonId) ;2223//当用户点击按钮时,发送Message的对象msg24button.setOnClickListener(newOnClickListener() {//使用匿名内部类为button绑定监听器2526            @Override27publicvoid onClick(View v) {28Log.i("onClick:", Thread.currentThread().getName());29Message msg = handler.obtainMessage() ;30                handler.sendMessage(msg) ;31}

    32        }) ;3334WorkerThread wt =new WorkerThread() ;35        wt.start() ;36    }3738//在WorkerThread生成handler39classWorkerThreadextends  Thread {40        @Override41publicvoid run() {42//准备Looper对象43            Looper.prepare() ;44//在WorkerThread当中生成一个Handler对象45handler =new Handler() {46                @Override47publicvoid handleMessage(Message msg) {48Log.i("handleMessage:", Thread.currentThread().getName());49Log.i("后台输出", "收到了消息对象");50                }51            };52//调用Looper的loop()方法之后,Looper对象将不断地从消息队列当中取出对象,然后调用handler的handleMessage()方法,处理该消息对象53//如果消息队列中没有对象,则该线程阻塞54Looper.loop() ;//通过Looper对象将消息取出来55        }5657    }585960    @Override61publicboolean onCreateOptionsMenu(Menu menu) {62// Inflate the menu; this adds items to the action bar if it is present.63        getMenuInflater().inflate(R.menu.main, menu);64returntrue;65    }6667} 

    上方的第42行至54行代码:这是MainThread中发送消息,在Worker Thread中接收消息的固定写法。上面的三个步骤再重复一下:

    准备Looper对象

    在WorkerThread当中生成一个Handler对象

    调用Looper的loop()方法之后,Looper对象将不断地从消息队列当中取出对象,然后调用handler的handleMessage()方法,处理该消息对象;如果消息队列中没有对象,则该线程阻塞

    注意,此时handleMessage()方法是在Worker Thread中运行的。

    运行程序后,当我们点击按钮,就会在后台输出“收到了消息对象”这句话:

    小小地总结一下:

    首先执行Looper的prepare()方法,这个方法有两个作用:一是生成Looper对象,二是把Looper对象和当前线程对象形成键值对(线程为键),存放在ThreadLocal当中,然后生成handler对象,调用Looper的myLooper()方法,得到与Handler所对应的Looper对象,这样的话,handler、looper 、消息队列就形成了一一对应的关系,然后执行上面的第三个步骤,即Looper在消息队列当中循环的取数据。

    Handler是如何实现延迟消息的,这是个老生常谈的问题了。

    这里我就带大家从源码的角度看看,并把handler各方面实现查漏补缺一下。

    handler核心的发送消息的方法是sendMessage,有的朋友会说那post呢?

    post的话其实算是一个handler的语法糖,传入runnable后帮助我们构建一个message。

    /**

        * Causes the Runnable r to be added to the message queue.

        * The runnable will be run on the thread to which this handler is

        * attached.

        * 

        * @param r The Runnable that will be executed.

        *

        * @return Returns true if the Runnable was successfully placed in to the

        *        message queue.  Returns false on failure, usually because the

        *        looper processing the message queue is exiting.

        */publicfinalbooleanpost(Runnabler){returnsendMessageDelayed(getPostMessage(r),0);}privatestaticMessagegetPostMessage(Runnabler){Messagem=Message.obtain();m.callback=r;returnm;}

    可以看到getPostMessage里帮我们构建出一个message然后再调用sendMessageDelayed。

    接下来看sendMessage,类似于startActivity最终都会走到startActivityForResult一样,handler所有发送消息的方法最终都会走到sendMessageDelayed,只是delayMillis不同而已,这个delayMillis就是延时的时间

    /**

        * Pushes a message onto the end of the message queue after all pending messages

        * before the current time. It will be received in {@link #handleMessage},

        * in the thread attached to this handler.

        * 

        * @return Returns true if the message was successfully placed in to the

        *        message queue.  Returns false on failure, usually because the

        *        looper processing the message queue is exiting.

        */publicfinalbooleansendMessage(Messagemsg){returnsendMessageDelayed(msg,0);}publicfinalbooleansendMessageDelayed(Messagemsg,longdelayMillis){if(delayMillis<0){delayMillis=0;}returnsendMessageAtTime(msg,SystemClock.uptimeMillis()+delayMillis);}

    然后这里会将DelayMillis加上当前开机的时间(这里可以理解就是这个time就是,现在的时间+需要延迟的时间=实际执行的时间),接下来进到sendMessageAtTime方法里面

    /**

        * Enqueue a message into the message queue after all pending messages

        * before the absolute time (in milliseconds) <var>uptimeMillis</var>.

        * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>

        * Time spent in deep sleep will add an additional delay to execution.

        * You will receive it in {@link #handleMessage}, in the thread attached

        * to this handler.

        *

        * @param uptimeMillis The absolute time at which the message should be

        *        delivered, using the

        *        {@link android.os.SystemClock#uptimeMillis} time-base.

        *       

        * @return Returns true if the message was successfully placed in to the

        *        message queue.  Returns false on failure, usually because the

        *        looper processing the message queue is exiting.  Note that a

        *        result of true does not mean the message will be processed -- if

        *        the looper is quit before the delivery time of the message

        *        occurs then the message will be dropped.

        */publicbooleansendMessageAtTime(Messagemsg,longuptimeMillis){MessageQueuequeue=mQueue;if(queue==null){RuntimeExceptione=newRuntimeException(this+" sendMessageAtTime() called with no mQueue");Log.w("Looper",e.getMessage(),e);returnfalse;}returnenqueueMessage(queue,msg,uptimeMillis);}privatebooleanenqueueMessage(MessageQueuequeue,Messagemsg,longuptimeMillis){msg.target=this;if(mAsynchronous){msg.setAsynchronous(true);}returnqueue.enqueueMessage(msg,uptimeMillis);}

    其实到这里handler的任务就完成了,把message发送到messageQueue里面,每个消息都会带有一个uptimeMillis参数,这就是延时的时间。

    接下来我们看messageQueue里面queue.enqueueMessage这个方法。

    booleanenqueueMessage(Messagemsg,longwhen){if(msg.target==null){thrownewIllegalArgumentException("Message must have a target.");}if(msg.isInUse()){thrownewIllegalStateException(msg+" This message is already in use.");}synchronized(this){if(mQuitting){IllegalStateExceptione=newIllegalStateException(msg.target+" sending message to a Handler on a dead thread");Log.w(TAG,e.getMessage(),e);msg.recycle();returnfalse;}msg.markInUse();msg.when=when;Messagep=mMessages;booleanneedWake;if(p==null||when==0||when<p.when){// New head, wake up the event queue if blocked.msg.next=p;mMessages=msg;needWake=mBlocked;}else{// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake=mBlocked&&p.target==null&&msg.isAsynchronous();Messageprev;for(;;){prev=p;p=p.next;if(p==null||when<p.when){break;}if(needWake&&p.isAsynchronous()){needWake=false;}}msg.next=p;// invariant: p == prev.nextprev.next=msg;}// We can assume mPtr != 0 because mQuitting is false.if(needWake){nativeWake(mPtr);}}returntrue;}

    首先是进行msg的一些属性判断,handler发出的target值必须不为空,是为了通过target值来判断是哪个handler发过来的消息的。

    顺便说一说 并不是所有的msg,target值都必须不为空

    (handler的同步屏障就是一个target为空的msg,用来优先执行异步方法的)

    同步屏障有一个很重要的使用场所就是接受垂直同步Vsync信号,用来刷新页面view的。因为为了保证view的流畅度,所以每次刷新信号到来的时候,要把其他的任务先放一放,优先刷新页面。

    接下来主要就是将这个msg根据实际执行时间进行排序插入到queue里面(看里面的for循环)。

    for(;;){prev=p;p=p.next;if(p==null||when<p.when){break;}if(needWake&&p.isAsynchronous()){needWake=false;}}

    好了,现在queue也构建完成了,假设我现在第一条消息就是要延迟10秒,怎么办呢。实际走一边咯。

    假设我现在是looper,我要遍历这个messageQueue,那肯定要调用next方法。

    next()方法比较长,我只贴关于延时消息的核心部分。

    for(;;){if(nextPollTimeoutMillis!=0){Binder.flushPendingCommands();}nativePollOnce(ptr,nextPollTimeoutMillis);synchronized(this){// Try to retrieve the next message.  Return if found.finallongnow=SystemClock.uptimeMillis();MessageprevMsg=null;Messagemsg=mMessages;if(msg!=null&&msg.target==null){// Stalled by a barrier.  Find the next asynchronous message in the queue.do{prevMsg=msg;msg=msg.next;}while(msg!=null&&!msg.isAsynchronous());}if(msg!=null){if(now<msg.when){// Next message is not ready.  Set a timeout to wake up when it is ready.nextPollTimeoutMillis=(int)Math.min(msg.when-now,Integer.MAX_VALUE);}………………以下省略

    可以看到这里也是一个for循环遍历队列,核心变量就是nextPollTimeoutMillis。可以看到,计算出nextPollTimeoutMillis后就调用nativiePollOnce这个native方法。这里的话大概可以猜到他的运行机制,因为他是根据执行时间进行排序的,那传入的这个nextPollTimeoutMillis应该就是休眠时间,类似于java的sleep(time)。休眠到下一次message的时候就执行。那如果我在这段时间又插入了一个新的message怎么办,所以handler每次插入message都会唤醒线程,重新计算插入后,再走一次这个休眠流程。

    nativiePollOnce这个native方法可以通过名字知道,他用的是linux中的epoll机制,具体是调用了epoll_wait这个方法。

    intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout);

    这个epoll和select一样都是linux的一个I/O多路复用机制,主要原理就不深入了,这里大概了解一下I/O多路复用机制和它与Select的区别就行。

    Linux里的I/O多路复用机制:举个例子就是我们钓鱼的时候,为了保证可以最短的时间钓到最多的鱼,我们同一时间摆放多个鱼竿,同时钓鱼。然后哪个鱼竿有鱼儿咬钩了,我们就把哪个鱼竿上面的鱼钓起来。这里就是把这些全部message放到这个机制里面,那个time到了,就执行那个message。

    epoll与select的区别:epoll获取事件的时候采用空间换时间的方式,类似与事件驱动,有哪个事件要执行,就通知epoll,所以获取的时间复杂度是O(1),select的话则是只知道有事件发生了,要通过O(n)的事件去轮询找到这个事件。

    作者:五月天外飞仙

    链接:https://www.jianshu.com/p/68083d432b3f

    来源:简书

    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    相关文章

      网友评论

          本文标题:Handler机制

          本文链接:https://www.haomeiwen.com/subject/powdrltx.html