Android中的异步消息处理机制,就是对核心类handler、looper类和Message类的应用。如果我们想要把一些耗时的操作(比如网络请求),放在worker线程里面去做,就需要对这个handler机制有一个深入的了解。本文将从源码的角度来分析handler内部是如何把消息发送出去,并且在完成时通知UI线程去进行相应的操作的。
说明:
除了这是一个final的类以外,私有的构造函数,也禁止了从外部来 new 一个Looper(),是这样的:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
但是查看了Looper的整个源码,我们会发现,只有这里:
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
是new了一个Looper,所以就解释了为什么一定要先调用Looper.prepare()才能变成有个LooperThread
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal sThreadLocal = new ThreadLocal();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
public static void loop() {
final Looper me = myLooper();
//返回当前线程里面的Looper
final MessageQueue queue = me.mQueue;
//拿到Looper里面的MessageQueue
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//没看懂但是不影响理解消息处理机制
for (;;) {
//判断条件为空,所有是一个无限循环
Message msg = queue.next(); // might block
//Message里面的next()方法,将下一个消息出队,
//并进行处理,当队列没有消息时候就阻塞在这里
msg.target.dispatchMessage(msg);
//对消息进行处理,非常重要!!
//msg.target返回的是Handler对象,message.target的
//初始化是在Handler中enqueueMessage()入队操作时候
msg.recycle();
//释放处理掉的消息所占用的资源
}
}
Loop中的一个for循环将MessageQueue驱动着动起来,如果队列里面有消息就拿出来进行处理,如果没有就一直阻塞并检查着。
构造函数一共有7个,调用关系如下:
通过构造函数的源码我们可以知道,在构造handler时,我们可以传入一个callback参数,也可以选择不传入这个参数
public interface Callback {
public boolean handleMessage(Message msg);
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
通过暴露这两个handlerMessage来实现对消息的处理操作。
发送消息有两种途径post系列和sendMessage系列,来张图说明下调用关系:
我们可以看到最终都是调用了enqueueMessage方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
//重要代码,为Message设置target,方便在looper里面
//拿到这个Message时候,找到对应的target(handler对象)
//从而调用里面的处理方法(dispatchMessage方法)
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
//把这个消息加入到当前handler所属的线程里面的looper中的
//messageQueue里面去
}
说明:两种方式Post和sendMesssage的不同点是,采用post时,传入的是一个runnable对象,再转化成message对象;采用sendMessage时,传入的直接就是Message对象。
当我们从Looper中loop方法的for(;;)里面拿到一个消息时候会回调Message.target.dispatchMessage方法,看下源码:
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
需要认真分析一下
private static void handleCallback(Message message) {
message.callback.run();
}
通过执行这个callback(Runnable)来达到消费Message的目的
final Callback mCallback;
public interface Callback {
public boolean handleMessage(Message msg);
}
并且只有当handleMessage返回false的时候,才会继续向下执行handler的成员函数handleMessage
一个Message就是一个要处理的事件,常用到的有以下几点:
* 这是一个final类,所以可以通过Message.obtain来获得一个空的消息
* 可以携带数据 有两个 int arg1 arg2 可以携带少量的数据,并且效率也比较高,还有一个成员变量object可以携带任何数据
* Message.what 用来标志Message的标识符,可以自己赋值,来区别Message
* Handler target 可以得到或者设置Message所绑定的handler
* Runnable callback 可以接收一个Runnable来对消息进行消费
到此,对handler分析就结束了,再提示一下handler的常用场景,就像例子里面一样,在UI线程创建handler,完成绑定,并通过最低优先级的方法—重写了空的成员函数handleMessage来消费这个事件,然后再新开一个Thread,在run方法里面通过已经创建的handler.post 或者handler.sendmessage 加入消息队列就可以了。
(其实我们也可以做到在thread1里面做网络请求,传到thread2,再传到thread3…..最后传到UI线程,只不过android都是依托UI线程来做的,这样实际效果更好一些。)
(如果不想看例子的直接可以跳过了跳过)
* 想要达到的效果:
* 主要代码:
* MainActivity.java
public class MainActivity extends Activity {
Button bt;
static ImageView iv;
static TextView tv;
final String address= "http://www.yydm.com/yfile/allimg/140708/1PI3L60-0.jpg";
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt = (Button) findViewById(R.id.btbt);
tv= (TextView) findViewById(R.id.tv);
iv= (ImageView) findViewById(R.id.iv);
final Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
//7.把位图对象显示至imageview
case 1:
tv.setText("一只大龙猫!");
iv.setImageBitmap((Bitmap) msg.obj);
break;
case 2:
Toast.makeText(MainActivity.this,"请求失败!",Toast.LENGTH_SHORT).show();
break;
}
}};
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(){
@Override
public void run() {
try {
//1.下载图片确定网址,将网址封装成url对象
URL url = new URL(address);
//2.获取客户端与服务器连接对象,此时还没有建立连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//3.对连接对象进行初始化
conn.setRequestMethod("GET");//连接方式
conn.setConnectTimeout(3000);//连接超时
conn.setReadTimeout(3000);//读取超时
//4.发送请求,与服务器建立连接
conn.connect();
//响应码=200,说明请求成功
if (conn.getResponseCode()==200){
//5.获取服务器响应头里的流,流中的数据就是请求端的数据
InputStream is = conn.getInputStream();
Log.d("InputStream", String.valueOf((is.toString()==null)));
//6.读取数据流里的数据,构造成位图对象
Bitmap bitmap = BitmapFactory.decodeStream(is);
Log.d("bitmap", String.valueOf(bitmap==null));
Message msg=handler.obtainMessage();
//消息对象可以携带数据
Log.d("Message", String.valueOf(msg==null));
msg.obj=bitmap;
Log.d("msg.obj", String.valueOf(msg.obj==null));
msg.what=1;
//把消息发送至主线程的消息队列
handler.sendMessage(msg);
}else {
Message msg = handler.obtainMessage();
msg.what=2;
handler.sendMessage(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}}.start();
}
});
}
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:id="@+id/btbt"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="click"
android:layout_gravity="center"
android:gravity="center">Button>
<TextView
android:id="@+id/tv"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Hello World, MyActivity"
/>
<ImageView
android:id="@+id/iv"
android:layout_width="fill_parent"
android:layout_height="fill_parent">ImageView>>
LinearLayout>
当然了,网络请求,得想着在manifest里面获取到权限:
<uses-permission android:name="android.permission.INTERNET">uses-permission>
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS">uses-permission>
<uses-permission android:name="android.permission.INSTALL_PACKAGES">uses-permission>
这样就可以让demo跑起来了
注:demo参考
参考:
[0]http://blog.csdn.net/iispring/article/details/47180325#t0
[1]http://www.cnblogs.com/codingmyworld/archive/2011/09/12/2174255.html
[2]http://www.jianshu.com/p/36a978b6cacc
[3]http://www.jianshu.com/p/86ea1c817fff
[4]http://www.cnblogs.com/JczmDeveloper/p/4403129.html
[5]http://www.cnblogs.com/codingmyworld/archive/2011/09/12/2174255.html