您的位置:首页 > 产品设计 > UI/UE

Android 关于子线程更新UI

2015-04-13 18:06 387 查看
如果你在网上搜索CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views. 那么你肯定能看到很多文章说android里子线程不能刷新UI。这句话不能说错,只是有些不太严谨。其实线程能否刷新UI的关键在于ViewRoot是否属于该线程。

让我们一起看看代码吧!

首先,CalledFromWrongThreadException这个异常是有下面的代码抛出的:

void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
该段代码出自 framework/base/core/java/android/view/ViewRoot.java

其次,看看RootView的构造函数:

public ViewRoot(Context context) {
super();

if (MEASURE_LATENCY && lt == null) {
lt = new LatencyTimer(100, 1000);
}

// For debug only
//++sInstanceCount;

// Initialize the statics when this class is first instantiated. This is
// done here instead of in the static block because Zygote does not
// allow the spawning of threads.
getWindowSession(context.getMainLooper());

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, context);
mInputMethodCallback = new InputMethodCallback(this);
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(sWindowSession, mWindow, this, this);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
}
最后,我们看看ViewRoot.checkThread的调用顺序:

com.david.test.helloworld.MainActivity$TestThread2.run

-> android.widget.TextView.setText

-> android.widget.TextView.checkForRelayout

-> android.view.View.invalidate

-> android.view.ViewGroup.invalidateChild

-> android.view.ViewRoot.invalidateChildInParent

-> android.view.ViewRoot.invalidateChild

-> android.view.ViewRoot.checkThread

到这里相信网友已经明白CalledFromWrongThreadException为什么出现了。那到底非主线程以外的线程能否刷新UI呢?呵呵,答案当然是能,前提条件是它要拥有自己的ViewRoot。如果你要直接创建ViewRoot的实例的话,你会失望的发现不能找到这个类。那么我们要如何做呢?让我们用实例来说说吧,代码如下:

class TestThread1 extends Thread{
@Override
public void run() {
Looper.prepare();

TextView tx = new TextView(MainActivity.this);
tx.setText("test11111111111111111");

WindowManager wm = MainActivity.this.getWindowManager();
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
250, 250, 200, 200, WindowManager.LayoutParams.FIRST_SUB_WINDOW,
WindowManager.LayoutParams.TYPE_TOAST,PixelFormat.OPAQUE);

wm.addView(tx, params);

Looper.loop();
}
}
MainActivity是建立android工程时生成的入口类,TestThread1是MainActivity的内部类。感兴趣的话,试试吧!看看是不是在屏幕上看到了"test11111111111111111"?

最后,说说那里创建了ViewRoot,这里:wm.addView(tx, params)。还是看看具体流程吧:

WindowManagerImpl.addView(View view, ViewGroup.LayoutParams params)

-> WindowManagerImpl.addView(View view, ViewGroup.LayoutParams params, boolean nest),奥妙就在这里,具体看看代码吧!

private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
{
if (Config.LOGV) Log.v("WindowManager", "addView view=" + view);

if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException(
"Params must be WindowManager.LayoutParams");
}

final WindowManager.LayoutParams wparams
= (WindowManager.LayoutParams)params;

ViewRoot root;
View panelParentView = null;

synchronized (this) {
// Here's an odd/questionable case: if someone tries to add a
// view multiple times, then we simply bump up a nesting count
// and they need to remove the view the corresponding number of
// times to have it actually removed from the window manager.
// This is useful specifically for the notification manager,
// which can continually add/remove the same view as a
// notification gets updated.
int index = findViewLocked(view, false);
if (index >= 0) {
if (!nest) {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
root = mRoots[index];
root.mAddNesting++;
// Update layout parameters.
view.setLayoutParams(wparams);
root.setLayoutParams(wparams, true);
return;
}

// 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 != null ? mViews.length : 0;
for (int i=0; i<count; i++) {
if (mRoots[i].mWindow.asBinder() == wparams.token) {
panelParentView = mViews[i];
}
}
}

root = new ViewRoot(view.getContext());
root.mAddNesting = 1;

view.setLayoutParams(wparams);

if (mViews == null) {
index = 1;
mViews = new View[1];
mRoots = new ViewRoot[1];
mParams = new WindowManager.LayoutParams[1];
} else {
index = mViews.length + 1;
Object[] old = mViews;
mViews = new View[index];
System.arraycopy(old, 0, mViews, 0, index-1);
old = mRoots;
mRoots = new ViewRoot[index];
System.arraycopy(old, 0, mRoots, 0, index-1);
old = mParams;
mParams = new WindowManager.LayoutParams[index];
System.arraycopy(old, 0, mParams, 0, index-1);
}
index--;

mViews[index] = view;
mRoots[index] = root;
mParams[index] = wparams;
}
// do this last because it fires off messages to start doing things
root.setView(view, wparams, panelParentView);
}
出自:frameworks/base/core/java/android/view/WindowManagerImpl.java
Ok,相信到了这里,大家都已经明白了:子线程是能够刷新UI的!!!

转自:http://blog.csdn.net/imyfriend/article/details/6877959

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: