Android系统分析之带着问题看Handler

1 Handler问题三连:是什么?有什么用?为什么要用,不用行不行?

1.1 Handler是什么?

答:Handler是Android FrameWork架构中的一个基础组件

1.2 Handler有什么用?

答:把子线程中的UI更新信息传递给主线程(UI线程),以此完成UI更新操作;

1.3 Handler为什么要用,不用行不行?

答:不行,Handler是Android在设计之初就封装的一套消息创建、传递、处理机制。Android要求在主线程(UI线程)中更新UI

2 真的只能在主(UI)线程中更新UI吗?

答:Android要求在主线程(UI线程)中更新UI,是要求,不是规定,硬要在子线程中更新UI也是可以的

2.1 为什么可在一个子线程中创建一个对话框,并可更新对话框的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()
}

2.2 为什么直接在子线程中更新主线程创建的UI,没有报错;而如果延迟300毫秒后就报错了?

答: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.");
     }
}

3 Android UI更新机制(GUI) 为何设计成了单线程的?

答:因为多线程对同一个UI控件操作,容易造成不可控的错误。而必须解决这种多线程安全问题,简单的做法是加锁,不是加一个,而是每层加锁(用户代码–GUI顶层–GUI底层),但是这意味着更多耗时以及UI更新效率低下,而如果每层共用一把锁的话,其实就是单线程。因此,Android没有采用线程锁,而是采用单线程消息队列机制,实现了一个伪锁

4 真的不能在主(UI)线程中执行网络操作吗?

答:通常,网络请求在主线程进行,会抛出异常NetworkOnMainThreadException,因为Android 2.3引入用于检测两大问题:ThreadPolicy(线程策略) 和 VmPolicy(VM策略)。在onCreate()的setContentView()后加上permitNetWork(),把严苟模式的网络监测关了,就可以在主线程执行网络请求了

5 Handler怎么用?

答:有两个方式,一是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)

5.1 那Runnable是怎么变成Message的呢?

答:在getPostMessage()方法中,通过Message.obtain()获取一个新的Mesage对象,把Runnable变量的值赋值给callback属性

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

5.2 其他两个种在子线程中更新UI的方法?

答:activity.runOnUiThread(),和view.post()与view.postDelay()

6 为什么建议使用Message.obtain()来创建Message实例?

答:因为随着事件不断发送,会频繁大量创建消息对象,带来内存消耗;因此使用Message.obtain()通过消息池复用消息对象,可以避免重复创建对象,节约内存

6.1 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();
    }

Android系统分析之带着问题看Handler_第1张图片
Android系统分析之带着问题看Handler_第2张图片

6.2 消息对象时什么时候加到池中?

答:当消息对象被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++;
            }
        }
    }

Android系统分析之带着问题看Handler_第3张图片

7 Handler涉及到的类有哪几个?

答:主要有6个,分别是如下:
Android系统分析之带着问题看Handler_第4张图片
图片来源于:1、换个姿势,带着问题看Handler

7 为什么子线程中不可以直接new 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;
}

7.1 具体的Looper.prepareMainLooper()的初始化过程是?

定位到: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与线程绑定
}

7.2 mQuitAllowed变量,直译「退出允许」,具体作用是?

答:用来防止开发者手动终止消息队列,停止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);
        }
    }

7.3 一个线程中初始化多个handler,会产生多少个Looper?主线程和子线程Looper是同一个么?

答:不会,一个线程只能对应一个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变量中
}

8 ThreadLocal是如何将Looper对象存放在Thread线程里,并解决并发访问的冲突问题的?

答: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();
}

10 主线程给子线程的Handler发送消息怎么写?

答:普通实现方法中,子线程初始化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()
        }
    }

11 HandlerThread实现的核心原理?

答:①HandlerThread = 继承线程 + 封装Looper;②getLooper()加锁死循环wait()等待,而堵塞线程;③线程的run方法中,加锁等待当前线程的Looper对象创建成功,再notifyAll()通知getLooper()中的wait()等待,说Looper对象已经创建成功了;④等待唤醒后,getLooper()返回在run方法中创建的Looper对象
Android系统分析之带着问题看Handler_第5张图片
Android系统分析之带着问题看Handler_第6张图片
图片来源于:1、换个姿势,带着问题看Handler

11 Looper是怎么循环分拣队列里的消息的?

答:ActivityThread在main函数中调用Looper.prepareMainLooper完成主线程的Loper初始化,然后调用Looper.loop()开启消息循环等待接收(分拣)消息。消息循环如下UML图,这个过程分为7个步骤:
Android系统分析之带着问题看Handler_第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函数:,
Android系统分析之带着问题看Handler_第8张图片

(3)第三步定位到:Looper → loop函数,

(4)第四步定位到:Looper → loop函数,

(5)第五步定位到:Looper → loop函数,

(6)第六步定位到:Looper → loop函数,

(7)第七步定位到:Looper → loop函数,

10 当我们用Handler发送一个消息,发生了什么?

答:

12 分发给Handler的消息是怎么处理的?

答:

9 如果只有一个looper,looper如何区分handler,handler发送了消息会不会导致Looper错乱,最终不知道谁处理。

答:

13 IdleHandler是什么?有什么作用?应用场景是什么?

答:

14 Looper在主线程中死循环,为啥不会ANR?

答:

15 Handler泄露的原因及正确写法?

答:

16 Handler中的同步屏障机制是什么?同步屏障使用场景?

答:

17 Android 11 Handler相关变更?

答:

20 学习链接

1、换个姿势,带着问题看Handler

2、Android Handler:手把手带你深入分析 Handler机制源码

你可能感兴趣的:(Android系统分析)