美文网首页Android知识Android开发Android技术知识
为什么我在工作线程刷新UI没报错?

为什么我在工作线程刷新UI没报错?

作者: Jenson_ | 来源:发表于2017-03-27 13:33 被阅读301次

    从做Android开发以来就一直被灌输“只能在主线程刷新UI”的思想,但是这两天发现个问题,在onCreate中创建工作线程并刷新UI没有报错。代码是这样的:

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
           final TextView tv = (TextView) findViewById(R.id.sample_text);
           new Thread(new Runnable() {
               @Override
               public void run() {
                   tv.setText("来自"+Thread.currentThread().getName()+"线程的更新");
               }
           }).start();
        }
    

    期待已久的crash没有出现,反而页面是这样显示的:

    屏幕快照 2017-03-26 下午7.14.46.png

    不禁让人陷入深思···


    思考.jpg

    第一步,分析问题原因。以前工作线程刷新UI会出现异常并提示只能在UI线程刷新UI。所以假设有个地方(函数)会在刷新UI前判断当前线程是不是主线程,那么现在问题就是验证下是否真的存在这么个地方。问题是从setText引起的,那就从这里出发看看。
    点进去看下发现又调用了其重载函数:

        @android.view.RemotableViewMethod
        public final void setText(CharSequence text) {
            setText(text, mBufferType);
        }
    

    继续深入发现这个方法代码挺多的,但是不用过于关注细节,粗略浏览下不难发现前面大部分代码大多是对text文本格式的一些处理判断,无关刷新UI,直到 checkForRelayout()方法执行,从名字看是“从新布局”,根据view绘制流程测量、布局、绘制,那布局完了不是就绘制刷新了吗?所以决定去checkForRelayout方法看看。

    
        private void setText(CharSequence text, BufferType type,
                             boolean notifyBefore, int oldlen) {
            if (text == null) {
                text = "";
            }
    
            // If suggestions are not enabled, remove the suggestion spans from the text
            if (!isSuggestionsEnabled()) {
                text = removeSuggestionSpans(text);
            }
    
            if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
    
            if (text instanceof Spanned &&
                ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
                if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
                    setHorizontalFadingEdgeEnabled(true);
                    mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
                } else {
                    setHorizontalFadingEdgeEnabled(false);
                    mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
                }
                setEllipsize(TextUtils.TruncateAt.MARQUEE);
            }
    
            int n = mFilters.length;
            for (int i = 0; i < n; i++) {
                CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
                if (out != null) {
                    text = out;
                }
            }
    
            if (notifyBefore) {
                if (mText != null) {
                    oldlen = mText.length();
                    sendBeforeTextChanged(mText, 0, oldlen, text.length());
                } else {
                    sendBeforeTextChanged("", 0, 0, text.length());
                }
            }
    
            boolean needEditableForNotification = false;
    
            if (mListeners != null && mListeners.size() != 0) {
                needEditableForNotification = true;
            }
    
            if (type == BufferType.EDITABLE || getKeyListener() != null ||
                    needEditableForNotification) {
                createEditorIfNeeded();
                Editable t = mEditableFactory.newEditable(text);
                text = t;
                setFilters(t, mFilters);
                InputMethodManager imm = InputMethodManager.peekInstance();
                if (imm != null) imm.restartInput(this);
            } else if (type == BufferType.SPANNABLE || mMovement != null) {
                text = mSpannableFactory.newSpannable(text);
            } else if (!(text instanceof CharWrapper)) {
                text = TextUtils.stringOrSpannedString(text);
            }
    
            if (mAutoLinkMask != 0) {
                Spannable s2;
    
                if (type == BufferType.EDITABLE || text instanceof Spannable) {
                    s2 = (Spannable) text;
                } else {
                    s2 = mSpannableFactory.newSpannable(text);
                }
    
                if (Linkify.addLinks(s2, mAutoLinkMask)) {
                    text = s2;
                    type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
    
                    /*
                     * We must go ahead and set the text before changing the
                     * movement method, because setMovementMethod() may call
                     * setText() again to try to upgrade the buffer type.
                     */
                    mText = text;
    
                    // Do not change the movement method for text that support text selection as it
                    // would prevent an arbitrary cursor displacement.
                    if (mLinksClickable && !textCanBeSelected()) {
                        setMovementMethod(LinkMovementMethod.getInstance());
                    }
                }
            }
    
            mBufferType = type;
            mText = text;
    
            if (mTransformation == null) {
                mTransformed = text;
            } else {
                mTransformed = mTransformation.getTransformation(text, this);
            }
    
            final int textLength = text.length();
    
            if (text instanceof Spannable && !mAllowTransformationLengthChange) {
                Spannable sp = (Spannable) text;
    
                // Remove any ChangeWatchers that might have come from other TextViews.
                final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
                final int count = watchers.length;
                for (int i = 0; i < count; i++) {
                    sp.removeSpan(watchers[i]);
                }
    
                if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
    
                sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
                           (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
    
                if (mEditor != null) mEditor.addSpanWatchers(sp);
    
                if (mTransformation != null) {
                    sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
                }
    
                if (mMovement != null) {
                    mMovement.initialize(this, (Spannable) text);
    
                    /*
                     * Initializing the movement method will have set the
                     * selection, so reset mSelectionMoved to keep that from
                     * interfering with the normal on-focus selection-setting.
                     */
                    if (mEditor != null) mEditor.mSelectionMoved = false;
                }
            }
    
            if (mLayout != null) {
                checkForRelayout();
            }
    
            sendOnTextChanged(text, 0, oldlen, textLength);
            onTextChanged(text, 0, oldlen, textLength);
    
            notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
    
            if (needEditableForNotification) {
                sendAfterTextChanged((Editable) text);
            }
    
            // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
            if (mEditor != null) mEditor.prepareCursorControllers();
        }
    

    找到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) {
                        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)) {
                        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,都要执行invalidate(),那就进去看看,一路进展比较顺利,好像离真理更进一步了。经过连续的三连调跳转到了invalidateInternal方法:

     public void invalidate() {
            invalidate(true);
        }
    
        /**
         * This is where the invalidate() work actually happens. A full invalidate()
         * causes the drawing cache to be invalidated, but this function can be
         * called with invalidateCache set to false to skip that invalidation step
         * for cases that do not need it (for example, a component that remains at
         * the same dimensions with the same content).
         *
         * @param invalidateCache Whether the drawing cache for this view should be
         *            invalidated as well. This is usually true for a full
         *            invalidate, but may be set to false if the View's contents or
         *            dimensions have not changed.
         */
        void invalidate(boolean invalidateCache) {
            invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
        }
    
       void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
                boolean fullInvalidate) {
            if (mGhostView != null) {
                mGhostView.invalidate(true);
                return;
            }
    
            if (skipInvalidate()) {
                return;
            }
    
            if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                    || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                    || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
                    || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
                if (fullInvalidate) {
                    mLastIsOpaque = isOpaque();
                    mPrivateFlags &= ~PFLAG_DRAWN;
                }
    
                mPrivateFlags |= PFLAG_DIRTY;
    
                if (invalidateCache) {
                    mPrivateFlags |= PFLAG_INVALIDATED;
                    mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
                }
    
                // Propagate the damage rectangle to the parent view.
                final AttachInfo ai = mAttachInfo;
                final ViewParent p = mParent;
                if (p != null && ai != null && l < r && t < b) {
                    final Rect damage = ai.mTmpInvalRect;
                    damage.set(l, t, r, b);
                    p.invalidateChild(this, damage);
                }
    
                // Damage the entire projection receiver, if necessary.
                if (mBackground != null && mBackground.isProjected()) {
                    final View receiver = getProjectionReceiver();
                    if (receiver != null) {
                        receiver.damageInParent();
                    }
                }
    
                // Damage the entire IsolatedZVolume receiving this view's shadow.
                if (isHardwareAccelerated() && getZ() != 0) {
                    damageShadowReceiver();
                }
            }
        }
    

    重点看invalidateChild()这个方法,方法里面有do while循环,每次循环出当前view的parent,最后一次循环返回的parent时ViewRootImpl类实例:

     public final void invalidateChild(View child, final Rect dirty) {
            ViewParent parent = this;
    
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) {
                // If the child is drawing an animation, we want to copy this flag onto
                // ourselves and the parent to make sure the invalidate request goes
                // through
                final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION)
                        == PFLAG_DRAW_ANIMATION;
    
                // Check whether the child that requests the invalidate is fully opaque
                // Views being animated or transformed are not considered opaque because we may
                // be invalidating their old position and need the parent to paint behind them.
                Matrix childMatrix = child.getMatrix();
                final boolean isOpaque = child.isOpaque() && !drawAnimation &&
                        child.getAnimation() == null && childMatrix.isIdentity();
                // Mark the child as dirty, using the appropriate flag
                // Make sure we do not set both flags at the same time
                int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;
    
                if (child.mLayerType != LAYER_TYPE_NONE) {
                    mPrivateFlags |= PFLAG_INVALIDATED;
                    mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
                }
    
                final int[] location = attachInfo.mInvalidateChildLocation;
                location[CHILD_LEFT_INDEX] = child.mLeft;
                location[CHILD_TOP_INDEX] = child.mTop;
                if (!childMatrix.isIdentity() ||
                        (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
                    RectF boundingRect = attachInfo.mTmpTransformRect;
                    boundingRect.set(dirty);
                    Matrix transformMatrix;
                    if ((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
                        Transformation t = attachInfo.mTmpTransformation;
                        boolean transformed = getChildStaticTransformation(child, t);
                        if (transformed) {
                            transformMatrix = attachInfo.mTmpMatrix;
                            transformMatrix.set(t.getMatrix());
                            if (!childMatrix.isIdentity()) {
                                transformMatrix.preConcat(childMatrix);
                            }
                        } else {
                            transformMatrix = childMatrix;
                        }
                    } else {
                        transformMatrix = childMatrix;
                    }
                    transformMatrix.mapRect(boundingRect);
                    dirty.set((int) (boundingRect.left - 0.5f),
                            (int) (boundingRect.top - 0.5f),
                            (int) (boundingRect.right + 0.5f),
                            (int) (boundingRect.bottom + 0.5f));
                }
    
                do {
                    View view = null;
                    if (parent instanceof View) {
                        view = (View) parent;
                    }
    
                    if (drawAnimation) {
                        if (view != null) {
                            view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
                        } else if (parent instanceof ViewRootImpl) {
                            ((ViewRootImpl) parent).mIsAnimating = true;
                        }
                    }
    
                    // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
                    // flag coming from the child that initiated the invalidate
                    if (view != null) {
                        if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
                                view.getSolidColor() == 0) {
                            opaqueFlag = PFLAG_DIRTY;
                        }
                        if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                            view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
                        }
                    }
    
                    parent = parent.invalidateChildInParent(location, dirty);
                    if (view != null) {
                        // Account for transform on current parent
                        Matrix m = view.getMatrix();
                        if (!m.isIdentity()) {
                            RectF boundingRect = attachInfo.mTmpTransformRect;
                            boundingRect.set(dirty);
                            m.mapRect(boundingRect);
                            dirty.set((int) (boundingRect.left - 0.5f),
                                    (int) (boundingRect.top - 0.5f),
                                    (int) (boundingRect.right + 0.5f),
                                    (int) (boundingRect.bottom + 0.5f));
                        }
                    }
                } while (parent != null);
            }
        }
    

    现在查看ViewRootImpl类的invalidateChildInParent()方法:

        @Override
        public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
            checkThread();
            if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty);
    
            if (dirty == null) {
                invalidate();
                return null;
            } else if (dirty.isEmpty() && !mIsAnimating) {
                return null;
            }
    
            if (mCurScrollY != 0 || mTranslator != null) {
                mTempRect.set(dirty);
                dirty = mTempRect;
                if (mCurScrollY != 0) {
                    dirty.offset(0, -mCurScrollY);
                }
                if (mTranslator != null) {
                    mTranslator.translateRectInAppWindowToScreen(dirty);
                }
                if (mAttachInfo.mScalingRequired) {
                    dirty.inset(-1, -1);
                }
            }
    
            final Rect localDirty = mDirty;
            if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
                mAttachInfo.mSetIgnoreDirtyState = true;
                mAttachInfo.mIgnoreDirtyState = true;
            }
    
            // Add the new dirty rect to the current one
            localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
            // Intersect with the bounds of the window to skip
            // updates that lie outside of the visible region
            final float appScale = mAttachInfo.mApplicationScale;
            final boolean intersected = localDirty.intersect(0, 0,
                    (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
            if (!intersected) {
                localDirty.setEmpty();
            }
            if (!mWillDrawSoon && (intersected || mIsAnimating)) {
                scheduleTraversals();
            }
    
            return null;
        }
    

    方法块的第一行好像就是我们要找的验证线程的地方checkThread(),而该方法内容也很简单,看到抛出的异常就很熟悉了:

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

    第二步,验证线程地方已经找到了,假设得到了证实,那么为什么该有的异常没有出现呢?现在再次假设是不是因为某些原因导致checkThread()没有执行呢?

    有人可能会问会不会是mThread == Thread.currentThread()?其实这是不可能的 ,先看下mThread赋值:

        public ViewRootImpl(Context context, Display display) {
            mContext = context;
            mWindowSession = WindowManagerGlobal.getWindowSession();
            mDisplay = display;
            mBasePackageName = context.getBasePackageName();
    
            mDisplayAdjustments = display.getDisplayAdjustments();
    
            mThread = Thread.currentThread();
    

    ViewRootImpl类实例化是在Activity启动时的主线程创建的,所以mThread是主线程实例,而checkThread()中的Thread.currentThread(),最初是由textView.setText()调用的一系列方法栈,并且setText是在子线程调用。

    继续刚才的假设,不过现在要逆推回去,checkThread()方法没有执行,可以认为ViewRootImpl的invalidateChildInParent()方法没有执行,它又是在ViewGroup中invalidateChild()方法里的do while中调用,要使do while不被调用,继续往上看,如果attachInfo为null,进不去if语句块也就不会执行checkThread()了,这是第一个可能的原因。记录下,继续逆推。

    View中的invalidateInternal()方法:

          // Propagate the damage rectangle to the parent view.
                final AttachInfo ai = mAttachInfo;
                final ViewParent p = mParent;
                if (p != null && ai != null && l < r && t < b) {
                    final Rect damage = ai.mTmpInvalRect;
                    damage.set(l, t, r, b);
                    p.invalidateChild(this, damage);
                }
    

    根据判断条件,如果p即mParent或者ai即mAttachInfo 任一为null,都不会执行invalidateChild,这是第二个可能原因,这里的ai和第一个原因的attachInfo是一样的,所以第一个原因可能忽略,精力关注在第二个原因上。

    现在的关注点是实例p和ai是什么时候创建的,有没有为null的现象?

         final AttachInfo ai = mAttachInfo;
    

    而mAttachInfo是在View的dispatchAttachedToWindow()方法中赋值的:

     void dispatchAttachedToWindow(AttachInfo info, int visibility) {
            //System.out.println("Attached! " + this);
            mAttachInfo = info;
    }
    

    View的dispatchAttachedToWindow()方法是在ViewRootImpl中的performTraversals()方法调用,这个方法是遍历View Tree,

     private void performTraversals() {
            // cache mView since it is used so much below...
            final View host = mView;
            ·······
            host.dispatchAttachedToWindow(mAttachInfo, 0);
    
    

    可见View中的mAttachInfo来自ViewrootImpl的mAttachInfo,而ViewrootImpl的mAttachInfo是在其构造方法中被赋值的:

    
        public ViewRootImpl(Context context, Display display) {
            mContext = context;
            mWindowSession = WindowManagerGlobal.getWindowSession();
            mDisplay = display;
            mBasePackageName = context.getBasePackageName();
    
            mDisplayAdjustments = display.getDisplayAdjustments();
    
            mThread = Thread.currentThread();
            mLocation = new WindowLeaked(null);
            mLocation.fillInStackTrace();
            mWidth = -1;
            mHeight = -1;
            mDirty = new Rect();
            mTempRect = new Rect();
            mVisRect = new Rect();
            mWinFrame = new Rect();
            mWindow = new W(this);
            mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
            mViewVisibility = View.GONE;
            mTransparentRegion = new Region();
            mPreviousTransparentRegion = new Region();
            mFirst = true; // true for the first time the view is added
            mAdded = false;
            mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
           
        }
    

    之前说了ViewRootImpl实例是在Activity启动时创建的,现在看看具体是启动的什么时候:在向window添加view的时候即:WindowManagerGlobal的addView方法中创建ViewrootImpl实例:

     public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow) {
            if (view == null) {
                throw new IllegalArgumentException("view must not be null");
            }
            if (display == null) {
                throw new IllegalArgumentException("display must not be null");
            }
            if (!(params instanceof WindowManager.LayoutParams)) {
                throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
            }
    
            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
            if (parentWindow != null) {
                parentWindow.adjustLayoutParamsForSubWindow(wparams);
            } else {
                // If there's no parent and we're running on L or above (or in the
                // system context), assume we want hardware acceleration.
                final Context context = view.getContext();
                if (context != null
                        && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                    wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
                }
            }
    
            ViewRootImpl root;
            View panelParentView = null;
    
            synchronized (mLock) {
                // Start watching for system property changes.
                if (mSystemPropertyUpdater == null) {
                    mSystemPropertyUpdater = new Runnable() {
                        @Override public void run() {
                            synchronized (mLock) {
                                for (int i = mRoots.size() - 1; i >= 0; --i) {
                                    mRoots.get(i).loadSystemProperties();
                                }
                            }
                        }
                    };
                    SystemProperties.addChangeCallback(mSystemPropertyUpdater);
                }
    
                int index = findViewLocked(view, false);
                if (index >= 0) {
                    if (mDyingViews.contains(view)) {
                        // Don't wait for MSG_DIE to make it's way through root's queue.
                        mRoots.get(index).doDie();
                    } else {
                        throw new IllegalStateException("View " + view
                                + " has already been added to the window manager.");
                    }
                    // The previous removeView() had not completed executing. Now it has.
                }
    
                // If this is a panel window, then find the window it is being
                // attached to for future reference.
                if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                        wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                    final int count = mViews.size();
                    for (int i = 0; i < count; i++) {
                        if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                            panelParentView = mViews.get(i);
                        }
                    }
                }
    
                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.
                synchronized (mLock) {
                    final int index = findViewLocked(view, false);
                    if (index >= 0) {
                        removeViewLocked(index, true);
                    }
                }
                throw e;
            }
        }
    
    

    而调用addView()方法是在ActivityThread类的handleResumeActivity()方法:

    
        final void handleResumeActivity(IBinder token,
                boolean clearHide, boolean isForward, boolean reallyResume) {
            // If we are getting ready to gc after going to the background, well
            // we are back active so skip it.
            unscheduleGcIdler();
            mSomeActivitiesChanged = true;
    
            // TODO Push resumeArgs into the activity for consideration
            ActivityClientRecord r = performResumeActivity(token, clearHide);
    
            if (r != null) {
                final Activity a = r.activity;
    
                if (localLOGV) Slog.v(
                    TAG, "Resume " + r + " started activity: " +
                    a.mStartedActivity + ", hideForNow: " + r.hideForNow
                    + ", finished: " + a.mFinished);
    
                final int forwardBit = isForward ?
                        WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
    
                // If the window hasn't yet been added to the window manager,
                // and this guy didn't finish itself or start another activity,
                // then go ahead and add the window.
                boolean willBeVisible = !a.mStartedActivity;
                if (!willBeVisible) {
                    try {
                        willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
                                a.getActivityToken());
                    } catch (RemoteException e) {
                    }
                }
                if (r.window == null && !a.mFinished && willBeVisible) {
                    r.window = r.activity.getWindow();
                    View decor = r.window.getDecorView();
                    decor.setVisibility(View.INVISIBLE);
                    ViewManager wm = a.getWindowManager();
                    WindowManager.LayoutParams l = r.window.getAttributes();
                    a.mDecor = decor;
                    l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                    l.softInputMode |= forwardBit;
                    if (a.mVisibleFromClient) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);
                    }
    
                // If the window has already been added, but during resume
                // we started another activity, then don't yet make the
                // window visible.
                } else if (!willBeVisible) {
                    if (localLOGV) Slog.v(
                        TAG, "Launch " + r + " mStartedActivity set");
                    r.hideForNow = true;
                }
    
                // Get rid of anything left hanging around.
                cleanUpPendingRemoveWindows(r);
    
                // The window is now visible if it has been added, we are not
                // simply finishing, and we are not starting another activity.
                if (!r.activity.mFinished && willBeVisible
                        && r.activity.mDecor != null && !r.hideForNow) {
                    if (r.newConfig != null) {
                        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
                                + r.activityInfo.name + " with newConfig " + r.newConfig);
                        performConfigurationChanged(r.activity, r.newConfig);
                        freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.newConfig));
                        r.newConfig = null;
                    }
                    if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
                            + isForward);
                    WindowManager.LayoutParams l = r.window.getAttributes();
                    if ((l.softInputMode
                            & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
                            != forwardBit) {
                        l.softInputMode = (l.softInputMode
                                & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
                                | forwardBit;
                        if (r.activity.mVisibleFromClient) {
                            ViewManager wm = a.getWindowManager();
                            View decor = r.window.getDecorView();
                            wm.updateViewLayout(decor, l);
                        }
                    }
                    r.activity.mVisibleFromServer = true;
                    mNumVisibleActivities++;
                    if (r.activity.mVisibleFromClient) {
                        r.activity.makeVisible();
                    }
                }
    
                if (!r.onlyLocalRequest) {
                    r.nextIdle = mNewActivities;
                    mNewActivities = r;
                    if (localLOGV) Slog.v(
                        TAG, "Scheduling idle handler for " + r);
                    Looper.myQueue().addIdleHandler(new Idler());
                }
                r.onlyLocalRequest = false;
    
                // Tell the activity manager we have resumed.
                if (reallyResume) {
                    try {
                        ActivityManagerNative.getDefault().activityResumed(token);
                    } catch (RemoteException ex) {
                    }
                }
    
            } else {
                // If an exception was thrown when trying to resume, then
                // just end this activity.
                try {
                    ActivityManagerNative.getDefault()
                        .finishActivity(token, Activity.RESULT_CANCELED, null, false);
                } catch (RemoteException ex) {
                }
            }
        }
    

    看到上面的方法有些人心里可能有底儿了,handleResumeActivity()方法是不是让Activity回调onResume?如果是的话一切就都明了了,因为ViewrootImpl是在onReume期间创建,而AttachInfo是在ViewRootImpl实例化时创建,我们的工作线程是在onCreate时刷新UI的,那时候还没有实例化AttachInfo,导致mAttachInfo为null,最终if条件判断失败没有进入。然而这一切都是在YY,实践才是检验真理的唯一标准,动起来:

    看到这一行代码```
    ActivityClientRecord r = performResumeActivity(token, clearHide);

    public final ActivityClientRecord performResumeActivity(IBinder token,
            boolean clearHide) {
        ActivityClientRecord r = mActivities.get(token);
        if (localLOGV) Slog.v(TAG, "Performing resume of " + r
                + " finished=" + r.activity.mFinished);
        if (r != null && !r.activity.mFinished) {
            if (clearHide) {
                r.hideForNow = false;
                r.activity.mStartedActivity = false;
            }
            try {
                r.activity.mFragments.noteStateNotSaved();
                if (r.pendingIntents != null) {
                    deliverNewIntents(r, r.pendingIntents);
                    r.pendingIntents = null;
                }
                if (r.pendingResults != null) {
                    deliverResults(r, r.pendingResults);
                    r.pendingResults = null;
                }
                r.activity.performResume();
    
                EventLog.writeEvent(LOG_ON_RESUME_CALLED,
                        UserHandle.myUserId(), r.activity.getComponentName().getClassName());
    
                r.paused = false;
                r.stopped = false;
                r.state = null;
                r.persistentState = null;
            } catch (Exception e) {
                if (!mInstrumentation.onException(r.activity, e)) {
                    throw new RuntimeException(
                        "Unable to resume activity "
                        + r.intent.getComponent().toShortString()
                        + ": " + e.toString(), e);
                }
            }
        }
        return r;
    }
    
    代码很短,很容易提取有用信息:``` r.activity.performResume();```在进去看看:
    
    final void performResume() {
        performRestart();
    
        mFragments.execPendingActions();
    
        mLastNonConfigurationInstances = null;
    
        mCalled = false;
        // mResumed is set by the instrumentation
        mInstrumentation.callActivityOnResume(this);
        if (!mCalled) {
            throw new SuperNotCalledException(
                "Activity " + mComponent.toShortString() +
                " did not call through to super.onResume()");
        }
    
        // Now really resume, and install the current status bar and menu.
        mCalled = false;
    
        mFragments.dispatchResume();
        mFragments.execPendingActions();
    
        onPostResume();
        if (!mCalled) {
            throw new SuperNotCalledException(
                "Activity " + mComponent.toShortString() +
                " did not call through to super.onPostResume()");
        }
    }
    
    看到```mInstrumentation```这个属性就能感觉到越来越接近SDK API层了,看看它的```callActivityOnResume()```方法做了什么:
    
    public void callActivityOnResume(Activity activity) {
        activity.mResumed = true;
        activity.onResume();
        
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    am.match(activity, activity, activity.getIntent());
                }
            }
        }
    }
    
    
    一行```activity.onResume();```已经水落石出。
    回到handleResumeActivity代码中,我精简下:
    

    final void handleResumeActivity(IBinder token,
    boolean clearHide, boolean isForward, boolean reallyResume) {
    //这里是最终回调Activity的onResume()方法
    ActivityClientRecord r = performResumeActivity(token, clearHide);
    //省略若干
    if (r != null) {
    final Activity a = r.activity;
    final int forwardBit = isForward ?
    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
    boolean willBeVisible = !a.mStartedActivity;

            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
    

    //这里是调用WindowManagerGlobal的addView()方法最终实例化ViewRootImpl,进而实例化AttachInfo
    wm.addView(decor, l);
    }

            } else if (!willBeVisible) {
                if (localLOGV) Slog.v(
                    TAG, "Launch " + r + " mStartedActivity set");
                r.hideForNow = true;
            }
        }
    }
    
    
    
    另外关于阅读源码问题,win下当然使用SourceInsight不二选,如果是在mac下可以参考我的[osx下如何使用SublimeText阅读Android系统源码](http://www.jianshu.com/p/c295d2729ecf).。
    

    相关文章

      网友评论

        本文标题:为什么我在工作线程刷新UI没报错?

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