答:Handler是Android FrameWork架构中的一个基础组件;
答:把子线程中的UI更新信息传递给主线程(UI线程),以此完成UI更新操作;
答:不行,Handler是Android在设计之初就封装的一套消息创建、传递、处理机制。Android要求在主线程(UI线程)中更新UI。
答:Android要求在主线程(UI线程)中更新UI,是要求,不是规定,硬要在子线程中更新UI也是可以的。
答:Android的UI更新(GUI)被设计成了单线程,子线程可更新子线程创建的UI、不能更新主线程创建的UI。案例如下:
private lateinit var textView: TextView
thread {
Looper.prepare()
val dialog = AlertDialog.Builder(this)
.apply {
setIcon(R.drawable.ic_launcher)
setTitle("子线程创建的对话框")
setCancelable(true)
setNegativeButton("子线程更新 主线程创建的UI", object : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface?, which: Int) {
// 抛出异常:android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
// 翻译后是:只有创建这个view的线程才能操作这个view;
textView.text = "子线程更新 主线程创建的UI ${Thread.currentThread().name}"
}
})
setPositiveButton("子线程更新 子线程创建的UI", object : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface?, which: Int) {
setTitle("子线程更新 子线程创建的UI ${Thread.currentThread().name}")
}
})
}.create()
dialog.show()
Looper.loop()
}
答:Android系统在onResume()时会检查:只有创建这个view的线程才能操作这个view,否则抛出异常。①ViewRootImp在onCreate()时还没创建,所以子线程中更新了 主线程创建的UI。②在onResume()时,ActivityThread的handleResumeActivity()执行后才创建ViewRootImp,然后调用requestLayout(),走到checkThread()检查时抛出异常。案例如下:
fun onCreate(savedInstanceState: Bundle?) {
thread {
// 直接在子线程中更新了 主线程创建的UI,却没有报错:
textView.text = "子线程更新UI ${Thread.currentThread().name}"
}
thread {
// 2、加上休眠300毫秒,程序就崩溃了
Thread.sleep(300)
textView.text = "子线程更新UI ${Thread.currentThread().name}"
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException ("Only the original thread that created a view hierarchy can touch its views.");
}
}
答:因为多线程对同一个UI控件操作,容易造成不可控的错误。而必须解决这种多线程安全问题,简单的做法是加锁,不是加一个,而是每层加锁(用户代码–GUI顶层–GUI底层),但是这意味着更多耗时以及UI更新效率低下,而如果每层共用一把锁的话,其实就是单线程。因此,Android没有采用线程锁,而是采用单线程消息队列机制,实现了一个伪锁。
答:能。通常,网络请求在主线程进行,会抛出异常NetworkOnMainThreadException,因为Android 2.3引入用于检测两大问题:ThreadPolicy(线程策略) 和 VmPolicy(VM策略)。在onCreate()的setContentView()后加上permitNetWork(),把严苟模式的网络监测关了,就可以在主线程执行网络请求了。
答:有两个方式,一是sendMessage() + handleMessage();二是post(runnable)。其中,post(Runnable r)调用的是sendMessageDelayed(getPostMessage®, 0)发送消息,只不过延迟秒数为0。案例如下:
// 方式-:sendMessage() + handleMessage()
// 步骤1:在主线程中 通过匿名内部类 创建Handler类对象
val mhandler = Handler() {
// 通过复写handlerMessage()从而确定更新UI的操作
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
// 需执行的UI操作
}
}
// 步骤2:创建消息对象
val msg = Message.obtain() // 实例化消息对象
msg.what = 1 // 消息标识
msg.obj = "AA" // 消息内容存放
// 步骤3:在工作线程中 通过Handler发送消息到消息队列中
mHandler.sendMessage(msg)
// 步骤4:开启工作线程(同时启动了Handler)
// 方式二是post(runnable)
// 步骤1:在主线程中创建Handler实例
val mhandler = Handler()
// 步骤2:在工作线程中 发送消息到消息队列中 & 指定操作UI内容,需传入1个Runnable对象
mHandler.post(Runnable {
// 需执行的UI操作
}
})
// 步骤3:开启工作线程(同时启动了Handler)
答:在getPostMessage()方法中,通过Message.obtain()获取一个新的Mesage对象,把Runnable变量的值赋值给callback属性。
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
答:activity.runOnUiThread(),和view.post()与view.postDelay();
答:因为随着事件不断发送,会频繁大量创建消息对象,带来内存消耗;因此使用Message.obtain()通过消息池复用消息对象,可以避免重复创建对象,节约内存。
答:分析obtain()的逻辑(如下),加锁判断消息池是否为空?不为空,取消息池的链表表头消息对象返回,正在使用标记为0,吃容量-1;为空,创建一个新的消息对象返回。Message池其实是一个单链表。获取消息池逻辑如下图:
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
答:当消息对象被Looper分发完后,在loop()最后会调用msg.recycleUnchecked()函数,回收没有被使用的消息对象。具体逻辑是:标记设置为FLAG_IN_USE,表示正在使用,相关属性重置;加锁判断消息池是否满50,未满,采用单链表头插法把消息插入到链表表头。回收消息逻辑如下图:
void recycleUnchecked() {
// ......
flags = FLAG_IN_USE; // 表示正在使用
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) { // MAX_POOL_SIZE = 50
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
答:主要有6个,分别是如下:
图片来源于:1、换个姿势,带着问题看Handler
答:在new Handler()时,调用Looper.myLooper()获取当前线程的Looper对象,若线程无Looper对象则抛出异常,异常和逻辑如下图所示。主线程在启动时在ActivityThread的main函数中,调用Looper.prepareMainLooper()创建了Looper和MessageQueue对象,完成了初始化。而子线程还需要额外调用Looper.prepare()和Looper.loop()开启轮询,否则会报错。
public Handler(Callback callback, boolean async) {
// 1. 指定Looper对象
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
}
// 2. handler对象绑定Looper的消息队列对象(MessageQueue)
mQueue = mLooper.mQueue;
}
定位到:ActivityThread.main()
public static void main(String[] args) {
//...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
定位到:Looper.prepareMainLooper();
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
定位到:Looper → prepare函数
public static final void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed)); // 创建Looper对象,并存放在ThreadLocal变量中
}
定位到:Looper → Looper构造函数
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); // 创建1个Looper对象时,创建一个与之绑定的消息队列对象
mRun = true;
mThread = Thread.currentThread(); // Looper与线程绑定
}
答:用来防止开发者手动终止消息队列,停止Looper循环。定位到:MessageQueue → quit函数:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
答:不会,一个线程只能对应一个Looper,主线程和子线程Looper不是同一个。在Looper.prepare()方法中会调用sThreadLocal.get()获取当前线程的Looper,当多次调用不为空时会抛出异常。逻辑如下:
public static final void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed)); // 创建Looper对象,并存放在ThreadLocal变量中
}
答:ThreadLocal是线程局部变量,为每个线程提供一个独立变量副本。每个线程内部都维护了一个ThreadLocalMap,变量名是threadLocals,这个map的key是ThreadLocal。在存值时,通过当前线程取出线程的变量threadLocals,以ThreadLocal为key,存储一个值;在获取值时,过当前线程取出线程的变量threadLocals,以ThreadLocal为key,获取一个值。因为存和取都在线程自己的变量中操作,所以不存在线程安全问题。以下3 个源码辅助理解:
(1)定位到:ThreadLocal → set函数:
public void set(T value) {
// 取出当前线程
Thread t = Thread.currentThread();
// 通过当前线程取出线程的成员(ThreadLocalMap)threadLocals(2)
ThreadLocalMap map = getMap(t);
if (map != null) {
// 向ThreadLocalMap,以ThreadLocal为key,存储一个值 (---->源码看:2.5 再深入分析)
map.set(this, value);
} else {
// 创建Thread成员threadLocals(3)
createMap(t, value);
}
}
(2)定位到:ThreadLocal → getMap函数:
ThreadLocalMap getMap(Thread t) {
// 取出Thread的成员
return t.threadLocals;
}
(3)定位到:ThreadLocal → get函数:
public T get() {
// 取出当前线程
Thread t = Thread.currentThread();
// 不同线程有不同ThreadLocalMap,就是有不同的副本
ThreadLocalMap map = getMap(t);
if (map != null) {
// 根据key获取table中的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
return (T)e.value;
}
}
return setInitialValue();
}
答:普通实现方法中,子线程初始化Handler,存在报空指针风险,因为:多线程并发的问题,当主线程执行到sendMessage时,子线程的Handler还没有初始化。因此,最优方法是:通过HandlerThread获取子线程的Looper,再在主线程初始化Handler,并传入子线程的Looper。
// 在主线程中给子线程的Handler发送信息
fun mainSendMessageToThread(view: View) {
val thread = LooperThread()
thread.start()
// 1.报空指针,因为:多线程并发的问题,当主线程执行到sendEmptyMessage时,子线程的Handler还没有初始化
// thread.mHandler!!.sendEmptyMessage(0x123)
// 2.解决方法是:主线程延时给子线程发消息,等待子线程的Handler完成初始化
Handler().postDelayed(Runnable {
thread.mHandler!!.sendMessage(obtainSyncMessage(0x123, "同步消息"))
}, 1000)
// 3.更优解决方法是:通过HandlerThread获取子线程的Looper,再在主线程初始化Handler,并传入子线程的Looper
initHandlerThread()
}
// 通过HandlerThread获取子线程的Looper,再在主线程初始化Handler,并传入子线程的Looper
private fun initHandlerThread() {
val handlerThread = HandlerThread("Thread-1")
handlerThread.start()
val handler = MyHandler(handlerThread.looper)
handler.sendMessage(obtainSyncMessage(0x123, "同步消息"))
}
/**
* 子线程的Handler接收信息
*/
private inner class LooperThread : Thread() {
var mHandler: Handler? = null
override fun run() {
Looper.prepare()
// 存在报空指针风险,因为:多线程并发的问题,当主线程执行到sendEmptyMessage时,子线程的Handler还没有初始化
mHandler = MyHandler(Looper.myLooper())
Looper.loop()
}
}
答:①HandlerThread = 继承线程 + 封装Looper;②getLooper()加锁死循环wait()等待,而堵塞线程;③线程的run方法中,加锁等待当前线程的Looper对象创建成功,再notifyAll()通知getLooper()中的wait()等待,说Looper对象已经创建成功了;④等待唤醒后,getLooper()返回在run方法中创建的Looper对象。
图片来源于:1、换个姿势,带着问题看Handler
答:ActivityThread在main函数中调用Looper.prepareMainLooper完成主线程的Loper初始化,然后调用Looper.loop()开启消息循环等待接收(分拣)消息。消息循环如下UML图,这个过程分为7个步骤:
(1)第一步定位到:Looper → loop函数,首先获得当前线程的Looper对象和消息队列,然后循环不断地检查消息队列中是否有新消息需要处理,有就取出消息判空后分发给Handerl处理,没有就在消息队列的next()中进入睡眠状态,等待新消息。
public static void loop() {
Looper.loop()final Looper me = myLooper(); // 获得当前线程的Looper实例
final MessageQueue queue = me.mQueue; // 获取消息队列
for (;;) { // 死循环
Message msg = queue.next(); // 取出队列中的消息
if (msg == null) {
return;
}
msg.target.dispatchMessage(msg); // 将消息分发给Handler
}
}
(2)第二步定位到:MessageQueue -> next函数:,
(3)第三步定位到:Looper → loop函数,
(4)第四步定位到:Looper → loop函数,
(5)第五步定位到:Looper → loop函数,
(6)第六步定位到:Looper → loop函数,
(7)第七步定位到:Looper → loop函数,
答:
答:
答:
答:
答:
答:
答:
答:
1、换个姿势,带着问题看Handler
2、Android Handler:手把手带你深入分析 Handler机制源码