当前位置: 代码迷 >> Android >> Android 里子线程真的不能刷新UI吗
  详细解决方案

Android 里子线程真的不能刷新UI吗

热度:31   发布时间:2016-04-28 06:56:11.0
Android 里子线程真的不能刷新UI吗?

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

让我们一起看看代码吧!

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

view source
?
print?
01????void checkThread() {
02?
03????????if (mThread != Thread.currentThread()) {
04?
05????????????throw new CalledFromWrongThreadException(
06?
07????????????????????"Only the original thread that created a view hierarchy can touch its views.");
08?
09????????}
10?
11}

?

该段代码出自 framework/base/core/java/android/view/ViewRoot.java

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

view source
?
print?
01public ViewRoot(Context context) {
02?
03????super();
04?
05?
06?
07????if (MEASURE_LATENCY && lt == null) {
08?
09????????lt = new LatencyTimer(100, 1000);
10?
11????}
12?
13?
14?
15????// For debug only
16?
17????//++sInstanceCount;
18?
19?
20?
21????// Initialize the statics when this class is first instantiated. This is
22?
23????// done here instead of in the static block because Zygote does not
24?
25????// allow the spawning of threads.
26?
27????getWindowSession(context.getMainLooper());
28?
29?????
30?
31????mThread = Thread.currentThread();
32?
33????mLocation = new WindowLeaked(null);
34?
35????mLocation.fillInStackTrace();
36?
37????mWidth = -1;
38?
39????mHeight = -1;
40?
41????mDirty = new Rect();
42?
43????mTempRect = new Rect();
44?
45????mVisRect = new Rect();
46?
47????mWinFrame = new Rect();
48?
49????mWindow = new W(this, context);
50?
51????mInputMethodCallback = new InputMethodCallback(this);
52?
53????mViewVisibility = View.GONE;
54?
55????mTransparentRegion = new Region();
56?
57????mPreviousTransparentRegion = new Region();
58?
59????mFirst = true; // true for the first time the view is added
60?
61????mAdded = false;
62?
63????mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this);
64?
65????mViewConfiguration = ViewConfiguration.get(context);
66?
67????mDensity = context.getResources().getDisplayMetrics().densityDpi;
68?
69}

?

最后,我们看看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的实例的话,你会失望的发现不能找到这个类。那么我们要如何做呢?让我们用实例来说说吧,代码如下:

view source
?
print?
01???class TestThread1 extends Thread{
02?
03??????????????@Override
04?
05??????????????public void run() {
06?
07?????????????????????Looper.prepare();
08?
09??????????????????????
10?
11?????????????????????TextView tx = new TextView(MainActivity.this);
12?
13?????????????????????tx.setText("test11111111111111111");
14?
15?????????????????????????????
16?
17?????????????????????WindowManager wm = MainActivity.this.getWindowManager();
18?
19??????????????????WindowManager.LayoutParams params = new WindowManager.LayoutParams(
20?
21250, 250, 200, 200, WindowManager.LayoutParams.FIRST_SUB_WINDOW,
22?
23???????????????????????????????WindowManager.LayoutParams.TYPE_TOAST,PixelFormat.OPAQUE);
24??????????????????????
25?????????????????????wm.addView(tx, params);
26????????????????????
27?????????????????????Looper.loop();
28?
29??????????????}
30?
31????}

?

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),奥妙就在这里,具体看看代码吧!

view source
?
print?
001????private void addView(View view, ViewGroup.LayoutParams params, boolean nest)
002?
003????{
004?
005????????if (Config.LOGV) Log.v("WindowManager", "addView view=" + view);
006?
007??
008?
009????????if (!(params instanceof WindowManager.LayoutParams)) {
010?
011????????????throw new IllegalArgumentException(
012?
013????????????????????"Params must be WindowManager.LayoutParams");
014?
015????????}
016?
017??
018?
019????????final WindowManager.LayoutParams wparams
020?
021????????????????= (WindowManager.LayoutParams)params;
022?
023?????????
024?
025????????ViewRoot root;
026?
027????????View panelParentView = null;
028?
029?????????
030?
031????????synchronized (this) {
032?
033????????????// Here's an odd/questionable case: if someone tries to add a
034?
035????????????// view multiple times, then we simply bump up a nesting count
036?
037????????????// and they need to remove the view the corresponding number of
038?
039????????????// times to have it actually removed from the window manager.
040?
041????????????// This is useful specifically for the notification manager,
042?
043????????????// which can continually add/remove the same view as a
044?
045????????????// notification gets updated.
046?
047????????????int index = findViewLocked(view, false);
048?
049????????????if (index >= 0) {
050?
051????????????????if (!nest) {
052?
053????????????????????throw new IllegalStateException("View " + view
054?
055????????????????????????????+ " has already been added to the window manager.");
056?
057????????????????}
058?
059????????????????root = mRoots[index];
060?
061????????????????root.mAddNesting++;
062?
063????????????????// Update layout parameters.
064?
065????????????????view.setLayoutParams(wparams);
066?
067????????????????root.setLayoutParams(wparams, true);
068?
069????????????????return;
070?
071????????????}
072?
073?????????????
074?
075????????????// If this is a panel window, then find the window it is being
076?
077????????????// attached to for future reference.
078?
079????????????if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
080?
081????????????????????wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
082?
083????????????????final int count = mViews != null ? mViews.length : 0;
084?
085????????????????for (int i=0; i<count; i++) {
086?
087????????????????????if (mRoots[i].mWindow.asBinder() == wparams.token) {
088?
089????????????????????????panelParentView = mViews[i];
090?
091????????????????????}
092?
093????????????????}
094?
095????????????}
096?
097?????????????
098?
099????????????root = new ViewRoot(view.getContext());
100?
101????????????root.mAddNesting = 1;
102?
103??
104?
105????????????view.setLayoutParams(wparams);
106?
107?????????????
108?
109????????????if (mViews == null) {
110?
111????????????????index = 1;
112?
113????????????????mViews = new View[1];
114?
115????????????????mRoots = new ViewRoot[1];
116?
117????????????????mParams = new WindowManager.LayoutParams[1];
118?
119????????????} else {
120?
121????????????????index = mViews.length + 1;
122?
123????????????????Object[] old = mViews;
124?
125????????????????mViews = new View[index];
126?
127????????????????System.arraycopy(old, 0, mViews, 0, index-1);
128?
129????????????????old = mRoots;
130?
131????????????????mRoots = new ViewRoot[index];
132?
133????????????????System.arraycopy(old, 0, mRoots, 0, index-1);
134?
135????????????????old = mParams;
136?
137????????????????mParams = new WindowManager.LayoutParams[index];
138?
139????????????????System.arraycopy(old, 0, mParams, 0, index-1);
140?
141????????????}
142?
143????????????index--;
144?
145??
146?
147????????????mViews[index] = view;
148?
149????????????mRoots[index] = root;
150?
151????????????mParams[index] = wparams;
152?
153????????}
154?
155????????// do this last because it fires off messages to start doing things
156?
157????????root.setView(view, wparams, panelParentView);
158?
159}

?

出自:frameworks/base/core/java/android/view/WindowManagerImpl.java

Ok,相信到了这里,大家都已经明白了:子线程是能够刷新UI的!!!

原文出处:http://blog.csdn.net/imyfriend/article/details/6877959

  相关解决方案