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