前言
Handler是安卓中的一个重要的概念,本篇没有记录Handler的用法,而是为了说明为什么要用Handler,算是学习Handler之前的“预习”吧。
一、Android的线程安全与UI线程
同Java类似,当一个应用程序第一次启动时,Android会同时启动一条主线程(MainThread),主线程主要负责处理与UI相关的事件,主线程通常又称作UI线程。出于性能优化考虑,Android的UI线程并不是线程安全的,这意味着如果有多线程并发操作UI组件,可能会导致线程安全问题。那么为了解决这个问题,Android制定了一条简单的规则:只允许UI线程修改Activity里的UI组件。下面可以通过一个程序的例子来验证一下,功能很简单,大致是这样的:
界面上有一个TextView和一个Button,通过点击Button来修改TextView的字体颜色,当然,这个操作要放到一个新的线程中去,也就是在其他线程中修改UI,看看会不会出错。
Layout代码(activity_main.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/tv_view1"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:text="@string/hello_world"
android:textSize="20sp"
android:gravity="center_horizontal|center_vertical"/>
<Button
android:id="@+id/btn_changeColor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Change Text Color" />
</LinearLayout>
Activity代码:
package com.example.handlertest;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends ActionBarActivity {
private TextView textView1;
private Button button1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView1 = (TextView) findViewById(R.id.tv_view1);
button1 = (Button) findViewById(R.id.btn_changeColor);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 创建一个WorkerThread并启动
Thread thread = new MyThread();
thread.start();
}
});
}
class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(3 * 1000); // 休眠3秒来模拟一个耗时任务
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
textView1.setTextColor(Color.RED); // 修改界面上TextView的字体颜色
}
}
}
运行程序效果如下:
很明显,程序出错了,那我们看看LogCat下的错误日志:
Only the original thread that created a view hierarchy can touch its views.
这句话的意思显而易见,也验证了Google的做法,也就是说哪个线程创建的UI,哪个线程才有权利去修改这个UI。但是这也并不是针对所有的UI组件,比如ProgressBar就是一个特例,它可以在WorkerThread中修改progress属性的值,在此就不再演示。既然Google不允许我们在WorkerThread里面操作UI,那我们只好放在主线程操作UI了,界面不变,Activity的代码修改如下:
package com.example.handlertest;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends ActionBarActivity {
private TextView textView1;
private Button button1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView1 = (TextView) findViewById(R.id.tv_view1);
button1 = (Button) findViewById(R.id.btn_changeColor);
button1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
try {
Thread.sleep(10 * 1000); // 休眠10秒来模拟一个耗时任务
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
textView1.setTextColor(Color.RED); // 修改界面上TextView的字体颜色
}
});
}
}
运行程序效果如下:
运行结果是显而易见的,程序被阻塞了,Button按下之后无法弹起,用户继续点击之后又弹出了错误窗口:
XXX is not responding.
Would you like to close it?
这个也就是所谓的“ANR”问题,应用程序无法响应,对于焦躁的用户来说,这是及其槽糕和致命的体验。
二、矛盾诞生
通过上面的例子我们发现,既不能在主线程中执行UI操作以及耗时任务,容易被阻塞;同时也不能在其他非UI线程中操作UI,因为操作的结果无法反馈给主线程,那我们应该如何处理?很明显,需要一种机制来解决Android线程之间的通信问题,而正是Handler为我们提供了完整的处理机制和解决方案。