Android 线程通信

Posted danfengw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 线程通信相关的知识,希望对你有一定的参考价值。

Handler相关


代码分析:

Handler 持有 Looper 和 MessaegeQueue

可以在子线程创建handler吗

可以,需要调用Looper.prepare和Looper.loop

主线程的Looper和子线程的Looper的区别

子线程的Looper可以退出,主线程的Looper不可以退出

Looper 和MessageQueue 有什么关系

一对一的关系

消息循环过程是怎么样的

Looper.loop() 通过 for (;😉 死循环,不断从messageQueue中获取消息,并进行处理

消息是怎么发送的

handler.sendMessage()发送消息后加入到消息队列中

  public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) 
        MessageQueue queue = mQueue;
        if (queue == null) 
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        
        return enqueueMessage(queue, msg, uptimeMillis);
    

消息是怎么处理的

handler.dispatchMessage()

 public void dispatchMessage(@NonNull Message msg) 
        if (msg.callback != null) 
            handleCallback(msg);
         else 
            if (mCallback != null) 
                if (mCallback.handleMessage(msg)) 
                    return;
                
            
            handleMessage(msg);
        
    

handler 的消息延时是怎么实现的

(1)消息队列按照消息触发时间排序
(2) 设置epoll_wait 的超时时间,使其在特定时间唤醒

是发送延时 还是消息处理延时?

处理延时

  public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) 
        if (delayMillis < 0) 
            delayMillis = 0;
        
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    

插入消息队列,从下方代码可以看出来,消息会根据时间when进行排队

 boolean enqueueMessage(Message msg, long when) 
 …………
            if (p == null || when == 0 || when < p.when) 
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
             else 
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) 
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) 
                        break;
                    
                    if (needWake && p.isAsynchronous()) 
                        needWake = false;
                    
                
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            
 …………
 

延时精度怎么样

延时精度不高

handler 内存泄漏解决(2点)

1 有延时消息,要在 Activity 销毁的时候移除 Messages
2 匿名内部类导致的泄露改为匿名静态内部类,并且对上下文或者 Activity 使用弱引用。

IdleHandler

IdleHandler 说白了,就是 Handler 机制提供的一种,可以在 Looper 事件循环的过程中,当出现空闲的时候,允许我们执行任务的一种机
制。

MessageQueue 提供了 add/remove IdleHandler 的方法,是否需要成对使用?

不需要,IdleHandler的queueIdle()返回false时,只会执行一次,并被移除

当 mIdleHanders 一直不为空时,为什么不会进入死循环?

只有在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;
pendingIdlehanderCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;

IdleHandler 的 queueIdle() 运行在那个线程?

陷进问题,queueIdle() 运行的线程,只和当前 MessageQueue 的 Looper 所在的线程有关;
子线程一样可以构造 Looper,并添加 IdleHandler;

主线程的IdleHandler 如果进行耗时操作会怎样?

1.Thread.sleep(n) n > 10 页面会卡死,但不会崩溃,如果页面有动图,则动图变为静态图(视频未尝试,猜测也会处于静止状态) 但此时如果点击页面按钮,则会无响应进入anr,如果不点击,n秒过后恢复正常。

2.网络请求: 不会崩溃,但会报错 IdleHandler threw exception android.os.NetworkOnMainThreadException

3.文件写入本地: 成功。测试所用文件为小文件,大文件猜测也一样,同sleep,中途不点击页面无事,点击anr。

哪些地方用到了IdleHandler

GcIdler ,Idler

适用场景–延迟执行、批量任务(任务密集、只关注最终结果)

(1)Activity启动优化(加快App启动速度):onCreate,onStart,onResume中耗时较短但非必要的代码可以放到IdleHandler中执行,减少启动时间
(2)想要在一个View绘制完成之后添加其他依赖于这个View的View,当然这个用View#post()也能实现,区别就是前者会在消息队列空闲时执行
(3)发送一个返回true的IdleHandler,在里面让某个View不停闪烁,这样当用户发呆时就可以诱导用户点击这个View,这也是种很酷的操作
(4)一些第三方库中有使用,比如LeakCanary,Glide中有使用到,具体可以自行去查

主线程进入Loop循环为什么没有anr

public static final void main(String[] args) 
        ...
        //创建Looper和MessageQueue
        Looper.prepareMainLooper();
        ...
        //轮询器开始轮询
        Looper.loop();
        ...
    

(1)ActivityThread的main方法主要就是做消息循环,一旦退出消息循环,那么你的应用也就退出了
(2)anr是应用没有在规定的时间内完成AMS指定的任务,而不是因为主线程loop循环,而是因为主线程中有耗时任务

消息屏障

消息种类

普通消息
屏障
异步消息
ps:屏障消息插入后会block住普通消息,优先处理异步消息,这样的异步消息类似于:vsyc信号刷新界面之类。

如何插入消息屏障的

MessageQueue

 private int postSyncBarrier(long when) 
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) 
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) 
                while (p != null && p.when <= when) 
                    prev = p;
                    p = p.next;
                
            
            if (prev != null)  // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
             else 
                msg.next = p;
                mMessages = msg;
            
            return token;
        
    

(1)消息屏障的target是空的
(2)消息屏障也是有时间戳的,并且也会按照时间戳排序
(3)消息屏障插入队列后,并没有唤醒线程,插入队列后会返回一个token,这个token主要是用来取消消息屏障的时候,需要根据这个token从消息队列中查找。
(4)此外postSyncBarrier方法是private的
(5)我们平时发送的消息是不允许target为空的

删除屏障:

 public void removeSyncBarrier(int token) 

删除屏障的时候需要唤醒线程

屏障的处理:

MessageQueue 的 next() 调用的时候,
如果第一条消息是屏障会block住后面的普通消息,不会block异步消息,后面如果有异步消息,异步消息如果到了时间了,就执行,否则就等待。

插入消息队列的时候

MessageQueue 的enqueueMessage方法
如果插入到消息队列头,如果当前线程是休眠的,就要唤醒他
如果没有插入到队列头,如果当前线程是休眠的,并且队列头是屏障,并且当前消息是最早的一条异步消息,就要唤醒线程。

消息队列是空的时候,插入一个屏障,会触发idleHandler吗

不会触发idleHandler,插入一个屏障是不会唤醒线程的。

如果删除了屏障,消息队列空了,会触发idleHandler吗

不会,因为idleHandler已经被调用过了,就不会触发了。

如果消息队列只有一个屏障消息,插一个普通消息会idleHandler吗

有可能,主要看屏障的时间到了没有

如果消息队列只有一个屏障消息,插一个异步消息会idleHandler吗

有可能,同上

ThreadLocal

java 的ThreadLocal 与 Android的ThreadLoacal

以上是关于Android 线程通信的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程-两种常用的线程计数器CountDownLatch和循环屏障CyclicBarrier

Android:同步屏障的简单理解和使用

Java多线程之内存模型

线程之间的内存栅栏/屏障如何与其他线程中的栅栏/屏障交互?

当通过的线程数量小于屏障限制时,屏障(例如 CyclicBarrier)是不是会导致死锁?

java CyclicBarrier同步屏障