• Android多线程----异步消息处理机制之Handler详解


    【声明】 

    欢迎转载,但请保留文章原始出处→_→ 

    生命壹号:http://www.cnblogs.com/smyhvae/

    文章来源:http://www.cnblogs.com/smyhvae/p/4003922.html

    联系方式:smyhvae@163.com 

    【正文】

    虽然是国庆佳节,但也不能停止学习的脚步,我选择在教研室为祖国母亲默默地庆生。

    关于Android的多线程知识,请参考本人之前的一篇博客:Android 多线程----AsyncTask异步任务详解

    在Android当中,提供了异步消息处理机制的两种方式来解决线程之间的通信问题,一种是今天要讲的Handler的机制,还有一种就是之前讲过的 AsyncTask 机制。

    一、handler的引入:

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

     1 package com.example.androidthreadtest;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.os.Handler;
     6 import android.os.Message;
     7 import android.view.View;
     8 import android.view.View.OnClickListener;
     9 import android.widget.Button;
    10 import android.widget.TextView;
    11 
    12 public class MainActivity extends Activity implements OnClickListener {
    13 
    14     public static final int UPDATE_TEXT = 1;
    15     private TextView text;
    16     private Button changeText;
    17     private Handler handler = new Handler() {
    18         public void handleMessage(Message msg) {
    19             switch (msg.what) {
    20             case UPDATE_TEXT:
    21                 text.setText("Nice to meet you");
    22                 break;
    23             default:
    24                 break;
    25             }
    26         }
    27     };
    28 
    29     @Override
    30     protected void onCreate(Bundle savedInstanceState) {
    31         super.onCreate(savedInstanceState);
    32         setContentView(R.layout.activity_main);
    33         text = (TextView) findViewById(R.id.text);
    34         changeText = (Button) findViewById(R.id.change_text);
    35         changeText.setOnClickListener(this);
    36     }
    37 
    38     @Override
    39     public void onClick(View v) {
    40         switch (v.getId()) {
    41         case R.id.change_text:
    42             new Thread(new Runnable() {
    43                 @Override
    44                 public void run() {
    45                     Message message = new Message();
    46                     message.what = UPDATE_TEXT;
    47                     handler.sendMessage(message);
    48                 }
    49             }).start();
    50             break;
    51         default:
    52             break;
    53         }
    54     }
    55 }

    上方第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组件的属性

     1 package com.example.m03_threadtest01;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.os.Handler;
     6 import android.view.View;
     7 import android.widget.Button;
     8 import android.widget.TextView;
     9 
    10 public class MainActivity extends Activity {
    11     private Button btnMes1,btnMes2;
    12     private TextView tvMessage;
    13     // 声明一个Handler对象
    14     private static Handler handler=new Handler();
    15     
    16     @Override
    17     protected void onCreate(Bundle savedInstanceState) {
    18         super.onCreate(savedInstanceState);
    19         setContentView(R.layout.activity_main);        
    20         
    21         btnMes1=(Button)findViewById(R.id.button1);
    22         btnMes2=(Button)findViewById(R.id.button2);
    23         tvMessage=(TextView)findViewById(R.id.TextView1);
    24         btnMes1.setOnClickListener(new View.OnClickListener() {
    25             
    26             @Override
    27             public void onClick(View v) {
    28                 // 新启动一个子线程
    29                 new Thread(new Runnable() {                    
    30                     @Override
    31                     public void run() {
    32                         // tvMessage.setText("...");
    33                         // 以上操作会报错,无法再子线程中访问UI组件,UI组件的属性必须在UI线程中访问
    34                         // 使用post方式修改UI组件tvMessage的Text属性
    35                         handler.post(new Runnable() {                    
    36                             @Override
    37                             public void run() {
    38                                 tvMessage.setText("使用Handler.post在工作线程中发送一段执行到消息队列中,在主线程中执行。");                        
    39                             }
    40                         });                                
    41                     }
    42                 }).start();
    43             }
    44         });
    45         
    46         btnMes2.setOnClickListener(new View.OnClickListener() {
    47             
    48             @Override
    49             public void onClick(View v) {
    50                 new Thread(new Runnable() {                    
    51                     @Override
    52                     public void run() {
    53                         // 使用postDelayed方式修改UI组件tvMessage的Text属性值
    54                         // 并且延迟3S执行
    55                         handler.postDelayed(new Runnable() {
    56                             
    57                             @Override
    58                             public void run() {
    59                                 tvMessage.setText("使用Handler.postDelayed在工作线程中发送一段执行到消息队列中,在主线程中延迟3S执行。");    
    60                                 
    61                             }
    62                         }, 3000);                        
    63                     }
    64                 }).start();
    65                 
    66             }
    67         });
    68     }
    69     
    70 }

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

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

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

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

    打开源码的目录sdksourcesandroid-19androidos,并找到Handler.java这个文件。找到post方法:

        public final boolean post(Runnable r)
        {
           return  sendMessageDelayed(getPostMessage(r), 0);
        }

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

        private static 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行:

        public final boolean post(Runnable r)
        {
           return  sendMessageDelayed(getPostMessage(r), 0);
        } 

    这行代码相当于:

    public final boolean 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行)

     1     /**
     2      * Handle system messages here.
     3      */
     4     public void dispatchMessage(Message msg) {
     5         if (msg.callback != null) {
     6             handleCallback(msg);
     7         } else {
     8             if (mCallback != null) {
     9                 if (mCallback.handleMessage(msg)) {
    10                     return;
    11                 }
    12             }
    13             handleMessage(msg);
    14         }
    15     } 

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

        private static void 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的内容修改为“从网络中获取的数据”

    【实际意义】点击按钮时,程序访问服务器,服务器接到请求之后,会返回字符串结果,然后更新到程序。
    完整版代码如下:
    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代码如下:

     1 package com.example.test0207_handler;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.os.Handler;
     6 import android.os.Message;
     7 import android.view.Menu;
     8 import android.view.View;
     9 import android.view.View.OnClickListener;
    10 import android.widget.Button;
    11 import android.widget.TextView;
    12 
    13 public class MainActivity extends Activity {
    14 
    15     private TextView textView ; 
    16     private Button button ;
    17     private Handler handler ;
    18     @Override
    19     protected void onCreate(Bundle savedInstanceState) {
    20         super.onCreate(savedInstanceState);
    21         setContentView(R.layout.activity_main);
    22         
    23         textView = (TextView)findViewById(R.id.TextViewId) ;
    24         button = (Button)findViewById(R.id.ButtonId) ;    
    25         
    26         handler = new MyHandler() ;
    27         
    28         button.setOnClickListener(new ButtonListener()) ;
    29         
    30     }
    31     //在MainAthread线程中接收数据,从而修改TextView的值
    32     class MyHandler extends Handler {
    33         @Override
    34         public void handleMessage(Message msg) {
    35             System.out.println("handleMessage--->"+Thread.currentThread().getName()) ;//得到当前线程的名字
    36             String s = (String)msg.obj ;
    37             textView.setText(s) ;
    38         }
    39         
    40     }
    41     //生成线程对象,让NetworkThread线程启动
    42     class ButtonListener implements OnClickListener {
    43         @Override        
    44         public void onClick(View arg0) {
    45             Thread t = new NetworkThread() ;
    46             t.start();
    47         }
    48         
    49     }
    50     
    51     //在Worker Thread线程中发送数据
    52     class NetworkThread extends Thread {
    53         @Override 
    54         public void run(){
    55             
    56             System.out.println("network--->"+Thread.currentThread().getName()) ;//得到当前线程的名字
    57             
    58             //模拟访问网络:当线程运行时,首先休眠2秒钟
    59             try {
    60                 Thread.sleep(2*1000) ;
    61             } catch (InterruptedException e) {
    62                 e.printStackTrace();
    63             }
    64             //变量s的值,模拟从网络当中获取的数据
    65             String s = "从网络中获取的数据" ;
    66             //textView.setText(s) ; //这种做法是错误的,只有在Mainthread中才能操作UI            
    67             
    68             //开始发送消息
    69             Message msg = handler.obtainMessage() ;    
    70             msg.obj = s ;
    71             handler.sendMessage(msg) ;//sendMessage()方法,在主线程或者Worker Thread线程中发送,都是可以的,都可以被取到
    72         }
    73     }    
    74     
    75     @Override
    76     public boolean onCreateOptionsMenu(Menu menu) {
    77         // Inflate the menu; this adds items to the action bar if it is present.
    78         getMenuInflater().inflate(R.menu.main, menu);
    79         return true;
    80     }
    81     
    82 }

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

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

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

    运行后结果如下:

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

     

    可以看到,子线程的名字是: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代码如下:
     1 package com.example.m03_handle01;
     2 
     3 import android.app.Activity;
     4 import android.os.Bundle;
     5 import android.os.Handler;
     6 import android.os.Looper;
     7 import android.os.Message;
     8 import android.util.Log;
     9 import android.view.Menu;
    10 import android.view.View;
    11 import android.view.View.OnClickListener;
    12 import android.widget.Button;
    13 public class MainActivity extends Activity {
    14     private Button button ;
    15     private Handler handler ;
    16     @Override
    17     protected void onCreate(Bundle savedInstanceState) {
    18         super.onCreate(savedInstanceState);
    19         setContentView(R.layout.activity_main);
    20         
    21         button = (Button)findViewById(R.id.ButtonId) ;
    22         
    23         //当用户点击按钮时,发送Message的对象msg
    24         button.setOnClickListener(new OnClickListener() {  //使用匿名内部类为button绑定监听器
    25             
    26             @Override
    27             public void onClick(View v) {
    28                 Log.i("onClick:", Thread.currentThread().getName());
    29                 Message msg = handler.obtainMessage() ;
    30                 handler.sendMessage(msg) ;
    31             }            
    32 }) ; 33 34 WorkerThread wt = new WorkerThread() ; 35 wt.start() ; 36 } 37 38 //在WorkerThread生成handler 39 class WorkerThread extends Thread { 40 @Override 41 public void run() { 42 //准备Looper对象 43 Looper.prepare() ; 44 //在WorkerThread当中生成一个Handler对象 45 handler = new Handler() { 46 @Override 47 public void handleMessage(Message msg) { 48 Log.i("handleMessage:", Thread.currentThread().getName()); 49 Log.i("后台输出", "收到了消息对象"); 50 } 51 }; 52 //调用Looper的loop()方法之后,Looper对象将不断地从消息队列当中取出对象,然后调用handler的handleMessage()方法,处理该消息对象 53 //如果消息队列中没有对象,则该线程阻塞 54 Looper.loop() ; //通过Looper对象将消息取出来 55 } 56 57 } 58 59 60 @Override 61 public boolean 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); 64 return true; 65 } 66 67 } 
    上方的第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在消息队列当中循环的取数据。
     
  • 相关阅读:
    更改hadoop native库文件后datanode故障
    解决 Unable to load native-hadoop library for your platform
    Windows 下Hadoop的环境变量配置
    在用VMware虚拟机的时候,有时会发现打开虚拟机时提示“该虚拟机似乎正在使用中。如果该虚拟机未在使用,请按“获取所有权(T)”按钮获取它的所有权。否则,请按“取消(C)”按钮以防损坏。配置文件: D:win10x64Windows 10 x64.vmx。”这是由于虚拟机未正常关闭引起的,下面看看解决办法
    Linux中chown和chmod的区别和用法(转)
    linux jps 命令
    org.apache.hadoop.yarn.exceptions.InvalidAuxServiceException: The auxService: mapreduce_shuffle do
    静默安装oracle 11g,环境预检查时报错,SEVERE: [FATAL] PRVF-0002 : 无法检索本地节点名
    windows,cmd中,如何切换到磁盘的根目录下
    windows,cmd中查看当前目录下的文件及文件夹
  • 原文地址:https://www.cnblogs.com/qianguyihao/p/4003922.html
Copyright © 2020-2023  润新知