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

一、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;}}};@Overrideprotected void 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);}@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.change_text:new Thread(new Runnable() {@Overridepublic void run() {Message message = new Message();//或者 Message message=handler.obtainMessage();message.what = UPDATE_TEXT;handler.sendMessage(message);}}).start();break;default:break;}}
}


上面的代码中,我们并没有在子线程中直接进行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();@Overrideprotected 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() {@Overridepublic void onClick(View v) {// 新启动一个子线程new Thread(new Runnable() {                    @Overridepublic void run() {// tvMessage.setText("...");// 以上操作会报错,无法再子线程中访问UI组件,UI组件的属性必须在UI线程中访问// 使用post方式修改UI组件tvMessage的Text属性handler.post(new Runnable() {                    @Overridepublic void run() {tvMessage.setText("使用Handler.post在工作线程中发送一段执行到消息队列中,在主线程中执行。");                        }});                                }}).start();}});btnMes2.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {new Thread(new Runnable() {                    @Overridepublic void run() {// 使用postDelayed方式修改UI组件tvMessage的Text属性值// 并且延迟3S执行handler.postDelayed(new Runnable() {@Overridepublic 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对象从消息队列中取出来,取出来之后,做了些什么呢?为什么在执行Pos的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行)

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

【实例】点击反扭,将下方的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" ><TextViewandroid:id="@+id/TextViewId"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="数据" />   <Buttonandroid:id="@+id/ButtonId"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="发送消息" android:layout_below="@id/TextViewId"/></RelativeLayout>
</pre><pre name="code" class="html">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 ;@Overrideprotected 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 {@Overridepublic 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线程中发送,都是可以的,都可以被取到}}    @Overridepublic 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行)。

运行后结果如下:


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

 

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

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

【代码】完整版代码如下:
XML布局文件代码如下:
</pre><pre name="code" class="html"><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" ><Buttonandroid:id="@+id/ButtonId"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="在主线程中发送消息" /></RelativeLayout>
</pre><pre name="code" class="html">import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {private Button button ;private Handler handler ;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);button = (Button)findViewById(R.id.ButtonId) ;//当用户点击按钮时,发送Message的对象msgbutton.setOnClickListener(new OnClickListener() {  //使用匿名内部类为button绑定监听器@Overridepublic void onClick(View v) {Log.i("onClick:", Thread.currentThread().getName());Message msg = handler.obtainMessage() ;handler.sendMessage(msg) ;}            }) ;WorkerThread wt = new WorkerThread() ;wt.start() ;}//在WorkerThread生成handlerclass WorkerThread extends  Thread {@Overridepublic void run() {//准备Looper对象Looper.prepare() ;//在WorkerThread当中生成一个Handler对象handler = new Handler() {@Overridepublic void handleMessage(Message msg) {Log.i("handleMessage:", Thread.currentThread().getName());Log.i("后台输出", "收到了消息对象");}};//调用Looper的loop()方法之后,Looper对象将不断地从消息队列当中取出对象,然后调用handler的handleMessage()方法,处理该消息对象//如果消息队列中没有对象,则该线程阻塞Looper.loop() ;   //通过Looper对象将消息取出来}}@Overridepublic 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行)。

运行后结果如下:


可以看到,子线程的名字是: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" ><Buttonandroid:id="@+id/ButtonId"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="在主线程中发送消息" /></RelativeLayout>


MainActivity.java代码如下:
package com.example.m03_handle01;import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity {private Button button ;private Handler handler ;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);button = (Button)findViewById(R.id.ButtonId) ;//当用户点击按钮时,发送Message的对象msgbutton.setOnClickListener(new OnClickListener() {  //使用匿名内部类为button绑定监听器@Overridepublic void onClick(View v) {Log.i("onClick:", Thread.currentThread().getName());Message msg = handler.obtainMessage() ;handler.sendMessage(msg) ;}            }) ;WorkerThread wt = new WorkerThread() ;wt.start() ;}//在WorkerThread生成handlerclass WorkerThread extends  Thread {@Overridepublic void run() {//准备Looper对象Looper.prepare() ;//在WorkerThread当中生成一个Handler对象handler = new Handler() {@Overridepublic void handleMessage(Message msg) {Log.i("handleMessage:", Thread.currentThread().getName());Log.i("后台输出", "收到了消息对象");}};//调用Looper的loop()方法之后,Looper对象将不断地从消息队列当中取出对象,然后调用handler的handleMessage()方法,处理该消息对象//如果消息队列中没有对象,则该线程阻塞Looper.loop() ;   //通过Looper对象将消息取出来}}@Overridepublic 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;}}

上方的第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在消息队列当中循环的取数据。

六、为什么在有些时候子线程中是可以直接更新UI的:

这道面试题应该是本文中最难的一个面试题了,需要好好理解。为了回答这个问题,我们需要先通过看源码去了解下面这三个问题:

(1)Android是如何检测非UI线程去更新UI的

(2)ViewRootImp是什么?

(3)ViewRootImp是在哪里创建的?


源码我就不贴出来了,这里我只是总结一下。

答案:

  非UI线程真的不能更新UI吗? 是可以的

解释:

  在线程中更新UI时会调用ViewParent.invalidateChild()方法检查当前的thread是否是Mainthread

具体源码如下:

final ViewParent p = mParent;if (p != null && ai != null && l < r && t < b) {final Rect damage = ai.mTmpInvalRect;damage.set(l, t, r, b);p.invalidateChild(this, damage);}

而ViewParent是一个接口类,其实现类是ViewRootImpl,通过查看invalidateChild()方法里面的代码就可以看到会他调用checkThread()方法。checkThread()方法如下:
void checkThread() {if (mThread != Thread.currentThread()) {   //检查更新UI的线程是否是MainThreadthrow new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");}}
 上面的第02行就是检查:在线程中更新UI时当前线程是否是MainThread。

但是,ViewRootImpl这个类是在activity的onResume()方法中创建。就算在子线程中更新UI,只要在ViewRootImpl创建之前更新UI(比如,程序在执行onCreate方法时,我就去执行setText方法区更新UI),就可以逃避掉checkThread()的检查。

关于本题,给出以下链接大家去细读一下源码吧:

Android更新Ui进阶精解(一):

http://www.jianshu.com/p/6de0a42a44d6

为什么我们可以在非UI线程中更新UI:

http://blog.csdn.net/aigestudio/article/details/43449123


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/295870.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

colorkey唇釉是否安全_colorkey空气唇釉,19/支

国货之光来喽 !!!!超火的colorkey空气唇釉给你们安排上 !!!R601 酒酿梅子超酷超性感的一支 !!这个颜色是偏调但是不显老的深草莓红 一点都不挑皮 谁涂谁白一个度&#xff5e;厚厚的涂一层气场值upup !R608 焦糖红棕偏橘棕调的红棕板栗 !!薄涂厚涂都显白、显气质&#xff01;味道…

初识Ildasm.exe——IL反编译的实用工具(转自Youngman)

Ildasm.exe 概要&#xff1a; 一.前言&#xff1a; 微软的IL反编译实用程序——Ildasm.exe&#xff0c;可以对可执行文件&#xff08;ex&#xff0c;经典的控制台Hello World 的 exe 可执行文件&#xff09;抽取出 IL 代码&#xff0c;并且给出命名空间以及类的视图。在讲述如何…

就想问问你们,这种女朋友从哪里来的?

1 女朋友从哪里来的2 能养好这宠物的对女朋友包容心肯定不赖3 墙都不扶就服这些停车的人4 成功避过所有的球&#xff0c;将白球打入底袋5 谁知道我六年级经历了什么6 这设计&#xff0c;做到了真正意义上的防盗7 这就是我想要的生活面朝大海、春暖花开我也想梦想成真↓ …

基于事件驱动架构构建微服务第10部分:在docker容器内运行单元测试

原文链接&#xff1a;https://logcorner.com/building-microservices-through-event-driven-architecture-part11-run-unit-tests-inside-a-docker-container/在本教程中&#xff0c;我将展示如何在docker容器中运行单元测试。因为我将使用kubernetes在容器中运行微服务&#x…

【iOS-Cocos2d游戏开发之二十】精灵的基础知识点总汇(位图操作/贴图更换/重排z轴等)以及利用CCSprite与CCLayerColor制作简单遮盖层!...

为什么80%的码农都做不了架构师&#xff1f;>>> 李华明Himi 原创,转载务必在明显处注明&#xff1a; 转载自 【黑米GameDev街区】 原文链接: http://www.himigame.com/iphone-cocos2d/516.html 最近写了不少Cocos2d的博文了&#xff0c;那么由于Himi介绍的一般都是…

中柏平板u盘启动_大冶深圳东莞平板硫化机推选得新科技自动化

大冶深圳东莞平板硫化机推选得新科技自动化东莞市得新科技有限公司, 在国内外的销售机器公司。公司位于广东省东莞市。企业致力于点胶设备&#xff0c;硅胶设备&#xff0c;纺织品涂层的先进技术、检测经验发展国外&#xff0c;全面提升国外产品质量&#xff0c;推动国内行业健…

Console-算法-递归算法示例

ylbtech-Arithmetic:Console-算法-递归算法示例1.A&#xff0c;案例-- -- ylb&#xff1a;算法-- type&#xff1a;递归算法-- thankyou:sunshine, 谢谢你的默默付出-- 10:50 2012-04-06-- 1.B&#xff0c;解决方案1.B.1,阶乘 using System;namespace ConsoleApplication1 {cla…

如何才能避免聚会尬聊

全世界只有3.14 % 的人关注了爆炸吧知识如何丰富空闲时光&#xff1f;如何在短时间内获取最最专业的文化、艺术&#xff1f;小编给大家推荐几个公众号它们有趣有颜有料长按二维码&#xff0c;选择“识别图中二维码”关注印客美学id&#xff1a;inkbetter△长按二维码“识别”关…

Excel快速改变行列的次序

改变行列次序是在Excel中常常需要进行的操作&#xff0c;多数用户的方法是先剪切要调整的行或列&#xff0c;然后选定目标位置&#xff0c;单击菜单“插入”→“剪切单元格”。事实上&#xff0c;使用键盘来配合的话&#xff0c;改变行列的次序可以更快捷。比如&#xff0c;在图…

.NET6 如期⽽⾄

.NET6大家好&#xff0c;经过多个月的迭代&#xff0c;.NET 6正式版如期和大家见面了&#xff01;你安装好了吗&#xff1f;还是等等党&#xff1f;希望看完这篇文章你会对.NET 6有更多了解。自2014年11月12日宣布开源后&#xff0c;.NET就成为了一个跨平台、面向多场景的技术。…

Wordpress 提速之 Gzip 压缩

今天来聊下 wordpress 提速&#xff0c;其实关于这方面的话题网上其实蛮多的&#xff0c;速度对一个网站来说无疑是非常重要的&#xff0c;对于速度的追求也是无止境的&#xff0c;在这方面的表率无疑就是 shawn 了&#xff0c;看他博客的很多技巧都是针对速度来的……不过话说…

忍“乳”负重,身材好的女孩子究竟有多不容易?我从科学的角度算出来了……...

全世界只有3.14 % 的人关注了爆炸吧知识大家好&#xff0c;满肚子奇怪知识&#xff0c;用科学眼光看世界的小编又来了。先问大家一个问题&#xff1a;你最喜欢看女孩子的哪里&#xff1f;相信不少人&#xff0c;不论男女都想到了一个地方。不论男女&#xff0c;都希望自己能“大…

getchar()到底怎么用_怎样才能真正发挥肥效,腐植酸水溶肥到底怎么用

施肥是农业生产中的重要环节&#xff0c;不管是大田作物还是花草苗木&#xff0c;水肥充足才能健康生长。而施肥不是看用了多少&#xff0c;而是看转化率&#xff0c;有多少真正被植物所吸收利用。许多化肥看着一大袋一大袋的用&#xff0c;结果都浪费了或者造成徒长一点用处都…

可视化ASP.Net Core Web API健康检查

前言ASP.NET Core提供运行状况检查Middleware和库&#xff0c;以便于报告应用的运行状况。使用方式也很简单。引用Nuget包Microsoft.Extensions.Diagnostics.HealthChecks。然后修改Startup.cs文件:public void ConfigureServices(IServiceCollection services) {...services.A…

利用网站模板创建子网站

1. 打开顶层站点&#xff0c;进入网站内容&#xff0c;最底部&#xff0c;子站点-->新建子网站 2.配置如下&#xff1a; 3. 可能会出现的BUG http://blog.csdn.net/chenxinxian/article/details/12189293 4, 解决方法&#xff1a;安装或卸载用于SharePoint的Reporting Servi…

解锁一个新技巧,让你舒服到不想起床

▲ 点击查看每天睡个好觉&#xff0c;真的是一件很爽的事情。怎样才能做到这么爽呢&#xff1f;答案当然是好好挑选我们的床上伴侣——床品四件套啦~在所有的床品中&#xff0c;四件套绝对是刚需&#xff0c;并且至少需要两套以上用来替换。如果有人问你挑选四件套的标准是什么…

面试常见问题_软件实施工程师面试中的常见问题都有哪些呢?

软件实施工程师面试中的常见问题都有哪些呢&#xff1f;首先需要看你自己找什么样的工资&#xff0c;是新手呢还是老手呢&#xff1f;对于软件实施的一些新手来说&#xff0c;那么相对应来说要看重的是人品的一个问题&#xff0c;因为毕竟他们家的是新手&#xff0c;所以对于一…

清华博士生被开除:告诉孩子,不吃学习的苦,就要吃生活的苦

全世界只有3.14 % 的人关注了爆炸吧知识还记得前两年&#xff0c;人民日报在微信上发过一篇文章——《致沉睡中的大学生&#xff1a;你不失业&#xff0c;天理难容&#xff01;》批判那些舒服地坐在象牙塔里&#xff0c;虚度光阴葬送自己前程的大学生们。当时不少网友评论&…

iOS-数据持久化-偏好设置

一、简单介绍 很多iOS应用都支持偏好设置&#xff0c;比如保存用户名、密码、字体大小等设置&#xff0c;iOS提供了一套标准的解决方案来为应用加入偏好设置功能 每个应用都有个NSUserDefaults实例&#xff0c;通过它来存取偏好设置。比如&#xff0c;保存用户名、字体大小、是…

怎么比对两个html列表,两个表格对比,如何快速找出不同数据!

给你两个表格&#xff0c;如何快速的找出不同数据呢&#xff1f;一行数据&#xff0c;一行数据的去查看对比&#xff1f;那样效率太低。今天&#xff0c;小汪老师就来给大家分享几种超实用的方法。01突出显示不同单元格所在行Sheet1和Sheet2两个表格&#xff0c;我们来对比一下…