Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android -- ViewRoot,關於子線程刷新UI

Android -- ViewRoot,關於子線程刷新UI

編輯:關於Android編程

Android在4.0之後執行線程更新UI操作會報異常: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.");         } }     其次,看看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的!!!   復制代碼 public class TestActivity extends Activity {       Button btn = null;       /** Called when the activity is first created. */     public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);         btn = (Button) findViewById(R.id.Button01);        TestThread2 t = new TestThread2(btn);        t.start();     }       class TestThread2 extends Thread {        Button btn = null;        public TestThread2(Button btn) {            this.btn = btn;        }        @Override        public void run() {            btn.setText("TestThread2.run");        }     } } 復制代碼 建立一個工程,將上述代碼拷貝進去,運行看看吧! Btn的文本一定改變為"TestThread2.run"了。   那麼這到底是怎麼回事呢?當我發現這個問題時,也困惑了。經過一番調查後,真相大白。現在和大家分享一下。奧秘在於ViewRoot的建立時間,它是在ActivityThread.java的final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward)裡創建的。   看看代碼吧:   復制代碼 final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {         // If we are getting ready to gc after going to the background, well        // we are back active so skip it.         unscheduleGcIdler();         ActivityRecord r = performResumeActivity(token, clearHide);         if (r != null) {            final Activity a = r.activity;             if (localLOGV) Slog.v(                 TAG, "Resume " + r + " started activity: " +                 a.mStartedActivity + ", hideForNow: " + r.hideForNow                 + ", finished: " + a.mFinished);              final int forwardBit = isForward ?                     WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;             // If the window hasn't yet been added to the window manager,             // and this guy didn't finish itself or start another activity,             // then go ahead and add the window.             boolean willBeVisible = !a.mStartedActivity;             if (!willBeVisible) {                 try {                     willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(                             a.getActivityToken());                 } catch (RemoteException e) {                 }             }             if (r.window == null && !a.mFinished && willBeVisible) {                 r.window = r.activity.getWindow();                View decor = r.window.getDecorView();                 decor.setVisibility(View.INVISIBLE);                 ViewManager wm = a.getWindowManager();                 WindowManager.LayoutParams l = r.window.getAttributes();                 a.mDecor = decor;                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;                 l.softInputMode |= forwardBit;                 if (a.mVisibleFromClient) {                     a.mWindowAdded = true;                    wm.addView(decor, l);                 }             // If the window has already been added, but during resume             // we started another activity, then don't yet make the             // window visible.             } else if (!willBeVisible) {                 if (localLOGV) Slog.v(                     TAG, "Launch " + r + " mStartedActivity set");                 r.hideForNow = true;             }             // The window is now visible if it has been added, we are not             // simply finishing, and we are not starting another activity.             if (!r.activity.mFinished && willBeVisible                     && r.activity.mDecor != null && !r.hideForNow) {                 if (r.newConfig != null) {                     if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "                             + r.activityInfo.name + " with newConfig " + r.newConfig);                     performConfigurationChanged(r.activity, r.newConfig);                     r.newConfig = null;                 }                 if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="                         + isForward);                 WindowManager.LayoutParams l = r.window.getAttributes();                 if ((l.softInputMode                         & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)                         != forwardBit) {                     l.softInputMode = (l.softInputMode                             & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))                             | forwardBit;                     if (r.activity.mVisibleFromClient) {                         ViewManager wm = a.getWindowManager();                         View decor = r.window.getDecorView();                         wm.updateViewLayout(decor, l);                     }                 }                 r.activity.mVisibleFromServer = true;                 mNumVisibleActivities++;                 if (r.activity.mVisibleFromClient) {                     r.activity.makeVisible();                 }             }             r.nextIdle = mNewActivities;             mNewActivities = r;             if (localLOGV) Slog.v(                 TAG, "Scheduling idle handler for " + r);             Looper.myQueue().addIdleHandler(new Idler());         } else {             // If an exception was thrown when trying to resume, then             // just end this activity.             try {                 ActivityManagerNative.getDefault()                     .finishActivity(token, Activity.RESULT_CANCELED, null);             } catch (RemoteException ex) {             }         } } 復制代碼 在Activity.onResume前,ViewRoot實例沒有建立,所以沒有ViewRoot.checkThread檢查。而btn.setText時設定的文本卻保留了下來,所以當ViewRoot真正去刷新界面時,就把"TestThread2.run"刷了出來!   注意onStart,因為onStart在oncreate->onStart->onResume過程中,子線程刷新UI沒問題的,但是在onPause->onRestart->onStart過程中,就有問題了。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved