三菱M70系统解锁密码

「Handler」,老生常谈,网上关于它的文章可谓是“泛滥成灾”,不过实际开发中用得不多。毕竟,现在写异步,「RxAndroid链式调用」就是「Kotlin协程同步方式写异步代码」,香?不过,面试官还是喜欢「章口就莱」一句:什么叫做国际巨星啊 不好意思读错台词…

当然,应对方法也很简单,找一篇《…Handler详解》之类的文章,背熟即可~不过,对于我这种好 寻花问柳 寻根问底的人来说,自己过一遍源码心理才踏实,而且,我发现「带着问题」看源码,思考理解本质,印象会更深,收获也更多,遂有此文。
罗列一波本文提及的问题,如果有答不出的可按需阅读本文,谢谢~

1、Handler问题三连:是什么?有什么用?为什么要用Handler,不用行不行?
2、真的只能在主(UI)线程中更新UI吗?
3、真的不能在主(UI)线程中执行网络操作吗?
4、Handler怎么用?
5、为什么建议使用Message.obtain()来创建Message实例?
6、为什么子线程中不可以直接new Handler()而主线程中可以?
7、主线程给子线程的Handler发送消息怎么写?
8、HandlerThread实现的核心原理?
9、当你用Handler发送一个Message,发生了什么?
10、Looper是怎么拣队列里的消息的?
11、分发给Handler的消息是怎么处理的?
12、Looper在主线程中死循环,为啥不会ANR?
13、Handler泄露的原因及正确写法

0x1、Handler问题三连

1.Handler是什么

答:Android定义的一套「子线程与主线程间通讯」的「消息传递机制」。

2.Handler有什么用
答:把子线程中的 UI更新信息,传递 给主线程(UI线程),以此完成UI更新操作。

3.为什么要用Handler,不用行不行

答:不行,因为android在设计之初就封装了一套消息创建、传递、处理。如果不遵循就不能更新UI信息,就会报出异常(所谓的异步消息处理)
在Android中,为了提高系统运行效率,没有采用「线程锁」,带来了:

多个线程并发更新UI时的线程安全问题

为了安全保证UI操作是线程安全的,规定

只能在主线程(UI线程)中完成UI更新

但,真的只能在UI线程中更新UI吗?

上面这段代码 直接在子线程中更新了UI,却没有报错:

这是要打脸吗?但如果在子线程中加句线程休眠模拟耗时操作的话:

程序就崩溃了,报错如下:

翻译一下异常信息:只有创建这个view的线程才能操作这个view。限于篇幅,这里就不去跟源码了,直接说原因:

ViewRootImp 在 onCreate() 时还没创建;在 onResume()时,即ActivityThread的handleResumeActivity() 执行后才创建,调用 requestLayout(),走到 checkThread() 时就报错了。

可以打个日志简单的验证下:

加上休眠:

行吧,以后去面试别人问「子线程是不是一定不可以更新UI」别傻乎乎的说是了。

4.引生的另一个问题

说到「只能在主线程中更新UI」我又想到另一个问题「不能在主线程中进行网络操作」

上述代码运行直接闪退,日志如下:

NetworkOnMainThreadException:网络请求在主线程进行异常。

em… 真的不能在主线程中做网络操作吗?

在 onCreate() 的 setContentView() 后插入下面两句代码:

运行下看看:

这…又打脸?先说下 StrictMode(严苟模式)

Android 2.3 引入,用于检测两大问题:ThreadPolicy(线程策略) 和 VmPolicy(VM策略)

相关方法如下:

把严苟模式的网络检测关了,就可以 在主线程中执行网络操作了,不过一般是不建议这样做的:

在主线程中进行耗时操作,可能会导致程序无响应,即 ANR (Application Not Responding)。

至于常见的ANR时间,可以在对应的源码中找到:
// ActiveServices.java → Service服务static final int SERVICE_TIMEOUT = 201000; // 前台static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10; // 后台// ActivityManagerService.java → Broadcast广播、InputDispatching、ContentProviderstatic final int BROADCAST_FG_TIMEOUT = 101000; // 前台static final int BROADCAST_BG_TIMEOUT = 601000; // 后台static final int KEY_DISPATCHING_TIMEOUT = 51000; // 关键调度static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT = 10*1000; // 内容提供者复制代码
时间统计区间:

起点:System_Server 进程调用 startProcessLocked 后调用 AMS.attachApplicationLocked()
终点:Provider 进程 installProvider及publishContentProviders 调用到 AMS.publishContentProviders()
超过这个时间,系统就会杀掉 Provider 进程。

0x2、Handler怎么用

1.sendMessage() + handleMessage()

代码示例如下:

黄色部分会有如下警告

Handler不是静态类可能引起「内存泄露」,原因以及正确写法等下再讲。另外,建议调用 Message.obtain() 函数来获取一个Message实例,为啥?点进源码:

从源码可以看到obtain()的逻辑:

1、判断Message池是否为空;
2、不为空,取出一个Message对象,池容量-1,返回;
3、否则,新建一个Message对象,返回;

这样可以「避免重复创建多个实例对象」节约内存,还有,Message池其实是一个「单链表结构」,定位到下述代码可以看到:池的容量为50

然后问题来了,Message信息什么时候加到池中?

当Message 被Looper分发完后,会调用 recycleUnchecked()函数,回收没有在使用的message对象。

如果你懂点数据结构的话,可以看出这是「单链表的头插法」

2.post(runnable)

代码示例如下:

跟下post():

实际上调用了 sendMessageDelayed() 发送消息,只不过延迟秒数为0,那Runnable是怎么变成Message的呢?跟下getPostMessage()

噢,获取一个新的Message示例后,把Runnable变量的值赋值给callback属性

3.附:其他两个种在子线程中更新UI的方法

activity.runOnUiThread()

view.post() 与 view.postDelay()

0x3、Handler底层原理解析

终于来到稍微有点技术含量的缓解,在观摩源码了解原理前,先说下几个涉及到的类。

1.涉及到的几个类

2.前戏

在我们使用Handler前,Android系统已为我们做了一系列的工作,其中就包括了

创建「Looper」和「MessageQueue」对象

上图中有写:ActivityThread的main函数是APP进程的入口,定位到 ActivityThread → main函数
// ActivityThread.main()Looper.prepareMainLooper(); // 创建Looper和MessageQueue对象,用于处理主线程消息ActivityThread thread = new ActivityThread(); // 实例化ActivityThread对象thread.attach(false, startSeq); // 与主线程进行绑定Looper.loop(); // 消息循环运行,死循环,正常不会执行后续代码throw new RuntimeException(“Main thread loop unexpectedly exited”);复制代码
定位到:Looper → prepareMainLooper函数
prepare(false) // 设置Looper不可关闭复制代码
定位到:Looper → prepare函数
sThreadLocal.set(new Looper(quitAllowed)); // 创建Looper复制代码
定位到:Looper → Looper构造函数
mQueue = new MessageQueue(quitAllowed); // Looper与MessageQueue绑定mThread = Thread.currentThread(); // Looper与线程绑定复制代码
另外这里的 quitAllowed 变量,直译「退出允许」,具体作用是?跟下 MessageQueue:

em…用来 防止开发者手动终止消息队列,停止Looper循环。

3.消息队列的运行

前戏过后,创建了Looper与MessageQueue对象,接着调用Looper.loop()开启轮询。定位到:Looper → loop函数
// Looper.loop()final Looper me = myLooper(); // 获得当前线程的Looper实例final MessageQueue queue = me.mQueue; // 获取消息队列for (; { // 死循环 Message msg = queue.next(); // 取出队列中的消息 msg.target.dispatchMessage(msg); // 将消息分发给Handler

你可能感兴趣的:(三菱M70系统解锁密码)