如果你在网上搜索CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views. 那么你肯定能看到很多文章说android里子线程不能刷新UI。这句话不能说错,只是有些不太严谨。其实线程能否刷新UI的关键在于ViewRoot是否属于该线程。
让我们一起看看代码吧!
首先,CalledFromWrongThreadException这个异常是有下面的代码抛出的:
01 | ???? void checkThread() { |
03 | ???????? if (mThread != Thread.currentThread()) { |
05 | ???????????? throw new CalledFromWrongThreadException( |
07 | ???????????????????? "Only the original thread that created a view hierarchy can touch its views." ); |
?
该段代码出自 framework/base/core/java/android/view/ViewRoot.java
其次,看看RootView的构造函数:
01 | public ViewRoot(Context context) { |
07 | ???? if (MEASURE_LATENCY && lt == null ) { |
09 | ???????? lt = new LatencyTimer( 100 , 1000 ); |
27 | ???? getWindowSession(context.getMainLooper()); |
31 | ???? mThread = Thread.currentThread(); |
33 | ???? mLocation = new WindowLeaked( null ); |
35 | ???? mLocation.fillInStackTrace(); |
41 | ???? mDirty = new Rect(); |
43 | ???? mTempRect = new Rect(); |
45 | ???? mVisRect = new Rect(); |
47 | ???? mWinFrame = new Rect(); |
49 | ???? mWindow = new W( this , context); |
51 | ???? mInputMethodCallback = new InputMethodCallback( this ); |
53 | ???? mViewVisibility = View.GONE; |
55 | ???? mTransparentRegion = new Region(); |
57 | ???? mPreviousTransparentRegion = new Region(); |
63 | ???? mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this , this ); |
65 | ???? mViewConfiguration = ViewConfiguration.get(context); |
67 | ???? 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的实例的话,你会失望的发现不能找到这个类。那么我们要如何做呢?让我们用实例来说说吧,代码如下:
01 | ??? class TestThread1 extends Thread{ |
03 | ?????????????? @Override |
05 | ?????????????? public void run() { |
07 | ????????????????????? Looper.prepare(); |
11 | ????????????????????? TextView tx = new TextView(MainActivity. this ); |
13 | ????????????????????? tx.setText( "test11111111111111111" ); |
15 | ???????????????????????????? ? |
17 | ????????????????????? WindowManager wm = MainActivity. this .getWindowManager(); |
19 | ?????????????????? WindowManager.LayoutParams params = new WindowManager.LayoutParams( |
21 | 250 , 250 , 200 , 200 , WindowManager.LayoutParams.FIRST_SUB_WINDOW, |
23 | ??????????????????????????????? WindowManager.LayoutParams.TYPE_TOAST,PixelFormat.OPAQUE); |
25 | ????????????????????? wm.addView(tx, params); |
27 | ????????????????????? 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),奥妙就在这里,具体看看代码吧!
001 | ???? private void addView(View view, ViewGroup.LayoutParams params, boolean nest) |
005 | ???????? if (Config.LOGV) Log.v( "WindowManager" , "addView view=" + view); |
009 | ???????? if (!(params instanceof WindowManager.LayoutParams)) { |
011 | ???????????? throw new IllegalArgumentException( |
013 | ???????????????????? "Params must be WindowManager.LayoutParams" ); |
019 | ???????? final WindowManager.LayoutParams wparams |
021 | ???????????????? = (WindowManager.LayoutParams)params; |
025 | ???????? ViewRoot root; |
027 | ???????? View panelParentView = null ; |
031 | ???????? synchronized ( this ) { |
047 | ???????????? int index = findViewLocked(view, false ); |
049 | ???????????? if (index >= 0 ) { |
051 | ???????????????? if (!nest) { |
053 | ???????????????????? throw new IllegalStateException( "View " + view |
055 | ???????????????????????????? + " has already been added to the window manager." ); |
059 | ???????????????? root = mRoots[index]; |
061 | ???????????????? root.mAddNesting++; |
065 | ???????????????? view.setLayoutParams(wparams); |
067 | ???????????????? root.setLayoutParams(wparams, true ); |
069 | ???????????????? return ; |
079 | ???????????? if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && |
081 | ???????????????????? wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { |
083 | ???????????????? final int count = mViews != null ? mViews.length : 0 ; |
085 | ???????????????? for ( int i= 0 ; i<count; i++) { |
087 | ???????????????????? if (mRoots[i].mWindow.asBinder() == wparams.token) { |
089 | ???????????????????????? panelParentView = mViews[i]; |
099 | ???????????? root = new ViewRoot(view.getContext()); |
101 | ???????????? root.mAddNesting = 1 ; |
105 | ???????????? view.setLayoutParams(wparams); |
109 | ???????????? if (mViews == null ) { |
111 | ???????????????? index = 1 ; |
113 | ???????????????? mViews = new View[ 1 ]; |
115 | ???????????????? mRoots = new ViewRoot[ 1 ]; |
117 | ???????????????? mParams = new WindowManager.LayoutParams[ 1 ]; |
121 | ???????????????? index = mViews.length + 1 ; |
123 | ???????????????? Object[] old = mViews; |
125 | ???????????????? mViews = new View[index]; |
127 | ???????????????? System.arraycopy(old, 0 , mViews, 0 , index- 1 ); |
129 | ???????????????? old = mRoots; |
131 | ???????????????? mRoots = new ViewRoot[index]; |
133 | ???????????????? System.arraycopy(old, 0 , mRoots, 0 , index- 1 ); |
135 | ???????????????? old = mParams; |
137 | ???????????????? mParams = new WindowManager.LayoutParams[index]; |
139 | ???????????????? System.arraycopy(old, 0 , mParams, 0 , index- 1 ); |
147 | ???????????? mViews[index] = view; |
149 | ???????????? mRoots[index] = root; |
151 | ???????????? mParams[index] = wparams; |
157 | ???????? root.setView(view, wparams, panelParentView); |
?
出自:frameworks/base/core/java/android/view/WindowManagerImpl.java
Ok,相信到了这里,大家都已经明白了:子线程是能够刷新UI的!!!
原文出处:http://blog.csdn.net/imyfriend/article/details/6877959