2017-08-04 75 views
0

我試圖在動態添加視圖到另一個視圖在MainActivity:當新線程立即操作UI時,爲什麼沒有CalledFromWrongThreadException?

activity_main.xml中

<?xml version="1.0" encoding="utf-8"?> 
<android.support.constraint.ConstraintLayout.xmlns:android="http://schemas.android.com/apk/res/android" 
xmlns:app="http://schemas.android.com/apk/res-auto" 
xmlns:tools="http://schemas.android.com/tools" 
android:layout_width="match_parent" 
android:layout_height="match_parent" 
tools:context="com.example.aronho.testfragment.MainActivity"> 
<FrameLayout 
    android:id="@+id/myView" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent"/> 

</android.support.constraint.ConstraintLayout> 

view_test.xml

<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
android:orientation="vertical" android:layout_width="match_parent" 
android:layout_height="match_parent" 
android:background="#FFFF00"> 
<TextView 
    android:text="testString" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" /> 
</LinearLayout> 

我知道當我這個代碼將拋出CalledFromWrongThreadException啓動應用程序:

public class MainActivity extends AppCompatActivity { 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 

     LayoutInflater vi = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
     final View view=vi.inflate(R.layout.view_test,null); 
     final FrameLayout parentView=(FrameLayout) findViewById(R.id.myView); 
     parentView.addView(view); 

     new Thread(new Runnable(){ 
      @Override 
      public void run() { 
       try{ 
        Thread.sleep(3000); 
       }catch (Exception e){ 
       } 
       parentView.removeView(view); 
      } 
     }).start(); 
    } 
} 

但是當我刪除了Thread.sleep(3000);,如:

public class MainActivity extends AppCompatActivity { 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 

     LayoutInflater vi = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
     final View view=vi.inflate(R.layout.view_test,null); 
     final FrameLayout parentView=(FrameLayout) findViewById(R.id.myView); 
     parentView.addView(view); 

     new Thread(new Runnable(){ 
      @Override 
      public void run() { 
       parentView.removeView(view); 
      } 
     }).start(); 
    } 
} 

應用程序不扔CalledFromWrongThreadException儘管我嘗試刪除使用一個新的線程的視圖。爲什麼會發生?

回答

1

崩潰的根本原因是ViewRootImpl.checkThread()方法。

void checkThread() { 

if (mThread != Thread.currentThread()) { 

    throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views."); 

} 

然而,當視圖不測量不叫invalidate()的方法,該方法invalidate()最終將執行到checkThread()

OK,請參閱:

new Thread(new Runnable() { 
     @Override 
     public void run() { 
      System.err.println("getMeasuredHeight:" + parentView.getMeasuredHeight()); 
      System.err.println("getMeasuredWidth:" + parentView.getMeasuredWidth()); 
      parentView.removeView(view); 
     } 
    }).start(); 

將獲得:

W/System.err: getMeasuredWidth:0 
W/System.err: getMeasuredHeight:0 

可以看到,在測量過程尚未完成,所以我們可以改變UI而不觸發invalidate()而不是觸發例外。

然後,把它睡50毫秒:

new Thread(new Runnable() { 
     @Override 
     public void run() { 
      try { 
       Thread.sleep(50); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
      System.err.println("getMeasuredHeight:" + parentView.getMeasuredHeight()); 
      System.err.println("getMeasuredWidth:" + parentView.getMeasuredWidth()); 
      parentView.removeView(view); 
     } 
    }).start(); 

將獲得:

W/System.err: getMeasuredHeight:360 
W/System.err: getMeasuredWidth:1080 

可以看到,在測量過程已經完成,那麼,當我們改變UI的invalidate會觸發,然後它調用checkThread()

0

總之,避免操縱Android UI元素脫離UI線程;你會遇到不可預知的行爲。

您可以使用Activity.runOnUiThread(Runnable r)將更新同步到UI,這應該可以緩解問題。 i.e.

new Thread(new Runnable(){ 
      @Override 
      public void run() { 
       try{ 
        Thread.sleep(3000); 
        // Synchronize on the UI Thread. 
        MainActivity.this.runOnUiThread(new Runnable() { @Override public final void run() { 
         // Remove the View. 
         parentView.removeView(view); 
        } }); 
       } 
       catch (Exception e){ 
        e.printStackTrace(); 
       } 
      } 
     }).start(); 

但是,有一個更好的構造,你應該使用;你千萬不要低估分配和運行新的Thread的計算負擔!

可以使用實現完全相同的功能:

(new Handler(Looper.getMainLooper())).postDelayed(new Runnable() { 
    /* Update the UI. */ 
}, 3000)); 

這將實現相同的行爲,不留你不必擔心沿着UI線程管理同步。此外,你不會推出一個全新的Thread,他們唯一的工作就是睡覺!

這應該會更好一些。

+0

也'parentView.post(() - > parentView.removeView(V));'應該工作(如果需要延遲,就像'parentView#postDelayed'一樣) – PPartisan

0

無需使用一個線程的延遲,而不是:

new Thread(new Runnable(){ 
      @Override 
      public void run() { 
       try{ 
        Thread.sleep(3000); 
       }catch (Exception e){ 
       } 
       parentView.removeView(view); 
      } 
     }).start(); 

務必:

new Handler().postDelayed(new Runnable() { 
       @Override 
       public void run() { 
        parentView.removeView(view); 
       } 
      }, 3000); 
相關問題