若是你在网上搜索CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views. 那么你确定能看到不少文章说android里子线程不能刷新UI。这句话不能说错,只是有些不太严谨。其实线程可否刷新UI的关键在于ViewRoot是否属于该线程。 java
让咱们一块儿看看代码吧! android
首先,CalledFromWrongThreadException这个异常是有下面的代码抛出的: ide
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的构造函数: oop
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的调用顺序: this
com.david.test.helloworld.MainActivity$TestThread2.run spa
-> android.widget.TextView.setText 线程
-> android.widget.TextView.checkForRelayout debug
-> android.view.View.invalidate code
-> 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的!!!