之前介绍了AsyncTask,今天介绍Handler+Thread的使用方式。
使用Handler+Thread也可以执行一个异步的任务,并可以通过handler更新UI。
注:这篇文章只讲API,关于Handler,Looper,Message,MessageQueue的原理我们下一篇讨论。
使用handler+Thread的典型方式是这样的:
必须重写Handler的handleMessage方法,默认实现是空实现。
package com.example.messagedemo2;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class MainActivity extends Activity implements OnClickListener
{
protected static final int TEST = 1;
protected static final String TAG = "MainActivity";
private Button but = null;
private Handler myHandler = new Handler()
{
public void handleMessage(android.os.Message msg)
{
switch (msg.what)
{
case TEST:
//可以执行UI操作
Log.i(TAG,(String)msg.obj);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
but = (Button) findViewById(R.id.but);
but.setOnClickListener(this);
}
@Override
public void onClick(View v)
{
if(v.getId() == R.id.but)
{
new Thread(new Runnable()
{
@Override
public void run()
{
//TODO 执行耗时任务
Message msg = Message.obtain();
msg.what = TEST;
msg.obj = "Rowandjj";
// msg.setTarget(myHandler);//如果不设置target那么调用sendToTarget将抛空针
// msg.sendToTarget();//其实是调用target也就是handler的sendMessage方法
myHandler.sendMessage(msg);//让handlet处理message
}
}).start();
}
}
}
我们在activity中创建了一个子线程,用于处理耗时任务,耗时任务处理完成之后,可能需要更改UI,但是我们知道,在子线程中是不能更改UI线程的UI的,故而我们需要定义一个Message对象,将Message发送给handler进行处理。为什么handler就能够处理UI呢?那是因为handler在创建的时候会绑定在创建的线程之上,因为上面的例子中我们的handler是绑定到UI线程上的,故而可以操作UI。
我们需要注意的是,handler在使用的时候需要一个Looper,handler内部通过调用Looper.myLooper方法返回与本线程绑定的looper对象,如果找不到这样的looper,则会抛出Runtime异常,异常信息为:Can't create handler inside thread that has not called Looper.prepare()。如果存在Looper对象,handler便可以拿到Looper对象中所包含的的mQueue变量,该变量的类型是MessageQueue,即消息队列,handler的sendMessage版本以及Post版本的方法都需要一个消息队列,拿到消息队列之后,这些方法都会调用MessageQueue类的enqueueMessage方法,即将消息入队。与此同时,Looper会调用loop方法不断的抽取消息队列中的消息(死循环),然后执行MessageQueue的next方法,即出队,出队的message会交由handler处理,handler通过dispatchMessage方法将消息分发下去。我们看到,经过一个循环,消息又被handler所接收。
如果该线程没有Looper,那我们应该创建一个Looper,通过调用Looper.prepare方法创建一个Looper对象,然后调用Looper.loop方法让消息队列运转起来。就像这样:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
需要注意的是,一个线程只能创建一个Looper对象,如果有多个Looper,将会抛Runtime异常,异常信息是这样的:Only one Looper may be created per thread。
通过上面的介绍,相信大家对Handler+Thread的使用方式有了一个大致的了解,下面总结下handler的作用:
1.在工作线程(子线程)中发送消息;
2.在UI线程中获取消息,并处理消息。(当然你也可以在子线程中创建handler获取/处理消息)
下面介绍一些API,方便大家使用。
首先是Handler的API。
handler的API分为两类,一类是post方法,另一类是sendMessage方法。post方法是将一个Runnable对象作为参数,该runnable对象中的run方法是被handler调用的(handleCallback方法),如果handler所在的线程为UI线程,则run方法可以更改UI。sendMessage方法是发送一个包装好的Message。并且这两类方法都提供了延时的重载方法,即延迟处理一个消息。
使用方式:
以下载网络图片为例:
1.采用post方法
package com.example.messagedemo1;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.os.Bundle;
import android.os.Handler;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ProgressBar;
/**
* @author Rowand jj
*
*一个使用handler+thread的简单示例
*下载网络图片
*/
public class MainActivity extends Activity implements OnClickListener
{
protected static final String PATH = "http://c.hiphotos.baidu.com/image/w%3D2048/sign=4facebbeb0b7d0a27bc9039dffd77709/8d5494eef01f3a29cf6d52c29b25bc315c607c11.jpg";
protected static final int SET_IMAGE = 1;
private Button but_load = null;
private ImageView iv_show = null;
private ProgressBar pb = null;
private Handler myhandler = new Handler()
{
public void handleMessage(android.os.Message msg)
{
if(msg.what == SET_IMAGE)
{
iv_show.setImageBitmap((Bitmap) msg.obj);
pb.setVisibility(View.GONE);
iv_show.setVisibility(View.VISIBLE);
but_load.setVisibility(View.VISIBLE);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv_show = (ImageView) findViewById(R.id.iv_show);
but_load = (Button) findViewById(R.id.but_load);
but_load.setOnClickListener(this);
pb = createProgressBar();
}
@Override
public void onClick(View v)
{
if(v.getId() == R.id.but_load)
{
pb.setVisibility(View.VISIBLE);
iv_show.setVisibility(View.GONE);
but_load.setVisibility(View.GONE);
new Thread(new Runnable()
{
@Override
public void run()
{
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(PATH);
try
{
HttpResponse resp = client.execute(get);
if(resp.getStatusLine().getStatusCode() == 200)
{
HttpEntity entity = resp.getEntity();
if(entity != null)
{
byte[] data = EntityUtils.toByteArray(entity);
Options opts = new Options();
opts.inSampleSize = 2;
final Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,data.length,opts);
myhandler.post(new Runnable()
{
@Override
public void run()
{
iv_show.setImageBitmap(bitmap);
pb.setVisibility(View.GONE);
iv_show.setVisibility(View.VISIBLE);
but_load.setVisibility(View.VISIBLE);
}
});
}
}
} catch (Exception e)
{
e.printStackTrace();
}
}
}).start();
}
}
public ProgressBar createProgressBar()
{
FrameLayout rootContainer = (FrameLayout) findViewById(android.R.id.content);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER;
ProgressBar pb = new ProgressBar(this);
pb.setLayoutParams(params);
pb.setVisibility(View.GONE);
rootContainer.addView(pb);
return pb;
}
}
2.采用sendMessage方法
在上面代码的基础上,将调用post方法那一段去掉,加上这样几行代码:
Message msg = Message.obtain(myhandler,SET_IMAGE,bitmap);
msg.sendToTarget();
或者是这样:
Message msg = myhandler.obtainMessage(SET_IMAGE,bitmap);
msg.sendToTarget();
handler类的obtainMessage方法其实内部也是调用Message的obtain方法,这样做的好处是Message不用在指定handler了。
当然最原始的是这样:
Message msg = Message.obtain(myhandler,SET_IMAGE,bitmap);
myhandler.sendMessage(msg);
上面的sendToTarget方法内部会调用target(即handler)的sendMessage方法。
上面就是Handler的基本使用介绍,下面简单介绍一下Message的API,我们看到上面的例子中我们发送消息时是通过obtain方法获取的一个消息的,那我们为什么不直接new Message呢??原因是这样的, message内部其实是有个message池的,使用message的obtain方法是从池中取出message,用完之后,looper会调用recycle方法回收message,将message重新放回池中,这样就可以防止资源的浪费了,所以推荐大家使用obtain方法获取message对象。
Message类的提供了四个公有成员变量,what,obj,arg1,arg2.what即标识消息的类型,handler通过switch消息的类型,来处理不同的消息。obj,arg1,arg2都是消息所携带的数据,建议存放简单的数据类型,如果需要传递复杂数据类型,应该使用setData(Bundle)方法,将复杂数据存到Bundle中。