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 [email protected] 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--->109-16 08:57:58.736: I/System.out(7410): Sub Thread ID--->68409-16 08:58:00.758: I/System.out(7410): Tha MainActivity is finished?09-16 08:58:00.908: I/System.out(7410): onPause09-16 08:58:01.909: I/System.out(7410): onStop09-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,还需要大家的验证。

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

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

更多相关文章

  1. android子线程创建handler
  2. android多线程下载详解
  3. android典型代码系列(三十)------DES加密算法
  4. android之网络资源多线程下载
  5. Android 多线程更新控件
  6. android获取屏幕相关信息
  7. 使用 Android NDK 重用现有的 C 代码
  8. android获取图片文件头信息
  9. android 获取设备硬件信息

随机推荐

  1. maven管理android项目 环境搭建
  2. android和java中常见 Exception
  3. Android中的onWindowFocusChanged()方法
  4. 几个Android云测试
  5. Android 驱动之旅 (Based on Galaxy Nexu
  6. Android编程心得分享――JSON学习过程
  7. android ANR产生原因和解决办法
  8. LeakCanary使用详细教程(附Demo)
  9. Android 系统框架介绍
  10. Android中通过view.getContext获取Activi