这是小弟我第一篇博客,说的不对或者错的地方请各位大大指错。

相信大家在学android的时候,都是说在android中不能在子线程中更新UI。
那么是真的吗?
那么,我们不妨先测试一下下面的代码:

mTv = (TextView) findViewById(R.id.id_text);
  new Thread(){
   public void run() {
    mTv.setText("Hello Gays...");
   };
  }.start();

Hello World 变成 Hello Gays了。madan,颠覆了我对android的认知。
那么我们进setText();这个方法看看。一路Ctrl + G:

private void setText(CharSequence text, BufferType type,
                         boolean notifyBefore, int oldlen) {
        if (text == null) {
            text = "";
        }
        略过....
        if (mLayout != null) {
            checkForRelayout();
        }
        略过...
        // SelectionModifierCursorController depends on textCanBeSelected, 
which depends on text
        if (mEditor != null) mEditor.prepareCursorControllers();
    }

发现到快结尾的时候会执行一个checkForRelayout()的方法,不管怎么判断都会去调用
invalidate()方法。

private void checkForRelayout() {
           略过...

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

然后继续沿着 invalidate()找,发现会执行到

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
    略过...
    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);
            }
}

这里调用了invalidateChild(this, damage),于是乎我们就到ViewParent
这个类下面找,发现ViewParent是一个接口,啊。那咋办,其实这里的ViewParent是一个
ViewRootImpl,那么我们进入到ViewRootImpl类下面找invalidateChild(this, damage)
这个方法。

@Override
    public void invalidateChild(View child, Rect dirty) {
        invalidateChildInParent(null, dirty);
    }

执行invalidateChildInParent(null, dirty)方法

@Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        略过...
        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.");
        }
    }

为什么没有抛出异常,不,为什么!!!
其实 ViewRootImpl 是在Activity 生命周期执行到 onResume() 产生的。
那么问题就解决了,我在子线程停止一下看看,在上面的run方法添加一行Thread.sleep(
5000);

android子线程间通信 android开启子线程_android

报错了,欧也。
那我们不妨再看看onResume()
我们知道整个activity创建是在ActivityThread里面创建的,那我们就去看看呗,我们找到了一个handleResumeActivity()的方法,在onResume()的时候会回调这个方法。

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
            略过....
                  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);
                }
           略过 ....
    }

这里的decor其实就是整个Activity的根布局(FrameLayout),然后最后调用addView(
decor,l)方法。
我们去ViewManager 看看 addView()的方法,发现其实这里的vm是WindowManager,而
WindowManager是一个接口,实际上就是调用了WindowManagerImpl里的addView
方法,那我们就瞧一眼。

@Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

addView()中又继续调用 mGlobal.addView(),那么我们到最终的目的地看看

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        略过...
        ViewRootImpl root;
        View panelParentView = null;
        略过...

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

OK,我们看到ViewRootImpl 了,之后就和我们上面所说的一样了。
总结:1.是可以在子线程进行时间很短很短的更新UI,但是谁都无法预料在子线程中会消耗多久,所以之所以认为不安全,应该就是这里吧。
2.关键的关键就在于Activity的生命周期 , onCreate()–>onStart()–>onResume(),在主线程开始的时候,子线程也开始了,也就是说还没执行到onResume里面得到ViewRootImpl对象的时候,即ViewRootImpl还没有创建,所以在子线程中更新UI是可行的,反之就会在ViewRootImpl中执行checkThread()方法,throw new CalledFromWrongThreadException 的一个异常。
PS:我把很多源码都贴上来了,如果觉得不明白可以去github上,把源码clone下来。

https://github.com/android/platform_frameworks_base