特殊流程
-
- 订单为延迟订单,比如客人要求30分钟后人齐了再制作,这时会把该订单按时间排序放到订单队列的合适位置,并通过SystemClock.uptimeMillis()定好闹铃。至于为什么用uptimeMillis是因为该时间是系统启动开始计算的毫秒时间,不受手动调控时间的影响。
-
- 如果打单机中全是延迟订单,则下令给后厨厨师休息,并在门口贴上免打扰的牌子(needWake),等待闹铃提醒,如有新的即时订单进来并且发现有免打扰的牌子,则通过nativeWake()唤醒厨师再开始制作上菜。
-
- 但是为了提升店铺菜品覆盖,很多相邻的店铺都选择合作经营,就是你可以混搭旁边店的餐到本店吃,此时只需点餐员提交旁边店的订单即可,这样旁边店的厨师长就可以通过打单机取出订单并进行制作和上菜。
总结
一家店可以有多个点餐员,但是厨师长只能有一个。打单机也只能有一个。
映射到以上场景中,一家店就好比一个Thread,而一个Thread中可以有多个Handler(点餐员),但只能有一个Looper(厨师长),一个MessageQueue(打单机),和多个Message(订单)。
根据以上的例子我们类比看下源码,充分研究下整个机制的流程,和实现原理。
Looper的工作流程
ActivityThread.main();//初始化入口
- Looper.prepareMainLooper(); //初始化
Looper.prepare(false); //设置不可关闭
Looper.sThreadLocal.set(new Looper(quitAllowed)); //跟线程绑定
1.1.Looper.mQueue = new MessageQueue(quitAllowed); //Looper和MessageQueue绑定
1.2.Looper.mThread = Thread.currentThread(); - Looper.loop();
2.1.myLooper().mQueue.next(); //循环获取MessageQueue中的消息
nativePollOnce(); //阻塞队列
native -> pollInner() //底层阻塞实现
native -> epoll_wait();
2.2.Handler.dispatchMessage(msg);//消息分发
myLooper().mQueue.next()实现原理
-
- 通过myLooper().mQueue.next() 循环获取MessageQueue中的消息,如遇到同步屏障 则优先处理异步消息.
-
- 同步屏障即为用Message.postSyncBarrier()发送的消息,该消息的target没有绑定Handler。在Hnandler中异步消息优先级高于同步消息。
-
- 可通过创建new Handler(true)发送异步消息。ViewRootImpl.scheduleTraversals方法就使用了同步屏障,保证UI绘制优先执行。
Handler.dispatchMessage(msg)实现原理
-
- 优先回调msg.callback。
-
- 其次回调handler构造函数中的callback。
-
- 最后回调handler handleMessage()。
Hander发送消息的流程
1.Handler handler = new Handler();//初始化Handler
1.Handler.mLooper = Looper.myLooper();//获取当前线程Looper。
2.Handler.mQueue = mLooper.mQueue;//获取Looper绑定的MessageQueue对象。
2.handler.post(Runnable);//发送消息
sendMessageDelayed(Message msg, long delayMillis);
sendMessageAtTime(Message msg, long uptimeMillis);
Handler.enqueueMessage();//Message.target 赋值为this。
Handler.mQueue.enqueueMessage();//添加消息到MessageQueue。
MessageQueue.enqueueMessage()方法实现原理
-
- 如果消息队列被放弃,则抛出异常。
-
- 如果当前插入消息是即时消息,则将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,并通过needWake唤醒Looper线程。
-
- 如果消息为异步消息则通过Message.when长短插入到队列对应位置,不唤醒Looper线程。
经常有人问为什么主线程的Looper阻塞不会导致ANR?
-
- 首先我们得知道ANR是主线程5秒内没有响应。
-
- 什么叫5秒没有响应呢?Android系统中所有的操作均通过Handler添加事件到事件队列,Looper循环去队列去取事件进行执行。如果主线程事件反馈超过了5秒则提示ANR。
-
- 如果没有事件进来,基于Linux pipe/epoll机制会阻塞loop方法中的queue.next()中的nativePollOnce()不会报ANR。
-
- 对于以上的例子来说,ANR可以理解为用户进行即时点餐后没按时上菜(当然未按时上菜的原因很多,可能做的慢(耗时操作IO等),也可能厨具被占用(死锁),还有可能厨师不够多(CPU性能差)等等。。。),顾客发起了投诉,或差评。但如果约定时间还没到,或者当前没人点餐,是不会有差评或投诉产生的,因此也不会产生ANR。
以上的所有内容均围绕原理,源码,接下来我们举几个特殊场景的例子
1.为什么子线程不能直接new Handler()?
new Thread(new Runnable() {
@Override
public void run() {
Handler handler = new Handler();
}
}).start();
以上代码会报以下下错误
java.lang.RuntimeException: Can’t create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
at android.os.Handler.(Handler.java:207)
at android.os.Handler.(Handler.java:119)
at com.example.test.MainActivity$2.run(MainActivity.java:21)
at java.lang.Thread.run(Thread.java:919)
- 通过报错提示**“not called Looper.prepare()”**可以看出提示没有调用Looper.prepare(),至于为什么我们还得看下源码
public Handler(Callback callback, boolean async) {
…省略若干代码
//通过 Looper.myLooper()获取了mLooper 对象,如果mLooper ==null则抛异常
mLooper = Looper.myLooper();
if (mLooper == null) {
//可以看到异常就是从这报出去的
throw new RuntimeException(
"Can’t create handler inside thread " + Thread.currentThread()
- " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public static @Nullable Looper myLooper() {
//而myLooper()是通过sThreadLocal.get()获取的,那sThreadLocal又是个什么鬼?
return sThreadLocal.get();
}
//可以看到sThreadLocal 是一个ThreadLocal对象,那ThreadLocal值从哪赋值的?
static final ThreadLocal sThreadLocal = new ThreadLocal();
//sThreadLocal 的值就是在这个方法里赋值的
private static 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.prepare()方法,而Looper.prepare()方法就是sThreadLocal赋值的位置。
那子线程怎么创建Handler呢?只需在new Handler()之前调用下Looper.prepare()即可。
2. 为什么主线程可以直接new Handler?
- 子线程直接new Handler会报错,主线程为什么就不会报错呢?主线程我也没有调用Looper.prepare()啊?那么我们还得看下源码了。
//我们看下ActivityMain的入口main方法,调用了 Looper.prepareMainLooper();
public static void main(String[] args) {
…
Looper.prepareMainLooper();
…
}
//看到这一下就明白了,原来主线程在启动的时候默认就调用了prepareMainLooper(),而在这个方法中调用了prepare()。
//提前将sThreadLocal 进行赋值了。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException(“The main Looper has already been prepared.”);
}
sMainLooper = myLooper();
}
}
3.Handler为什么会内存泄露?
-
首先普及下什么叫内存泄露,当一个对象不再使用本该被回收时,但另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这种情况下就产生了内存泄漏。
-
我们举一个Handler内存泄露的场景。
public class HandlerActivity extends AppCompatActivity {
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
handler.sendEmptyMessageDelayed(1,5000);
}
}
当以上代码写完后编译器立马会报黄并提示 “this handler should be static or leaks might occur…Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.”
大致意思就说 “由于这个处理程序被声明为一个内部类,它可以防止外部类被垃圾回收。如果处理程序正在对主线程以外的线程使用Looper或MessageQueue,则不存在问题。如果处理程序正在使用主线程的Looper或MessageQueue,则需要修复处理程序声明,如下所示:将处理程序声明为静态类;并且通过WeakReference引用外部类”。
说了这么一大堆,简单意思就是说以上这种写法,默认会引用HandlerActivity,当HandlerActivity被finish的时候,可能Handler还在执行不能会回收,同时由于Handler隐式引用了HandlerActivity,导致了HandlerActivity也不能被回收,所以内存泄露了。
我们来写一种正确的写法
public class HandlerActivity extends AppCompatActivity {
MyHandler handler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
handler.sendEmptyMessageDelayed(1,5000);
}
private static class MyHandler extends Handler{
private WeakReference activityWeakReference;
public MyHandler(HandlerActivity activity) {
activityWeakReference = new WeakReference<>(activity);
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/51aa154949c4e850048546cd2fd44dd9.jpeg)
文末
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
最后想要拿高薪实现技术提升薪水得到质的飞跃。最快捷的方式,就是有人可以带着你一起分析,这样学习起来最为高效,所以为了大家能够顺利进阶中高级、架构师,我特地为大家准备了一套高手学习的源码和框架视频等精品Android架构师教程,保证你学了以后保证薪资上升一个台阶。
当你有了学习线路,学习哪些内容,也知道以后的路怎么走了,理论看多了总要实践的。
进阶学习视频
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-N59USV6u-1713606530916)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!