本篇文章给大家谈谈Android如何打印handler的消息队列,以及android打印的知识点,同时本文还将给你拓展androidHandler消息传递机制、AndroidHandler消息机制、A
本篇文章给大家谈谈Android如何打印handler的消息队列,以及android 打印的知识点,同时本文还将给你拓展android Handler 消息传递机制、Android Handler 消息机制、Android HandlerThread 详解 Android Handler 源码分析(详细)、Android Handler消息机制等相关知识,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:- Android如何打印handler的消息队列(android 打印)
- android Handler 消息传递机制
- Android Handler 消息机制
- Android HandlerThread 详解 Android Handler 源码分析(详细)
- Android Handler消息机制
Android如何打印handler的消息队列(android 打印)
- 原因
handler的消息队列里的数据处理正常是很快的,但是如果存在耗时的处理,会导致不能及时处理队列中的消息,这个时候可以把消息队列中的数据都打印出来
- 方法
在你想开始打印的地方增加
Looper.getMainLooper().setMessageLogging(new LogPrinter(4, "xuwenping"));
,这样,log里就会出现I/xuwenping: <<<<< Finished to Handler (android.view.ViewRootImpl$ViewRootHandler) {672b748} null
的类似log
android Handler 消息传递机制
出于性能优化的考虑,Android 的 UI 操作并不是线程安全的,这意味着如果有多个线程并发操作 UI,可能导致线程安全问题。为了解决这个问题,Android 制定了一条简单的规则:只允许 UI 线程修改 Activity 的 UI 组件。
当一个程序第一次启动时,Activity 会同时启动一条主线程,主线程主要负责处理与 UI 相关的事件,如用户的按键操作、用户触摸屏幕的事件及屏幕绘制事件,并把相关的事件分发到对应的组件进行处理。所以主线程通常又被叫做 UI 线程。
Android 的消息传递机制是另一种形式的 “事件处理”,这种机制主要为解决 Android 应用的多线程问题 ---Android 平台只允许 UI 线程修改 Activity 里的 UI 组件,这就会导致新启动的线程无法动态改变界面组件的属性值。但实际在开发中,尤其是涉及动画的游戏开发中,需要让新启动的线程周期性的改变界面组件的属性值,这就需要借助于 Handler 的消息传递机制。
Handler 类主要作用有两个:
--- 在新启动的线程中发送消息;
--- 在主线程中获取、处理消息。
上面的说法看上去很简单,似乎只分成两步:在新线程中发送消息,然后再主线程中获取并处理消息即可。但过程中涉及一个问题,新启动的线程何时发送消息?主线程又何时处理消息?时机如何控制?
为了解决处理消息问题,只能通过回调的方式来实现 --- 重写 Handler 类的 handleMessage () 方法。 当新启动的线程发送消息时,消息会发送到与之关联的 MessageQueue, 而 Handler 会不断从 MessageQueue 中获取并处理消息 -- 这将导致 Handler 中处理消息的方法被回调。
计算质数实例:
package com.example.calprime;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final String DATA_NUM = "num";
private static final String RESULT_KEY = "result_key";
EditText editText;
MyThread myThread;
TextView result;
class MyThread extends Thread {
public Handler mHandler;
@Override
public void run() {
// super.run();
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0x110) {
int num = msg.getData().getInt(DATA_NUM);
List<Integer> nums = new ArrayList<>();
//计算从2开始到num的所有质数
for (int i = 2; i <= num; i++) {
//用i除以从2开始,到i的平方根的所有数
boolean isadd = true;
for (int j = 2; j <= Math.sqrt(i); j++) {
//如果可以整除,则不是质数
if (i != 2 && i % j == 0) {
isadd = false;
continue;
}
}
if (isadd) {
nums.add(i);
}
}
//Toast显示
Toast.makeText(MainActivity.this, nums.toString(), Toast.LENGTH_LONG).show();
Message message = new Message();
message.what = 0x111;
Bundle bundle = new Bundle();
bundle.putString(RESULT_KEY, nums.toString());
message.setData(bundle);
mUiHandler.sendMessage(message);
}
}
};
Looper.loop();
}
}
Handler mUiHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0x111) {
result.setText(msg.getData().getString(RESULT_KEY));
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.edit);
result = findViewById(R.id.result);
myThread = new MyThread();
myThread.start();
}
public void cal(View view) {
Message message = new Message();
message.what = 0x110;
Bundle bundle = new Bundle();
bundle.putInt(DATA_NUM, Integer.parseInt(editText.getText().toString()));
message.setData(bundle);
myThread.mHandler.sendMessage(message);
}
}
运行结果:
Android Handler 消息机制
前言
Android的消息机制主要是指Handler的运行机制,对于大家来说Handler已经是轻车熟路了,可是真的掌握了Handler?本文主要通过几个问题围绕着Handler展开深入并拓展的了解。
Questions
-
Looper 死循环为什么不会导致应用卡死,会消耗大量资源吗?
-
主线程的消息循环机制是什么(死循环如何处理其它事务)?
-
ActivityThread 的动力是什么?(ActivityThread执行Looper的线程是什么)
-
Handler 是如何能够线程切换,发送Message的?(线程间通讯)
-
子线程有哪些更新UI的方法。
-
子线程中Toast,showDialog,的方法。(和子线程不能更新UI有关吗)
-
如何处理Handler 使用不当导致的内存泄露?
1. Looper 死循环为什么不会导致应用卡死?
线程默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
首先我们看一段代码
new Thread(new Runnable() { @Override public void run() { Log.e("qdx", "step 0 "); Looper.prepare(); Toast.makeText(MainActivity.this, "run on Thread", Toast.LENGTH_SHORT).show(); Log.e("qdx", "step 1 "); Looper.loop(); Log.e("qdx", "step 2 "); } }).start();
我们知道Looper.loop();
里面维护了一个死循环方法,所以按照理论,上述代码执行的应该是
step 0 –>step 1
也就是说循环在Looper.prepare();
与Looper.loop();
之间。
在子线程中,如果手动为其创建了Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待(阻塞)状态,而如果退出Looper以后,这个线程就会立刻(执行所有方法并)终止,因此建议不需要的时候终止Looper。
执行结果也正如我们所说,这时候如果了解了ActivityThread
,并且在main方法中我们会看到主线程也是通过Looper方式来维持一个消息循环。
public static void main(String[] args) { `````` Looper.prepareMainLooper();//创建Looper和MessageQueue对象,用于处理主线程的消息 ActivityThread thread = new ActivityThread(); thread.attach(false);//建立Binder通道 (创建新线程) if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); //如果能执行下面方法,说明应用崩溃或者是退出了... throw new RuntimeException("Main thread loop unexpectedly exited"); }
那么回到我们的问题上,这个死循环会不会导致应用卡死,即使不会的话,它会慢慢的消耗越来越多的资源吗?
摘自:Gityuan
对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。
主线程的死循环一直运行是不是特别消耗cpu资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放cpu资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量cpu资源。
Gityuan–Handler(Native层)
2. 主线程的消息循环机制是什么?
事实上,会在进入死循环之前便创建了新binder线程,在代码ActivityThread.main()中:
public static void main(String[] args) { .... //创建Looper和MessageQueue对象,用于处理主线程的消息 Looper.prepareMainLooper(); //创建ActivityThread对象 ActivityThread thread = new ActivityThread(); //建立Binder通道 (创建新线程) thread.attach(false); Looper.loop(); //消息循环运行 throw new RuntimeException("Main thread loop unexpectedly exited"); }
Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施:一旦退出消息循环,那么你的程序也就可以退出了。
从消息队列中取消息可能会阻塞,取到消息会做出相应的处理。如果某个消息处理时间过长,就可能会影响UI线程的刷新速率,造成卡顿的现象。
thread.attach(false)方法函数中便会创建一个Binder线程(具体是指
ApplicationThread
,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程。「Activity 启动过程」比如收到msg=
H.LAUNCH_ACTIVITY
,则调用ActivityThread.handleLaunchActivity()
方法,最终会通过反射机制,创建Activity实例,然后再执行Activity.onCreate()等方法;再比如收到msg=
H.PAUSE_ACTIVITY
,则调用ActivityThread.handlePauseActivity()
方法,最终会执行Activity.onPause()等方法。主线程的消息又是哪来的呢?当然是App进程中的其他线程通过Handler发送给主线程
system_server进程
system_server进程是系统进程,java framework框架的核心载体,里面运行了大量的系统服务,比如这里提供
ApplicationThreadProxy
(简称ATP),ActivityManagerService
(简称AMS),这个两个服务都运行在system_server进程的不同线程中,由于ATP和AMS都是基于IBinder接口,都是binder线程,binder线程的创建与销毁都是由binder驱动来决定的。
App进程
App进程则是我们常说的应用程序,主线程主要负责Activity/Service等组件的生命周期以及UI相关操作都运行在这个线程; 另外,每个App进程中至少会有两个binder线程
ApplicationThread
(简称AT)和ActivityManagerProxy
(简称AMP),除了图中画的线程,其中还有很多线程
Binder
Binder用于不同进程之间通信,由一个进程的Binder客户端向另一个进程的服务端发送事务,比如图中线程2向线程4发送事务;而handler用于同一个进程中不同线程的通信,比如图中线程4向主线程发送消息。
结合图说说Activity生命周期,比如暂停Activity,流程如下:
-
线程1的AMS中调用线程2的ATP;(由于同一个进程的线程间资源共享,可以相互直接调用,但需要注意多线程并发问题)
-
线程2通过binder传输到App进程的线程4;
-
线程4通过handler消息机制,将暂停Activity的消息发送给主线程;
-
主线程在looper.loop()中循环遍历消息,当收到暂停Activity的消息时,便将消息分发给
ActivityThread.H.handleMessage()方法,再经过方法的调用,
最后便会调用到Activity.onPause(),当onPause()处理完后,继续循环loop下去。
补充:
ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的程序也就可以退出了。
从消息队列中取消息可能会阻塞,取到消息会做出相应的处理。如果某个消息处理时间过长,就可能会影响UI线程的刷新速率,造成卡顿的现象。
最后通过《Android开发艺术探索》的一段话总结 :
ActivityThread通过ApplicationThread和AMS进行进程间通讯,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityThread中去执行,即切换到主线程中去执行,这个过程就是。主线程的消息循环模型
另外,ActivityThread实际上并非线程,不像HandlerThread类,ActivityThread并没有真正继承Thread类
那么问题又来了,既然ActivityThread不是一个线程,那么ActivityThread中Looper绑定的是哪个Thread,也可以说它的动力是什么?
3. ActivityThread 的动力是什么?
进程
每个app运行时前首先创建一个进程,该进程是由Zygote fork出来的,用于承载App上运行的各种Activity/Service等组件。进程对于上层应用来说是完全透明的,这也是google有意为之,让App程序都是运行在Android Runtime。大多数情况一个App就运行在一个进程中,除非在AndroidManifest.xml中配置Android:process属性,或通过native代码fork进程。
线程
线程对应用来说非常常见,比如每次new Thread().start都会创建一个新的线程。该线程与App所在进程之间资源共享,从Linux角度来说进程与线程除了是否共享资源外,并没有本质的区别,都是一个task_struct结构体,在cpu看来进程或线程无非就是一段可执行的代码,cpu采用CFS调度算法,保证每个task都尽可能公平的享有cpu时间片。
其实承载ActivityThread的主线程就是由Zygote fork而创建的进程。
4. Handler 是如何能够线程切换
其实看完上面我们大致也清楚,线程间是共享资源的。所以Handler处理不同线程问题就只要注意异步情况即可。
这里再引申出Handler的一些小知识点。
Handler创建的时候会采用当前线程的Looper来构造消息循环系统,Looper在哪个线程创建,就跟哪个线程绑定,并且Handler是在他关联的Looper对应的线程中处理消息的。(敲黑板)
那么Handler内部如何获取到当前线程的Looper呢—–ThreadLocal。ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是①线程是默认没有Looper的,如果需要使用Handler,就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,②ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
系统为什么不允许在子线程中访问UI?
这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那么为什么系统不对UI控件的访问加上锁机制呢?缺点有两个:
①首先加上锁机制会让UI访问的逻辑变得复杂
②锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
所以最简单且高效的方法就是采用单线程模型来处理UI操作。
5. 子线程有哪些更新UI的方法。
-
主线程中定义Handler,子线程通过mHandler发送消息,主线程Handler的
handleMessage
更新UI。 -
用Activity对象的runOnUiThread方法。
-
创建Handler,传入
getMainLooper
。 -
View.post(Runnable r) 。
runOnUiThread
第一种咱们就不分析了,我们来看看第二种比较常用的写法。
先重新温习一下上面说的
Looper在哪个线程创建,就跟哪个线程绑定,并且Handler是在他关联的Looper对应的线程中处理消息的。(敲黑板)
new Thread(new Runnable() { @Override public void run() { runOnUiThread(new Runnable() { @Override public void run() { //DO UI method } }); } }).start();
//Activity final Handler mHandler = new Handler(); public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action);//子线程(非UI线程) } else { action.run(); } }
进入Activity类里面,可以看到如果是在子线程中,通过mHandler
发送的更新UI消息。
而这个Handler是在Activity中创建的,也就是说在主线程中创建,所以便和我们在主线程中使用Handler更新UI没有差别。
因为这个Looper,就是ActivityThread中创建的Looper(Looper.prepareMainLooper()
)。
创建Handler,传入getMainLooper
那么同理,我们在子线程中,是否也可以创建一个Handler,并获取MainLooper
,从而在子线程中更新UI呢?
首先我们看到,在Looper
类中有静态对象sMainLooper
,并且这个sMainLooper
就是在ActivityThread中创建的MainLooper
。
private static Looper sMainLooper; // guarded by Looper.class public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
所以不用多说,我们就可以通过这个sMainLooper
来进行更新UI操作。
new Thread(new Runnable() { @Override public void run() { Log.e("qdx", "step 1 "+Thread.currentThread().getName()); Handler handler=new Handler(getMainLooper()); handler.post(new Runnable() { @Override public void run() { //Do Ui method Log.e("qdx", "step 2 "+Thread.currentThread().getName()); } }); } }).start();
View.post(Runnable r)
老样子,我们点入源码
//View /** * <p>Causes the Runnable to be added to the message queue. * The runnable will be run on the user interface thread.</p> * * @param action 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. * */ public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); //一般情况走这里 } // Postpone the runnable until we kNow on which thread it needs to run. // Assume that the runnable will be successfully placed after attach. getRunQueue().post(action); return true; } /** * A Handler supplied by a view's {@link android.view.ViewRootImpl}. This * handler can be used to pump events in the UI events queue. */ final Handler mHandler;
居然也是Handler从中作祟,根据Handler的注释,也可以清楚该Handler可以处理UI事件,也就是说它的Looper也是主线程的sMainLooper
。这就是说我们常用的更新UI都是通过Handler实现的。
另外更新UI 也可以通过AsyncTask
来实现,难道这个AsyncTask
的线程切换也是通过 Handler 吗?
没错,也是通过Handler……
6.子线程中Toast,showDialog,的方法。
可能有些人看到这个问题,就会想: 子线程本来就不可以更新UI的啊
兄台且慢,且听我把话写完
new Thread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "run on thread", Toast.LENGTH_SHORT).show();//崩溃无疑 } }).start();
转存失败重新上传取消
看到这个崩溃日志,是否有些疑惑,因为一般如果子线程不能更新UI控件是会报如下错误的(子线程不能更新UI)
所以子线程不能更新Toast的原因就和Handler有关了,据我们了解,每一个Handler都要有对应的Looper对象,那么。
满足你。
new Thread(new Runnable() { @Override public void run() { Looper.prepare(); Toast.makeText(MainActivity.this, "run on thread", Toast.LENGTH_SHORT).show(); Looper.loop(); } }).start();
这样便能在子线程中Toast,不是说子线程…?
老样子,我们追根到底看一下Toast内部执行方式。
//Toast /** * Show the view for the specified duration. */ public void show() { `````` Inotificationmanager service = getService();//从SMgr中获取名为notification的服务 String pkg = mContext.getopPackageName(); TN tn = mTN; tn.mNextView = mNextView; try { service.enqueuetoast(pkg, tn, mDuration);//enqueue? 难不成和Handler的队列有关? } catch (remoteexception e) { // Empty } }
在show
方法中,我们看到Toast的show方法和普通UI 控件不太一样,并且也是通过Binder进程间通讯方法执行Toast绘制。这其中的过程就不在多讨论了,有兴趣的可以在notificationmanagerService
类中分析。
现在把目光放在TN
这个类上(难道越重要的类命名就越简洁,如H
类),通过TN
类,可以了解到它是Binder的本地类。在Toast的show方法中,将这个TN
对象传给notificationmanagerService
就是为了通讯!并且我们也在TN中发现了它的show方法。
private static class TN extends ITransientNotification.Stub {//Binder服务端的具体实现类 /** * schedule handleShow into the right thread */ @Override public void show(IBinder windowToken) { mHandler.obtainMessage(0, windowToken).sendToTarget(); } final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { IBinder token = (IBinder) msg.obj; handleShow(token); } }; }
看完上面代码,就知道子线程中Toast报错的原因,因为在TN
中使用Handler,所以需要创建Looper
对象。
那么既然用Handler来发送消息,就可以在handleMessage
中找到更新Toast的方法。
在handleMessage
看到由handleShow
处理。
//Toast的TN类 public void handleShow(IBinder windowToken) { `````` mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); mParams.x = mX; mParams.y = mY; mParams.verticalMargin = mVerticalMargin; mParams.horizontalMargin = mHorizontalMargin; mParams.packageName = packageName; mParams.hideTimeoutMilliseconds = mDuration == Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT; mParams.token = windowToken; if (mView.getParent() != null) { mWM.removeView(mView); } mWM.addView(mView, mParams);//使用WindowManager的addView方法 trySendAccessibilityEvent(); } }
看到这里就可以总结一下:
Toast
本质是通过window
显示和绘制的(操作的是window
),而主线程不能更新UI 是因为ViewRootImpl
的checkThread
方法在Activity维护的View树的行为。 Toast
中TN
类使用Handler
是为了用队列和时间控制排队显示Toast
,所以为了防止在创建TN
时抛出异常,需要在子线程中使用Looper.prepare();
和Looper.loop();
(但是不建议这么做,因为它会使线程无法执行结束,导致内存泄露)
7. 如何处理Handler 使用不当导致的内存泄露?
首先上文在子线程中为了节目效果,使用如下方式创建Looper
Looper.prepare(); `````` Looper.loop();
实际上这是非常危险的一种做法
在子线程中,如果手动为其创建Looper,那么在所有的事情完成以后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待的状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。(【 Looper.myLooper().quit(); 】)
那么,如果在Handler的handleMessage
方法中(或者是run方法)处理消息,如果这个是一个延时消息,会一直保存在主线程的消息队列里,并且会影响系统对Activity的回收,造成内存泄露。
总结一下,解决Handler内存泄露主要2点
-
有延时消息,要在Activity销毁的时候移除
Messages
-
匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者Activity使用弱引用。
总结
想不到Handler居然可以腾出这么多浪花,与此同时感谢前辈的摸索。
另外Handler还有许多不为人知的秘密,等待大家探索,下面我再简单的介绍两分钟
-
HandlerThread
-
IdleHandler
HandlerThread
HandlerThread继承Thread,它是一种可以使用Handler的Thread,它的实现也很简单,在run方法中也是通过
Looper.prepare()
来创建消息队列,并通过Looper.loop()
来开启消息循环(与我们手动创建方法基本一致),这样在实际的使用中就允许在HandlerThread中创建Handler了。
由于HandlerThread的run方法是一个无限循环,因此当不需要使用的时候通过quit或者quitSafely方法来终止线程的执行。
HandlerThread的本质也是线程,所以切记关联的Handler中处理消息的handleMessage
为子线程。
IdleHandler
/** * Callback interface for discovering when a thread is going to block * waiting for more messages. */ public static interface IdleHandler { /** * Called when the message queue has run out of messages and will Now * wait for more. Return true to keep your idle handler active, false * to have it removed. This may be called if there are still messages * pending in the queue, but they are all scheduled to be dispatched * after the current time. */ boolean queueIdle(); }
根据注释可以了解到,这个接口方法是在消息队列全部处理完成后或者是在阻塞的过程中等待更多的消息的时候调用的,返回值false表示只回调一次,true表示可以接收多次回调。
具体使用如下代码
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { return false; } });
另外提供一个小技巧:在HandlerThread中获取Looper的MessageQueue方法之反射。
因为
-
Looper.myQueue()如果在主线程调用就会使用主线程looper
-
使用handlerThread.getLooper().getQueue()最低版本需要23
//HandlerThread中获取MessageQueue Field field = Looper.class.getDeclaredField("mQueue"); field.setAccessible(true); MessageQueue queue = (MessageQueue) field.get(handlerThread.getLooper());
那么Android的消息循环机制是通过Handler,是否可以通过IdleHandler
来判断Activity的加载和绘制情况(measure,layout,draw等)呢?并且IdleHandler
是否也隐藏着不为人知的特殊功能?
作者:波斯汪
链接:https://www.jb51.cc/article/68425
来源:慕课网
Android HandlerThread 详解 Android Handler 源码分析(详细)
概述
HandlerThread 相信大家都比较熟悉了,从名字上看是一个带有 Handler 消息循环机制的一个线程,比一般的线程多了消息循环的机制,可以说是 Handler + Thread 的结合,从源码上看也是如此的设计。
对 Handler 不熟悉的可以看 Android Handler 源码分析(详细) 一文,会教你一步步去认识 Handler 。
一般情况下如果需要子线程和主线程之间相互交互,可以用 HandlerThread 来设计,这比单纯的 Thread 要方便,而且更容易管理,因为大家都知道Thread 的生命周期在一些情况下是不可控制的,比如直接 new Thread().start() 这种方式在项目中是不推荐使用的,实际上 Android 的源码中也有很多地方用到了 HandlerThread,下面我将分析一下 HandlerThread 用法以及源码解析。
使用示例
// 实例对象,参数为线程名字 HandlerThread handlerThread = new HandlerThread("handlerThread"); 启动线程 handlerThread.start(); 参数为 HandlerThread 内部的一个 looper Handler handler = new Handler(handlerThread.getLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } };
注意:这个使用的顺序是不能更改的!!!,因为如果不先让子线程 start 起来,那么创建主线程的 handler 的参数 getLooper 是获取不到的,这一点可以看源码就清楚。
Demo 详解
这里模拟在子线程下载东西,然后和主线程之间进行通信。主线程知道了下载开始和下载结束的时间,也就能及时改变界面 UI。DownloadThread
类,继承于 HandlerThread
,用于下载。
class DownloadThread extends HandlerThread{ private static final String TAG = "DownloadThread"; final int TYPE_START = 2;通知主线程任务开始 int TYPE_FINISHED = 3;通知主线程任务结束 private Handler mUIHandler;主线程的Handler public DownloadThread(String name) { (name); } /* * 执行初始化任务 * */ @Override protected onLooperPrepared() { Log.e(TAG,"onLooperPrepared: 1.Download线程开始准备"); .onLooperPrepared(); } 注入主线程Handler setUIHandler(Handler UIhandler) { mUIHandler = UIhandler; Log.e(TAG,"setUIHandler: 2.主线程的handler传入到Download线程"); } Download线程开始下载 startDownload() { Log.e(TAG,"startDownload: 3.通知主线程,此时Download线程开始下载"); mUIHandler.sendEmptyMessage(TYPE_START); 模拟下载 Log.e(TAG,"startDownload: 5.Download线程下载中..."); SystemClock.sleep(2000); Log.e(TAG,"startDownload: 6.通知主线程,此时Download线程下载完成"); mUIHandler.sendEmptyMessage(TYPE_FINISHED); } }
然后是 MainActivity
部分,UI 和处理消息。
class MainActivity AppCompatActivity { final String TAG = "MainActivity"private DownloadThread mHandlerThread;子线程 private Handler mUIhandler;主线程的Handler onCreate(Bundle savedInstanceState) { .onCreate(savedInstanceState); setContentView(R.layout.activity_main); 初始化,参数为线程的名字 mHandlerThread = new DownloadThread("mHandlerThread"调用start方法启动线程 mHandlerThread.start(); 初始化Handler,传递 mHandlerThread 内部的一个 looper mUIhandler = Handler(mHandlerThread.getLooper()) { @Override handleMessage(Message msg) { 判断mHandlerThread里传来的msg,根据msg进行主页面的UI更改 switch (msg.what) { case DownloadThread.TYPE_START: 不是在这里更改UI哦,只是说在这个时间,你可以去做更改UI这件事情,改UI还是得在主线程。 Log.e(TAG,"4.主线程知道Download线程开始下载了...这时候可以更改主界面UI"); break; DownloadThread.TYPE_FINISHED: Log.e(TAG,"7.主线程知道Download线程下载完成了...这时候可以更改主界面UI,收工"default: ; } .handleMessage(msg); } }; 子线程注入主线程的mUIhandler,可以在子线程执行任务的时候,随时发送消息回来主线程 mHandlerThread.setUIHandler(mUIhandler); 子线程开始下载 mHandlerThread.startDownload(); } @Override onDestroy() { 有2种退出方式 mHandlerThread.quit(); mHandlerThread.quitSafely(); 需要API>=18 .onDestroy(); } }
运行的Log日志如下
源码解析
class HandlerThread extends Thread {
HandlerThread
本质是一个线程,只是其持有了 handler,所以可在子线程进行消息处理和分发。
int mPriority;优先级 int mTid = -1; Looper mLooper;自带的Looper private @Nullable Handler mHandler; HandlerThread(String name) { (name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } /** * Constructs a HandlerThread. * @param name * priority The priority to run the thread at. The value supplied must be from * {@link android.os.Process} and not from java.lang.Thread. */ public HandlerThread(String name,int priority) { priority; }
HandlerThread(String name)
,一个 HandlerThread(String name,int priority)
,我们可以自己设定线程的名字以及优先级。注意!是 Process 里的优先级而不是Thread 的。* Call back method that can be explicitly overridden if needed to execute some * setup before Looper loops. onLooperPrepared() { } @Override run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }
run
方法中首先获取线程 id
,然后就调用了 Looper.prepare
方法创建一个 Looper,
接着调用了 Looper.myLooper
方法获取到了当前线程的 Looper
。
接着通过 notifyAll
通知等带唤醒,这里的等待是在 HandlerThread
的 getLooper
方法里调用的 wait
方法,getLooper
方法是为了获取该 HandlerThread
中的 Looper
。
如果在没调用 HandlerThread
的 start
方法开启线程前就调用 getLooper
方法就通过 wait
方法暂时先进入等待,等到 run
方法运行后再进行唤醒。唤醒之后 run
方法中继续设置了构造函数中传入的优先级,接着调用了onLooperPrepared
方法,该方法是个空实现,该方法是为了在 Looper
开启轮询之前如果要进行某些设置,可以复写该方法。
最后调用Looper.loop
开启轮询。退出的时候,将 mTid = -1;
Looper getLooper() { if (!isAlive()) { return null; } If the thread has been started,wait until the looper has been created. ) { while (isAlive() && mLooper == ) { try { wait(); } catch (InterruptedException e) { } } } return mLooper; }
这个方法是获取当前的 Looper,可以看到如果没有获取的时候就一直等待直到获取,而前面也提到了获取到了就唤醒了所有的线程,看来这是线程的等待-唤醒机制应用。
Handler getThreadHandler() { if (mHandler == ) { mHandler = Handler(getLooper()); } mHandler; }
这个是获取 HandlerThread 绑定的 Looper 线程的 Handler
boolean quit() { Looper looper = getLooper(); if (looper != ) { looper.quit(); true; } false; } quitSafely() { Looper looper =) { looper.quitSafely(); ; }
可以看到这两个方法去退出线程的 Looper 循环,那么这两个方法有什么区别呢,实际上都是调用了 MessageQueue 的 quit() 方法,源码如下:
void quit( safe) { mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } if (mQuitting) { ; } mQuitting = ; (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } We can assume mPtr != 0 because mQuitting was prevIoUsly false. nativeWake(mPtr); } }
可以看到: 当我们调用 quit 方法的时候,实际上执行了 MessageQueue 中的 removeAllMessagesLocked 方法,该方法的作用是把 MessageQueue 消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过 sendMessageDelayed 或通过 postDelayed 等方法发送的需要延迟执行的消息,只要不是立即执行的消息都是延迟消息)还是非延迟消息。
而 quitSafely 方法时,实际上执行了 MessageQueue 中的 removeAllFutureMessagesLocked 方法,通过名字就可以看出,该方法只会清空 MessageQueue 消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让 Handler 去处理,quitSafely 相比于 quit 方法安全之处在于清空消息之前会派发所有的非延迟消息,一句话,就是清除未来需要执行的消息。
这两个方法有一个共同的特点就是:Looper 不再接收新的消息了,消息循环就此结束,此时通过 Handler 发送的消息也不会在放入消息杜队列了,因为消息队列已经退出了。应用这2个方法的时候需要注意的是:quit 方法从 API 1 就开始存在了,比较早,而 quitSafely 直到 API 18 才添加进来.
-
如果经常要开启线程,接着又是销毁线程,这是很耗性能的,
HandlerThread
很好的解决了这个问题; -
HandlerThread
由于异步操作是放在Handler
的消息队列中的,所以是串行的,但只适合并发量较少的耗时操作。
-
HandlerThread
用完记得调用退出方法。 -
注意使用 handler 避免出现内存泄露
总结
以上是小编为你收集整理的Android HandlerThread 详解 Android Handler 源码分析(详细)全部内容。
如果觉得小编网站内容还不错,欢迎将小编网站推荐给好友。
Android Handler消息机制
该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!另外,本系列文章知识可能需要有一定Android开发基础和项目经验的同学才能更好理解,也就是说该系列文章面向的是Android中高级开发工程师。
前言
上一篇我们介绍了LeakCanary工具用来分析内存泄漏以及谈了下几种常见内存泄漏的表现和解决方法。本篇内容我们来分析Android的消息机制。我们为什么要介绍Android的消息机制呢,因为Android系统本质上来说就是一个消息驱动的系统。我们在开发中什么时候会用到Handler呢,工作年限较长的开发工程师应该对这个Handler很熟悉了,因为在早期的开发中,无论是网络请求刷新UI还是子线程耗时任务的通知的应用场景都能看到Handler的身影。现在Handler在我们的日常开发中少了一些,因为我们有了RxJava、Retrofit等对Handler进行了很精美的封装。但是理解Android的消息机制对于理解Android系统的运作包括那些开源框架的原理都有很大帮助。
关于Android的消息机制网上也有好多文章,我本人也看了好多。但是不仅没有让我更清晰明了,反而让我陷入更深的迷惑。本篇的目的在于以一种相对更容易理解的方式来解释。
我们先来模拟一个场景,在Activity中执行了耗时操作,耗时操作完成之后显示一个Toast。这种应用场景还是比较常见的。我们来模拟代码。
public class MessageActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_message);
new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟耗时操作
Thread.sleep(3* 60 * 1000);
//耗时操作完成之后显示一个通知
Toast.makeText(MessageActivity.this,"test",Toast.LENGTH_SHORT).show();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
我们来运行上面的代码。呦呵,崩溃了,我们查看日志得到以下信息。
关于上面的崩溃我们稍后分析。
ActivityThread
既然讨论Android 消息机制,如果我们所有的操作都能在一个线程中完成貌似就不需要这个消息处理机制了,,但这又是不现实的,正是因为我们不能在一个线程中把所有的工作(网络请求、耗时操作、更新UI)在一个线程中完成,我们才有了多线程,多线程的互相协作才造就了我们这个Android欣欣向荣的世界。由此我们不得不说到我们Android App中的主线程(UI)线程,关于这个线程的叫法有很多。读者只需要知道不能在这个线程之外的线程直接对UI进行操作就行了。Android 4.0 以上甚至不能在主线程中(UI线程)中进行网络操作。否则的话会报android.os.NetworkOnMainThreadException,这个错误大家应该都见过把。那我们就从这个主线程(UI线程说起)。
public static void main(String[] args) {
......
//1 创建Looper 和 MessageQueue,本来该线程也是一个普通的线程,但是创建了Looper以及结合后文的Looper.loop()方法,使这个线程成为了Looper线程(读者可以简单的理解为拥有Looper的线程,而这个Looper就是Android消息处理机制的一部分)。
Looper.prepareMainLooper();
//2 建立与AMS的通信
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
......
//3 无限循环
Looper.loop();
//可以看出来主线程也是在无限的循环的,异常退出循环的时候会报错.
throw new RuntimeException("Main thread loop unexpectedly exited");
}
1 创建Looper 和 MessageQueue
public final class Looper {
......
public static void prepare() {
prepare(true);
}
//prepare 函数
private static void prepare(boolean quitAllowed) {
//判断sThreadLocal.get()是否为空,如果不为空说明已经为该线程设置了Looper,不能重复设置。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//如果sThreadLocal.get()为空,说明还没有为该线程设置Looper,那么创建Looper并设置
sThreadLocal.set(new Looper(quitAllowed));
}
//ActivityThread 调用Looper.prepareMainLooper();该函数调用prepare(false);
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
......
}
在这里呢有个静态变量sThreadLocal,它的定义如下
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
那么我们就得来讲解ThreadLocal这个类:
线程本地存储区(Thread Local Storage,简称为TLS),每个线程都有自己的私有的本地存储区域,不同线程之间彼此不能访问对方的TLS区域。这里线程自己的本地存储区域存放是线程自己的Looper。简单的来说就是通过ThreadLocal来进行Looper的统一存储和读取。那么接着来看被ThreadLocal存储的对象Looper的构造函数。
//Looper的构造函数
private Looper(boolean quitAllowed) {
//这里创建了MessageQueue
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
这里创建了MessageQueue为后续的步骤做准备,MessageQueue可以简单理解为一个“队列”(其底层实际上是一个单向链表),之所以是打上引号的“队列”,是因为其并不是严格意义上的队列,而是一个单项链表,使用者可以根据节点的优先级等等插入该链表。链表上的节点就是Message。第①步 整个的结构图如下所示
2 建立与AMS的通信
关于这一部分内容必须得对Android Binder知识有相关了解才能更好的理解。我们下一篇就会讲解Android Binder,到时候我们在回来这里。
3 无限循环
在上面的工作中我们已经准备好Looper和MessageQueue,下面就有了两个问题,① Message从何而来,② Message如何处理。
Message
我们在讨论Message的来源以及如何处理之前,先来看一下Message类
public class Message{
//消息码
public int what;
//handler
Handler target;
//下一级节点
Message next;
//消息发送的时间
long when;
}
上面的代码也从侧面证明了我们的MessageQueue是一个由Message组成的单向链表
我们先来看Message如何处理,至于为什么,当然是保证因为我们的思路不被打断,我们先分析ActivityThread的最后Looper.loop()函数做了什么。
② Message如何处理
我们来到了ActivityThread的最后一步Looper.loop()
ActivityThread.java
public static void loop() {
//得到Looper
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn''t called on this thread.");
}
//得到MessageQueue
final MessageQueue queue = me.mQueue;
......
for (;;) {//无限循环
Message msg = queue.next(); // 取下一个Message 可能阻塞在这里
if (msg == null) {
//如果队列为空直接return直接结束了该方法,即循环结束
return;
}
......
try {
//分发message到指定的target handler
msg.target.dispatchMessage(msg);
......
} finally {
}
......
}
}
主线程由此进入无限循环等待消息,有人看到这里就由疑问了,执行到for循环时,不就“卡死”在这个无限循环内了吗?其他的操作无法得到CPU怎么执行呢?关键点就在于queue.next()方法。
为了更好的理解这个方法我们先来讲一下关于线程阻塞与唤醒的知识
线程阻塞
什么是阻塞呢?比如某个时候你在等快递,但是你不知道快递什么时候过来,而且你没有别的事可以干(或者说接下来的事要等快递来了才能做);那么你可以去睡觉了,因为你知道快递把货送来时一定会给你打个电话(假定一定能叫醒你)。结合我们上面的代码。我们的代码运行Message msg = queue.next();这一句时,主线程可能一直阻塞在这里等待消息的到来(它去睡觉去了,也就是说我们的主线程,居然是大部分时间都在睡觉,心真大啊)。
注:线程阻塞跟线程忙循环轮询是有本质区别的,不要听到线程阻塞就以为是CPU一直在无限循环轮询状态啊。线程阻塞是不占用CPU资源的,但是线程忙循环轮询就不一样了,将几乎占满CPU资源。什么是CPU资源,简单的来说CPU资源就是分配给程序的执行时间。
线程唤醒
要想把主线程活动起来一般有两种方式:一种是系统唤醒主线程,并且将点击事件传递给主线程;第二种是其他线程使用Handler向MessageQueue中存放了一条消息,导致loop被唤醒继续执行。在下面的Message从何而来中我们这里使用了hander向MessageQueue中存放了一条消息,导致loop被唤醒继续执行。
① Message从何而来
public class MessageActivity extends AppCompatActivity {
private Handler mHandler= new Handler(){
//处理消息
@Override
public void handleMessage(Message msg) {
handleMsg(msg);
}
};
private void handleMsg(Message msg) {
switch (msg.what){
case 0:
Toast.makeText(this,"成功",Toast.LENGTH_SHORT).show();
break;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_message);
new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟耗时操作
Thread.sleep(3*1000);
//发送消息
mHandler.sendEmptyMessage(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
我们经常使用上面的代码来做耗时操作,那么这里这里我们的猪脚就出场了,mHandler是Handler的对象。我们来看一下Handler类
public class Handler {
//handler类有个Looper
final Looper mLooper;
//handler类有个MessageQueue
final MessageQueue mQueue;
//handler类有个Callback
final Callback mCallback;
public Handler() {//我们使用的是这一个
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
public Handler(boolean async) {
this(null, async);
}
public Handler(Callback callback, boolean async) {
//这里获取主线程的Looper,Handler的mLooper指向ThreadLocal内的Looper对象
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can''t create handler inside thread that has not called Looper.prepare()");
}
//这里获取主线程的Looper的MessageQueue,Handler的mQueue指向ThreadLocal内Looper对象内的MessageQueue对象
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
}
创建Handler 之后就调用 mHandler.sendEmptyMessage(0);发送消息(Handler的发送消息的方式有好多种,但这不是我们的重点),最终调用到Handler enqueueMessage 方法
Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
设置msg.target 为当前Handler对象
msg.target = this;
......
//调用MessageQueue的enqueueMessage()方法
return queue.enqueueMessage(msg, uptimeMillis);
}
我们再来看一下MessageQueue的enqueueMessage()
MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
......
msg.when = when;
Message p = mMessages;
//检测当前头指针是否为空(队列为空)或者没有设置when 或者设置的when比头指针的when要前
if (p == null || when == 0 || when < p.when) {
//插入队列头部,并且唤醒线程处理msg
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 几种情况要唤醒线程处理消息:1)队列是堵塞的 2)barrier,头部结点无target 3)当前msg是堵塞的
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // 将当前msg插入第一个比其when值大的结点前。
prev.next = msg;
}
//调用Native方法进行底层操作,在这里把那个沉睡的主线程唤醒
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
我们的Handler在发送消息的时候把自身设置给了msg.target,发送消息并唤醒Looper,Looper被唤醒后便使用queue.next()取出Message,并根据msg.target进行派发。Handler整体过程如下图
我们再稍微看下Handler的dispatchMessage方法
Handler.java
public void dispatchMessage(Message msg) {
if (msg.callback != null) {//判断有没有为Message设置callback(这里的callback是个Runnable接口,我们在为Message设置callback的时候需要自己实现run方法),如果设置了,那么调用Runnable实例的run方法
handleCallback(msg);
} else {
if (mCallback != null) {//判断Handler的mCallback是否为空(这里的Handler是个Callback接口,我们在为Handler设置mCallback的时候需要自己实现handleMessage方法),如果设置了,那么调用Callback实例的handleMessage方法
if (mCallback.handleMessage(msg)) {
return;
}
}
//调用handleMessage方法
handleMessage(msg);
}
}
我们在创建Handler使用的是无法构造函数,并重写了handleMessage方法,所以我们的重写的handleMessage得到调用,弹出了Toast
本篇总结
本篇比较详细的介绍了Android的消息机制,不过有一部分内容需要其他的知识作为基础才能更好的理解。不过这不影响我们分析Android的消息机制的整个流程。我们在这里再梳理一下。
1. 主线程准备Looper和MessageQueue
2. 创建一个线程(因为下面我们进入死循环了,所以在这之前创建一个线程用来处理,这是个Binder线程)
3. 主线程进入无限循环等待并处理消息。(这个消息可能是系统本身的消息,也有可能是我们自己的消息。在本例中分析的是我们自己创建的Handler发送的消息。)
我们再上个整图
这里呢我们呢是使用Activity的创建作为分析,因为这是Activity的起点。在注释第2步中的代码sendMessage(H.LAUNCH_ACTIVITY, r);与我们例子中 mHandler.sendEmptyMessage(0);并没有什么大的不同。
现在也是揭晓我们文章开头的那个崩溃的秘密的时候了,相信读者也有答案了。没错,是因为我们在非UI线程中更新了UI,导致了异常。原因是我们在子线程没有Looper啊。你可以做出如下更改就不会有异常了。
public class MessageActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_message);
new Thread(new Runnable() {
@Override
public void run() {
try {
//模拟耗时操作
Thread.sleep(3* 60 * 1000);
//在子线程中更新UI之前,先准备一个Looper,与主线程相同
if (Looper.myLooper() != null){
Looper.prepare();
}
//耗时操作完成之后显示一个通知
Toast.makeText(MessageActivity.this,"test",Toast.LENGTH_SHORT).show();
//无限循环
Looper.loop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
下篇预告
好了,我们下一篇介绍Android的Binder,Binder是个大工程哈。。
此致,敬礼
关于Android如何打印handler的消息队列和android 打印的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于android Handler 消息传递机制、Android Handler 消息机制、Android HandlerThread 详解 Android Handler 源码分析(详细)、Android Handler消息机制的相关信息,请在本站寻找。
本文标签: