android 面试题 谈谈Handler

Handler几乎每个人都会用,但是如果面试被问到,相信也不是每个人都能回答出来,特别是大点公司,特别喜欢问原理,或者你工作3年以上,别人不再问你怎么用了,而是问你底层实现原理,不然怎么区分你是高级还是中级 ,不给你定级别,工资不好给啊,

 

Handler作用:主要用于异步消息的处理,那么可能好奇,什么是异步消息,我们在学线程这块就知道,线程一般是用于处理比较耗时的操作,就是结果不是马上就能看的见的而是等时间过了才能看到结果的就是异步了,也就是说我在子线程中发送消息,然后要在主线程或者UI线程去更新UI的操作,

 

第一个问题:为什么android源码设计一定要在UI线程更新UI

比如我在子线程更新UI操作,那么有四个线程我去更新TextView控件上的文字,学会多线程的知道这可能会造成结果不是你想要的,多线程有三大特性:原子性。可见性   有序性  ,如果多个线程去操作TextView的话就涉及到线程安全问题了,如果是不安全就不能保证TextView显示的文字是你想要的结果,如果想让多线程变成安全的,那么就要加锁去控制,一旦加锁必然会影响性能,还可能导致过度绘制,这就是上面的答案.

第二个问题:难道子线程一定不能更新UI么?

针对上面的问题 我们写个代码:

 new Thread(){
            @Override
            public void run() {
                super.run();
                tv.setText("更新了");
            }
        }.start();

这个运行后是不会报错的,那我在线程中调用sleep()方法再试试


        new Thread(){
            @Override
            public void run() {
                SystemClock.sleep(3000);
                tv.setText("我在子线程中更新了");
            }
        }.start();

还是不会报错, 为什么,我相信网上很多博客  没有讲到这点,我们找到TextView的setText()方法:最终会调用TextView的这个方法:

 private void setText(CharSequence text, BufferType type,
                         boolean notifyBefore, int oldlen) {

而这个方法有一段代码:

 if (mLayout != null) {
            checkForRelayout();
        }

这个mLayout是不会为空,这个是Layout对象,它是封装了一些view的属性,在TextView中mLayout是可以获取到的:

 Layout layout =  tv.getLayout();

如果在onCreate()方法中去获取肯定为null,相当于你去onCreate ()方法获取view的高度或者宽度一样为0 ,那么就要走checkForRelayout()方法,

private void checkForRelayout() {
        // If we have a fixed width, we can just swap in a new text layout
        // if the text height stays the same or if the view height is fixed.

        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
                || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
                && (mHint == null || mHintLayout != null)
                && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
            // Static width, so try making a new text layout.

            int oldht = mLayout.getHeight();
            int want = mLayout.getWidth();
            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();

            /*
             * No need to bring the text into view, since the size is not
             * changing (unless we do the requestLayout(), in which case it
             * will happen at measure).
             */
            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
                          false);

            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
                // In a fixed-height view, so use our new text layout.
                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
                        && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
                    autoSizeText();
                    invalidate();
                    return;
                }

                // Dynamic height, but height has stayed the same,
                // so use our new text layout.
                if (mLayout.getHeight() == oldht
                        && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
                    autoSizeText();
                    invalidate();
                    return;
                }
            }

            // We lose: the height has changed and we have a dynamic height.
            // Request a new view layout using our new text layout.
            requestLayout();
            invalidate();
        } else {
            // Dynamic width, so we have no choice but to request a new
            // view layout with a new text layout.
            nullLayouts();
            requestLayout();
            invalidate();
        }
    }

你会看到不管if还是else都会调用:

 requestLayout();
 invalidate();

但是请注意上面有return语句,如果执行走到return语句就不回调用了,

if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
                // In a fixed-height view, so use our new text layout.
                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
                        && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
                    autoSizeText();
                    invalidate();
                    return;
                }

                // Dynamic height, but height has stayed the same,
                // so use our new text layout.
                if (mLayout.getHeight() == oldht
                        && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
                    autoSizeText();
                    invalidate();
                    return;
                }
            }

到这里答案就出来了,

上面有二个判断

第一个判断是如果view的高度时

WRAP_CONTENT或者MATCH_PARENT就不会调用requestLayout()方法

第二个判断是它父容器的高度没有变化都会return

那看看我的布局



    

这个时候我把宽度改变成wrap_content试试

android 面试题 谈谈Handler_第1张图片

这就报错了,为什么报错了,接着上面的源码分析会走到requestLayout();再继续跟踪下去,发现会到View类的requestLayout() 方法中:

public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

根据上面的代码会执行到

if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }

我们看下mParent这个变量在那赋值的,

 void assignParent(ViewParent parent) {
        if (mParent == null) {
            mParent = parent;
        } else if (parent == null) {
            mParent = null;
        } else {
            throw new RuntimeException("view " + this + " being added, but"
                    + " it already has a parent");
        }
    }

是在assignParent()方法赋值,那assignParent()方法在那被调用的,这就涉及到activity的启动流程分析了,在ActivityThread类中找到handleResumeActivity()里面会把DecorView添加到WindowManager,如果懂的view的绘制流程的话就清楚了,最终是到ViewRootImpl里面,在ViewRootImpl类中的

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

下会调用到

 view.assignParent(this);

这个this就是ViewRootImpl,然后我们就ViewRootImpl类中去找requestLayout()

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

里面看看checkTread() 方法:

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

mThread是主线程的实力对象,这才找到了原因,再看看打印的错误:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)

对应找到了类 方法  错误描述   分析的好累

所以第二个问题子线程不能更新UI么?这是有条件的,不是绝对的。

在子线程中我们new 一个Handler会是怎么样

 new Thread(){
            @Override
            public void run() {
                new Handler();
            }
        }.start();

报错了,日记:

为什么会报这个错呢?只有看源码了,点击进去

/**
     * Default constructor associates this handler with the {@link Looper} for the
     * current thread.
     *
     * If this thread does not have a looper, this handler won't be able to receive messages
     * so an exception is thrown.
     */
    public Handler() {
        this(null, false);
    }

最终是调这个构造函数

 public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

发现有个Looper.getLooper()获取到Looper对象,

 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

ThreadLocal的作用就是存在线程中的变量,看作是一个map集合 key是Thread,value是Looper对象,子线程是没有存在Looper对象,所以它获取的是null,就报上面的错了,

而主线程的Looper对象是什么时候存储进去的,进入到ActivityThread类中找到main()

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("");

        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

有这段代码:

 Looper.prepareMainLooper();

点击进去看看有啥:

/**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

看看它的注释就知道了,而且Looper对象是私有的,所以说一个Looper对象对应一个Thread

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

所以我们要在子线程中new一个Handler对象就必须要把Looper对象设置到ThreadLocal中,

 /** 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);
    }

要调用这个方法在子线程中,它是调用了一个带参数的prepare()方法,

 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));
    }
quitAllowed参数表示是否可以退出,这就是主线程Looper和子线程Looper对象的区别 主线程是不能退出的额,现在看看我子线程中的代码:
  new Thread(){
            @Override
            public void run() {
                Looper.prepare();
                new Handler();
            }
        }.start();

它就不报错了,但是如果你发送消息会发现收不到消息,因为Looper只是创建了,而消息是发送不出去的,我们对比上面的Activity Thread中的main方法有段代码:

Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");

点击进去查看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();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        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;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

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

从上面的代码发现Looper对象中维护了一个MessageQueue,在loop()方法中是有一个死循环:

for (;;) {

当有消息的时候就会取出来,然后分发出去,这些消息是按照时间排序,关键代码:

 final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

者就是我们为什么能发送延迟的消息,

我们发送消息,最终都会做到这里:

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

这里有一个关键是Message中有一个Handler 我看到赋值 是把当前的Handler对象赋值给Message对象中的Handler变量,这是把消息存储到MessageQueue队列中,

那发送消息和处理消息都是在Looper中 看看它的loop()

消息的分发是在这里:

try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

target是Message中的一个变量就是Handler对象,然后调用了dispatchMessage()方法把消息分发出去了,

从上面的分析发现一个Thread对应一个Looper,一个Looper对象对应一个MessageQueue,现在问题来了

第四个问题:消息延迟一个靠谱么,

这肯定是不靠谱的,因为我们知道MessageQueue是一个队列,如果我们在队列中添加了很多很多消息,这个时候你延迟2秒,可能队列中的消息还没处理完呢,这延迟2秒就不起作用了,

第五个问题,Message类中的obtail()和new一个Message对象有啥区别:

直接看源码:

 /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

我们看到什么pool就知道这是一个消息池,结合我们比如线程池的作用就知道了是服用的,减少了堆内存的地址分配,从而达到了节省内存的目的.

第六个问题:主线程的Looper 为什么不会导致anr

这是因为Looper类中的loop()是有一个死循环的,这个前面的代码已经分析了.

其实里面有一段这个代码:

Binder.clearCallingIdentity();

点击进去发现它是BInder对象中的native方法 由NDK帮助我们去管理,这就是为什么主线程的Looper对象一直循环而不会导致an r的原因. 好了 太累了.

 

你可能感兴趣的:(android,面试题)