您的当前位置:首页正文

【Android】Dialog异常CalledFromWrongThreadException深入分析

2024-11-23 来源:个人技术集锦

问题

在使用Dialog时,因为线程问题,在调用dismiss方法时出现了CalledFromWrongThreadException的Crash,如下:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

抛出异常为CalledFromWrongThreadException,很明显第一反应就是出现了非ui线程进行了ui操作造成了此异常。通过分析工程代码,发现本质上是因为在非ui线程中创建了Dialog,而在主线程(即ui线程)中调用了show()以及dismiss()方法,我把问题模型写成测试代码如下:

public class MainActivity extends BaseActivity {
   
    private static final String TAG = "MainActivity test";
    private ProgressDialog dialog;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EventBus.getDefault().register(this);

        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();

                //子线程中创建Dialog
                dialog = new ProgressDialog(MainActivity.this);
                dialog.setCanceledOnTouchOutside(true);
                dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
                    @Override
                    public void onCancel(DialogInterface dialog) {
                        Log.d(TAG, "Dialog onCancel thread: " + getThreadInfo());
                    }
                });
                dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
                    @Override
                    public void onDismiss(DialogInterface dialog) {
                        Log.d(TAG, "Dialog onDismiss thread: " + getThreadInfo());
                    }
                });
                dialog.setMessage("正在加载...");
                Log.d(TAG, "Dialog create thread: " + getThreadInfo());

                Looper.loop();
            }
        }).start();


        Button btn = (Button) findViewById(R.id.btn_helloworld);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //UI主线程中show,然后点击空白区域dismiss
                dialog.show();
                Log.d(TAG, "Dialog show thread: " + getThreadInfo());
            }
        });
    }


    /**
     * 输出线程信息
     */
    private String getThreadInfo(){
        return "[" + Thread.currentThread().getId() + "]" +
                ((Looper.myLooper() == Looper.getMainLooper())? " is UI-Thread" : "");
    }
} 

就是Activity打开的时候,使用work子线程创建了一个Dialog,然后手动点击按钮的时候,显示Dialog。再点击空白处,dialog本应该dismiss的,但是直接crash了。抛出了CalledFromWrongThreadException的异常。

在上面的代码中,我顺便输出了Dialog每个操作的线程ID,同时会判定是不是ui主线程。我们来看看log:

可以看到,以上出现的问题中执行Dialog操作的线程信息如下:

  • 创建Dialog:work子线程
  • show():ui主线程
  • cancel():work子线程
  • dismiss():因为crash没有执行到,未知
<
显示全文