Android Handler部分源码,面试中遇到问题与解答

本篇文章主要目的是 搞清楚 Handler原理,以及面试中问道的一些问题。

1:Handler、 message、Looper和MessageQueue的原理以及他们之间的关系?

2:可以在非UI线程可以使用Handler吗?

3:Loop 既然是一个死循环,那么在主线程为什么不会造成ANR?

 

1、Handler 是Android 进行线程间通讯的主要方法

通常我们会在代码中这样使用Handler

        // Handler 基本使用 子线程 和 主线程见通讯
        final Handler hanler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // 主线程 进行UI操作
                switch (msg.what) {
                    case 1: {
                    }
                }
            }
        };

        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(10000);
                Message message = new Message();

                Message message1 = Message.obtain(); // 推荐使用obtain  减少内存申请

                message.obj = "";
                message.what = 1;
                hanler.sendMessage(message);
            }
        });


        new Thread(new Runnable() {
            @Override
            public void run() {
                hanler.post(new Runnable() {
                    @Override
                    public void run() {
                        // 可以直接进行UI 的操作
                    }
                });
            }
        });

sendMessage(msg) 方法是把消息放入队列,可以直接点击源码查看

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }


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

    public boolean sendMessageAtTime(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);
    }

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

最终通过 queue enqueueMessage() 方法吧消息放入队列 

那这里也就知道  Message 和 MessageQueue 之间关系 , Message 是消息,MessageQueue 是消息队列,从名字上就观察出,那么MessageQueue 真的是队列吗? 肯定不是,犹记得背诵面试题的时候,MessageQueue 采用的数据结构是单向链表数据结构而非队列。它的next()方法指向下一个Message元素

发散下思维,这里解释下单向链表链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是不像顺序表一样连续存储数据,而是在每一个节点(数据存储单元)里存放下一个节点的位置信息(即地址)。

回到MessageQeue,我知道它是一个单向链表结构就可以,关于消息的存入到此为止,主要需要我们关心的是怎么出队列的。从我们背诵的概念,知道Looper 帮助 message 出队列,而Looper是死循环,会不停的从消息队列中取消息。

如果想搞清楚原理,流程 请仔细查看我所贴的源码,因为我一般都把解释接在源码中间。源码有删减

   // 这里是Message 出队列的过程,为了方便写注释,我直接CV出来

                boolean enqueueMessage(Message msg, long when) {
                    // ... 省略一些检查代码
                    synchronized (this) {
                        // ... 省略一些检查代码
                        msg.markInUse();
                        msg.when = when;
                        Message p = mMessages;
                        boolean needWake;
                        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;
                        }

                        // We can assume mPtr != 0 because mQuitting is false.
                        if (needWake) {

                        }
                            nativeWake(mPtr);  // 唤醒  nativePollOnce() 下方有解释
                        }
                    }
                    return true;
                }




    Message next() {
        // ...
        int nextPollTimeoutMillis = 0;
        /*
         * 关注下nextPollTimeoutMillis 这个值,被nativePollOnce() 方法使用,
         * 而nativePollOnce() 是一个native方法,实际作用是通过native层的MeaageQeue阻塞当前调用栈线程,
         * 那么
         * nextPollTimeoutMillis : 小于零 一直阻塞,直到被唤醒
         *                          等于零 不会阻塞
         *                          大于零 最长阻塞时间为 nextPollTimeoutMillis ,期间如被唤醒会立即返回
         * */

        for (;;) {
            // 无限循环
            // ...
            nativePollOnce(ptr, nextPollTimeoutMillis); // 阻塞  被MessageQeue #enqueueMessage()方法中 nativeWake() 方法唤醒
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                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());
                }
                if (msg != null) {
                    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);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                //...
            }

到这里,我们总结下以上我要说明的问题和关系。 Handler 发送 message ,message 到 MessageQeue 进行存储,

所使用的的两个主要方法 MessageQeue# enqueueMessage() 进入队列,MessageQeue # next() 轮寻消息出队列 一切都清楚了,那么好像没有Looper什么事情了? 哈哈,我看到这里也迷糊了,不是说轮寻是Looper的事情吗?怎么MessageQeue就把事情给干了呢,不过仔细想一想,方法是next 那么是谁调用的呢?这里我没有寻找源码,而是交给了百度。得知Looper调用MessageQeue 的next() 方法,

还记得背诵的概念吗? 子线程使用Handler 首先需要Looper.prepare() 创建一个looper。那么我就从这里进入源码,我们也知道Looper的最主要方法就是loop() , 所以我们直接查看loop方法就可以

 public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {


            Message msg = queue.next(); 

      if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            //进入这个方法,我第一眼就看到这个方法,死循环调用 queue.next()



            // 后面还有 为了篇幅 不贴了,你们可以自行查看下
    }

我整理下这个方法: 进入首先 获取Looper 对象, 为空就报错, 然后进入死循环 走MessageQueue # next()方法,获取消息,消息为空 就退出消息,不为空,分发消息 dispatchMessage()

那么现在可以回答第一个问题了,

回答 1:

Handler 、 message、 Looper、 Messagequeue 组成Android 的消息机制。

首先Handler 发送 message 到 Messagequeue ,MessageQueue 存储消息,等待Looper 轮寻器调用 next()方法,获取消息,调用 dispatchMessage()方法, 这个方法中其实就一个   message.callback.run  然后我们就开始根据what 获取自己需要的消息,执行我们的逻辑。


回答 2:

从我们背诵的概念就可以知道,非UI线程可以执行Handler操作,但是需要获取Looper对象,而前文也说明了,我们查看Looper . loop()方法,就是在子线程中执行 Looper.prepare() 获取对象

衍生问题,为什么在主线程中不需要获取Looper对象,就可以使用Handler了呢?

从问题1,我知道Looper是为了调用MessageQueue的next方法,获取message ,如果没有没有Looper对象是肯定不能调用。毫无疑问会发生空指针,如果在子线程不使用 Looper.prepare() 方法,直接使用Handler 抛出:"No Looper; Looper.prepare() wasn't called on this thread."可以证明

那么假设,在主线程肯定是系统已经创建了Looper 对象,所以不用我们再去创建。那是怎么创建的呢?还记得之前查看Loop() 方法中的   final Looper me = myLooper(); 

这里贴的都是源码: 主要确认 Looper 对象怎么获取的。 

    
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

发现是 sThreadLoacl 对象中获取的Looper对象 继续进入查看

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

看着像是获取当前线程,从map中获取。到这里我已经看不下去,这要是从头开始查,就太多了 ~~~陷入沉思

ThreadLocal  这个类好像似曾相识。百度下、小猿笔记

一个线程的局部变量,每条线程只能看到自己的值,并不知道其他线程中业存在该变量(我理解就是 每一个线程都有一个ThreadLoacl ,别的线程无法访问其他线程的ThreadLoal )

实现原理:每个Thread的对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型。

OK ,大概理解了ThreadLocal 的作用,知道Looper 是set() 方法进入 ThreadLocal  中 ThreadLocalMap() 中的。那么按照我的猜测,主线程替我们创建了 Looper 对象,然后set()到主线程的ThreadlocalMap中,我们这里只是获取。

那么,我们要搞懂衍生问题,可能还需要查看主线程启动的整个过程

因为APP启动不是我们主要研究的重点,这里涉及一些知识,大家可以看下这个图。我们知道程序的运行都有一个主入口,学习java的时候,程序的主入口是 main函数,而Android 开发使用语言是java 所以理所当然 入口也Main函数啊。

所以我直接找到 ActivityThread # main 方法中

 public static void main(String[] args) {
        // ...
        Looper.prepareMainLooper();
        // ...
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        //...
        Looper.loop();
        // ...
    }

很显然,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();
    }
}

prepare(false) 是不是很熟 , 子线程中 Looper. prepare() 方法 ,还记prepare()的实现吗?

    public static void prepare() {
        prepare(true);
    }

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

sThreadLocal.set(new Looper(quitAllowed)); 这里不就是new一个放入了Threadlocal 的 
ThreadloaclMap中了吗?


回答 问题2 衍生问题:

为什么在主线程中不需要获取Looper对象,就可以使用Handler了呢?

因为在App启动的时候 ActivityTread # Main 函数 帮我们创建了Looper 对象,所以我们使用Handler不需要再创建。(整个APP启动运行在UI线程,不需要我解释为什么Main 函数中创建的Looper 对象在 主线程的 ThreadLcalMap 中吧。)


回答问题3:

Looper 运行既然是一个死循环,在UI线程运行为什么不会造成anr 异常。

我们再次回想一下 ANR 造成的原因:ANR,是“Application Not Responding”的缩写,即“应用程序无响应”

主线程处理屏幕时间,也就是在Activity中, 5秒无响应

主线程在执行BroadcastRecetive 的 onRreceive 中 10秒无响应

主线程执行服务各个生命周期20秒无响应

显然:提问这个问题的面试官是在给你挖一个陷阱, ANR出现的原因 是无响应, 避免anr 我就要避免在主线程进行耗时操作。那么接下来直接问为什么Looper 都死循环了,程序没有造成anr ,我的脑袋直接宕机 .。 对App 运行 、 Handler原理 不熟悉,这个问题就死定了。

我先确定一个概念,什么是Android ANR 什么是无响应。  你怎么知道一个事件无响应,那就是主线程在处理事件,5秒内来不及处理你触发的事件。如果在在主线程睡眠 10秒, 但是此期间,不触发任何事件,会报 ANR吗?肯定不会报的。

为什么呢? 那么我想问下各位,在学习 java的时候,例如main 方法执行一个计算,控制台输出,运行程序,执行完毕,你想再次计算怎么办?再次运行程序。而 APP呢? 为啥执行完Main 函数,程序没有退出呢?

Looper.loop() 死循环保证App 不退出。

再次回想 我们看Message#next()方法中的nativePollOnce() 如果没有消息,程序一直处于堵塞状态!一直到 被另一个消息唤醒或者系统唤醒~

说到这里是不是就清楚了,

主线程一直处于堵塞状态,主线程运行的looper 一直处于运行状态,ANR 是事件无响应,  你压根不给我事件唤醒我,我为什么要报anr, 你给我事件了,那我就唤醒了啊,然后给你麻溜的处理了。为啥要报anr。

面试第四问:handler的消息排序~

回答:这里只是记录下吧,群里的小伙伴提供一个问题,就是handler 的排序问题,刚开始听到这个问题,满脑子不解,消息存在排序吗?不是单向链表结构,next指向下一个消息地址。为什么存在呢!但是别说,潜意识里 貌似记得面试的过程遇到过这样的问题。

那么我们继续看源码这次简单点

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


发现sendMessage 中单调用 sendMessageDelayed() 这个方法是我们发送延迟消息的。那么首先可以确定如果存在排序 排序的 根据是 时间
其中SystemClock.uptimeMillis() App 运行到目前的时间间隔


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

一路跟着时间元素, when 这里果然出现了排序

细心的朋友会发现mMessages这个对象,我们可以把它理解为是待执行的message队列,该队列是按照when的时间排序的且第一个消息是最先执行。

首先先看真对when的判断 三个条件,如果mMessages对象为空,或者when为0也就是立刻执行,或者新消息的when时间比mMessages队列的when时间还要早,符合以上一个条件就把新的msg插到mMessages的前面 并把next指向它,也就是msg会插进上图中队列的最前面,等待loop的轮询。

如果上面的条件都不符合就进入else代码中,when < p.when when是新消息的执行时间,p.when的是队列中message消息的执行时间,如果找到比新的message还要晚执行的消息,就执行
简单的比对时间 ,插入MessageQueue 中, 这也可以解释为什么 MessageQueue 采用的数据结构命名是链表,为啥命名成队列。按照时间顺序,先进先出~

 

 

 

OK完结~ Hndler原理, 外加三问, 都是之前面试中遇到的。历时一天,终于整理完毕,脑子中回想一下面试官问我handler原理:我会怎么回答~貌似还没有背诵原理说出来的多~哎

 

 

 

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值