Android 在非UI线程直接更新UI信息

Android 说明文档明确指出不能在非UI线程直接更新UI线程的信息,否则系统会抛出类似如下异常:

09-15 21:37:09.335: E/AndroidRuntime(4507): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

文档建议使用Handler+Thread实现异步线程的UI更新,但是在日常工作中,突然发现在非UI线程居然可以直接更新UI信息,当时令我非常惊讶。

这是一个见证奇迹的旅程,请跟随我的思路,为你揭开Android非UI线程直接更新UI信息的神秘面纱!

以下是简单的代码验证(MainActivity.java):

package com.jony.update.ui;

import android.os.Bundle;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.Menu;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class MainActivity extends Activity {
    private TextView message;
    private ProgressDialog pd;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        message = (TextView) findViewById(R.id.message);
        pd = ProgressDialog.show(this, "Title", "Loading...");
        // Main thread ID
        System.out.println("Main Thread ID--->" + Thread.currentThread().getId());
        new Thread(){
            @Override
            public void run() {
                // Sub thread ID
                System.out.println("Sub Thread ID--->" + Thread.currentThread().getId());
                LinearLayout layout = (LinearLayout) message.getParent();
                // Change the text view
                message.setBackgroundColor(0Xffbbcc);
                message.setText(getResources().getString(R.string.update_content));
                message.setText("Hello jony");
                message.setTextSize(32);
                // Add text view
                TextView add_view = new TextView(getApplicationContext());
                add_view.setText(getResources().getString(R.string.update_content));
                add_view.setTextSize(25);
                add_view.setText("Change content");
                // Add image view
                ImageView imageView = new ImageView(getApplicationContext());
                Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
                imageView.setImageBitmap(bitmap);
                layout.addView(add_view);
                layout.addView(imageView);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
//                Button button = new Button(getApplicationContext());
//                button.setText("commit");
//                layout.addView(button);
                pd.dismiss();
            }
        }.start();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
}

以上代码片段开启了一个非UI线程,并在该线程中直接更新UI信息。经过运行测试,能够正常运行,证明了在非UI线程可以直接更新UI信息。My God,正不是和Google官方文档说的互相矛盾吗?猜想——难道这是Google大神的Bug?

当解除上述代码片段的注释部分:

Button button = new Button(getApplicationContext());
                button.setText("commit");
                layout.addView(button);

再次运行以上测试代码,系统会抛出如下异常:

09-15 22:17:34.371: E/WindowManager(6248): Activity com.jony.update.ui.MainActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@414250a0 that was originally added here

简单说明一下以上异常,以上异常信息描述的大概是——MainActivity窗口已经泄露,证明MainActivity已经被finish()或者是被销毁了

既然找到问题所在了,那我们就要证明一下是不是在执行以下代码的时候,MainActivity已经被系统finish了?

layout.addView(button);

简单更改一下以上代码,更改后的代码如下所示(MainActivity):

package com.jony.update.ui;

import android.os.Bundle;
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.Menu;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class MainActivity extends Activity {
    private TextView message;
    private ProgressDialog pd;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        message = (TextView) findViewById(R.id.message);
        pd = ProgressDialog.show(this, "Title", "Loading...");
        // Main thread ID
        System.out.println("Main Thread ID--->" + Thread.currentThread().getId());
        new Thread(){
            @Override
            public void run() {
                // Sub thread ID
                System.out.println("Sub Thread ID--->" + Thread.currentThread().getId());
                LinearLayout layout = (LinearLayout) message.getParent();
                // Change the text view
                message.setBackgroundColor(0Xffbbcc);
                message.setText(getResources().getString(R.string.update_content));
                message.setText("Hello jony");
                message.setTextSize(32);
                // Add text view
                TextView add_view = new TextView(getApplicationContext());
                add_view.setText(getResources().getString(R.string.update_content));
                add_view.setTextSize(25);
                add_view.setText("Change content");
                // Add image view
                ImageView imageView = new ImageView(getApplicationContext());
                Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
                imageView.setImageBitmap(bitmap);
                layout.addView(add_view);
                layout.addView(imageView);
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                pd.dismiss();
                Button button = new Button(getApplicationContext());
                button.setText("commit");
                System.out.println("Tha MainActivity is finished?");
                layout.addView(button);
                System.out.println("Yes,It is.");
            }
        }.start();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
    @Override
    protected void onPause() {
        // TODO Auto-generated method stub
        super.onPause();
        System.out.println("onPause");
    }
    
    @Override
    protected void onStop() {
        // TODO Auto-generated method stub
        super.onStop();
        System.out.println("onStop");
    }
    
    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        System.out.println("onDestroy");
    }
}

执行以上代码,打印的Log信息如下:

09-16 08:57:58.726: I/System.out(7410): Main Thread ID--->1
09-16 08:57:58.736: I/System.out(7410): Sub Thread ID--->684
09-16 08:58:00.758: I/System.out(7410): Tha MainActivity is finished?
09-16 08:58:00.908: I/System.out(7410): onPause
09-16 08:58:01.909: I/System.out(7410): onStop
09-16 08:58:01.909: I/System.out(7410): onDestroy

仔细分析打印出来的Log信息,在执行layout.addView(button);代码的时候系统已经在MainActivity窗口Destroy了,因此当执行layout.addView(button);代码的时候因为找不到MainActivity的实例了,所以会抛出MainActivity has leaked window 异常。

至此,这趟奇迹之旅已经接近尾声,最关键的时刻即将呈现:如果大家仔细回味一下以上的各种现象,不难看出,Android系统框架在对非UI线程直接操作UI信息进行判断的时候,准确度不够。在时间差内(代码片段中进行耗时操作,在本例中使用Thread.sleep模拟耗时操作)是可以在非UI线程直接更新UI信息的。这是否是Android系统框架的一个Bug,还需要大家的验证。

(备注:大家对此有什么疑问或者需要指正的地方,欢迎大家在回复中提出来,相互学习,共同进步)

最后,再次感谢大家和我一起见证这次奇妙之旅,接下来就是大家自己见证奇迹的时候了……

你可能感兴趣的:(UI,android)