在详解之前,贴出我们的基本代码。
layout_main.xml
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name" />
</LinearLayout>
MainActivity
public class MainActivity extends Activity {
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.layout_main);
initView();
initEvent();
}
private void initView() {
textView = (TextView) findViewById(R.id.textview);
new Thread() {
public void run() {
try {
Thread.sleep(1000);
textView.setText("更新");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
}
private void initEvent() {
}
}
- 在子线程中更新UI会抛出什么样的异常?
首先,当我们在子线程中直接更新UI会抛出什么异常呢。运行我们的程序,贴出我们的报错代码。
05-29 22:43:10.524: E/AndroidRuntime(20222): FATAL EXCEPTION: Thread-13219
05-29 22:43:10.524: E/AndroidRuntime(20222): Process: com.scp.handler, PID: 20222
05-29 22:43:10.524: E/AndroidRuntime(20222): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
05-29 22:43:10.524: E/AndroidRuntime(20222): at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6386)
05-29 22:43:10.524: E/AndroidRuntime(20222): at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:874)
05-29 22:43:10.524: E/AndroidRuntime(20222): at android.view.View.requestLayout(View.java:16500)
05-29 22:43:10.524: E/AndroidRuntime(20222): at android.view.View.requestLayout(View.java:16500)
05-29 22:43:10.524: E/AndroidRuntime(20222): at android.view.View.requestLayout(View.java:16500)
05-29 22:43:10.524: E/AndroidRuntime(20222): at android.view.View.requestLayout(View.java:16500)
05-29 22:43:10.524: E/AndroidRuntime(20222): at android.view.View.requestLayout(View.java:16500)
05-29 22:43:10.524: E/AndroidRuntime(20222): at android.widget.TextView.checkForRelayout(TextView.java:6714)
05-29 22:43:10.524: E/AndroidRuntime(20222): at android.widget.TextView.setText(TextView.java:3893)
05-29 22:43:10.524: E/AndroidRuntime(20222): at android.widget.TextView.setText(TextView.java:3744)
05-29 22:43:10.524: E/AndroidRuntime(20222): at android.widget.TextView.setText(TextView.java:3719)
05-29 22:43:10.524: E/AndroidRuntime(20222): at com.scp.handler.MainActivity$1.run(MainActivity.java:25)
产生这个异常的根本原因是Android中不允许在子线程中直接更新UI,那么我们要更新UI可以怎么做呢?接下来,我们通过handler这种机制来更新UI。
handler.post
用法
public class MainActivity extends Activity {
private TextView textView;
private Handler handler = new Handler();//Handler
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.layout_main);
initView();
initEvent();
}
private void initView() {
textView = (TextView) findViewById(R.id.textview);
new Thread() {
public void run() {
try {
Thread.sleep(1000);
handler.post(new Runnable() {
@Override
public void run() {
textView.setText("更新");
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
}
private void initEvent() {
}
}
我们这里呢使用了handler一个比较简单点的更新UI的方法。运行我们的程序,没有报错。那么为什么它能执行成功呢,这是因为我们这个线程是执行在UI线程里的,所以它是可以直接更新UI的。
handler.postDelayed
用法
修改我们的布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/app_name" />
<ImageView
android:id="@+id/imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
</LinearLayout>
修改我们的MainActivity
,各位自己准备三张图片,我们做一个图片轮播的程序。
public class MainActivity extends Activity {
private TextView textView;
private ImageView imageView;
private int[] imgIds = new int[] { R.drawable.img1, R.drawable.img2,
R.drawable.img3 };
private int index;// 图片索引
private MyRunnable myRunnable = new MyRunnable();
private Handler handler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.layout_main);
initView();
initEvent();
}
private void initView() {
textView = (TextView) findViewById(R.id.textview);
imageView = (ImageView) findViewById(R.id.imageview);
handler.postDelayed(myRunnable, 1000);
}
class MyRunnable implements Runnable {
@Override
public void run() {
index++;
index = index % 3;
imageView.setImageResource(imgIds[index]);
/**
* r:Runnable对象
*
* delayMillis:每隔一段时间执行一次,单位是毫秒
*/
handler.postDelayed(myRunnable, 1000);
}
}
private void initEvent() {
}
}
上面程序运行正常。
- 注意我们不能直接
setText
//省略上面代码
public void run() {
index++;
index = index % 3;
imageView.setImageResource(imgIds[index]);
textView.setText(index);//这是我们新加的
/**
* r:Runnable对象
*
* delayMillis:每隔一段时间执行一次,单位是毫秒
*/
handler.postDelayed(myRunnable, 1000);
}
运行我们的程序,发现会出错。这是因为我们不能直接这样频繁setText
05-29 23:26:14.774: E/AndroidRuntime(24525): FATAL EXCEPTION: main
05-29 23:26:14.774: E/AndroidRuntime(24525): Process: com.scp.handler, PID: 24525
05-29 23:26:14.774: E/AndroidRuntime(24525): android.content.res.Resources$NotFoundException: String resource ID #0x1
05-29 23:26:14.774: E/AndroidRuntime(24525): at android.content.res.Resources.getText(Resources.java:289)
05-29 23:26:14.774: E/AndroidRuntime(24525): at android.widget.TextView.setText(TextView.java:3968)
05-29 23:26:14.774: E/AndroidRuntime(24525): at com.scp.handler.MainActivity$MyRunnable.run(MainActivity.java:41)
05-29 23:26:14.774: E/AndroidRuntime(24525): at android.os.Handler.handleCallback(Handler.java:733)
05-29 23:26:14.774: E/AndroidRuntime(24525): at android.os.Handler.dispatchMessage(Handler.java:95)
05-29 23:26:14.774: E/AndroidRuntime(24525): at android.os.Looper.loop(Looper.java:136)
05-29 23:26:14.774: E/AndroidRuntime(24525): at android.app.ActivityThread.main(ActivityThread.java:5336)
05-29 23:26:14.774: E/AndroidRuntime(24525): at java.lang.reflect.Method.invokeNative(Native Method)
05-29 23:26:14.774: E/AndroidRuntime(24525): at java.lang.reflect.Method.invoke(Method.java:515)
05-29 23:26:14.774: E/AndroidRuntime(24525): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:871)
05-29 23:26:14.774: E/AndroidRuntime(24525): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687)
05-29 23:26:14.774: E/AndroidRuntime(24525): at dalvik.system.NativeStart.main(Native Method)
handler.sendMessage
用法
//修改部分代码
private void initView() {
textView = (TextView) findViewById(R.id.textview);
imageView = (ImageView) findViewById(R.id.imageview);
new Thread() {
public void run() {
try {
Thread.sleep(2000);
Message message = new Message();
message.what = 01;
message.obj = "更新UI";
handler.sendMessage(message);// 发送消息
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 在子线程中更新UI文本信息
switch (msg.what) {
case 01:
String update = (String) msg.obj;
textView.setText(update);
break;
default:
break;
}
}
};
上面这段代码中,我们可以看到
Message message = new Message();
message.what = 01;
message.obj = "更新UI";
handler.sendMessage(message);// 发送消息
其中
message.what = 01;
是handleMessage
区分我们发送的是哪一个消息。作为一个执行ID来用。
message.obj = "更新UI";
则是存储数据用的,它不仅仅可以发送一段文本,还可以发送一个对象,大家可以自行试试发送一个对象,这里我们就不演示了,使用方法都相同。
handler.sendMessage(message);
发送一个消息。
public void handleMessage(Message msg) {
// 在子线程中更新UI文本信息
switch (msg.what) {
case 01:
String update = (String) msg.obj;
textView.setText(update);
break;
default:
break;
}
}
获取
message
对象- 这里,我们不但可以使用
Message message = new Message();
来得到一个message
对象; - 还可以使用
Message message = handler.obtainMessage();
来得到一个message
对象。他们大体上是一样的。
- 这里,我们不但可以使用
发送
message
消息- 我们通常使用
handler.sendMessage(message);
来发送一个消息; - 但是还有一种方法也可以发送一个消息。那就是使用
message.sendToTarget();
我们需要注意的是,当我们使用这种方式来发送一个消息时,我们message
对象必须是通过Message message = handler.obtainMessage();
来得到的,否则会出现异常。
- 我们通常使用
05-30 00:14:26.974: E/AndroidRuntime(28528): FATAL EXCEPTION: Thread-13840
05-30 00:14:26.974: E/AndroidRuntime(28528): Process: com.scp.handler, PID: 28528
05-30 00:14:26.974: E/AndroidRuntime(28528): java.lang.NullPointerException
05-30 00:14:26.974: E/AndroidRuntime(28528): at android.os.Message.sendToTarget(Message.java:360)
05-30 00:14:26.974: E/AndroidRuntime(28528): at com.scp.handler.MainActivity$2.run(MainActivity.java:34)
message.setTarget(handler)
用法
- 解释原因
通过异常我们可以看到,这个是没有handler
来处理我们发送的消息。我们需要绑定一个handler
来处理这个消息。 - 为什么
Message message = handler.obtainMessage();
就可以?
那么我们刚刚也说了,我们必须通过Message message = handler.obtainMessage();
来得到message
对象,之后才能使用message.sendToTarget();
来发送消息。大家想一想,我们这个时候是通过handler
来得到的一个消息,换言之这个message
绑定了handler
,所以它可以。 - 解决办法
我们来看看message
还有一个方法message.setTarget(handler);
这个方法是设置一个handler来处理我们发送的消息。当我们在使用Message message = new Message();
来得到一个message
对象;时,我们就可以使用这样一个方法来解决消息无人处理的问题。 - 源码
- 解释原因
public void sendToTarget(){
target.sendMessage(this);//target也就是handler
}
实质上还是通过handler
在发送消息,只不过方式不同而已。
更多Handler用法将在下篇进行讲解。
y1笑而过的优快云博客
y1笑而过的博客园
y1笑而过的新浪博客
y1笑而过的安卓巴士博客
y1笑而过的51CTO技术博客