子线程中不可以更新UI,大多数Android开发者都是这么认为的。Android官方这样描述the Android UI toolkit is not thread-safe and must always be manipulated on the UI thread。那么非UI线程到底能不能更新UI?请看下面一段代码:

public class MainActivity extends Activity {    Button btn_test;    TextView tv_content;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        btn_test = (Button) findViewById(R.id.btn_test);        tv_content = (TextView) findViewById(R.id.tv_content);        new Thread(new Runnable() {            @Override            public void run() {                tv_content.setText("Not UIThread");            }        }).start();    }}

非UI线程可不可以更新UI(一)_第1张图片

你会发现这段代码正常运行。

再看下面这段代码

public class MainActivity extends Activity {    Button btn_test;    TextView tv_content;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        btn_test = (Button) findViewById(R.id.btn_test);        tv_content = (TextView) findViewById(R.id.tv_content);        new Thread(new Runnable() {            @Override            public void run() {                try {                    Thread.sleep(500);                } catch (InterruptedException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                tv_content.setText("Not UIThread");            }        }).start();    }}

程序崩溃:Only the original thread that created a view hierarchy can touch its views.
非UI线程可不可以更新UI(一)_第2张图片

从logcat上面可以看到ViewRootImpl.checkThread方法报错。通过查看源码checkThread方法源码

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

mThread在ViewRootImpl的构造方法中

public ViewRootImpl(Context context, Display display) {        ///省略代码        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();        ///省略部分代码        loadSystemProperties();    }

从checkThread方法中可以看到ViewRootImpl会检查当前线程是不是创建它的线程,如果不是则会抛出上面的异常。
那为什么开始的那段代码没有出问题呢,但是等待了500毫秒后程序崩溃了呢?
只能是checkThread方法没有执行。 查看ViewRootImpl源码可以看出,只要是和UI相关的,都会调用checkThread。所以只能猜测ViewRootImpl 对象还未创建。
在Activity启动过程中,ViewRootImpl 创建在ActivityThread类中的handleResumeActivity方法中的 r.activity.makeVisible();方法中创建

public final class ActivityThread {    //只列出有关代码    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) {                    r.tmpConfig.setTo(r.newConfig);                    if (r.overrideConfig != null) {                        r.tmpConfig.updateFrom(r.overrideConfig);                    }                    if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "                            + r.activityInfo.name + " with newConfig " + r.tmpConfig);                    performConfigurationChanged(r.activity, r.tmpConfig);                    freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));                    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) {            }        }    }}

makeVisible方法实现在Activity.java中实现

void makeVisible() {        if (!mWindowAdded) {            ViewManager wm = getWindowManager();            wm.addView(mDecor, getWindow().getAttributes());            mWindowAdded = true;        }        mDecor.setVisibility(View.VISIBLE);    }

通过对getWindowManager()方法追踪,最后可以发现addView方法的实现在WindowManagerGlobal.java文件中

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

root = new ViewRootImpl(view.getContext(), display);
ViewRootImpl对象在addview时候创建。那么ViewRootImpl 创建是在performResumeActivity方法之后调用的,即是在Activity的onResume方法调用之后创建的。

所以当你无论在Activity第一次启动时,你将下面这段代码放到onCreate OnStart 或者 onResume中都不会报异常,因为此时ViewRootImpl还未创建(注意是Activity第一次启动时)。如果放在OnStart 或者 onResume中在activity创建完成后,将应用在前后台切换,程序还是会崩溃。因为此时ViewRootImpl已经创建完成。在线程中等待500毫秒也是为了等待ViewRootImpl创建。

new Thread(new Runnable() {            @Override            public void run() {                tv_content.setText("Not UIThread");            }        }).start();

更多相关文章

  1. Android多线程——Handler
  2. Android 入门第八讲02-WebView的高级用法(Android调用 JS 代码( lo
  3. 在eclipse中查看android SDK的源代码
  4. Android-线程笔记
  5. Android线程学习
  6. Android彻底组件化—代码和资源隔离

随机推荐

  1. android 2.2+ 完全退出程序的方法
  2. Android(安卓)AD Manifest
  3. android中Search之Creating a Searchable
  4. ionic emulate android log
  5. 分享:Android程序员,必备精品网站大汇总
  6. Android(安卓)ProgressBar 几乎全部的用
  7. 判断手机类型
  8. Android中ListVIew高度自适应,解决ScrollV
  9. Linearlayout 添加divider
  10. Android之Gridview