前言
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为我们提供了完整的处理机制和解决方案。