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,还需要大家的验证。
(备注:大家对此有什么疑问或者需要指正的地方,欢迎大家在回复中提出来,相互学习,共同进步)
最后,再次感谢大家和我一起见证这次奇妙之旅,接下来就是大家自己见证奇迹的时候了……