我试图将视图动态添加到MainActivity中的另一个视图中:
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。为什么会这样?
答案 0 :(得分: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()
。
好的,请参阅:
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
可以看到,测量过程尚未完成,因此我们可以在不触发invalidate()
的情况下更改UI而不会触发异常。
然后,保持睡眠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
可以看到,测量过程已经完成,然后,当我们更改用户界面时,invalidate
将会触发,然后调用checkThread()
。
答案 1 :(得分:0)
简而言之,避免在UI线程上操纵Android UI元素;你会遇到不可预测的行为。
您可以使用Activity.runOnUiThread(Runnable r)
将更新同步到用户界面,这样可以解决问题。 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
,其唯一的工作就是睡觉!
这应该会更好。
答案 2 :(得分: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);