网上关于这三者的博客太多了,都非常详尽,有些博客深入源码分析,非常不错,但难免有些晦涩难懂。本篇文章从概念层面讲解这三者关系,熟读本篇文章后会让你对这三者概念及关系有比较清晰的了解,再去看其他源码分析文章会有事半功倍的效果。
问题
1、一个应用的运行过程是怎样的(消息层面)?
2、Service和Handler都运行在主线程,是怎样避免同步问题的?
3、到底能不能在非UI线程操作UI?
4、子线程怎样创建Handler?意义在哪?
5、多个Handler工作机制是怎样的?
概念
MessageQueued
消息队列,这里的“消息”可以理解为事务,例如一次点击、一次刷新都是一个“消息”
Looper
可以理解为消息泵,内部维护了一个消息队列,不断从消息队列里面取出消息,交给不同的handler
Handler
从Looper的MessageQueued中取出消息并执行,主要用于线程间通信。如果不考虑线程间通信,那其实Looper和MessageQueued就能实现一个事务顺序执行的队列了,Handler的存在就是为了解决线程间通信问题。
Activity
这个大家都理解,运行在主线程(UI线程)。从ActivityThread的main方法可知,创建了Looper循环,而Looper循环是一个死循环,用户所有的操作都是一个消息(即事务),在这个死循环中不断被读取执行,如果没有消息,则会阻塞,此时主线程会进入休眠状态,直到有新的消息来唤醒。注意,这里所有执行都和Handler无关,因为所有事务执行都在主线程,没有涉及到线程间通信,所以不需要用到Handler。
Service
这个大家也熟悉,运行在主线程,相对于Activity来说一个前台可见一个后台默默运行。
解决问题
1、一个应用的运行过程是怎样的(消息层面)?
1、默认Activity过程:
ActivityThread创建Looper实例,由此创建了消息队列MessageQueued,Looper.loop()会进入一个死循环,不断从MessageQueued中读取消息出来执行
2、子线程执行过程:
在Activity中创建Handler,子线程中执行自己的任务(默认没有自己的Looper,但可新建Looper),如果有需要,则通过Activity的Handler把消息发送到主线程,在主线程中执行消息(这里发送到主线程的消息,其实是加入到了主线程的消息队列里面,接下来的执行过程就和上面1相同)。
2、Service和Handler都运行在主线程,是怎样避免同步问题的?
由上面的默认Activity运行过程可知,主线程维护了一个消息队列,这样不管是Activity还是Service,所有的事务处理都是往这个消息队列里面添加消息。站在系统的角度来说,不用管是Activity还是Service,不用管是前台(Activity)的消息还是后台(Service)的消息,所有东西都是一个消息,只要在消息队列里面取出消息执行就行了,对于系统来说根本没有区别,这也就是为什么service不能做阻塞线程的操作,因为阻塞了线程会导致Activity的消息在消息队列里面一直排队执行不了,最后ANR了。这也就解释了同步问题——消息队列的存在不会出现同步问题。
3、到底能不能在非UI线程操作UI?
众所周知,在Android中是不能在子线程更新UI的,否则会报错如下:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
但是这段代码却可以正常运行:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.main_text);
new Thread(new Runnable() {
@Override
public void run() {
textView.setText("hello everyone");
}
}).start();
}
为什么:
首先上面报错的地方在ViewRootImpl的checkThread方法
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
但是这段代码写在onCreate里面,而这时候onCreate还没有初始化UI完成,而ViewRootImpl的checkThread还没有被调用,所以没有报错,可以这样验证一下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.main_text);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
textView.setText("hello everyone");
}
}).start();
}
如愿以偿的报错:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6556)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:907)
at android.view.View.requestLayout(View.java:18722)
at android.view.View.requestLayout(View.java:18722)
at android.view.View.requestLayout(View.java:18722)
at android.view.View.requestLayout(View.java:18722)
at android.view.View.requestLayout(View.java:18722)
at android.view.View.requestLayout(View.java:18722)
at android.support.constraint.ConstraintLayout.requestLayout(ConstraintLayout.java:1959)
at android.view.View.requestLayout(View.java:18722)
at android.widget.TextView.checkForRelayout(TextView.java:7172)
at android.widget.TextView.setText(TextView.java:4342)
at android.widget.TextView.setText(TextView.java:4199)
at android.widget.TextView.setText(TextView.java:4174)
at com.twsm.testdemo.MainActivity$1.run(MainActivity.java:32)
at java.lang.Thread.run(Thread.java:818)
结论:可以在非UI线程操作UI,但这是不安全的,也不是官方推荐的,所以尽量不要在非UI线程操作UI。
PS:查看checkThread代码可知:判断的是该UI是否是创建的线程,那么如果我在子线程创建的UI然后在子线程操作它呢?可以试试。
4、子线程怎样创建Handler?意义在哪?
先说怎么在子线程创建Handler:
public class TestThread extends Thread{
public static Handler handler;
public TestThread() {
}
@Override
public void run() {
Looper.prepare();
handler = new Handler(Looper.myLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
Log.e("tag", "这是来自子线程的消息");
break;
case 1:
Log.e("tag", "这是来自主线程的消息");
break;
}
}
};
handler.sendEmptyMessage(0);
Looper.loop();
}
}
注意前后的 Looper.prepare()和Looper.loop(),如果没有这两句则会报错:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.(Handler.java:200)
at android.os.Handler.(Handler.java:114)
因为没有Looper的话,Handler也没地方取数据,所以在Handler的构造函数里面强制判断了是否有Looper。
为什么在Activity创建Handler不需要添加Looper.prepare()和Looper.loop()呢?在问题1里面就说了,ActivityThread的Main方法(整个APP的入口方法)中就创建了Looper,所以在Activity创建Handler不需要添加。
子线程创建Handler的意义在哪?
1、子线程和主线程通信,是通过Handler的,大部分时候我们都是从子线程发送消息到主线程,这时候使用的是主线程的handler来发送消息,当主线程需要发送消息给子线程时,调用子线程的handler发送消息。
2、消息队列可是个好东西,可以避免很多线程安全问题,所有东西都由于执行,不会产生同步问题。
5、多个Handler工作机制是怎样的?
如果一个Activity创建了多个Handler,Looper是怎样把消息交给对应的handler的呢?看下Looper的源码就知道了:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycle();
}
}
注意有一句代码:msg.target.dispatchMessage(msg),这个msg.target就是handler对象,这样就和对应的handler关联起来了,dispatchMessage代码:
/**
* 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);
}
}
可以看到熟悉的handleMessage,这样就可以在你发送消息的那个handler的handleMessage执行这个消息。
站在系统的角度来看,一个Handler和多个Handler并没有区别,还是上面那句话:
系统只是从消息队列里取出消息执行,所有的Handler操作的消息最终还是会添加到主线程的消息队列中
只是在开发者看来,消息会被发送到不同Handler中最终在对应的handleMessage被执行,“看起来好像”在不同地方执行了,其实都是同一个消息队列,只是代码写在不同地方而已,这个概念不要弄混淆。