2017-08-04 78 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); 
相关问题