美文网首页
Handler常见知识点

Handler常见知识点

作者: 业精于勤_荒于嬉 | 来源:发表于2020-04-28 13:29 被阅读0次

    (1)主线程为什么不能进行耗时的操作。

    大家都知道在Android中,主线程中不能进行耗时的操作,比如请求网络,可能会报NetworkOnMainThreadException。这是为什么呢?
    这是因为:
    一些耗时的操作,比如(访问网络,下载数据,查询数据库等),很容易造成主线程的阻塞,导致事件停止分发(包括绘制事件)。轻则降低用户体验,更坏的情况是,如果主线程阻塞超过5秒,就会导致ANR。

    (2)子线程为什么不能更新UI?

    在子线程中更新UI,会抛出:

    Only the original thread that created a view hierarchy can touch its views.
    

    从at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7905)这个异常路径,我们可以看出是在ViewRootImpl这个类中做了判断,
    我们一起来看一下这个类中是怎样判断的?

    @Override
        public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                checkThread();
                mLayoutRequested = true;
                scheduleTraversals();
            }
        }
    

    我们看到在ViewRootImpl的requestLayout方法中调用了checkThread()方法:

    void checkThread() {
            if (mThread != Thread.currentThread()) {
                throw new CalledFromWrongThreadException(
                        "Only the original thread that created a view hierarchy can touch its views.");
            }
        }
    

    那么我再向上追溯一下,看这个checkThread到底是在什么调用的:

    我们都知道一个App进程在启动的时候会创建一个主线程,在ActivityThread类里面管理这个主线程。
    里面有一个handleResumeActivity方法:

    @Override
        public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,String reason) {
       ........
      ViewManager wm = a.getWindowManager();
       ..........
      wm.addView(decor, l);
    }
    

    这里我们可以看到通过activity获取得到Activity里面的WindowManger,而WindowManager类是一个接口,它的实现类是WindowManagerImpl:

    public interface WindowManager extends ViewManager {
    }
    

    所以这里的wm.addView(decor,l)实际上调用的是WindowManagerImpl类里面的addView方法,我们来看一下WindowManagerImpl类里面的addView方法:

    @Override
        public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
            applyDefaultToken(params);
            mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
        }
    

    发现这里又调用了WindowManagerGlobal中的addView方法,我们看一下WindowManagerGlobal中的addView方法:

    public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow) {
        .........
                root = new ViewRootImpl(view.getContext(), display);
    
                view.setLayoutParams(wparams);
    
                mViews.add(view);
                mRoots.add(root);
                mParams.add(wparams);
    
                // do this last because it fires off messages to start doing things
                try {
                    root.setView(view, wparams, panelParentView);
                } catch (RuntimeException e) {
                    // BadTokenException or InvalidDisplayException, clean up.
                    if (index >= 0) {
                        removeViewLocked(index, true);
                    }
                    throw e;
                }
        .........
    }
    

    我们发现在WindowManagerGlobal这个类的addView方法中创建了ViewRootImpl这个类的实例,并调用了ViewRootImpl中的setView方法,来看一下这个方法的实现:

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
     .........
    requestLayout();
     .........
    }
    

    方法很长,我们只看里面的关键代码,调用了requestLayout这个方法,好了,到这就和我们上面分析的对上了。:

    @Override
        public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                checkThread();
                mLayoutRequested = true;
                scheduleTraversals();
            }
        }
    
    void checkThread() {
            if (mThread != Thread.currentThread()) {
                throw new CalledFromWrongThreadException(
                        "Only the original thread that created a view hierarchy can touch its views.");
            }
        }
    
    

    由此,我们也可以看出,子线程也可以更新UI,只要在Activity的onResume之前修改就不会抛出这个异常了。Android UI操作并不是线程安全的,并且这些操作必须在UI线程执行。

    (3)Looper在主线程中死循环为什么没有导致界面的卡死?

    1.导致卡死的是在UI线程执行耗时操作导致页面出现掉帧,设置ANR,Looper.loop()这个操作本身不会导致这个情况。
    2.有人可能会说,我在点击事件中设置死循环会导致页面卡死,同样是死循环,不都一样的吗?Looper会在没有消息的时候阻塞当前线程,释放CPU资源,等到有消息的时候,再唤醒主线程。
    3.App进程中是需要死循环的,如果循环都结束了的话,APP进程也就结束了。

    (4)Handler是如何做到延迟发送的?

    我们以handler.sendMessageAtTime();发送消息的方法为例,来说一下Handler是怎么样做到延迟发送的:

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

    Handler里面的sendMessageAtTime最终是调用了handler的enqueueMessage方法,并把延迟的时间当做参数传给了这个方法:

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

    而handler的enqueueMessage方法最终又调用了MessageQueue里面的enqueueMessage方法,并把延迟的时间也传给了这个方法:

    boolean enqueueMessage(Message msg, long when) {
            if (msg.target == null) {
                throw new IllegalArgumentException("Message must have a target.");
            }
            if (msg.isInUse()) {
                throw new IllegalStateException(msg + " This message is already in use.");
            }
    
            synchronized (this) {
                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;
                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);
                }
            }
            return true;
        }
    

    我们发现,这里是把这个延迟的时间赋值给了Message的when。这个是消息入队的方法。
    再看一下从消息对列中取消息的方法:Looper.looper

    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;
    
          ................
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
            ......
          }
    }
    

    发现从队列中取Message是调用了MessageQueue的next方法:

    Message next() {
            final long ptr = mPtr;
            if (ptr == 0) {
                return null;
            }
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
    
                nativePollOnce(ptr, nextPollTimeoutMillis);
    
                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 {
                            .....
                        }
                    } else {
                        // No more messages.
                        nextPollTimeoutMillis = -1;
                    }
                    ......
            }
        }
    

    注意这里的nativePollOnce(ptr, nextPollTimeoutMillis);这里是一个 for 死循环,唤醒后计算出根据now 和msg.when,如果now < msg.when 则计算出nextPollTimeoutMillis,下一个循环赋值给nativePollOnce(ptr,nextPollTimeoutMillis),告诉阻塞时间:

    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 {
       .....
    }
    

    注意:Handler 只是利用到了 native 层的 NativeMessageQueue和 native 层的 Looper 来完成阻塞和唤醒工作。消息队列的插入和消息的读取都是在 Java 层完成的。

    相关文章

      网友评论

          本文标题:Handler常见知识点

          本文链接:https://www.haomeiwen.com/subject/ahlvwhtx.html