非UI线程可不可以更新UI
Posted coderzdz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了非UI线程可不可以更新UI相关的知识,希望对你有一定的参考价值。
子线程中不可以更新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();
你会发现这段代码正常运行。
再看下面这段代码
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.
从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();
以上是关于非UI线程可不可以更新UI的主要内容,如果未能解决你的问题,请参考以下文章