Android界面显示和更新的线程分析

更新View的线程

在Android开发时有一条重要的原则:不能在非创建View的线程(通常都是主线程)中更新View,否则会抛出CalledFromWrongThreadException异常,如图所示。

这里写图片描述

错误描述中已经很明确的指出只有创建View视图树的线程才能够对View进行操作。

这里如下几点需要注意:

  1. 能够对View进行操作的线程是创建View视图树的线程。它和创建View的线程是不一样的。

    如下代码在子线程中创建了一个TextView,然后在主线程中将其添加到当前界面中。虽然TextView是在子线程中创建,但是仍然需要在主线程中对其进行操作,这里可以正常执行。

    TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                mTextView = new TextView(MainActivity.this);        // 子线程中创建View
            }
        }).start();
        getContentView().postDelayed(new Runnable() {
            @Override
            public void run() {
                getContentView().addView(mTextView);               // 主线程中添加View
                mTextView.setText("hello");
            }
        }, 1000);
    }
    
    private ViewGroup getContentView(){
        return (ViewGroup) this.findViewById(android.R.id.content);
    }
  2. 能够对View进行操作的线程是创建View视图树的线程。创建View视图树的线程通常是主线程,但也可以不在主线程。

  3. CalledFromWrongThreadException异常只有在视图树创建完成后才会抛出,在此之前可以在非创建视图树的线程中对View进行操作。

    如下代码在onCreate()中创建了一个子线程,由于在子线程中执行mTextView.setText(“hello”);时Activity的视图树很大可能还未生成,所以这里大部分时候这里不会有异常。这里只是为了说明情况,由于后台线程执行时机和视图树加载之间没有明确的先后顺序,所以一定不要按照这里的方式来实现。

    TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview1); 
        new Thread(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("hello");
            }
        }).start();
    }

    将上述代码稍作修改。在执行mTextView.setText(“hello”);前增加一秒延时,这时执行mTextView.setText(“hello”);就会抛出CalledFromWrongThreadException异常。

    TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)findViewById(R.id.textview1); 
        new Thread(new Runnable() {
            @Override
            public void run() {
                 try {
                     Thread.sleep(1000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                mTextView.setText("hello");
            }
        }).start();
    }

启动Activity的线程

对Activity来说,我们一般都是在主线程中调用startActivity()或startActivityForResult()来启动Activity,但其实startActivity()或startActivityForResult()是可以在非主线程中执行的。不过即使是在非主线程中调用startActivity,新启动的Activity生命周期的相关方法(包括构造方法)仍然会在主线程中执行。

如下代码可以正常执行,不会产生异常。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    new Thread(new Runnable() {
        @Override
        public void run() {
            Intent intent = new Intent(MainActivity.this, SecondActivity.class);
            startActivity(intent);
        }
    }).start();
}

创建和显示Dialog的线程

虽然startActivity()可以在任意的线程中执行,但是创建Dialog却不能。

如下代码会抛出RuntimeException异常。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    new Thread(new Runnable() {
        @Override
        public void run() {
            Dialog dialog = new MyDialog(this, R.style.dialog);
        }
    }).start();
}

这里写图片描述

从错误信息看,产生异常是因为在创建Dialog的时候需要创建Handler,而创建Dialog的线程中没有Looper,从而导致异常。

将上述代码稍作修改,在子线程中增加消息循环后,即可正常创建对话框。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            Dialog dialog = new MyDialog(this, R.style.dialog);
            dialog.show();
            Looper.loop();
        }
    }).start();
}

对话框创建之后还需要调用show()方法来显示。显示Dialog的线程要求如下。

  1. 调用show()方法显示对话框的线程同样需要在包含Looper的线程中
  2. 调用show()方法显示对话框的线程必须和创建对话框的线程相同,否则对话框在关闭的时候会抛出CalledFromWrongThreadException异常。

此外,和Activity不同的是,如果在子线程中执行创建和显示对话框,对话框生命周期的各个方法都在子线程中执行,不会回到主线程中。因此,在子线程中执行创建的对话框的View更新也必须在创建它的线程中执行。

创建和显示PopUpWindow的线程

创建和显示PopUpWindow的线程要求如下。

  1. 通过new创建PopUpWindow的线程可以是任意线程
  2. 通过showAsDropDown(),showAtLocation()显示PopUpWindow的线程必须是包含Looper的线程
  3. 创建PopUpWindow的线程可以和显示PopUpWindow的线程不同
已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页