Android面试常考点整理

本文深入探讨Android中UI更新的限制与Handler机制的工作原理。解释为何子线程不能直接更新UI,以及如何通过Handler在主线程中安全地执行UI更新。详细分析了Looper、MessageQueue和Handler的内部运作,包括消息的发送、处理流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Handler机制,子线程为什么不能更新UI?

Android UI操作并不是线程安全的,并且这些操作必须在UI线程执行。

利用handler可以从子线程发送消息到主线程达到更新UI的目的;在子线程里可以更新在子线程中加载的view(需要looper);在主线程的onCreate()中创建的子线程也可以更新主线程的UI,前提是不做其他耗时操作;除外,在onCreate()的子线程做耗时后更新UI报错;子线程没有looper想更新自己的UI也报错;点击事件(可以看做耗时事件)中的子线程更新主线程UI报错。由此可以看出,持有view的线程都可以更改自己的view,主线程默认looper不需要手动添加。一般的更新其他线程的UI需要handler即线程间的通信但handler只是线程间传递数据,更新操作还是要rootview来完成。那为什么在onCreate()的子线程更新主线程UI没有报错呢?而稍一耗时就报错了呢?必然是因为更新UI快于异常线程检测以至UI更新已经完了可能ViewRootImpl才刚刚初始化完成,但这样是不安全的,大家都不推荐这种方式。

而Handler更新主线程的UI也是在主线程中进行的,只不过通过handler对象将子线程等耗时操作中得到的数据利用message传到了主线程。关于handler的原理,老生常谈。温故而知新。今天试着解释一下相关的源码,6.0以上的。

Looper是final修饰,不可继承。

Class used to run a message loop for a thread.

Threads by default do not have a message loop associated with them; to create one ,call #prepare in the thread that is to run the loop,and then #loop to have it process messages until the loop is stopped.

Most interaction with a message loop is through the #Handler class.

Looper类的解释告诉我们两个主要方法,prepare和loop。主线程不需要显示调用Looper的两个方法。但在子线程中,则需要显式调用。几个全局变量:

private static final String TAG = "Looper";

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
    final Thread mThread;

ThreadLocal在这里理解为将Looper对象与当前线程绑定,在同一个线程作用域内可见,是一个Java工具类。一个静态的looper引用,一个messageQueue引用,一个线程引用。

/** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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));
}

prepare方法中传入的布尔值最终传给了MessageQueue的构造方法中,它代表了 True if the message queue can be quit。prepare方法1.得到了looper对象并且2.looper在实例化的时候同时获取到当前线程的引用,还会3.实例化一个成员变量MessageQueue。

Looper中的构造方法是私有的:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

Looper的loop方法,首先会拿到looper和消息队列实例,接着在无限循环中调用queue.next()取出队列的消息,交给msg.target.dispatchMessage(msg)处理。这其中消息的发送正是由handler.sendMessageAtTime()来做。

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    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;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

在第6行,调用myLooper方法返回了ThreadLocal保存的looper对象:

/**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

第十行将looper实例化时的messageQueue传给了新的引用MessageQueue,十七到二十行是无限循环,queue.next()取出消息。msg.target.dispatchMessage(msg)处理消息,msg.recycleUnchecked()回收资源。到此,消息队列和轮询已经建立,下面应该是发送消息了。那就来看一下Handler的代码:

/**
 * A Handler allows you to send and process {@link Message} and Runnable      Handler对象允许发送处理和一个线程的消息队列相关联的message和runnable对象。
 * objects associated with a thread's {@link MessageQueue}.  Each Handler     每一个Handler实例都与一个单独的线程和它的消息队列关联。
 * instance is associated with a single thread and that thread's message    当创建一个Handler对象时,它就与创建它的线程和线程的消息队列绑定了。
 * queue.  When you create a new Handler, it is bound to the thread /
 * message queue of the thread that is creating it -- from that point on,   至此,它就会分发消息和runnable对象到绑定的消息队列,并且在它们从消息队列取出时执行。
 * it will deliver messages and runnables to that message queue and execute
 * them as they come out of the message queue.
 * 
 * <p>There are two main uses for a Handler: (1) to schedule messages and    handler主要有两个用处:一是安排messages和runnable对象在未来的某一刻执行;
 * runnables to be executed as some point in the future; and (2) to enqueue   二是为在其他线程执行的动作排队(此处翻译不当)
 * an action to be performed on a different thread than your own.
 * 
 * <p>Scheduling messages is accomplished with the                  借助post和send系列方法,调度消息得到完成。
 * {@link #post}, {@link #postAtTime(Runnable, long)},
 * {@link #postDelayed}, {@link #sendEmptyMessage},
 * {@link #sendMessage}, {@link #sendMessageAtTime}, and
 * {@link #sendMessageDelayed} methods.  The <em>post</em> versions allow    post允许当Runnable对象被接收且将要被messagequeue调用时为它们排队;
 * you to enqueue Runnable objects to be called by the message queue when    sendMessage允许为一个包含了数据集且将会被handler的handleMessage方法(需要自己重写)处理的消息对象排队。
 * they are received; the <em>sendMessage</em> versions allow you to enqueue
 * a {@link Message} object containing a bundle of data that will be
 * processed by the Handler's {@link #handleMessage} method (requiring that
 * you implement a subclass of Handler).
 * 
 * <p>When posting or sending to a Handler, you can either            当用post或者send向handler发消息时,可以在消息队列就绪时立即处理也可以指定延迟做延时处理。后者需要实现超时等时间行为。
 * allow the item to be processed as soon as the message queue is ready
 * to do so, or specify a delay before it gets processed or absolute time for
 * it to be processed.  The latter two allow you to implement timeouts,
 * ticks, and other timing-based behavior.
 * 
 * <p>When a
 * process is created for your application, its main thread is dedicated to  当应用中的进程创建时,主线程致力于运行消息队列。队列着重于顶层的应用组件如活动,广播接收者等和任何这些组件创建的window。
 * running a message queue that takes care of managing the top-level      可以创建子线程并且通过handler与主线程通信。和以前一样,是靠调用post或者sendMessage来实现,当然,是在子线程中调用。
 * application objects (activities, broadcast receivers, etc) and any windows 发出的Runnable对象或者消息就会调度到handler的消息队列中并在恰当时处理。
 * they create.  You can create your own threads, and communicate back with
 * the main application thread through a Handler.  This is done by calling
 * the same <em>post</em> or <em>sendMessage</em> methods as before, but from
 * your new thread.  The given Runnable or Message will then be scheduled
 * in the Handler's message queue and processed when appropriate.
 */

说来惭愧,这一段类注释翻译花了好长时间,好歹六级也过了好多年了。从类的注释中得知,handler主要用send和post发送消息,在重写的handleMessage方法处理消息。

所有的send方法底层都是通过sendMessageAtTime实现的,其中在sendMessageDelayed方法中调用sendMessageAtTime时传入了SystemClock.uptimeMills():

/**
     * Enqueue a message into the message queue after all pending messages
     * before (current time + delayMillis). You will receive it in
     * {@link #handleMessage}, in the thread attached to this handler.
     *  
     * @return Returns true if the message was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.  Note that a
     *         result of true does not mean the message will be processed -- if
     *         the looper is quit before the delivery time of the message
     *         occurs then the message will be dropped.
     */
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    /**
     * Enqueue a message into the message queue after all pending messages
     * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
     * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
     * Time spent in deep sleep will add an additional delay to execution.
     * You will receive it in {@link #handleMessage}, in the thread attached
     * to this handler.
     * 
     * @param uptimeMillis The absolute time at which the message should be
     *         delivered, using the
     *         {@link android.os.SystemClock#uptimeMillis} time-base.
     *         
     * @return Returns true if the message was successfully placed in to the 
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.  Note that a
     *         result of true does not mean the message will be processed -- if
     *         the looper is quit before the delivery time of the message
     *         occurs then the message will be dropped.
     */
    public boolean sendMessageAtTime(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);
    }


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

handler的enqueueMessage方法中将handler对象赋给了msg的target属性,接着调用了MessageQueue的enqueueMessage方法。MessageQueue也是final类,算是这几个类中比较native的,很多都是与底层交互的方法。在它的enqueueMessage方法中将message压入消息队列,接着loop()方法中msg.target.dispatchMessage(msg),上文已经提到了。Message也是final类。所以最后是调用handler的dispatchMessage方法:

/**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }
    
    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

所以看到消息最后的处理正是在我们实例化handler时覆写的HandlerMessage方法,至此,消息发送处理机制走完了。

  那么总结一下:消息机制的过程是,Looper.prepare()实例化looper对象和消息队列,handler实例化获得上一步的looper对象和消息队列的引用,handler.sendMessageAtTime()发送消息到消息队列(这其中包括了给message的target赋值,将message压入到消息队列),Looper.loop()轮询队列取出消息交给message.target.dispatchMessage()处理,实质上是调用了我们自己重写的handleMessage()。而Android为我们做了大量的封装工作。开发人员只需要构造message并发送,自定义消息处理逻辑就可以了。

在研究源码时,首先看类注释,接着明确自己的需求,再去找关键方法,千万莫要在庞杂的代码中迷失。

  在探寻源码的过程中,发现了下一次博客的内容,就是WindowManager.LayoutParams,SystemClock,ThreadLocal,AtomicInteger。

  水往低处流,人往高处走。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值