【Android学习】消息机制Handler
2017-12-04 19:02
316 查看
1,概念
1)消息机制
Handler是Android消息机制的上层接口。2)Handler、MessaegQueue和Loop
一个线程有一个Loop,一个MessageQueue。可以有很多个Handler,发送各自的Message到这个MessageQueue中。2,Handler
Handler是Android消息机制的上层接口。Handler的运行需要底层的MessageQueue和Looper的支撑。
1)概念
2)场景
①更新UI只有主线程可以访问UI。
②处理消息
③将任务(耗时任务)切换到某个指定的线程中执行
3)MessageQueue(消息队列)
①概念
Android启动程序时会建立一个MessageQueue。②数据结构
单链表③功能
消息的存储单元,不能处理消息。④工作原理
MessageQueue有两个操作:插入和读取。enqueueMessage(插入)
向队列中插入一条消息。
next(读取)
从消息队列读取数据并移除。
4)Looper(消息循环)
①概念
一个线程可以产生一个Looper对象,由它来管理此线程里的Message Queue(消息队列)。线程默认没有Looper,使用时需要创建Looper。
ActivityThread(主线程)被创建时会初始化Looper,故主线程中默认可以使用Looper。
以无限循环的形式查找是否有新消息,如果有就处理,否则一直等待。
注意:Looper运行在创建Handler所在的线程中。
②ThreadLocal
不是线程,是一个线程内部的数据存储类,作用是可以在每个线程中存储数据。ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,Handler通过ThreadLocal可以轻松获取每个线程的Looper。
即时是用同一个ThreadLocal的get方法,ThreadLocal内部会从各自的线程中去取各自的数组,数据之前是互不干扰的。
使用场景:
一、当某些数据是以线程为作用域并且不同线程具有不同的数据副本。
二、复杂逻辑下的对象的传递,如监听器的传递。
采用ThreadLocal可以让监听器作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。
③实现
i>创建Looper一、Looper.prepare(),通过Looper.loop()开启消息循环。
loop方法是一个死循环,唯一跳出方式:MessageQueue的next方法返回null。
二、prepareMainLooper方法,这个方法是给主线程创建Looper使用。
通过getMainLooper方法,可以在任何地方获取到主线程的Looper。
ii>退出Looper
建议不需要的时候,终止Looper。
一、quit
直接退出Looper。
二、quitSafely
设定退出标记,然后吧消息队列中的已有消息处理完才安全退出。
④Can’t create handler inside thread that has not called Looper.prepare()
Looper类用来为一个子线程开启一个消息循环。默认情况下Android中新诞生的非主线程没有开启消息循环(主线程诞生时系统会自动为其创建开启消息循环的Looper对象),对于非主线程需要先调用Looper.prepare()启用Looper,然后通过调用。问题来自于在AsyncTask的onPostExecute(Integer result)方法中,即非主线程中直接new Handler。
除了启用Looper,另一种解决方式:
i>定义一个接口
public interface UIListener { /** * 获得字符串,更新UI * @param type UI更新类型 * @param msg UI更新信息 */ public void onEvent(int type, String msg); }
ii>I更新层,实现接口。
在onEvent方法具体实现,发送消息给handle,由handle执行toast。
@Override public void onEvent(int type, String msg) { switch (type) { case UIType.showCustomToast: dismissLoadingDialog(); Message msg_toast = new Message(); msg_toast.what = 8; msg_toast.obj = msg; handler.sendMessage(msg_toast); break; default: break; } }
iii>在AsyncTask中,通过调用接口的方式,将异步线程的执行结果传到UI更新层。
@Override protected void onPostExecute(Integer result) { super.onPostExecute(result); if (Result.NETWORK_ERROR == result) { ((MainActivity) mContext).onEvent(UIType.showCustomToast, "网络异常,未进行正常更新"); } else if (Result.SUCCESS == result) { RefreshData(); } if (Result.ERROR == result) { ((MainActivity) mContext).onEvent(UIType.showCustomToast, "新任务获取失败"); } }
5)工作原理
①Handler创建时采用当前线程的Looper来构建内部的消息循环系统(如果当前线程没有Looper会报错,需创建Looper)。
②Handler创建完毕后
通过Handler的post方法将一个Runnable投递到Handler内部的Looper中处理,也可以通过send方法来完成。
③send方法
post方法最终也是通过send方法来完成的。
当Handler的send方法被调用时,它会调用MessageQueue的enqueueMessage方法将这个消息放入消息队列中,然后Looper发现有新消息到来时,就会处理这个消息,最终消息中的Runnable或者Handler的handleMessage方法就好被调用。
6)Demo
package com.luo.activity; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.widget.Toast; public class MainActivity extends Activity { private Handler handler;// 获取数据变更 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); sendMsg(0, 1); } private void init() { handler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case 0: Toast.makeText(MainActivity.this, "this is handler" + msg.arg1, Toast.LENGTH_LONG).show(); break; default: break; } } }; } public void sendMsg(int flag, int value) { Message message = new Message(); message.what = flag; message.arg1 = value; handler.sendMessage(message); } }
3,内存泄漏
1)原因
使用内部类、匿名类来创建Handler,这样会造成内存泄露!点击查看什么是内存泄漏
原因:当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用。
而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。
如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用,这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。
2)解决方案
①通过程序逻辑来进行保护(推荐)
i>在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。ii>如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
②将Handler声明为静态类
静态类不持有外部类的对象,所以Activity可以随意被回收。由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference),具体实现如下:static class MyHandler extends Handler { @Override public void handleMessage(Message msg) { mImageView.setImageBitmap(mBitmap); } }
static class MyHandler extends Handler { WeakReference<Activity > mActivityReference; MyHandler(Activity activity) { mActivityReference= new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg) { final Activity activity = mActivityReference.get(); if (activity != null) { mImageView.setImageBitmap(mBitmap); } } }
相关文章推荐
- android学习----Handler使用
- Android入门:深入学习理解 Handler HandlerThread AsyncQueryHandler 三者的关系 收藏
- Android开发学习笔记(8):浅谈Handler实现多线程和异步处理
- android-Handler基本用法学习总结
- Android WebView学习,创建步骤并附代码(Handler、线程、WebView结合)
- Android之Handler学习
- Android 之 handler 学习(转)
- android Handler 机制研究学习笔记
- Android开发十:Handler学习记录
- android 消息机制 Handler Looper 原理分析
- android学习--Handler的使用
- Android学习之问题处理程序Handler
- Android学习札记26:深入理解Android中的消息处理机制——Thread、Looper、MessageQueue和Handler(1)
- android handler 学习
- Android入门:深入学习理解 Handler HandlerThread AsyncQueryHandler 三者的关系
- Android学习第14课—Handler的使用(一)
- Android 之 handler 学习
- Android学习—— Handler 的消息发送,在新的线程处理消息
- 学习android 中的 handler的体会(1)
- 【Android】学习笔记(5)——浅谈Handler