在以前的博文中,讲过一些和Handler有关的知识,例如:html
Android 多线程----AsyncTask异步任务详解 java
Android多线程----异步消息处理机制之Handler详解android
今天再把Handler的知识回顾总结一下。面试
本文包括与Handler有关的如下问题:安全
(1)Handler的做用微信
(2)为何Android中要设计为只能在UI线程中去更新UI呢?多线程
(3)Handler的两个异常架构
(4)Handler、Looper MessageQueue之间的关系(源码角度)并发
(5)跟线程相关的Handler,即HandlerThread(源码角度分析)app
(6)主线程往子线程发消息
1、Handler的做用:
(1)在非UI线程中完成耗时操做,在UI线程中去更新UI。
(2)能够在主线程中发送延时消息。
2、为何Android中要设计为只能在UI线程中去更新UI呢?
(1)解决多线程并发问题(根本缘由)
(2)提升界面更新的性能问题
(3)架构设计的简单
你可能会说,既然是担忧多线程并发问题,那我在子线程中加锁进行更新UI行不行呢?你这样想的话,会容易形成UI卡顿的,并且性能也很差。
注1:大部分面试者很难去说出一个令面试官满意的答案。
注2:关于多线程,这里举一个例子,好比说银行取款的问题。正常状况下,银行卡余额不能少于取款金额,若是多线程进行取款的话,就会形成线程不安全。
注3:Android中之因此说架构简单,是由于帮咱们封装了不少更新UI的操做。
3、Handler的两个异常:
在使用Handler时,常常会出现如下两个异常:
(1)CalledFromWrongThreadException:这种异常是由于尝试在子线程中去更新UI,进而产生异常。
(2)Can't create handle inside thread that ha not called Looper.prepared:是由于咱们在子线程中去建立Handler,而产生的异常。
咱们接下来经过代码来把这两个异常演示一下。
一、子线程中更新UI的异常:
(1)activity_main.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" tools:context=".MainActivity"> <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world"/> <Button android:id="@+id/btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="按钮"/> </RelativeLayout>
上方代码中,一个文本,一个按钮,代码比较简单。
(2)MainActivity.java:
1 import android.app.Activity; 2 import android.os.Bundle; 3 import android.view.View; 4 import android.widget.Button; 5 import android.widget.TextView; 6 7 public class MainActivity extends Activity { 8 9 private TextView tv; 10 private Button btn; 11 12 @Override 13 protected void onCreate(Bundle savedInstanceState) { 14 super.onCreate(savedInstanceState); 15 setContentView(R.layout.activity_main); 16 tv = (TextView) findViewById(R.id.tv); 17 btn = (Button) findViewById(R.id.btn); 18 19 //点击按钮后,尝试在子线程中更新UI 20 btn.setOnClickListener(new View.OnClickListener() { 21 @Override 22 public void onClick(View v) { 23 24 new Thread(new Runnable() { 25 @Override 26 public void run() { 27 tv.setText("smyhvae"); //子线程中更新UI 28 } 29 }).start(); 30 31 } 32 }); 33 } 34 }
上方代码中,核心代码是第27行:点击按钮后,在子线程中更新UI。
运行程序后,点击按钮,效果以下:
此时,咱们来看一下后台的log日志:
上图中报的错误日志就是由于咱们在子线程中去更新UI。
解决方案:
在子线程中建立Message消息,经过Handler发给主线程,以后在Handler的handleMessage方法中得到Message消息,进而处理更新UI界面。代码以下:
1 import android.app.Activity; 2 import android.os.Bundle; 3 import android.os.Handler; 4 import android.os.Message; 5 import android.view.View; 6 import android.view.View.OnClickListener; 7 import android.widget.Button; 8 import android.widget.TextView; 9 10 public class MainActivity extends Activity implements OnClickListener { 11 public static final int UPDATE_TEXT = 1; 12 private TextView text; 13 private Button changeText; 14 15 //程序一加载,直接在主线程中建立Handler 16 private Handler handler = new Handler() { 17 public void handleMessage(Message msg) { 18 switch (msg.what) { 19 case UPDATE_TEXT: 20 text.setText("Nice to meet you"); 21 break; 22 default: 23 break; 24 } 25 } 26 }; 27 28 @Override 29 protected void onCreate(Bundle savedInstanceState) { 30 super.onCreate(savedInstanceState); 31 setContentView(R.layout.activity_main); 32 text = (TextView) findViewById(R.id.text); 33 changeText = (Button) findViewById(R.id.change_text); 34 changeText.setOnClickListener(this); 35 } 36 37 @Override 38 public void onClick(View v) { 39 switch (v.getId()) { 40 case R.id.change_text: 41 new Thread(new Runnable() { 42 @Override 43 public void run() { 44 Message message = new Message(); 45 message.what = UPDATE_TEXT; 46 handler.sendMessage(message); 47 } 48 }).start(); 49 break; 50 default: 51 break; 52 } 53 } 54 }
上方第44行代码也能够换成:
Message message = handler.obtainMessage();
二、在子线程中建立Handler的异常:
MainActivity.java:
1 import android.app.Activity; 2 import android.os.Bundle; 3 import android.os.Handler; 4 import android.widget.TextView; 5 6 public class MainActivity extends Activity { 7 8 private TextView tv; 9 10 @Override 11 protected void onCreate(Bundle savedInstanceState) { 12 super.onCreate(savedInstanceState); 13 setContentView(R.layout.activity_main); 14 tv = (TextView) findViewById(R.id.tv); 15 16 //尝试在子线程中去建立Handler 17 new Thread(new Runnable() { 18 @Override 19 public void run() { 20 new Handler(); 21 } 22 }).start(); 23 } 24 }
运行程序后, 报错以下:
4、Handler、Looper MessageQueue之间的关系:(源码角度)
若是要问到Handler,这个问题基本是面试必问的。
原理分析:
Handler是Android类库提供的用于发送、处理消息或Runnable对象的处理类,它结合Message、MessageQueue和Looper类以及当前线程实现了一个消息循环机制,用于实现任务的异步加载和处理。整个异步消息处理流程的示意图以下图所示:
根据上面的图片,咱们如今来解析一下异步消息处理机制:
如今能够作出以下总结:
(1)Handler负责发送消息,Looper负责接收Handler发送的消息放到MessageQueue,Looper又将消息回传给Handler本身。
(2)一个Handler对应一个Looper对象,一个Looper对应一个MessageQueue对象(Looper内部包含一个MessageQueue),一个Handler能够生成多个Message。
(3)Handler就是公开给外部线程的接口,用于线程间的通讯。Looper是由系统支持的用于建立和管理MessageQueue的依附于一个线程的循环处理对象,而Handler是用于操做线程内部的消息队列的,因此 Handler也必须依附一个线程,并且只能是一个线程。
(4)因为Handler是在主线程中建立的,因此此时handleMessage()方法中的代码也会在主线程中运行,因而咱们在这里就能够安心地进行UI操做了
生活中的例子:
上图中,能够这么理解:开会时,我(Handler)想中途离开去作别的事情,经过sendMessage发消息给领导,领导思考了一下子,赞成以后,经过Looper.looep()方法将消息回传给我,说我能够离开,而后我就调用handleMessage方法去作别的事情去了。
注:面试的时候,若是只是从字面的角度来解释Handler、Looper MessageQueue之间的关系,并不可以真正打动面试官,倒不如再举一个生动的例子,让面试官以为你是懂面向对象的思惟的。
5、跟线程相关的Handler,即HandlerThread(源码角度分析)
这个问题能够看一下这篇博客:
http://blog.csdn.net/lmj623565791/article/details/47079737
6、主线程往子线程发消息:(考察你是否真的理解Handler机制)
咱们在平时开发的过程当中,常常是子线程往主线程中发消息,让主线程更新UI。可是根据具体的项目需求,也可能会要求让你在主线程中往子线程中发消息。
1 import android.app.Activity; 2 import android.os.Bundle; 3 import android.os.Handler; 4 import android.os.Looper; 5 import android.os.Message; 6 import android.util.Log; 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 public static final int UPDATE_TEXT = 1; 14 private TextView tv; 15 private Button btn; 16 private Handler handler; 17 18 @Override 19 protected void onCreate(Bundle savedInstanceState) { 20 super.onCreate(savedInstanceState); 21 setContentView(R.layout.activity_main); 22 tv = (TextView) findViewById(R.id.tv); 23 btn = (Button) findViewById(R.id.btn); 24 btn.setOnClickListener(this); 25 //疑问:为何这段代码若是写在onClick方法里面会报空指针? 26 new Thread(new Runnable() { 27 @Override 28 public void run() { 29 //一、准备Looper对象 30 Looper.prepare(); 31 //二、在子线程中建立Handler 32 handler = new Handler() { 33 @Override 34 public void handleMessage(Message msg) { 35 super.handleMessage(msg); 36 Log.i("handleMessage:", Thread.currentThread().getName()); 37 Log.i("后台输出", "收到了消息对象"); 38 } 39 }; 40 //三、调用Looper的loop()方法,取出消息对象 41 Looper.loop(); 42 } 43 }).start(); 44 45 } 46 @Override 47 public void onClick(View v) { 48 Log.i("onClick:", Thread.currentThread().getName()); 49 switch (v.getId()) { 50 case R.id.btn: 51 Message msg = handler.obtainMessage(); 52 handler.sendMessage(msg); 53 break; 54 55 default: 56 break; 57 } 58 } 59 }
上方的第29行至41行代码:这是MainThread中发送消息,在子线程中接收消息的固定写法。上面的三个步骤再重复一下:
注意,此时handleMessage()方法是在子线程中运行的。
后台运行效果:
小小地总结一下:
首先执行Looper的prepare()方法,这个方法有两个做用:一是生成Looper对象,而是把Looper对象和当前线程对象造成键值对(线程为键),存放在ThreadLocal当中,而后生成handler对象,调用Looper的myLooper()方法,获得与Handler所对应的Looper对象,这样的话,handler、looper 、消息队列就造成了一一对应的关系,而后执行上面的第三个步骤,即Looper在消息队列当中循环的取数据。
另外,在本文最开头的第一段中,咱们在主线程中建立Handler也没有调用Looper.prepare()方法,为何就没有崩溃呢?,这是因为在程序启动的时候,系统已经帮咱们自动调用了Looper.prepare()方法。查看ActivityThread中的main()方法,代码以下所示:
1 public static void main(String[] args) { 2 SamplingProfilerIntegration.start(); 3 CloseGuard.setEnabled(false); 4 Environment.initForCurrentUser(); 5 EventLogger.setReporter(new EventLoggingReporter()); 6 Process.setArgV0("<pre-initialized>"); 7 Looper.prepareMainLooper(); 8 ActivityThread thread = new ActivityThread(); 9 thread.attach(false); 10 if (sMainThreadHandler == null) { 11 sMainThreadHandler = thread.getHandler(); 12 } 13 AsyncTask.init(); 14 if (false) { 15 Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); 16 } 17 Looper.loop(); 18 throw new RuntimeException("Main thread loop unexpectedly exited"); 19 }
上方代码中,能够看到,在第7行调用了Looper.prepareMainLooper()方法,而这个方法又会再去调用Looper.prepare()方法,代码以下所示:
1 public static final void prepareMainLooper() { 2 prepare(); 3 setMainLooper(myLooper()); 4 if (Process.supportsProcesses()) { 5 myLooper().mQueue.mQuitAllowed = false; 6 } 7 }
总结:这样基本就将Handler的建立过程彻底搞明白了,总结一下就是在主线程中能够直接建立Handler对象,而在子线程中须要先调用Looper.prepare()才能建立Handler对象。
6、为何在有些时候子线程中是能够直接更新UI的:
这道面试题应该是本文中最难的一个面试题了,须要好好理解。为了回答这个问题,咱们须要先经过看源码去了解下面这三个问题:
(1)Android是如何检测非UI线程去更新UI的
(2)ViewRootImp是什么?
(3)ViewRootImp是在哪里建立的?
源码我就不贴出来了,这里我只是总结一下。
答案:
非UI线程真的不能更新UI吗? 是能够的。
解释:
在线程中更新UI时会调用ViewParent.invalidateChild()方法检查当前的thread是不是Mainthread。
具体源码以下:
1 final ViewParent p = mParent; 2 if (p != null && ai != null && l < r && t < b) { 3 final Rect damage = ai.mTmpInvalRect; 4 damage.set(l, t, r, b); 5 p.invalidateChild(this, damage); 6 }
而ViewParent是一个接口类,其实现类是ViewRootImpl,经过查看invalidateChild()方法里面的代码就能够看到会他调用checkThread()方法。checkThread()方法以下:
1 void checkThread() { 2 if (mThread != Thread.currentThread()) { //检查更新UI的线程是不是MainThread 3 throw new CalledFromWrongThreadException( 4 "Only the original thread that created a view hierarchy can touch its views."); 5 } 6 }
上面的第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
下图是个人微信公众号(生命团队id:vitateam
),欢迎有心人关注。博客园分享技术,公众号分享心智。
我会很感激第一批关注个人人。此时,年轻的我和你,一无全部;然后,富裕的你和我,满载而归。