Handler系列面试题:如何深挖原理进大厂,Android面试题库

        msg.setAsynchronous(true);

    }

    //转到 MessageQueue 的 enqueueMessage 方法

    return queue.enqueueMessage(msg, uptimeMillis);

}



enqueueMessage方法最终调用了MessageQueue的enqueueMessage方法,将消息放入队列。  **(2)MessageQueue** MessageQueue是一个优先级队列,核心方法是enqueueMessage和next方法,也就是将插入队列,将消息取出队列的操作。 之所以说MessageQueue是一个优先级队列是因为enqueueMessage方法中会根据Message的执行时间来对消息插入,这样越晚执行的消息会被插入到队列的后边。



而next方法是一个死循环,如果队列中有消息,则next方法会将Message移除队列并返回该Message,如果队列中没有消息该方法则会处于阻塞状态。



**(3)Looper** Looper可以理解为一个消息泵,Looper的核心方法是loop。注意loop方法的第一行会首先通过myLooper来得到当前线程的Looper,接着拿到Looper中的MessageQueue,然后开启一个死循环,它会不断的通过MessageQueue的next方法将消息取出来,并执行。代码如下:



public static void loop() {

    final Looper me = myLooper();// 这里要特别注意,是从ThreadLocal中拿到当前线程的Looper。

    if (me == null) {

        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

    }

    final MessageQueue queue = me.mQueue;



    for (;;) {

        //从 MessageQueue 中取消息

        Message msg = queue.next(); // might block

        if (msg == null) {

            // No message indicates that the message queue is quitting.

            return;

        }

		//通过 Handler 分发消息

        msg.target.dispatchMessage(msg);

        //回收消息

        msg.recycleUnchecked();

    }

}



可以看到在取出Message后则会调用Message.target调用dispatchMessage方法,这里target就是Handler,它是在Handler的enqueueMessage时赋值的。紧接着将Message进行了回收。 接下来再回到Handler看dispatchMessage,代码如下:



public void dispatchMessage(Message msg) {

    if (msg.callback != null) {

        //通过 handler.postXxx 形式传入的 Runnable

        handleCallback(msg);

    } else {

        if (mCallback != null) {

            //以 Handler(Handler.Callback) 写法

            if (mCallback.handleMessage(msg)) {

                return;

            }

        }

        //以 Handler(){} 内存泄露写法

        handleMessage(msg);

    }

}



> 相关知识点参考:[github.com/733gh/xiong…]( )



可以看到,这里最终会调用到我们自己的实现方法。至此完结。



2.一个线程有几个Handler?一个线程有几个Looper?如何保证?

------------------------------------



Handler的个数与所在线程无关,可以在线程中实例化任意多个Handler。一个线程中只有一个Looper。Looper的构造方法被声明为了private,我们无法通过new关键字来实例化Looper,唯一开放的可以实例化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));

}



我们知道ThreadLocal是一个线程内部的数据存储类,当某个线程调用prepare方法的时候,会首先通过ThreadLocal检查这个线程是否已经创建了Looper,如果还没创建,则实例化Looper并将实例化后的Looper保存到ThreadLocal中,而如果ThreadLocal中已经保存了Looper,则会抛出一个RuntimeException的异常。那么意味着在一个线程中最多只能调用一次prepare方法,这样就保证了Looper的唯一性。



3.Handler线程是如何切换的?

------------------



(1)假设现在有一个线程A,在A线程中通过Looper.prepare和Looper.loop来开启Looper,并且在A线程中实例化出来一个Handler。Looper.prepare()方法被调用时会为会初始化Looper并为ThreadLocal 设置Looper,此时ThreadLocal中就存储了A线程的Looper。另外MessageQueue也会在Looper中被初始化。



(2)接着当调用Loop.loop方法时,loop方法会通过myLooper得到A线程中的Looper,进而拿到Looper中的MessageQueue,接着开启死循环等待执行MessageQueue中的方法。 (3)此时,再开启一个线程B,并在B线程中通过Handler发送出一个Message,这个Message最终会通过sendMessageAtTime方法调用到MessageQueue的equeueMessage方法将消息插入到队列。



(3)由于Looper的loop是一个死循环,当MessageQueue中被插入消息的时候,loop方法就会取出MessageQueue中的消息,并执行callback。而此时,Looper是A线程的Looper,进而调用的Message或者Handler的Callback都是执行在A线成中的。以此达到了线程的切换。



4.Handler内存泄漏的原因是什么?如何解决?

-------------------------



通常在使用Handler的时候回通过匿名内部类的方式来实例化Handler,而非静态的匿名内部类默认持有外部类的引用,即匿名内部类Handler持有了外部类。而导致内存泄漏的根本原因是是因为Handler的生命周期与宿主的生命周期不一致。



比如说在Activity中实例化了一个非静态的匿名内部类Handler,然后通过Handler发送了一个延迟消息,但是在消息还未执行时结束了Activity,此时由于Handler持有Activity,就会导致Activity无法被GC回收,也就是出现了内存泄漏的问题。



**解决方式**:可以把Handler声明为静态的匿名内部类,但这样一来,在Handler内部就没办法调用到Activity中的非静态方法或变量。那么最终的解决方案可以使用静态内部类 + 弱引用来解决。代码如下:



public class MainActivity extends AppCompatActivity {

private MyHandler mMyHandler = new MyHandler(this);



@Override

protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

}



private void handleMessage(Message msg) {



}



static class MyHandler extends Handler {

    private WeakReference<Activity> mReference;



    MyHandler(Activity reference) {

        mReference = new WeakReference<>(reference);

    }



    @Override

    public void handleMessage(Message msg) {

        MainActivity activity = (MainActivity) mReference.get();

        if (activity != null) {

            activity.handleMessage(msg);

        }

    }

}



@Override

protected void onDestroy() {

    mMyHandler.removeCallbacksAndMessages(null);

    super.onDestroy();

}

}




> 相关知识点参考:[github.com/733gh/xiong…]( )



5.主线程为什么不用初始化Looper?

--------------------



**答:因为应用在启动的过程中就已经初始化主线程Looper了。**



每个java应用程序都是有一个main方法入口,Android是基于Java的程序也不例外。Android程序的入口在ActivityThread的main方法中:



public static void main(String[] args) {

...

// 初始化主线程Looper

Looper.prepareMainLooper();

...

// 新建一个ActivityThread对象

ActivityThread thread = new ActivityThread();

thread.attach(false, startSeq);



// 获取ActivityThread的Handler,也是他的内部类H

if (sMainThreadHandler == null) {

    sMainThreadHandler = thread.getHandler();

}



...

Looper.loop();

// 如果loop方法结束则抛出异常,程序结束

throw new RuntimeException("Main thread loop unexpectedly exited");

}




main方法中先初始化主线程Looper,新建ActivityThread对象,然后再启动Looper,这样主线程的Looper在程序启动的时候就跑起来了。我们不需要再去初始化主线程Looper。



6.Handler如何保证MessageQueue并发访问安全?

--------------------------------



**答:循环加锁,配合阻塞唤醒机制。**



我们可以发现MessageQueue其实是“生产者-消费者”模型,Handler不断地放入消息,Looper不断地取出,这就涉及到死锁问题。如果Looper拿到锁,但是队列中没有消息,就会一直等待,而Handler需要把消息放进去,锁却被Looper拿着无法入队,这就造成了死锁。Handler机制的解决方法是**循环加锁**。在MessageQueue的next方法中:



Message next() {

for (;;) {

    nativePollOnce(ptr, nextPollTimeoutMillis);

    synchronized (this) {

        ...

    }

}

}




我们可以看到他的等待是在锁外的,当队列中没有消息的时候,他会先释放锁,再进行等待,直到被唤醒。这样就不会造成死锁问题了。



那在入队的时候会不会因为队列已经满了然后一边在等待消息处理一边拿着锁呢?这一点不同的是MessageQueue的消息没有上限,或者说他的上限就是JVM给程序分配的内存,如果超出内存会抛出异常,但一般情况下是不会的。



> 相关知识点参考:[github.com/733gh/xiong…]( )



7.Handler的阻塞唤醒机制是怎么回事?

----------------------



**答: Handler的阻塞唤醒机制是基于Linux的阻塞唤醒机制。**



这个机制也是类似于handler机制的模式。在本地创建一个文件描述符,然后需要等待的一方则监听这个文件描述符,唤醒的一方只需要修改这个文件,那么等待的一方就会收到文件从而打破唤醒。和Looper监听MessageQueue,Handler添加message是比较类似的。



8.能不能让一个Message加急被处理?/ 什么是Handler同步屏障?

--------------------------------------



**答:可以 / 一种使得异步消息可以被更快处理的机制**



如果向主线程发送了一个UI更新的操作Message,而此时消息队列中的消息非常多,那么这个Message的处理就会变得缓慢,造成界面卡顿。所以通过同步屏障,可以使得UI绘制的Message更快被执行。



什么是同步屏障?这个“屏障”其实是一个Message,插入在MessageQueue的链表头,且其target==null。Message入队的时候不是判断了target不能为null吗?不不不,添加同步屏障是另一个方法:



public int postSyncBarrier() {

return postSyncBarrier(SystemClock.uptimeMillis());

}

private int postSyncBarrier(long when) {

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;

    // 把当前需要执行的Message全部执行

    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;

}

}




可以看到同步屏障就是一个特殊的target,哪里特殊呢?target==null,我们可以看到他并没有给target属性赋值。**那这个target有什么用呢?看next方法:**



Message next() {

...



// 阻塞时间

int nextPollTimeoutMillis = 0;

for (;;) {

    ...

    // 阻塞对应时间 

    nativePollOnce(ptr, nextPollTimeoutMillis);

// 对MessageQueue进行加锁,保证线程安全

    synchronized (this) {

        final long now = SystemClock.uptimeMillis();

        Message prevMsg = null;

        Message msg = mMessages;

        /**

        *  1

        */

        if (msg != null && msg.target == null) {

            // 同步屏障,找到下一个异步消息

            do {

                prevMsg = msg;

                msg = msg.next;

写在最后

在技术领域内,没有任何一门课程可以让你学完后一劳永逸,再好的课程也只能是“师傅领进门,修行靠个人”。“学无止境”这句话,在任何技术领域,都不只是良好的习惯,更是程序员和工程师们不被时代淘汰、获得更好机会和发展的必要前提。

如果你觉得自己学习效率低,缺乏正确的指导,可以一起学习交流!

CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》

加入我们吧!群内有许多来自一线的技术大牛,也有在小厂或外包公司奋斗的码农,我们致力打造一个平等,高质量的Android交流圈子,不一定能短期就让每个人的技术突飞猛进,但从长远来说,眼光,格局,长远发展的方向才是最重要的。

35岁中年危机大多是因为被短期的利益牵着走,过早压榨掉了价值,如果能一开始就树立一个正确的长远的职业规划。35岁后的你只会比周围的人更值钱。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值