您的位置:首页 > 运维架构

Handler、Looper消息传递机制

2016-05-24 15:45 357 查看
Handler、Looper消息传递机制


一、Handler消息传递机制初步认识:

(一)、引入:
        子线程没有办法对UI界面上的内容进行操作,如果操作,将抛出异常:CalledFromWrongThreadException
为了实现子线程中操作UI界面,Android中引入了Handler消息传递机制,目的是打破对主线程的依赖性。

        什么是Handler?
        handler通俗一点讲就是用来在各个线程之间发送数据的处理对象。在任何线程中,只要获得了另一个线程的handler,则可以通过  handler.sendMessage(message)方法向那个线程发送数据。基于这个机制,我们在处理多线程的时候可以新建一个thread,这个thread拥有UI线程中的一个handler。当thread处理完一些耗时的操作后通过传递过来的handler向UI线程发送数据,由UI线程去更新界面。 
        主线程:运行所有UI组件,它通过一个消息队列来完成此任务。设备会将用户的每项操作转换为消息,并将它们放入正在运行的消息队列中。主线程位于一个循环中,并处理每条消息。如果任何一个消息用时超过5秒,Android将抛出ANR。所以一个任务用时超过5秒,应该在一个独立线程中完成它,或者延迟处理它,当主线程空闲下来再返回来处理它。

(二)、常用类:(Handler、Looper、Message、MessageQueue)

Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。
Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message
msg)方法来对特定的Message进行处理,例如更新UI等。Handler类的主要作用:(有两个主要作用)1)、在工作线程中发送消息;2)、在主线程中获取、并处理消息。
MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message串联起来的,等待Looper的抽取。
Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。
Thread:线程,负责调度整个消息循环,即消息循环的执行场所。

(三)、Handler、Looper、Message、MessageQueue之间的关系:

Handler,Looper和MessageQueue的三角关系

                                                                                           

Looper和MessageQueue一一对应,创建一个Looper的同时,会创建一个MessageQueue;
而Handler与它们的关系,只是简单的聚集关系,即Handler里会引用当前线程里的特定Looper和MessageQueue;
在一个线程中,只能有一个Looper和MessageQueue,但是可以有多个Handler,而且这些Handler可以共享一个Looper和MessageQueue;
Message被存放在 MessageQueue中,一个 MessageQueue中可以包含多个Message对象。
【备注:】
Looper对象用来为一个线程开启一个消息循环,从而操作MessageQueue;
默认情况下,Android创建的线程没有开启消息循环Looper,但是主线程例外。
系统自动为主线程创建Looper对象,开启消息循环;
所以主线程中使用new来创建Handler对象。而子线程中不能直接new来创建Handler对象就会异常。
子线程中创建Handler对象,步骤如下:
Looper.prepare();
Handler handler = new Handler() {
    //handlemessage(){}

}
Looper.loop();

(四)、Handler类中常用方法:

handleMessage()    用在主线程中,构造Handler对象时,重写handleMessage()方法。该方法根据工作线程返回的消息标识,来分别执行不同的操作。
sendEmptyMessage()     用在工作线程中,发送空消息。
sendMessage()      用在工作线程中,立即发送消息。
(五)、Message消息类中常用属性:

arg1     用来存放整型数据
arg2      用来存放整型数据
obj        用来存放Object数据
what     用于指定用户自定义的消息代码,这样便于主线程接收后,根据消息代码不同而执行不同的相应操作。

【重点】:使用Message需要注意4点:

1、Message虽然也可以通过new来获取,但是通常使用Message.obtain()或Handler.obtainMessage()方法来从消息池中获得空消息对象,以节省资源;
2、如果一个Message只需要携带简单的int型数据,应优先使用arg1和arg2属性来传递数据,这样比其他方式节省内存;
3、尽可能使用Message.what来标识信息,以便用不同的方式处理Message;
4、如果需要从工作线程返回很多数据信息,可以借助Bundle对象将这些数据集中到一起,然后存放到obj属性中,再返回到主线程。

(六)、示例代码一:【重点】



private Handler handler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
text_main_info = (TextView) findViewById(R.id.text_main_info);
pDialog = new ProgressDialog(MainActivity.this);
pDialog.setMessage("Loading...");
image_main = (ImageView) findViewById(R.id.image_main);

// 主线程中的handler对象会处理工作线程中发送的Message。根据Message的不同编号进行相应的操作。
handler = new Handler() {
public void handleMessage(android.os.Message msg) {
// 工作线程中要发送的信息全都被放到了Message对象中,也就是上面的参数msg中。要进行操作就要先取出msg中传递的数据。
switch (msg.what) {
case 0:
// 工作线程发送what为0的信息代表线程开启了。主线程中相应的显示一个进度对话框
pDialog.show();
break;
case 1:
// 工作线程发送what为1的信息代表要线程已经将需要的数据加载完毕。本案例中就需要将该数据获取到,显示到指定ImageView控件中即可。
image_main.setImageBitmap((Bitmap) msg.obj);
break;
case 2:
// 工作线程发送what为2的信息代表工作线程结束。本案例中,主线程只需要将进度对话框取消即可。
pDialog.dismiss();
break;
}
}
};

new Thread(new Runnable() {
@Override
public void run() {
// 当工作线程刚开始启动时,希望显示进度对话框,此时让handler发送一个空信息即可。
// 当发送这个信息后,主线程会回调handler对象中的handleMessage()方法。handleMessage()方法中
// 会根据message的what种类来执行不同的操作。
handler.sendEmptyMessage(0);

// 工作线程执行访问网络,加载网络图片的任务。
byte[] data = HttpClientHelper.loadByteFromURL(urlString);
// 工作线程将网络访问获取的字节数组生成Bitmap位图。
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,
data.length);
// 工作线程将要发送给主线程的信息都放到一个Message信息对象中。
// 而Message对象的构建建议使用obtain()方法生成,而不建议用new来生成。
Message msgMessage = Message.obtain();
// 将需要传递到主线程的数据放到Message对象的obj属性中,以便于传递到主线程。
msgMessage.obj = bitmap;
// Message对象的what属性是为了区别信息种类,而方便主线程中根据这些类别做相应的操作。
msgMessage.what = 1;
// handler对象携带着Message中的数据返回到主线程
handler.sendMessage(msgMessage);

// handler再发出一个空信息,目的是告诉主线程工作线程的任务执行完毕。一般主线程会接收到这个消息后,
// 将进度对话框关闭
handler.sendEmptyMessage(2);
}
}).start();
}


(七)、示例代码二:图片定时切换:
1、思路:利用多线程,子线程每隔2秒发送一个消息给主线程,主线程中Handler接收消息,并更新ImageView中的图片。这样就实现了循环切换的动态效果。

handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
image_main_pic.setImageResource(imageId[position++]);
if (position >= imageId.length) {
position = 0;
}
break;
default:
break;
}
}
};

// 第一种解决办法:利用Thread和Thread的sleep
// new Thread(new Runnable() {
// @Override
// public void run() {
// while (flag) {
// try {
// Thread.sleep(2000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// handler.sendEmptyMessage(0);
// }
// }
// }).start();

// 第二种解决办法:利用Timer定时器和定时器的schedule()方法。
//schedule()方法中有三个参数:
/*第一个:表示定时任务TimerTask。 TimerTask 类实现了Runnable接口,所以要new  TimerTask(),一定要实现run()方法。
第二个:表示第一次执行前的等待延迟时间;
第三个:表示两次定时任务执行之间的间隔时间。*/
new Timer().schedule(new TimerTask() {
@Override
public void run() {
handler.sendEmptyMessage(0);
//sendEmptyMessage()方法等同于以下几句话。所以。如果只发送一个what,就可以使用sendEmptyMessage()。这样更简单。
//Message message = Message.obtain();
// Message message2 = handler.obtainMessage();
//message.what = 0;
//handler.sendMessage(message);
}
}, 1, 1500);


(八)、示例代码三:打地鼠:

handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
image_main_mouse.setVisibility(View.VISIBLE);
// 获取0-8之间的随机数[0,8),半闭合区间。目的是随机获取给定的8个坐标位置。
// 获取随机数有两种办法:
// 方法一:
// Math.random()*positionArr.length,注意伪随机数是个半闭合区间。即随机数不可能为positionArr.length
// 方法二:
// new Random().nextInt(positionArr.length);
position = (int) (Math.random() * positionArr.length);
image_main_mouse.setX(positionArr[position][0]);
image_main_mouse.setY(positionArr[position][1]);
break;
default:
break;
}
}
};

image_main_mouse = (ImageView) findViewById(R.id.image_main_mouse);

new Thread(new Runnable() {
@Override
public void run() {
while (flag) {
try {
// 获取0-500之间的随机数,再加上500,目的是让老鼠出现的间隙时间也随机,最短出现间隙为500毫秒,最长为999毫秒。
Thread.sleep(new Random().nextInt(500) + 500);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
}
}).start();

image_main_mouse.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
image_main_mouse.setVisibility(View.GONE);
return false;
}
});

【备注:】
        在案例《打地鼠》中使用到了横竖屏幕切换,请参考以下代码:
        关于Android中Activity的横竖屏切换问题可以通过AndroidManifest.xml文件中的Activity来配置:
android:screenOrientation=["unspecified" | "user" | "behind" |"landscape" | "portrait" | "sensor" | "nonsensor"]

screenOrientation 用来指定Activity的在设备上显示的方向,每个值代表如下含义:
"
unspecified
"
默认值 由系统来判断显示方向.判定的策略是和设备相关的,所以不同的设备会有不同的显示方向.
"
landscape
"
横屏显示(宽比高要长)
"
portrait
"
竖屏显示(高比宽要长)

 
"
user
"
用户当前首选的方向
"
behind
"
和该Activity下面的那个Activity的方向一致(在Activity堆栈中的)
"
sensor
"
有物理的感应器来决定。如果用户旋转设备这屏幕会横竖屏切换。
"
nosensor
"
忽略物理感应器,这样就不会随着用户旋转设备而更改了 ( "
unspecified
"设置除外
)。
二、Handler、Looper源码分析:

(一)、Handler的概念:

Handler是用于发送和处理消息和一个线程的MessageQueue相关联的Runable对象。 
每个Handler实例关联到一个单一线程和线程的messagequeue。 
当您创建一个Handler,从你创建它的时候开始,它就绑定到创建它的线程以及对应的消息队列,handler将发送消息到消息队列,并处理从消息队列中取出的消息。

Handler的主要用途有两个:

(1)、在将来的某个时刻执行消息或一个runnable;

(2)、为运行在不同线程中的多个任务排队。

主要依靠以下方法来完成消息调度:

post(Runnable)、 
postAtTime(Runnable, long)、 
postDelayed(Runnable, long)、 
sendEmptyMessage(int)、 
sendMessage(Message)、 
sendMessageAtTime(Message)、 
sendMessageDelayed(Message, long) 
【备注:】
post方法是当到Runable对象到达就被插入到消息队列; 
sendMessage方法允许你把一个包含有信息的Message插入消息队列,它会在Handler的handlerMessage(Message)方法中执行(该方法要求在Handler的子类中实现)。
当Handler post或者send消息的时候,可以在消息队列准备好的时候立刻执行,或者指定一个延迟处理或绝对时间对它进行处理,后两个是实现了timeout、ticks或者其他timing-based的行为。
当你的应用创建一个进程时,其主线程(UI线程)会运行一个消息队列,负责管理优先级最高的应用程序对象(Activity、广播接收器等)和任何他们创建的windows。你也可以创建自己的线程,通过handler与主线程进行通信,在新创建的线程中handler通过调用post或sendMessage方法,将传入的Runnable或者Message插入到消息队列中,并且在适当的时候得到处理。 

(二)、Handler的用法:
当你实例化一个Handler的时候可以使用Callback接口来避免写自定义的Handler子类。这里的机制类似与Thread与runable接口的关系。
在Handler里面,子类要处理消息的话必须重写handleMessage()这个方法,因为在handler里面它是个空方法:

(三)、源码分析:

A、Handler.java:(3个属性,9个方法)

3个属性:final MessageQueue mQueue;
final Looper mLooper;
final Callback mCallback;

9个方法:public boolean handleMessage(Message msg); 
public final Message obtainMessage()
public final boolean sendMessage(Message msg)
public final boolean sendEmptyMessage(int what)
public final boolean post(Runnable r)
public final boolean postAtTime(Runnable r, long uptimeMillis)
public void dispatchMessage(Message msg)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
public final boolean sendMessageDelayed(Message msg, long delayMillis)

B、Looper.JAVA:(4个属性,4个方法)
        每个ThreadLocal中只能有一个Looper,也就是说一个Thread中只有一个Looper

4个属性:    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    final MessageQueue  mQueue;
    final Thread  mThread;
    private static Looper mMainLooper = null;

4个方法:public static void prepare()
public static void prepareMainLooper()
public static void loop()
public static Looper myLooper()

C、Message.java:(8个属性,5个方法)

8个属性:public int what;
public int arg1;
public int arg2;
public Object obj;
Handler target; 
Message sPool;
int sPoolSize;
int MAX_POOL_SIZE=10;

5个方法:public static Message obtain()
public void recycle()
public void setTarget(Handler target)
public Handler getTarget()
public void sendToTarget()
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: