1、数据通信会带来什么开发中的问题?
(1)线程间如何进行通信
Handler通信实现的方案实际上是内存共享方案。
(2)为什么线程间不会干扰
(3)为什么wait/notify的用武之地不大
因为Handler已经将需要这部分功能进行了Linux层的封装
1、Looper的创建,如果想在一个线程中使用Handler则第一步为执行Looper.prepare ,Looper中存在一个静态的变量ThreadLocal,prepare方法中new一个Looper,并将这个Looper保存到ThreadLocal中。
2、Looper的无线循环启动,在子线程的run方法中的最后,启动Looper.loop, 这个是一个无限循环,一直从MQ中获取消息进行处理。
3、MessageQuenue的创建,MQ是Looper的成员变量,在Looper的构造函数中创建。
4、Handler的创建,在任意线程中创建Handler,都会通过Looper的ThreadLocal获取一下当前线程的Looper,如果没有获得Looper则抛出异常,Handler会持有Looper和从Looper中获取的MQ、
5、使用,Handler.sendMessage,最终都会调用MQ的enqueueMessage将消息入队
6、在Looper从MQ中拿到消息后,就会调用与消息绑定的Handler的handlerMessage方法,也就是msg.handler.handlerMessage处理消息。
1、主线程的Handler启动
一个APP的启动流程是从桌面启动器Lanuncher点击图标-->fork一个zygote进程,分配一个JVM,JVM的main函数在ActivityThread中,在ActivityThread的main方法中,执行
Looper.prepareMainLooper,为主线程准备一个Looper,执行Loop.loop开始循环MQ。
Loop.loop中是一个死循环,一直调用MQ的queue.next查询读取消息。
因为loop是个死循环,也就是所有的代码都会在Loop中执行。
线程间通信只是Handler的一个附属的功能,真实的作用是所有的代码都在Handler中执行。维持着Android APP运行的框架。所以要重视。
那Loop如何停掉,
for(;;){
Message msg = queue.next();
if(msg == null){//什么时候返回一个为空的msg的message,1、应用退出,调用quit()
//没有消息 ,意味着这messageQueue正在退出。
return;
}
}
handler-->sendMessage,消息起点
handlerMessage //消息结束。中间发生了什么,需要看源码。
image.png
优先级队列
入队: Handler.sendMessage-> MQ-> enqueueMessage,向MQ中放入Message,在消息队列中插入一个消息。
出队: Loop.loop 中MQ.next()方法,出队列。那是谁来取吶,是Loop来调用,
Looper.loop->MQ.next->msg.target.dispatch->handleMessage()。
问题,从细节上来说,Message在动的过程
Message怎么来的,new 或者 obtain;无论Message怎么创建,都是一块内存。
子线程
主线程
因为内存不分线程,所以子线程和主线程都可以使用一块内存。
handle = new Handler(){
handleMessage()
{
}
}
new Thead(new Runable(){
void run(){
Looper.prepare
Loop.loop
handle.sendEmptyMessage(new Message);//子线程创建消息。
}
})
MQ的数据结构,由单链表形成的优先级队列。优先级队列是有顺序的。
Message.next-->Message-->next-->Message
先后顺序,时间,先后顺序。
有sendMessageAtTime方法,有时间顺序。
那么是怎么排序的吶?看enqueueMessage
if (p == null || when == 0 || when < p.when) {这个是队列为空。看else不为空
{
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {//找到一个p,当p为空或者p的执行时间大于当前入队msg的时间
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next//将msg按照时间顺序插入队列
prev.next = msg;
}
排序算法,插入。
为什么是一个队列,先进先出。
先进无法保证,因为有时间排序,那么先出吶?
看,loop ,一直都是mq.next 一直取最前面的一个,所以是一个队列。
Looper源码分析。
核心在与构造函数,一个数loop函数,ThreadLocal
1、初始化
私有构造函数,如果构造函数是共有的,那么Loop就满天飞了,不能进行控制。
ThreadLocal,多线程,线程上下文的存储变量。每一个线程都有一个ThreadLocalMap,里面有个如因信用的Entry键值对(ThreadLocal k,Object v)
ThreadLocal.set 先获取当前线程对应的map,map.set(this,value);
一个线程只有一个Looper ,并且Looper是不能改的。为什么?
因为一个线程中只有一个ThreadLocalMap >> (this,value) this是唯一的threadLocal,所以value是唯一的,如何保证ThreadLocal/只对应一个value
<key,value> set(key1,value1) set(key1,value2);
因为在prepare之前判断一下
if(sThreadLocal.get() != null){
throw 异常.
}
2、MessageQuenue属于哪个线程
这个说法是错误的,只有执行的函数才能说属于哪个线程的。变量是可以共享的。
Handler设计的亮点
面试题:
1、一个线程有几个Handler?
无数个,Handler机制只有一个
2、一个线程有几个Looper?,只有一个,用过threadLocap和prepare 的 threadLocal,get来保证只能为一个线程设置一个Looper
3、Handler内存泄露的原因?为什么其他的内部类没有说过这个问题?
static :
内部类持有外部类的对象。
recycleView adpter ViewHolder 也是内部类?
是生命周期的问题。
enqueueMessage{
msg.target = thiss;
}
Message持有了Handler ,Handler持有了Activity ,message可能会特定的时间后执行,那么message就不能被释放,也就是Handler不能被释放,Handler持有的Activity也不能被释放,形成了内存泄露。
4、为啥主线程可以直接new Handler,子线程想要new Handler要干什么?
5、子线程中维护的Looper,消息队列无消息的时候的处理方案是什么,有什么用?
需要quit
涉及到,睡眠和唤醒。MessageQueue的等待和唤醒机制。
Message的enqueueMessage没有限制入队的消息数量,因为这个MQ是大家公用的。
MQ的轮询取消息 如果没有消息则会阻塞
这里的阻塞有两个方面阻塞
1、如果取出的消息还没有到要执行的时刻,那就得阻塞
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}计算
循环一遍后从头开始, nativePollOnce(ptr, nextPollTimeoutMillis);//睡眠执行等待操作
2、第二层等待
如果消息队列为空,
nextPollTimeoutMillis = -1;//标识永久 等待,直到有人过来唤醒。
唤醒 if(needWake){
nativeWake(mPtr)
}
native层
最终是调用到了Linux层的epoll_wait来实现等待。
enquenueMessage最终也是调用到了Linux层的。
子线程的消息为空时一定要调用quit来退出:
quit:唤醒线程-->messageQuenue->null->退出loop
6、既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同的线程,那么如何确保线程安全的?)
enqueueMessage会加synchronized:内置锁?为啥叫内置锁,是由jvm完成,系统的。
可以锁代码块,对象。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) //只要是锁了this,其他的函数和方法都不能调用,都是等待状态
一个线程只有一个可以操作MQ的地方,而这个地方又加了锁,所以可以确保线程安全。
enqueueMessage next quit方法都要进行加锁。
到底:Message:从子线程->>发送到主线程
首先,内存是没有线程的
子线程:里面执行的函数,这个函数就在子线程里面
thread: handler.sendMessage(msg) -> MessageQueue.enqueMessage(msg) MessageQueue是一个容器。
主线程的Loop就会去轮询主线程对应的MessageQueue,loop函数是在主线程调度,所以在MessageQueue的next中会调用msg.target.dispatchMessage方法,调用handleMessage处理Message
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>( );整个APP里面唯一,所有线程公用一个。
Looper线程唯一。
7、Message如何创建
obtain 和 new
注意:在Looper的loop中取出一个msg
msg.target.dispatchMessage(msg);后并没有直接return 而是 执行msg.recycleUnchecked();对msg进行回收,放到sPool中,
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
在池子中插入一个msg,这个是防止oom,和内存抖动。避免多次new造成,使得内存碎片严重,造成内存抖动,造成OOM,注意new的内存是连续的,如果内存够,但是不连续,所以要不断回收,无法回收就OOM了。
这种设计模式就用了享元设计模式,内存复用。
8、Looper的死循环为什么不会导致应用卡死
ANR 点击事件 5s:也就是一个点击Message的处理事件超过5s,然后用handler发送一个ANR消息,提醒。ANR的优先级高。
广播10s 。
这个是无关的问题,因为这些点击事件被封装为Message,
MSG:为啥block不会导致ANR?
block 线程没事做了,CPU让出。
消息机制之同步屏障:架构思维
我们都知道,Android系统16ms会刷新一次屏幕,如果主线程的消息过多,在16ms之内没有执行完,必然会造成卡顿或者掉帧。那怎么才能不排队,没有延时的处理呢?这个时候就需要异步消息,在处理异步消息的时候,我们就需要同步屏障,让异步消息不用排队等候处理。可以理解为同步屏障是一堵墙,把同步消息队列拦住,先处理异步消息,等异步消息处理完了,这堵墙就会取消,然后继续处理同步消息Silly_Monkey原文链接。
异步消息:立刻执行
同步消息/普通消息: 放在MQ中执行
消息时根据执行时间进行先后排序,然后消息时保存在队列中,因而消息只能从队列的头取出来,那么问题来了,那需要紧急处理的消息怎么办?
msg.target = null 做一个标志(这就是一个同步屏障);msg1 -> msg2 -> msg3-> msg4 ->
20: 第20个消息非常重要,必须马上执行。 如何去做?如何确保立即执行。
msg.target = null -> msg1 -> msg2 -> msg3-> msg4 ->
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());找到一个异步消息,退出处理同步消息。
}
什么时候用到:就是在更新UI。
ViewRootImpl;
shceduleTraversalsf方法,调用MessageQueue得postSyncBarrier方法发送同步屏障和removeSyncBarrier方法移除同步屏障。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//发送同步屏障
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);//发送异步消息
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);//UI更新消息是异步得
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
什么时候删除同步屏障
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
HandlerThread存在得意义
1、方便使用,方便初始化
2、保证了线程得安全,解决有可能得异步问题。
如果不用,HandlerThread,那么我们要在使用Handler在子线程中处理消息,那么就需要在子线程得run方法创建Handler。
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
当然,也可以将子线程得Looper保存到子线程外部,用Handler(Looper)来创建Handler,但是把Looper放在外部,外部类持有Looper得引用,无法进行释放,可能导致内存问题。
public void run() {
Looper.prepare();
looper = Looper.myLooper();
Looper.loop();
}
HandlerThread就是一个线程,不过在run方法中完了Looper得工作。
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();//此时唤醒其他等待得锁,但并不释放锁,一定等到整个synchronized代码块执行完了才释放锁。
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
处理完任务>>service自动停止,内存释放。
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();//发现Looper没有创建完成有人想要获取looper,等待,释放锁,wait();synchronized什么关系
//notifyall 释放锁吗?
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
IntentService
抽象类,子类继承并实现onHandleIntent来处理耗时任务。
Handler得应用
Service 处理后台任务
一般new Thread处理任务,而在IntenService中,onCreate方法里面创建了HandlerThread和一个ServiceHandler,在onStart方法中
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);执行任务
stopSelf(msg.arg1);自杀,关闭Service
}
}
应用需求:一项任务分为几个子任务,等子任务全部完成后,这项任务才算完成,
这个需求可以用多线程来处理,一个线程处理完 -->下一个-->下一个。
IntentService就可以帮我们完成这个工作,而且,能够很好得管理线程,保证只有一个子线程处理工作,而且是一个一个得完成任务,有条不紊。
到底还有别的地方在用吗?
fragment生命周期管理
如何保证attach先执行FragmentPagerAdapter
mCurTransaction.attach(fragment); mCurTransaction.detach((Fragment)object);
Glide生命周期管理
Handler loop休眠为什么不会导致ANR
Messagequeue队列处理机制,在fragment生命周期管理中的应用
网友评论