Handler源码分析及面试题

目录

扩展:下图是在手机桌面点击应用图标,内部都发生了什么?涉及哪些通讯方式?如何打开了activity?

基础:

handler作用:

1)传递消息Message

2)子线程通知主线程更新ui       

常用api   

根据问题进行源码分析

一、线程切换的原理?

二、Handler引起的内存泄漏,最终是谁持有了Acitivity?

三、looper什么时候启动循环的?

四、handlerThread的原理?

五、子线程中维护的Looper,消息队列中无消息时的处理方案是什么?有什么用?主线程呢?

六、Handler如何发送延迟消息的?延迟的原理是什么?是不是阻塞的?

七、我们使用Message应该如何创建它?

八、Handler没有消息处理是阻塞的还是非阻塞的?为什么不会有ANR产生?


首先看下下面的面试题,根据问题进行源码分析。

扩展:下图是在手机桌面点击应用图标,内部都发生了什么?涉及哪些通讯方式?如何打开了activity?

 

1、在Launhaer界面点击按钮时,它会通过startActivity去打开Activity,但是此时没有Activity怎么办呢?通过AMS检测这些Activity的状态,发现此时Activity状态为不存在,那么,它会发送创建进程的请求到Zygote进程。Zygote进程就会去fork一个新的APP进程,新的进程是由AMS来进行管理。AMS交给自己的代理ActivityManagerProxy,这个代理首先会发送attach Application,因为首先要先启动Application才能启Activity,此时又回到了AMS里面进行进程间通讯,在AMS里面发送一个消息realStartActivityLocked,这个消息会启动ApplicationThreadProxy,之后ApplicationThreadProxy会发送scheduleLaunchaActivity消息去启动ApplicationThraed,之后发送消息7、8最终启动了Activity.onCrate。

2、此流程涉及了三种通讯,红色的Binder通讯和粉丝的Socket通讯以及绿色的Handler通讯。

3、为什么会有Socket通讯呢?因为此时进程还没有启动,Zygote进程属于C++层即流程图中2、3属于Linux中的流程。

4、红色属于跨进程间的通讯,所有跨进程间的通讯一定是Binder通讯!!!!!只要是跨进程通讯,其底层原理一定是Binder通讯。比如Activity之间的通讯,广播怎么传播的,service的底层原理等都是Binder通讯。

5、App进程内部的通讯都是通过Handler通讯。下面我们主要就是分析Handler通讯。

 

基础:

handler作用:

1)传递消息Message

//传递的数据
Bundle bundle = new Bundle();
bundle.putString("msg", "传递我这个消息");
//发送数据
Message message = Message.obtain();
message.setData(bundle);   //message.obj=bundle  传值也行
message.what = 0x11;
handler.sendMessage(message);
 
 
 
//数据的接收
final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (msg.what == 0x11) {
                Bundle bundle = msg.getData();
                String date = bundle.getString("msg");
            }
        }
};

2)子线程通知主线程更新ui       

 //创建handler
        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (msg.what == 0x11) {
                    //更新ui
                          ......
                }
            }
        };
 
        new Thread(new Runnable() {
            @Override
            public void run() {
                //FIXME 这里直接更新ui是不行的
                //还有其他更新ui方式,runOnUiThread()等          
                message.what = 0x11;     
                handler.sendMessage(message);  
            }
        }).start();

常用api   

 //消息
    Message message = Message.obtain();
    //发送消息
        new Handler().sendMessage(message);
    //延时1s发送消息
        new Handler().sendMessageDelayed(message, 1000);
    //发送带标记的消息(内部创建了message,并设置msg.what = 0x1)
        new Handler().sendEmptyMessage(0x1);
    //延时1s发送带标记的消息
        new Handler().sendEmptyMessageDelayed(0x1, 1000);
    //延时1秒发送消息(第二个参数为:相对系统开机时间的绝对时间,而SystemClock.uptimeMillis()是当前开机时间)
        new Handler().sendMessageAtTime(message, SystemClock.uptimeMillis() + 1000);
 
    //避免内存泄露的方法:
    //移除标记为0x1的消息
        new Handler().removeMessages(0x1);
    //移除回调的消息
        new Handler().removeCallbacks(Runnable);
    //移除回调和所有message
        new Handler().removeCallbacksAndMessages(null);

根据问题进行源码分析


一、线程切换的原理?

答:线程切换是由Handler来实现的。因为主线程是咱们的显示线程,所有的子线程和主线程的通讯都是通过Handler完成的。

举个例子:Retrofit可以完成切换线程,它可以自动切换到主线程中,OK则不可以,是因为Retrofit的源码中有通过Handler向主线程发送消息,将子线程切换到了主线程,所以Retrofit不可用于java后台的使用,因为它和android是强关联的。

如下图所有,通过上文的常用API发的消息,最后都会指向sendMessageAtTime();方法,最终都会调用最核心的方法MessageQueue.enqueuMessage()方法。这个方法将msg消息加入消息队列。此时为消息入队列。

此时我们重点分析一下这个方法:

当前出入的消息为msg时,首先判断mMessages消息队列中的第一个消息mMessages.prev,要是当前队列中消息为空,或者msg立即发送,则将该消息插到消息队列mMessages的头部;反之,则会在一个for的死循环中遍历消息队列并将传入消息msg插到单链表中合适的位置。事实上,消息队列是按照消息处理的时间when,按照从近到远的顺序排列的,最先要执行的任务放在消息队列的头部,依次排列。

/**
     * 发消息最核心的方法;将消息msg按照延迟时间when顺序加入队列。
     * 队列中的Message触发时间是有先后顺序的。当消息加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,
     * 以保证所有消息的时间顺序(内部遍历队列中Message,找到when比当前Message的when大的Message,将Message插入到该Message之前,如果没找到则将Message插入到队列最后)。
     * 一般是当前队列为空的情况下,next那边会进入睡眠,需要的时候MessageQueue这边会唤醒next方法。
     * @param msg
     * @param when 代表:实际执行的时间 = SystemClock.uptimeMillis() + delayMillis(即现在的时间+需要延迟的时间)
     * @return
     */
    boolean enqueueMessage(Message msg, long when) {
        // 每一个普通Message必须有一个target-handler
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        //此处加锁目的:为了消息进队列时能一个一个的进,进完一个,释放锁,再进下一个消息
        synchronized (this) {
            //已在使用状态(检查信息是否有使用过)
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
            //消息在退出状态->回收msg,被回收到消息池
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
            //标记使用状态,记录执行时间
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的, 则进入该该分支
            //when什么时候会等于0,在调用sendMessageAtFrontOfQueue时
            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 {
                //将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非
                //消息队头存在阻塞,并且同时Message是队列中最早的异步消息
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    //一直循环到没有消息(p==null)或者循环到当前延迟的时间小于下一个消息的时间。停止循环
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //将msg插入到此队列循环出来的位置的下一个位置
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            //去唤醒消息队列所在的线程
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

以上是消息入队列,那么消息又是如何出队列呢?

Handler中sendMessageDelayed方法只是将消息按照要执行的先后顺序插入到消息队列中的,插入好了并不意味着就会按照设定的延时时间处理消息,那Handler时如何延时处理该消息的呢?

消息出队列,是通过类Looper的loop方法,此方法里面有个死循环,此死循环通过Message msg=queue.next()从队列类MessageQueue中取消息,取出消息后调用msg.target.dispatchMessage(msg)方法,这个方法中调用了handlerMessage(),之后将在主线程(调用线程,也可能是子线程)中更新UI或者处理消息。

首先我们知道,Looper.loop()之后,线程就进入了消息监听的阶段:此时我们重点关注Looper类,此类中的loop方法:循环取出messagequeue消息队列中的消息,并分发出去。再把分发后的Message回收到消息池,以便重复利用。

public static void loop() {
    final Looper me = myLooper(); //从存储区拿出looper
    if (me == null) {
       throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;  //获取Looper对象中的消息队列
    ......
    //进入loop的主循环方法
    for (;;) { 
        Message msg = queue.next(); //一直出入阻塞状态,知道取到消息 
        if (msg == null) { //没有消息,则退出循环
            return;
        }
        ......
        //target是handler,此处用于分发Message 
        msg.target.dispatchMessage(msg); 
        ......
        msg.recycleUnchecked();  //将Message放入消息池
    }
}

当Handler中没有可用消息的时候,上面代码会一直阻塞在queue.next()的地方,直到消息返回,才会调用dispatchMessage进行消息的处理,要是返回的msg为空,那么Handler就会结束消息监听,不再监听任何消息。

通过Message msg=queue.next()从队列类MessageQueue中取消息,如何取得呢

可以看到这里也是一个for(;;)循环遍历队列,核心变量就是nextPollTimeoutMillis。可以看到,计算出nextPollTimeoutMillis后就调用nativiePollOnce这个native方法。这里的话大概可以猜到他的运行机制,因为他是根据执行时间进行排序的,那传入的这个nextPollTimeoutMillis应该就是休眠时间,类似于java的sleep(time)。休眠到下一次message的时候就执行。那如果我在这段时间又插入了一个新的message怎么办,所以handler每次插入message都会唤醒线程,重新计算插入后,再走一次这个休眠流程。

//不停提取下一条message
Message next() {
    final long ptr = mPtr;
    //判断是否退出消息循环
    if (ptr == 0) {
        return null;
    }
    int pendingIdleHandlerCount = -1; 
    //代表下一个消息到来前,还需要等待的时长
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //native层阻塞cpu。如果被阻塞,唤醒事件队列
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            ........

            //获取到了非异步消息
            if (msg != null) {
                //任务执行时间大于现在的时间
                if (now < msg.when) {
                //设置下一次轮询的超时时长
        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    mBlocked = false;//指定为非阻塞任务
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    //设置消息的使用状态,即flags |= FLAG_IN_USE
                    msg.markInUse();
                    return msg;   //成功地获取MessageQueue中的下一条即将要执行的消息
                }
            } else {
                //表示消息队列中无消息,会一直等待下去
                nextPollTimeoutMillis = -1;
            }

            ......
            
    }

上面是阻塞的从消息队列中获取可用消息的过程。其中nativePollOnce方法是一个native方法,其内部会根据传入的nextPollTimeoutMillis,在延迟这么长时间之后唤醒线程从消息队列中读取消息,内部调用的是epoll_wait方法。

我们知道当线程中没有新消息要处理的时候,线程处于休眠状态,当其他线程向Handler的消息队列中写入消息,这一动作并不会唤醒当前线程处理该消息,还需要向线程的eventfd中写入数据,从而唤醒休眠的线程开始处理数据,此处也是一样的,nativePollOnce函数内部会调用epoll_wait方法,设置超时时间为nextPollTimeoutMillis,epoll_wait在这个超时时间之后,就会唤醒线程,开始处理消息队列中的消息。当队列中没有消息时nextPollTimeoutMillis置为-1.此时nativePollOnce处于无限等待状态,直到调用quit()方法退出线程,或者收到新的消息。

next方法中,每次会从消息队列mMessages中获取链表中头部的消息,要是头部消息的设定执行的时间要比当前时间大,说明消息队列中所有的消息都还没有到可执行的时间,这是因为消息队列中消息在插入消息队列的时候,按照执行时间的先后顺序已经排序好了。这种情况下,会计算出一个等待时间,传递到nativePollOnce函数中,让native层在这个等待时间之后再唤醒线程读取消息队

nativiePollOnce这个native方法可以通过名字知道,他用的是linux中的epoll机制,具体是调用了epoll_wait这个方法。

 int epoll_wait(int epfd, struct epoll_event * events, intmaxevents, int timeout);

这个epoll和select一样都是linux的一个I/O多路复用机制,主要原理就不深入了,这里大概了解一下I/O多路复用机制和它与Select的区别就行。

Linux里的I/O多路复用机制:举个例子就是我们钓鱼的时候,为了保证可以最短的时间钓到最多的鱼,我们同一时间摆放多个鱼竿,同时钓鱼。然后哪个鱼竿有鱼儿咬钩了,我们就把哪个鱼竿上面的鱼钓起来。这里就是把这些全部message放到这个机制里面,那个time到了,就执行那个message。

epoll与select的区别:epoll获取事件的时候采用空间换时间的方式,类似与事件驱动,有哪个事件要执行,就通知epoll,所以获取的时间复杂度是O(1),select的话则是只知道有事件发生了,要通过O(n)的事件去轮询找到这个事件。

 

 

dispatchMessage将消息分发给接口Handler.Callback的方法handleMessage(),然后由主线程的去更新UI或者处理消息。

/**
     * 此处处理发送过来的消息,并将消息传给handleMessage
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

通过上述流程,我们可以看出Handler的工作流程如下图:

其中关联子线程和主线程的主要类就是MessageQueue,从整个流程可以看出:

子线程发消息:handler.sendMessage- - - >MessageQueue.enqueuMessage()----->然后存到MessageQueue。

主线程:取消息:activityThread.main-----> Looper.loop----->MessageQueue.next()------>handlerMessage().

 

二、Handler引起的内存泄漏,最终是谁持有了Acitivity?

答:activityThread.

匿名内部类Handler持有外部类activity的引用,但是这个引用最终传给了谁呢?

通过核心方法MessageQueue.enqueuMessage()中将handler的引用赋值给了msg.target。此时Message持有了handler的引用。msg将值传给了queue.enqueueMessage,此时MessageQueue类持有了Msg,MessageQueue的引用mQueue是在Looper中创建的。所以此时Looper持有了msg,Looper是在main方法创建的。即在activityThread中一直运行着looper.loop()。所以当时在调用位置以匿名内部类的形式创建handler实例时,匿名内部类持有外部类的引用,所以此时handler持有activity,然后赋值给了msg.target,最终由activityThread持有,如果msg一直存在,那么activityThread就一直持有此activity的引用,造成此acativity一直不能被释放,造成内存泄漏。所以将handler置为static就可以了,因为静态内部类不持有外部类的引用了。

 

通过源码分析上述描述:

1、分析activityThread:

主线程中Looper,MessageQueue,Handler三者关联的思路:

主线程-->prepareMainLooper()(内部调用prepare() ,去实例化Looper,Looper实例化同时创建了messagequeue,一一对应关系)-->主线程中的handler获取当前线程的Looper

子线程-->直接通过Looper.prepare()去实例化Looper,Looper实例化同时创建了messagequeue(一一对应关系) -->实例化Handler同时获取当前子线程的Looper

 

在Handler类中,有上图可知,发送消息最后都会指向Handler中的enqueueMessage方法,此方法将handler的引用(在activity中创建匿名类Handler时,将activity的引用传给了handler)赋值给了msg,

msg将值传给了queue.enqueueMessage,此时MessageQueue类持有了Msg。

 

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        //将Handler的引用this赋值给了msg
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

从下图可以看出MessageQueue的引用mQueue是在Looper中创建的。所以此时Looper持有了msg,而Looper的引用又是谁创建的呢?

   
     /*
      *在创建Looper实例的时候创建MessageQueue的引用
      */
    private Looper(boolean quitAllowed) {
         //创建MessageQueue的引用
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }



     /**
      *在从初始化Handler时候从looper类中获取MessageQueue的引用
      */
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
        mLooper = looper;
      //从looper类中获取MessageQueue的引用
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

 

Looper是在main方法创建的。即在activityThread中一直运行着looper.loop()。

ActivityThread中的main函数如下:

public static void main(String[] args) {
        ......

        //启动Looper
        Looper.prepareMainLooper();
 
        ......
        //区别:子线程是创建handler;
        //主线程是通过getHandler()获取内部类实例
        if(sMainThreadHandler==null){
        sMainThreadHandler=thread.getHandler();
        }
        ......

       //开始循环loop
       Looper.loop();
}
 

 

 

三、looper什么时候启动循环的?

答:APP中的每一个点击、界面刷新等所有操作都是基于消息的,所以message是APP的心脏,一直处于跳动状态,即looper.loop()一直处于运行循环中,从上图可知,在应用已启动时ActivityThread就会触发main函数,此时就开始运行looper.loop()。

四、handlerThread的原理?

主要考察的是并发编程的知识,多线程、锁等知识点。(异步存在形式有thread,handlerThead,asyncTask,线程池,intentService)

handlerThread的基本使用:

public class MainActivity extends AppCompatActivity {
 
    private HandlerThread thread;
    static Handler mHandler;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //创建一个HandlerThread并启动它
        thread = new HandlerThread("MyHandlerThread");
        thread.start();
 
        //使用HandlerThread的looper对象创建Handler
        mHandler = new Handler(thread.getLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                //这个方法是运行在 handler-thread 线程中的,可以执行耗时操作,因此不能更新ui,要注意
                if (msg.what == 0x1) {
                    try {
                        Thread.sleep(3000);
                        Log.e("测试: ", "执行了3s的耗时操作");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //这个方法是运行在 handler-thread 线程中的,可以执行耗时操作,因此不能更新ui,要注意
//                    ((Button) MainActivity.this.findViewById(R.id.button)).setText("hello");
                }
                return false;
            }
        });
 
        //停止handlerthread接收事件
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                thread.quit();
            }
        });
 
        //运行
        mHandler.sendEmptyMessage(0x1);
    }
 

首先handlerThread也是一个子线程,继承thread,不过内部比普通线程多了一个Looper,因为存在looper才能存在handler,不然handler的存在就没有意义了。

通过源码分析:在调用start()方法时就会启动run方法,此时开始启动Looper,之后使用HandlerThread的looper对象创建Handler,如果此时还没有启动完成Looper(此流程在子线程),那么getLooper就会获取为null,所以此处添加锁机制,使用的内置锁,这个锁是的开锁和关锁都是在JVM中由JVM自动完成的,所以叫做内置锁。

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
//此时是在子线程中初始化Looper,而其他获取looper引用的位置可能在其他线程(主线程),所以此处加锁是等初始化完成后再由其他位置调用调用。
        synchronized (this) {
//            notifyAll();
            mLooper = Looper.myLooper();
            //notifyAll含义是锁所在的代码块执行完毕后再释放所有被加锁的位置。所以notifyAll放在上面也没问题
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    /**
     * 供调用者获取Looper的引用mLooper
     */
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        // 调用者获取ooperL,加锁来判断是否mLooper存在,如果不存在等待mLooper引用的创建,创建成功后被notifyAll唤醒。
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

 

几个地方要注意:

a.handleMessage()可以做耗时操作,但是不能更新ui,因为此时的handler处于子线程

b.如果不手动的调用HandlerThread.quit()或者HandlerThread..quitSafely()方法,HandlerThread会将持续的接收新的任务事件。

c.只有handleMessage()方法执行完,这轮的任务才算完成,HandlerThread才会去执行下一个任务。而且在此次执行时,即使手动的去调用quit()方法,HandlerThread的此次任务也不会停止但是,会停止下轮任务的接收。


举例:
 
//耗时任务换成这个,点击按钮执行quit()方法,发现此次任务依旧执行
for (int i = 0; i < 99999999; i++) {
    Log.e("测试: ", "输出" +i);
}

d.即使多次执行mHandler.sendEmptyMessage(0x1),任务队列中的任务依然只能一个一个的被处理。上一任务结束,开始执行下一个。

 

五、子线程中维护的Looper,消息队列中无消息时的处理方案是什么?有什么用?主线程呢?

答:让线程退出。

如果线程不退出,Looper.loop是一个死循环,就会一直执行,此时子线程一直占用资源,就会引起内存泄漏。

 

让线程停止接收事件的2种方法。(上文中的handlerThread也适用)

第一个就是quit(),实际上执行了MessageQueue中的removeAllMessagesLocked方法,该方法的作用是把MessageQueue消息池中所有的消息全部清空,无论是延迟消息(带Delayed的)还是非延迟消息。

第二个就是quitSafely(),执行了MessageQueue中的removeAllFutureMessagesLocked方法,该方法只会清空MessageQueue消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让Handler去处理,quitSafely相比于quit方法安全之处在于清空消息之前会派发所有的非延迟消息。


void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }
        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;
 
            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }
            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
}

六、Handler如何发送延迟消息的?延迟的原理是什么?是不是阻塞的?

我们从上文分析MessageQueue的enqueuMessage(Message msg, long when)方法时可知,传入的when即为:实际执行的时间 = SystemClock.uptimeMillis() + delayMillis(即现在的时间+需要延迟的时间)。通过for(;;)遍历消息队列,一直循环到没有消息(p==null)或者循环到当前延迟的时间小于下一个消息的时间时,将msg插入到此位置的下一个位置。

所以就是根据实际时间将msg插入单链表的队列中,nativePollOnce函数内部会调用epoll_wait方法,设置超时时间为nextPollTimeoutMillis,epoll_wait在这个超时时间之后,就会唤醒线程,开始处理消息队列中的消息。所以其延迟的原理就是由nativePollOnce函数根据时间顺序去处理不同的消息,达到nextPollTimeoutMillis就处理消息,其他时间就休眠,通过next()使其处于阻塞状态。

七、我们使用Message应该如何创建它?


//2种创建消息方法
//1.通过handler实例获取
Handler handler = new Handler();
Message message=handler.obtainMessage();
 
//2.通过Message获取
Message message=Message.obtain();
 
 
//源码中第一种获取方式其实也是内部调用了第二种:
public final Message obtainMessage(){
    return Message.obtain(this);
}

Message obtain()时Message的复用机制(享元模式)(比如RecycleView的bindView)其目的为了减少资源浪费。

假设创建Message都通过new message,由于App中所有操作都是通过消息处理的,那么很多的message占用大量内存,而且在他们不用的时候也不会立即释放回收,而是等待GC,那么由于message占用大量内存,就会频繁的触发GC,频繁的释放掉,GC的时候就会造成卡顿,回收各种msg,所以使用复用,而不是new message()

(JVM可达性分析,不可达的就回收,new 对象,不用的时候不是立即回收,而是被JVM标记为可清除,等内存不足时,在通过可达性分析,不可达的回收,GC的时候会停止所有的线程,就会造成卡顿)

 

 

八、Handler没有消息处理是阻塞的还是非阻塞的?为什么不会有ANR产生?

没有消息就会阻塞,主线程一直处于休眠状态。。。。。。

对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出,例如,binder线程也是采用死循环的方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单地死循环,无消息时会休眠。但这里可能又引发了另一个问题,既然是死循环又如何去处理其他事务呢?通过创建新线程的方式。真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死。
主线程的死循环一直运行是不是特别消耗CPU资源呢? 其实不然,这里就涉及到Linux pipe/epoll机制,简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。 

ANR的原理:如下图在各种场景下都有一个触发ANR的时间点,这是通过Message来设置的一个定时器,达到定时器的事件,还没有完成相应的操作,就会发生ANR。但是主线程阻塞,处于休眠状态,和ANR完全没有关系。处于完全不同的体系中

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HMP*

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值