Android面试6家一线大厂,这个问题是必问

想进BAT一线互联网大厂,该怎么准备技术面试?一位6年老Android的面经总结(附300+面试题) 前言 准备面试其实已经准备了挺久了,当时打算面试准备了差不多以后,跟公司谈谈涨薪的事情,谈不拢的话,就年后直接找其他的公司。谁想到婚假还没休完,老板就在公司宣布了撤出上海的决定,愿意去深圳的就去,不愿意的就自己找工作,给了一个多月的时间找工作,就没有赔偿了,反正公司也没钱。悲哀…. 幸好提前准备过,不过都是断断续续的今天看点这个,明天看点那个,三天打鱼两天晒网的,也没用心去准备。这下好了,直接到了最差的地步了,没办法,用心准备吧。 简历内容 基本信息 姓名,联系方式,工作年限,学校,专业,学历.. 阅读详情

年后面了六家大厂,每家都会问的一个问题就是Android的消息机制!可见Android的消息机制是多么重要!

消息机制之所以这么重要是因为Android应用程序是通过消息来驱动的,Android某种意义上也可以说成是一个以消息驱动的系统,UI、事件、生命周期都和消息处理机制息息相关,并且消息处理机制在整个Android知识体系中也是尤其重要,在太多的源码分析的文章讲得比较繁琐,很多人对整个消息处理机制依然是懵懵懂懂。

这篇文章通过一些问答的模式结合Android主线程(UI线程)的工作原理来讲解,源码注释很全,还有结合流程图,如果你对Android 消息处理机制还不是很理解,我相信只要你静下心来耐心的看,肯定会有不少的收获的。

目录

  1. Android消息机制流程
  2. Handler
  3. Message
  4. MessageQueue
  5. Looper
  6. HandleThread
  7. 文末福利

篇外话

最近想把自己比较薄弱的Java&Android基础抽时间进行学习加强些,这也更符合自己的内心追求和自我期待。并行的开始另外一段学习旅程,从Handler消息机制开启,结合消息机制的流程以及源码进行学习分析。

一、Android消息机制流程

我们先通过下面两张图来对Android消息机制流程以及关键类之间的关系有个了解,后面我们再结合源码一一进行分析。

消息机制的流程

Handler、Message、MessageQueue、Looper之间的关系

二、Handler

Handler有两个主要的用途:

  1. 调度消息在某个时间点执行;
  2. 不同线程之间通信

2.1 全局变量

 final Looper mLooper;     //有Looper的引用
 final MessageQueue mQueue;//有MessageQueue的引用
 final Callback mCallback;
 final boolean mAsynchronous;
 IMessenger mMessenger;

2.2 构造方法

    public Handler() {
        this(null, false);
    }
    public Handler(@NonNull Looper looper) {
        this(looper, null, false);
    }

 public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

2.3 获取Message

//从Message复用池中获取一个Message   
 public final Message obtainMessage()
    {
        return Message.obtain(this);
    }

//和上面的方法基本一致,差异在于从复用池中获取到Message后给what赋值
    public final Message obtainMessage(int what)
    {
        return Message.obtain(this, what);
    }
//...其他obtainMessage类似

2.4 发送消息

下面我们挑几个发送方法来看下

sendMessage: 发送一个Message,when为当前的时间

MessageQueue根据when进行匹配插入位置

    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;
        ......
        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>();

    final MessageQueue mQueue;

    final Thread mThread;

构造方法
在构造Looper的时候 创建和Looper一一对应的MessageQueue

    private Looper(boolean quitAllowed) {
        //在构造Looper的时候 new一一对应的MessageQueue
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

prepare
我们这里可以看到消息机制是 如何保证一个线程只有一个Looper。

//quitAllowed参数是否允许quit,UI线程的Looper不允许退出,其他的允许退出
private static void prepare(boolean quitAllowed) {
    //保证一个线程只能有一个Looper,这里的sThreadLocal
    if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

loop
我们在MessageQueue的next方法已经分析过nativePollOnce这个方法可能会阻塞,直到拿到message。
如果next返回一个null的Message退出Looper循环,否则进行msg的派发。
取出的msg执行完之后,会加入到回收池中等待复用。recycleUnchecked我们在Message中也已经分析过了。不清楚的可以再回看。

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
    ……
    for (;;) {
            //next方法是一个会阻塞的方法,MessageQueue的next方法前面我们已经分析过nativePollOnce这个方法会可能阻塞,直到拿到message。
            Message msg = queue.next(); 
            //收到为空的msg,Loop循环退出。那么何时会收到为空的msg呐? quit
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            //msg的派发,msg.target就是Handler,即调用Handler的dispatchMessage派发消息
            msg.target.dispatchMessage(msg);

            ……
            //msg回收
            msg.recycleUnchecked();
        }

六、HandleThread

HandlerThread是一个带有Looper的Thread。

全局变量

public class HandlerThread extends Thread {
    int mPriority;//线程优先级
    int mTid = -1;//线程id
    Looper mLooper;
    private Handler mHandler;
    ......
}

构造方法

    public HandlerThread(String name) {
        super(name);
        //用于run时设置线程的优先级Process.setThreadPriority(mPriority);

        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

run方法
进行Looper的prepare和loop的调用,配置好Looper环境

    @Override
    public void run() {
        //线程id
        mTid = Process.myTid();
        //调用Looper的prepare方法,把当前该线程关联的唯一的Looper加入到sThreadLocal中
        Looper.prepare();

        synchronized (this) {
            //从sThreadLocal中获取Looper
            mLooper = Looper.myLooper();

            notifyAll();
        }
        //设置线程的优先级,默认THREAD_PRIORITY_DEFAULT,如果是后台业务可以配置为THREAD_PRIORITY_BACKGROUND,根据具体场景进行设置
        Process.setThreadPriority(mPriority);
        //可以做一些预设置的操作
        onLooperPrepared();
        //开始looper循环
        Looper.loop();
        mTid = -1;
    }

使用HandlerThread的一般流程如下

// Step 1: 创建并启动HandlerThread线程,内部包含Looper
HandlerThread handlerThread = new HandlerThread("xxx");
handlerThread.start();

// Step 2: 创建Handler
Handler handler = new Handler(handlerThread.getLooper());

handler.sendMessage(msg);

这样有一个弊端,就是每次使用Handler都要new HandlerThread,而Thread又是比较占用内存,
能不能减少Thread的创建,或者说是Thread的复用.
并且实现Message能够得到及时执行,不被队列中前面的Message阻塞;
这的确是一个有很有意思很有挑战的事情。

七、资料

最后的最后,如果你打算开始读源码了,可以先看看我整理的这份资料。

《Android Framework精编内核解析》

本笔记讲解了Framework的主要模块,从环境的部署到技术的应用,再到项目实战,让我们不仅是学习框架技术的使用,而且可以学习到使用架构如何解决实际的问题,由浅入深,详细解析Framework,让你简单高效学完这块知识!

第一章:深入解析Binder

Binder机制作为进程间通信的一种手段,基本上贯穿了andorid框架层的全部。所以首先必须要搞懂的Android Binder的基本通信机制。

本章知识点

  • Binder 系列—开篇
  • Binder Driver 初探
  • Binder Driver 再探
  • Binder 启动 ServiceManager
  • 获取 ServiceManager
  • 注册服务(addService)
  • 获取服务(getService)
  • Framework 层分析
  • 如何使用 Binder
  • 如何使用 AIDL
  • Binder 总结
  • Binder 面试题全解析

第二章:深入解析Handler

本章先宏观理论分析与 Message 源码分析,再到MessageQueue 的源码分析,Looper 的源码分析,handler 的源码分析,Handler 机制实现原理总结。最后还整理Handler 所有面试题大全解析。

Handler这章内容很长,但思路是循序渐进的,如果你能坚持读完我相信肯定不会让你失望。

第三章:Dalvik VM 进程系统

Andorid系统启动、init 进程、Zygote、SystemServer启动流程、 应用程序的创建使用,Activity的创建、销毁 Handler和Looper。

第四章 深入解析 WMS

窗口管理框架 系统动画框架 View的工作原理。

第五章 PackagerManagerService

包管理服务,资源管理相关类。

由于篇幅限制,这里只能展示部分内容,朋友们如果需要这份完整版的PDF资料合集,微信扫描下方优快云官方二维码【免费获取】。

【HDL系列】除法器(3)——基2 SRT算法 目录 一、除法表示 二、数字递归算法基础公式 三、QDS(Quotient Digit Selection)函数 四、基2 SRT算法 一、除法表示 除法被定义如下: 其中,x是被除数,d是除数,q是商,rem是余数。 商的精度由ulp(unit of last position)来决定: 如果ulp=1, 商q则是整数; 如果ulp=r^(-n),n是商数个数,r是所有输入操作数的基,此时商为小数。 二、数字递归算法基础公式 在使用数字递归算法(Digit Re... 阅读详情

相关推荐

为什么PCB设计中的50Ω阻抗如此重要?从USB到以太网的实战解析

本文深入解析了PCB设计中50Ω特征阻抗的核心重要性及其工程权衡起源。文章从传输线理论出发,阐释了阻抗匹配对防止信号反射、确保信号完整性的关键作用,并对比了USB、以太网等不同应用场景的阻抗标准。同时,文章结合实际案例,剖析了在两层板等简单设计中实现精准阻抗控制的难点与实战解决方案。

weixin_29268385的博客 297

探究Handler的postAtFrontOfQueue方法

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一、pandas是什么? 目录 文章目录 前言 一、pandas是什么? 二、使用步骤 1.引入库 2.读入数据 总结 二、使用步骤 1.引入库 2.读入数据 总结 前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。 提示:以下是本篇文章

baochengchen的博客 4745

先进封装Interposer仿真步骤及说明

摘要: Interposer仿真是2.5D/3DIC封装设计的关键环节,涵盖信号完整性(SI)、电源完整性(PI)、热及结构应力分析。流程分为五个阶段:1)前处理,定义层叠结构与材料属性;2)参数提取,通过3D电磁建模获取TSV、传输线等寄生参数;3)仿真设置,选择求解器并设定频率范围;4)结果分析,评估信号损耗、阻抗及电源网络性能;5)后处理,进行多物理场协同仿真并生成模型。核心挑战在于多尺度与多物理场耦合,需优先确保高速信号与电源网络的仿真精度。常用工具包括Ansys HFSS、Cadence Sigr

DSY_0324的博客 1345

Handler处理消息的优先级策略

MessageQueue.next()方法中,进行消息遍历的时候,如果发现了一个msg.target =null的时候,就说明当前消息就是一个屏障消息,然后会遍历消息链表直到msg.isAsynchronous为true,也就是找到第一个异步消息的时候,才能继续进行消息的分发和处理,否则会一直遍历直到移除屏障消息,然后才能够执行队列中的同步消息。消息的处理顺序为3,4,1,2,同步屏障加入得到消息队列中后,会优先处理消息队列中的异步消息,直到移除屏障消息之后才会处理消息队列中的同步消息。

m0_50417702的博客 1993

群晖NAS下docker安装MySql

本文将一步一步讲解如何在群晖NAS的docker中启用一个MySQL容器 , 算是初学者对问题解决的一个记录.

qq_46375248的博客 5976

电阻应变式力传感器

电阻应变式力传感器

lu_fresh_student的博客 5075

Android消息机制源码解析(二)——消息的执行者Handler

这一节来分析Handler的源码实现,Handler从字面理解就是处理程序、处理器的意思,在Android消息机制中它扮演着执行者的角色,消息的发送、接收、处理都是由Handler来完成的,它也是跟开发者打交道最多的,因此我们更有必要深入了解Handler的实现原理。 一、首先是Handler类的定义: public class Handler Handler有以下几个主要属性字段: fi

Battling against our own ignorance 1835

Vitis Model Composer闪退?手把手教你用命令行启动Vivado 2021后的System Generator

本文详细解析了Vitis Model Composer闪退问题的根本原因,并提供了命令行启动Vivado 2021后System Generator的完整解决方案。从环境变量配置、组件缺失排查到权限问题处理,手把手教你通过命令行深度排错,并分享高级自动化脚本和系统优化技巧,帮助开发者高效使用Vitis Model Composer与System Generator进行HDL开发。

weixin_26905487的博客 348

小知识

getPreferences():获取到作用域是本Activity的preferencegetSharedPreferences():获取到作用域是本应用程序的preferencegetDefaultSharedPreferences():获取到全局作用域的preference        1:sendMessage() 立即发送Message到消息队列       2:

chuchu521的专栏 671

论文必备 - YOLOv8训练前一键扩充数据集,支持9种扩充方法,支持图像和标签同步扩充

在网络模型训练前,大量的数据集是必备的。而对于一些特殊场景中的特殊目标,大量的数据集往往难以获取,无法满足模型训练的基本需求。本文提供了,脚本可一键运行,。

Limiiiing的博客 769

飞牛NAS1.1.19更新后SSH连不上?两步快速恢复连接

飞牛NAS1.1.19版本更新后SSH无法连接😟,实则是官方安全策略升级默认关闭了该服务,本文分享两步实操配置恢复SSH连接💻,还同步了官方的设备安全配置建议,帮助用户兼顾使用与设备防护🔒。

XiaoqiangClub的博客 2098

c4android sleep函数,Android Handler机制5--消息发送

1、Handler发送消息2、Handler的send方案3、Handler的post方案1 Handler发送消息send方案.pngpost方案.pngsend方案发送消息(需要回调才能接收消息)sendMessage(Message) 立即发送Message到消息队列sendMessageAtFrontOfQueue(Message) 立即发送Message到队列,而且是放在队列的最前面se...

weixin_39899776的博客 343

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

Android面试6家一线大厂,这个问题是必问!

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。下图是我进阶学习所积累的历年腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节祝大家都能得到自己心仪工作。《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

2401_84123077的博客 838

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Android最全Android面试6家一线大厂,这个问题是必问!,2024年最新50w字+的Android技术类校招面试题汇总

最后这里放上我这段时间复习的资料,这个资料也是偶然一位朋友分享给我的,里面包含了腾讯、字节跳动、阿里、百度2020-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。还有高级架构技术进阶脑图、高级进阶架构资料帮助大家学习提升进阶,这里我也免费分享给大家也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。一起互勉~网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

2401_84563220的博客 413

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

2024年最全Android面试6家一线大厂,这个问题是必问!(1),2024年最新嵌入式软件开发面试题库

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示。网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。需要这份系统化学习资料的朋友,可以戳这里获取一个人可以走的很快,但一群人才能走的更远!

2301_82242426的博客 490

Android 一线大厂面试总结

最近因为团队项目被边缘化,新项目接连流产,已经可以上市的项目突然宣布不做了,又加上外包岗的离职,作为正岗也有被裁员的风险,于是出去看了一下机会。面试了阿里巴巴,百度,头条,趣头条,小米,爱奇艺,映客,快手等一线的大厂,最终拿到了满意的offer。下面将面经分享出来。 想看更多东西详情请看主页 面试前的准备 下面我将从以下几个方面来分享一下面试前的准备。 一.给自己定位 首先要给自己定位,自己现在是...

beibei_programme的博客 1687

Android:金九银十怎么从中小企业挤进一线大厂?大厂面试题问的深度?

一名程序员的职业进阶阶段: 初入职场 > 小规模公司 > 中型团队开发 > 一线公司团队开发 > 一或二线公司团队管理 小规模公司 小规模公司按我理解指的是 0 - 200 人公司,Android 开发 1 ~ 3 个,拍着脑袋做事,团队不稳定,容易整个部门被GC (Garbage Collection)。 面试中型公司会问哪些问题? 中型规模的公司有一定研发人员,一般情况下会有5 - 12 Android 开发维护一个 App。团队成员有一定的项目开发经验,不会像...

weixin_44339238的博客 800

移动开发最新大厂Android岗面试,居然还问这些问题!受教了!,2024年最新安卓面试2024

*要想成为高级安卓工程师,必须掌握许多基础的知识。**在工作中,这些原理可以极大的帮助我们理解技术,在面试中,更是可以帮助我们应对大厂面试官的刁难。网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。需要这份系统化学习资料的朋友,可以戳这里获取一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

2401_84520271的博客 855

2020我的-Android-年中面试复盘:怎么挤进一线大厂?需要掌握些什么?

中型规模的公司有一定研发人员,一般情况下会有5 - 12 Android 开发维护一个 App。团队成员有一定的项目开发经验,不会像小公司经理一样问你:「这个功能会不会做,能不能独立开发App」这种毫无水平的问题。HashMap 数据结构是怎样的,可以多线程使用吗,多线程需要使用哪些集合?只要你能回答上以下内容,面试官会微微一笑数组作为哈希桶,扩容时候已链表形式,超过8个节点结构改为红黑树。

2401_85792673的博客 778

一线大厂的面试经历

先说说我是如何展开面试复习的,主要是按照以下的四大类展开的: 1.安卓基础知识 2.JAVA基础知识 3.算法和数据结构 4.职业发展规划相关问题 First: 1.Activity 任务栈和 4 种启动模式关联性问题 2.IPC 非常重要,想要达到中级和高级的水平就必须要读懂,跨进程几种方式必须了解 Binder肯定是必问的,当然还有 AIDL ...

DuncanDavid的博客 920
上一篇: 史上最全Android性能优化总结,经典收藏
下一篇: 大厂需要什么样的 Android 开发?
Agent学习路线
博客等级 码龄6年 1万+粉丝 1902原创
评论 11
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值