Android面试6家一线大厂,这个问题是必问,30岁转行程序员

2021/9/6 20:08:59

本文主要是介绍Android面试6家一线大厂,这个问题是必问,30岁转行程序员,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

        delayMillis = 0;

    }

    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);

}



public boolean sendMessageAtTime(Message msg, long uptimeMillis) {

    MessageQueue queue = mQueue;

    ......

    return enqueueMessage(queue, msg, uptimeMillis);

} 


**post:从消息复用池中获取Message,设置Message的Callback**



public final boolean post(Runnable r)

{

   return  sendMessageDelayed(getPostMessage(r), 0);

}



private static Message getPostMessage(Runnable r) {

    Message m = Message.obtain();

    m.callback = r;

    return m;

} 


**postAtFrontOfQueue(): 将消息插入到队列头部**



通过调用sendMessageAtFrontOfQueue 加入一个when为0的message到队列,即插入到队列的头部,需要注意的是 MessageQueue#enqueueMessage的插入到链表中时是根据when比较的(when < p.when),如果之前已经有多个when等于0的消息在队列中,这个新的会加入到前面when也为0的后面。



public final boolean postAtFrontOfQueue(Runnable r)

{

    return sendMessageAtFrontOfQueue(getPostMessage(r));

}



public final boolean sendMessageAtFrontOfQueue(Message msg) {

    MessageQueue queue = mQueue;

    ......

    //第三个参数为0,即Message的when为0,插入到队列的头部,注意到MessageQueue#enqueueMessage的插入到链表中时是根据when比较的(when < p.when),如果之前已经有多个when等于0的消息在队列中,这个新的会加入到前面when也为0的后面。

    return enqueueMessage(queue, msg, 0);

} 


### []( )2.5 派发消息 dispatchMessage



优先级如下:



Message的回调方法callback.run() >  

Handler的回调方法mCallback.handleMessage(msg) > Handler的默认方法handleMessage(msg)



public void dispatchMessage(@NonNull Message msg) {

  //Message的回调方法,优先级最高  

if (msg.callback != null) {

        handleCallback(msg);

    } else {

        //Handler的mCallBack优先级次之

        if (mCallback != null) {

            if (mCallback.handleMessage(msg)) {

                return;

            }

        }

        //Handler的handleMessage方法优先级最低(大部分都是在该方法中实现Message的处理)

        handleMessage(msg);

    }

} 


[]( )三、Message

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



**全局变量**



//一些重要的变量

public int arg1;

public int arg2;

public Object obj;

public long when;

Bundle data;

Handler target;  //Message中有个Handler的引用

Runnable callback;



//Message有next指针,可以组成单向链表

Message next;



public static final Object sPoolSync = new Object();



//复用池中的第一个Message

private static Message sPool;



//复用池的大小,默认最大50个(如果短时间内有超过复用池最大数量的Message会怎样,重新new)

private static int sPoolSize = 0;

private static final int MAX_POOL_SIZE = 50; 


**构造方法**



查看下是否有可以复用的message,如果有,复用池的中可复用的Message个数减一,返回该Message;如果没有重新new一个。注意复用池默认最大数量为50。



public static Message obtain() {

   synchronized (sPoolSync) {

       //查看下是否有可以复用的message

       if (sPool != null) {

           //取出第一个Message

           Message m = sPool;

           sPool = m.next;

           m.next = null;

           m.flags = 0; // clear in-use flag

           //复用池的中可复用的Message个数减一

           sPoolSize--;

           return m;

       }

   }

   //如果复用池中没有Message了重新new

   return new Message();

}



**recycleUnchecked**



//标记一个Message时异步消息,正常的情况都是同步的Message,当遇到同步屏障的时候,优先执行第一个异步消息。关于同步屏障,我们在MessageQueue中在结合next等方法再介绍。



public void setAsynchronous(boolean async) {

    if (async) {

        flags |= FLAG_ASYNCHRONOUS;

    } else {

        flags &= ~FLAG_ASYNCHRONOUS;

    }

}

void recycleUnchecked() {

    // Mark the message as in use while it remains in the recycled object pool.

    // Clear out all other details.

    flags = FLAG_IN_USE;

    what = 0;

    arg1 = 0;

    arg2 = 0;

    obj = null;

    replyTo = null;

    sendingUid = UID_NONE;

    workSourceUid = UID_NONE;

    when = 0;

    target = null;

    callback = null;

    data = null;



    synchronized (sPoolSync) {

        //可以复用的message为50个,如果超过了就不会再复用

        if (sPoolSize < MAX_POOL_SIZE) {

            next = sPool;

            sPool = this;

            sPoolSize++;

        }

    }

} 


//toString和dumpDebug可以Dump出message信息,遇到一些问题时可以帮助分析  

android.os.Message#toString(long)



android.os.Message#dumpDebug



[]( )四、MessageQueue

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



MessageQueue是一个单链表优先队列  

Message不能直接添加到MessageQueue中,要通过Handler以及相对应的Looper进行添加。



**变量**



//MessageQueue链表中的第一个Message

Message mMessages;



**next:从消息队列中取出下一条要执行的消息**



如果是同步屏障消息,找到第一个队列中中第一个异步消息



如果第一个Message的执行时间比当前时间见还要晚,记录还要多久开始执行;否则就找到下一条要执行的Message。



后面的Looper的loop方法会从过queue.next调用该方法,获取需要执行的下一个Message,其中会调用到阻塞的native方法nativePollOnce,该方法用于“等待”, 直到下一条消息可用为止. 如果在此调用期间花费的时间很长, 表明对应线程没有实际工作要做,不会因此会出现ANR,ANR和这个没有半毛钱关系。



关键代码如下:



Message next() {

    //native层MessageQueue的指针

    final long ptr = mPtr;

    if (ptr == 0) {

        return null;

    }



 ......

    for (;;) {



        //阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒

        //nativePollOnce用于“等待”, 直到下一条消息可用为止. 如果在此调用期间花费的时间很长, 表明对应线程没有实际工作要做,不会因此会出现ANR,ANR和这个没有半毛钱关系。

        nativePollOnce(ptr, nextPollTimeoutMillis);



        synchronized (this) {



            final long now = SystemClock.uptimeMillis();

            Message prevMsg = null;



            //创建一个新的Message指向 当前消息队列的头

            Message msg = mMessages;



            //如果是同步屏障消息,找到第一个队列中中第一个异步消息

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

                do {

                    prevMsg = msg;

                    msg = msg.next;

                } while (msg != null && !msg.isAsynchronous());

            }



            if (msg != null) {

                //如果第一个Message的执行时间比当前时间见还要晚,记录还要多久开始执行

                if (now < msg.when) {                    

                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);

                } else {

                    //否则从链表中取出当前的Message ,并且把链表中next指向指向下一个Message



                    mBlocked = false;

                    if (prevMsg != null) {

                        prevMsg.next = msg.next;

                    } else {

                        mMessages = msg.next;

                    }

                    //取出当前的Message的值,next置为空

                    msg.next = null;

                    msg.markInUse();

                    return msg;

                }

            } 

            .....



            //android.os.MessageQueue#quit时mQuitting为true

            //如果需要退出,立即执行并返回一个null的Message,android.os.Looper.loop收到一个null的message后退出Looper循环

            if (mQuitting) {

                dispose();

                return null;

            }

    ......

        if (pendingIdleHandlerCount <= 0) {

                            // 注意这里,如果没有消息需要执行,mBlocked标记为true,在enqueueMessage会根据该标记判断是否调用nativeWake唤醒

                            mBlocked = true;

                            continue;

                        }

    ......

}

......

}



**enqueueMessage:向消息队列中插入一条Message**



如果消息链表为空,或者插入的Message比消息链表第一个消息要执行的更早,直接插入到头部  

否则在链表中找到合适位置插入,通常情况下不需要唤醒事件队列,以下两个情况除外:



1.  消息链表中只有刚插入的这一个Message,并且mBlocked为true即,正在阻塞状态,收到一个消息后也进入唤醒

2.  链表的头是一个同步屏障,并且该条消息是第一条异步消息



唤醒谁?MessageQueue.next中被阻塞的nativePollOnce



具体实现如下,



关于如何找到合适的位置?这涉及到链表的插入算法:引入一个prev变量,该变量指向p也message(如果是for循环的内部第一次执行),然后把p进行向next移动,和需要插入的Message进行比较when



关键代码如下:



boolean enqueueMessage(Message msg, long when) {

    ......



    synchronized (this) {



        msg.markInUse();

        msg.when = when;

        Message p = mMessages;

        boolean needWake;



        //如果消息链表为空,或者插入的Message比消息链表第一个消息要执行的更早,直接插入到头部

        if (p == null || when == 0 || when < p.when) {



            msg.next = p;

            mMessages = msg;

            needWake = mBlocked;

        } else {



            //否则在链表中找到合适位置插入

            //通常情况下不需要唤醒事件队列,除非链表的头是一个同步屏障,并且该条消息是第一条异步消息

            needWake = mBlocked && p.target == null && msg.isAsynchronous();

            //具体实现如下,这个画张图来说明

            //链表引入一个prev变量,该变量指向p也message(如果是for循环的内部第一次执行),然后把p进行向next移动,和需要插入的Message进行比较when

            Message prev;



            for (;;) {

                prev = p;

                p = p.next;

                if (p == null || when < p.when) {

                    break;

                }

                if (needWake && p.isAsynchronous()) {

                    needWake = false;

                }

            }

            msg.next = p; 

            prev.next = msg;

        }



        //如果插入的是异步消息,并且消息链表第一条消息是同步屏障消息。

//或者消息链表中只有刚插入的这一个Message,并且mBlocked为true即,正在阻塞状态,收到一个消息后也进入唤醒

唤醒谁?MessageQueue.next中被阻塞的nativePollOnce

        if (needWake) {

            nativeWake(mPtr);

        }

    }

    return true;

} 


简单着看下native的epoll (这块还没有深入分析,后面篇章补上吧)



> nativePollOnce 和 nativeWake 利用 epoll 系统调用, 该系统调用可以监视文件描述符中的 IO 事件. nativePollOnce 在某个文件描述符上调用 `epoll_wait`, 而 nativeWake 写入一个 IO 操作到描述符  

> epoll属于IO复用模式调用,调用`epoll_wait`等待. 然后 内核从等待状态中取出 epoll 等待线程, 并且该线程继续处理新消息



**removeMessages: 移除消息链表中对应的消息**



需要注意的是,在该函数的实现中分为了头部meg的移除,和非头部的msg的移除。



移除消息链表中头部的和需要移除相同的msg



eg:msg1.what=0;msg2.what=0;msg3.what=0; msg4.what=1; 需要移除what为0的msg,即移除前三个



移除消息链表中非头部的对应的消息,eg:msg1.what=1;msg2.what=0;msg3.what=0; 需要移除what为0的消息,即移除后续的消息,处处体现链表的查询和移除算法



关键代码如下:



void removeMessages(Handler h, int what, Object object) {

    synchronized (this) {

        Message p = mMessages;



        //移除消息链表中头部的和需要移除相同的msg eg:msg1.what=0;msg2.what=0;msg3.what=0; msg4.what=1; 需要移除what为0的msg,即移除前三个

        while (p != null && p.target == h && p.what == what

               && (object == null || p.obj == object)) {

            Message n = p.next;

            mMessages = n;

            p.recycleUnchecked();

            p = n;

        }



        //移除消息链表中非头部的对应的消息,eg:msg1.what=1;msg2.what=0;msg3.what=0; 需要移除what为0的消息,即移除后续的消息,处处体现链表的查询和移除算法

        while (p != null) {

            Message n = p.next;

            if (n != null) {

                if (n.target == h && n.what == what

                    && (object == null || n.obj == object)) {

                    Message nn = n.next;

                    n.recycleUnchecked();

                    p.next = nn;

                    continue;

                }

            }

            p = n;

        }

    }

} 


**postSyncBarrier:发送同步屏障消息**



同步屏障也是一个message,只不过这个Message的target为null,. 通过ViewRootImpl#scheduleTraversals()发送同步屏障消息



同步屏障消息的插入位置并不是都是消息链表的头部,而是根据when等信息而定:如果when不为0,消息链表也不空,在消息链表中找到同步屏障要插入入的位置;如果prev为空,该条同步消息插入到队列的头部。



关键代码如下:



/**

 * android.view.ViewRootImpl#scheduleTraversals()发送同步屏障消息

 * @param when

 * @return

 */

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++;



        //同步屏障也是一个message,只不过这个Message的target为null



        final Message msg = Message.obtain();

        msg.markInUse();

        msg.when = when;

        msg.arg1 = token;



        Message prev = null;

        Message p = mMessages;

        if (when != 0) {

            //如果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 {

            //如果prev为空,该条同步消息插入到队列的头部

            msg.next = p;

            mMessages = msg;

        }

        return token;

    }

} 


**dump: MessageQueue信息**  

有时候我们需要dump出当前looper的Message信息来分析一些问题,比不,是否Queue中有很多消息,如果太多就影响队列中后面的Message的执行,可能造成逻辑处理比较慢,甚至可能导致ANR等情况,MessageQueue的默认复用池是50个,如果太多排队的Message也会影响性能。通过dump Message信息可以帮助分析。`mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);`



void dump(Printer pw, String prefix, Handler h) {

    synchronized (this) {

        long now = SystemClock.uptimeMillis();

        int n = 0;

        for (Message msg = mMessages; msg != null; msg = msg.next) {

            if (h == null || h == msg.target) {

                pw.println(prefix + "Message " + n + ": " + msg.toString(now));

            }

            n++;

        }

        pw.println(prefix + "(Total messages: " + n + ", polling=" + isPollingLocked()

                + ", quitting=" + mQuitting + ")");

    }

} 


[]( )五、Looper

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



Looper主要涉及到构造、prepare和loop几个重要的方法,在保证一个线程有且只有一个Looper的设计上,采用了ThreadLocal以及代码逻辑的控制。



**变量**



//一些重要的变量

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

总结

可以看出,笔者的工作学习模式便是由以下 「六个要点」 组成:

❝ 多层次的工作/学习计划 + 番茄工作法 + 定额工作法 + 批处理 + 多任务并行 + 图层工作法❞

希望大家能将这些要点融入自己的工作学习当中,我相信一定会工作与学习地更富有成效。

下面是我学习用到的一些书籍学习导图,以及系统的学习资料。每一个知识点,都有对应的导图,学习的资料,视频,面试题目。

**如:我需要学习 **Flutter的知识。(大家可以参考我的学习方法)

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

  • Flutter 的思维导图(无论学习什么,有学习路线都会事半功倍)

  • Flutter进阶学习全套手册

  • Flutter进阶学习全套视频

    }

} 


[]( )五、Looper

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



Looper主要涉及到构造、prepare和loop几个重要的方法,在保证一个线程有且只有一个Looper的设计上,采用了ThreadLocal以及代码逻辑的控制。



**变量**



//一些重要的变量

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

总结

可以看出,笔者的工作学习模式便是由以下 「六个要点」 组成:

❝ 多层次的工作/学习计划 + 番茄工作法 + 定额工作法 + 批处理 + 多任务并行 + 图层工作法❞

希望大家能将这些要点融入自己的工作学习当中,我相信一定会工作与学习地更富有成效。

下面是我学习用到的一些书籍学习导图,以及系统的学习资料。每一个知识点,都有对应的导图,学习的资料,视频,面试题目。

**如:我需要学习 **Flutter的知识。(大家可以参考我的学习方法)

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

  • Flutter 的思维导图(无论学习什么,有学习路线都会事半功倍)

[外链图片转存中…(img-UrifgbOu-1630922321037)]

  • Flutter进阶学习全套手册

[外链图片转存中…(img-1Bszo86a-1630922321039)]

  • Flutter进阶学习全套视频

[外链图片转存中…(img-WksyM2m5-1630922321040)]

大概就上面这几个步骤,这样学习不仅高效,而且能系统的学习新的知识。



这篇关于Android面试6家一线大厂,这个问题是必问,30岁转行程序员的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程