如果你在网上搜索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的!!!

更多相关文章

  1. android EditText设置不可写
  2. android 使用html5作布局文件: webview跟javascript交互
  3. android studio调试c/c++代码
  4. IM-A820L限制GSM,WCDMA上网的原理(其他泛泰机型可参考)7.13
  5. 锁屏界面
  6. 浅析Android中的消息机制-解决:Only the original thread that cr
  7. android(NDK+JNI)---Eclipse+CDT+gdb调试android ndk程序
  8. Android(安卓)version and Linux Kernel version
  9. Android异步消息机制之Handler

随机推荐

  1. [Android] 开发资料收集:视图、组件、自定
  2. Android中的Application类
  3. listView 无法响应事件
  4. react native android百度地图定位
  5. 适用于新版 AIDE 的幸运破解器自定义补丁
  6. Error:(27, 13) Failed to resolve: com.
  7. Android(安卓)- Intent
  8. Android实现多参数文件和数据上传
  9. StageFright框架流程解读
  10. Android如何获取视频首帧图片