> Handler在Android中的重要性不言而喻,本文将围绕以下有关Handler的问题一一展开。
> Handler是什么?
> Handler怎么用呢?
> 为什么要用handler?
> android为什么要设计只能通过Handler机制更新UI呢?
> handler的原理是什么?
> 使用handler时候遇到的问题
> 如何实现一个与线程相关的Handler?
> HandlerThread又是什么呢?
> 如何在主线程给子线程发送消息呢?
> android中更新UI的几种方式
> 非UI线程真的不能更新UI吗?
【1】Handler是什么?
handler是android给我们提供用来更新UI的一套机制,也是一套消息处理的机制,我们可以发送消息,也可以通过它来处理消息。
【2】Handler怎么用呢?能不能不用呢?
a.先来回答第二个问题,能不能不用呢?不能。因为Android在设计的时候,就封装了一套消息创建、传递、处理机制,如果不遵循这样的机制,就没办法更新UI信息,会抛出异常信息。
b.怎么用handler呢?(用法1)
- 我们来创建一个工程Handler_01,布局中添加一个TextView控件。然后在Activity中创建一个子线程,局部代码如下:
<span style="white-space:pre"> </span>textView=(TextView) findViewById(R.id.textView);
new Thread(){
public void run() {
try {
Thread.sleep(1000);
textView.setText("thread update");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
}.start();
运行上面代码的结果出现异常,结果为:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
- 以上问题是由于在非主线程中更新UI导致的,怎么办呢?我们用handler机制去解决。创建一个handler对象。
<span style="white-space:pre"> </span>private Handler handler=new Handler();
然后在子线程中用handler的post(Runnable r)方法去更新UI,修改后代码为:
<span style="white-space:pre"> </span>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("update thread");
}
});
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
}.start();
运行结果正常,结果为:
update thread
- 在淘宝和很多场景中我们见到商品等图片会在间隔时间中来回切换,handler有一个方法postDelayed方法就可以实现这种效果。在上面的布局文件中增加一个ImageView控件,用来放图片。
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView"
android:layout_marginLeft="27dp"
android:layout_marginTop="80dp"
android:layout_toRightOf="@+id/textView"
android:src="@drawable/ic_launcher" />
知道了ImageView的id,在Activity中实现逻辑部分:
<span style="white-space:pre"> </span>private ImageView imageView;
private int images[]={R.drawable.image1,R.drawable.image2,R.drawable.image3};
private int index;
private MyRunnable myRunnable=new MyRunnable();
class MyRunnable implements Runnable{
@Override
public void run() {
index++;
index=index%3;
imageView.setImageResource(images[index]);
handler.postDelayed(myRunnable, 1000);
}
}
在上面的代码中,我们使用handler的方法postDelayed()实现几张图片每隔1s就切换展示的效果。做完了上面这些,还必须要在主线程中调用一下,方可实现:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
imageView=(ImageView) findViewById(R.id.imageView);
<span style="color:#ff0000;">handler.postDelayed(myRunnable, 1000);</span>
}
运行效果自己可以测试。
- 完整的Handler_01Activity代码如下:
package com.beijing.handler_01;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.Menu;
import android.widget.ImageView;
import android.widget.TextView;
public class MainActivity extends Activity {
private TextView textView;
private Handler handler = new Handler();
private ImageView imageView;
private int images[]={R.drawable.image1,R.drawable.image2,R.drawable.image3};
private int index;
private MyRunnable myRunnable=new MyRunnable();
class MyRunnable implements Runnable{
@Override
public void run() {
index++;
index=index%3;
imageView.setImageResource(images[index]);
handler.postDelayed(myRunnable, 1000);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.textView);
imageView=(ImageView) findViewById(R.id.imageView);
handler.postDelayed(myRunnable, 1000);
/*new Thread() {
public void run() {
try {
Thread.sleep(1000);
handler.post(new Runnable() {
@Override
public void run() {
textView.setText("update thread");
}
});
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
}.start();*/
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
c.handler用法2
- handler有个方法sendMessage()方法,下面来演示sendMessage()方法的用法。要是用sendMessage()方法,须修改一下handler对象,修改后为:
<span style="white-space:pre"> </span>private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
textView.setText(""+msg.arg1);
};
};
为了演示,我们将handler接收的消息设置给textView进行展示。利用handler发送消息要在子线程中进行,同样我们新建一个子线程:
<span style="white-space:pre"> </span>new Thread(){
public void run() {
try {
Thread.sleep(2000);
Message message=new Message();
message.arg1=88;
handler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
以上代码中我们创建了一条要发送的消息message,并将其参数内容设置为88,然后发送。运行效果如图:
88
handler不仅可以发送arg1参数,还可以发送arg2参数的消息。
<span style="white-space:pre"> </span>message.arg1=88;
<span style="white-space:pre"> </span>message.arg2=100;
然后将接收到的消息设置给textView:
<span style="white-space:pre"> </span>textView.setText(""+msg.arg1+"-"+msg.arg2);
结果为:
88 - 100
但是当我们从网络请求数据时,服务器返回的数据一般都比较多,又是Json格式,此时arg1与arg2就无法胜任了。handler还有一个参数obj(就是object对象),可以用obj参数去传递比较复杂的数据。我们在上面的例子中继续演示,先创建一个类Person,其中设置两个变量age与name,作为handler传递的内容,最后设置给textView展示。
但是当我们从网络请求数据时,服务器返回的数据一般都比较多,又是Json格式,此时arg1与arg2就无法胜任了。handler还有一个参数obj(就是object对象),可以用obj参数去传递比较复杂的数据。我们在上面的例子中继续演示,先创建一个类Person,其中设置两个变量age与name,作为handler传递的内容,最后设置给textView展示。
<span style="white-space:pre"> </span>class Person{
public int age;
public String name;
@Override
public String toString() {
return "age="+age+" name:"+name;
}
}
接着创建一个person实例,为person实例设置ageyuname属性值,最后传递:
<span style="white-space:pre"> </span>Person person=new Person();
person.age=25;
person.name="caocao";
message.obj=person;
handler.sendMessage(message);
将数据设置给textView进行展示:
<span style="white-space:pre"> </span>textView.setText(""+msg.obj);
最后效果为:
age=25 name:caocao
有时候,我们不需要新创建一个Message对象,可以运用handler的方法obtainMessage()方法获得一个message实例,照样可以传递数据,实现目标,至于底层逻辑是怎么实现的,可以点开源码研究。同样,我们也可以不同handler发送消息,而用message.sendToTarget()即可达到目的。源码为:
<span style="white-space:pre"> </span>public void sendToTarget() {
<span style="white-space:pre"> </span>target.sendMessage(this);
<span style="white-space:pre"> </span>}
其实上面代码中的target就是handler自己,所以封装了一个Target,本质上没啥区别。
- hangdler除了可以发送发送消息,还可以移除消息。我们在代码中演示,现在activity_main布局中增加一个Button,当点击这个Button时,ImageView的图片停止切换。Button:
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/textView"
android:text="Button" />
初始化Button,给button设置点击事件时,让Activity去实现onClickListener,重写onClick()方法,并调用handler的removeCallBacks(Runnable r)方法:
<span style="white-space:pre"> </span>public void onClick(View v) {
handler.removeCallbacks(myRunnable);
}
运行,点击Button按钮,图片就会停止切换。
- handler还有一个方法可以实现拦截消息的效果,其中的参数为callback.重新创建带有参数的handler对象,代码:
<span style="white-space:pre"> </span>private Handler handler=new Handler(new Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(getApplicationContext(), ""+1, 1).show();
return true;
}
}){
public void handleMessage(Message msg) {
Toast.makeText(getApplicationContext(), ""+2, 1).show();
}
};
这里第一个handleMessage()方法,有个返回值,可为true也可以为false.为false时,当方法里弹出一个toast之后,不会拦截接收到的消息,所以第二个handMessage中的内容也会显示。为true时,当符合拦截条件时,当第一个handleMessage()方法执行之后,就会拦截其他消息。这里需要点击Button实现,在Button中我们为handler发送一条空消息:
<span style="white-space:pre"> </span>public void onClick(View v) {
//<span style="white-space:pre"> </span>handler.removeCallbacks(myRunnable);
handler.sendEmptyMessage(1);
}
运行结果为,当返回值为false时,界面弹出了1后接着弹出了2;当返回值为true时,界面弹出1后就没有了。
> 以上只是Handler的一部分,由于篇幅已经很大,剩余的内容在下一篇博客中展示,本文的代码链接
http://download.youkuaiyun.com/detail/programmerteny/9470755,可下载研究