Android学习笔记040之Handler
来源:互联网 发布:实用魔术教学软件 编辑:程序博客网 时间:2024/05/16 05:51
Android开发中我们常常会用到多线程,但是进行UI界面的更新只能在UI线程,而请求网络获取数据不能在UI线程,这就涉及到了线程之间的通信问题,Android系统给我提供了一个线程间通信的解决办法–Handler,下面我们来介绍一下Handler:
1、Handler简介
Handler是Android操作系统提供的线程通信工具,它主要有两个作用:将消息或者Runnable在主线程中的某个地方执行;安排一个动作在另外一个线程中执行。每一个Handler对象都会维护两个队列:消息队列和Runnable队列,都是由Android操作系统提供。Handler可以通过消息队列来发送、接收、处理消息;通过Runnable队列来启动、结束、休眠线程。也就是说,Handler可以很方便的实现线程之间的通信。
2、Handler机制
Handler机制产生的原因:一个APP启动之后就会自动创建一个UI线程来管理UI控件和处理对事件的分发,当界面上的一个控件,比如Button响应了点击事件之后,我们需要进行长时间的耗时操作,比如联网获取数据,但是Android的机制是在UI线程中5秒内没有响应就会ANR异常,所以我们需要将耗时操作放在子线程中处理,子线程处理完成之后去更新UI线程的界面,但是,我们知道UI线程是不安全的,所以子线程不能直接更新UI线程中的界面,所以Android系统提供了Handler机制来解决这个问题。
Handler运行在UI线程,它通过接受子线程传递过来的Message更新UI线程的控件。有几个核心类:Message、Looper、Handler、MessageQueue,下面我们来学习一下这几个核心类:
2.1、Message
Message消息类,主要实现对消息的封装和指定对消息的操作形式。Message定义的变量和方法有:
public int what:变量,用于定义此Message属于何种操作
public Object obj:变量,用于定义此Message传递的信息数据,通过它传递信息
public int arg1:变量,传递一些整型数据时使用
public int arg2:变量,传递一些整型数据时使用
常用的方法:
copyFrom(Message o):复制一个消息
getCallback():获取当前消息执行时的回调对象
getData():获取在Message中传递的Bundle对象
getTarget():获取到处理当前消息的Handler对象
getWhen():获取到处理当前消息的时间,单位是毫秒
obtain(Handler h, int what, int arg1, int arg2, Object obj):获取到Message对象,重载方法有:obtain(Handler h, int what, Object obj)、obtain(Handler h, int what)、obtain(Handler h)、obtain(Handler h, Runnable callback)、obtain()、obtain(Handler h, int what, int arg1, int arg2)、obtain(Message orig)
setData(Bundle data):传递一个Bundle对象
setTarget(Handler target):对应getTarget()
recycle():将一个Message对象释放到消息池中
常用的Message的方法和变量就是这些了,不过使用的时候也是有一些注意事项的:
Message可以直接New出来,但是尽量使用obtain()方法从消息池中获取,节省资源
如果只是需要携带简单的int类型消息,推荐使用Message.arg1和Message.arg2,比Bundle节省内存
用Message.what来标志不同的消息,以便处理,关于Message就简单介绍到这里,附上Message的国内镜像API
2.2、Looper
Looper消息管道,Handler处理Message需要一个管道,Looper只有两个方法:prepare()和loop(),其中prepare()是初始化的方法。在Android的Activity中,系统帮我们启动了一个Looper对象,但是在我们自定义的类中,需要我们自己去启动一个Looper对象,只需要实现下面的代码,就可以将一个普通的线程变成Looper线程:
class LooperThread extends Thread { public Handler mHandler; public void run() { //将当前线程初始化为Looper线程 Looper.prepare(); ...... //循环处理消息 Looper.loop(); } }
Looper是循环者的意思,Looper通过调用prepare()方法进行初始化,我们来分析一下prepare()方法:
public class Looper {// 每个线程中的Looper对象其实是一个ThreadLocal,即线程本地存储(TLS)对象private static final ThreadLocal sThreadLocal = new ThreadLocal();// Looper内的消息队列final MessageQueue mQueue;// 当前线程Thread mThread;//其他属性// 每个Looper对象中有它的消息队列,和它所属的线程private Looper() { mQueue = new MessageQueue(); mRun = true; mThread = Thread.currentThread();}// 我们调用该方法会在调用线程的TLS中创建Looper对象public static final void prepare() { if (sThreadLocal.get() != null) { // 试图在有Looper的线程中再次创建Looper将抛出异常 throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper());}// 其他方法}
Looper内部维护着一个MessageQueue对象,每一个Thread中只能有一个Looper对象。prepare()方法的核心功能就是将Looper对象定义为ThreadLocal,不明白ThreadLocal的可以去看一下这篇文章:理解ThreadLocal
调用了loop()方法,Looper才真正开始工作,loop()不断的从自己的MessageQueue中取出任务执行,我们看一下loop方法代码:
public static final void loop() { Looper me = myLooper(); //得到当前线程Looper MessageQueue queue = me.mQueue; //得到当前looper的MQ Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); // 开始循环 while (true) { Message msg = queue.next(); // 取出message if (msg != null) { if (msg.target == null) { // message没有target为结束信号,退出循环 return; } // 日志。。。 if (me.mLogging!= null) me.mLogging.println( ">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what ); // 非常重要!将真正的处理工作交给message的target,即后面要讲的handler msg.target.dispatchMessage(msg); // 还是日志。。。 if (me.mLogging!= null) me.mLogging.println( "<<<<< Finished to " + msg.target + " " + msg.callback); // 下面没看懂,同样不影响理解 final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf("Looper", "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } // 回收message资源 msg.recycle(); } }}
除了prepare()和loop()方法之外,Looper还提供了一些其他的方法:
myLooper():获取当前线程Looper对象
getThread():获取到当前Looper所属的线程
quit():退出Looper的循环
最后我们总结一下Looper:
每一个线程只能有一个Looper对象,而且Looper是一个ThreadLocal
Looper中维护着一个MessageQueue,loop()方法不断地从MessageQueue中取出任务执行
普通的线程可以通过Looper变成一个Looper线程
2.3、Handler
Handler消息处理类,Handler主要是往MessageQueue中添加消息和处理消息,但是只处理自己添加的消息。即是:通知MessageQueue它要执行一个任务,在取到自己的时候执行那个任务,这个过程是异步的。这里我们需要了解一下同步和异步的概念,举一个简单的例子:
张三跟李四打电话,张三想要再跟王五通话,那么就必须要等到跟李四结束通话才可以。这个就是同步通信,就是发送一个请求,等待这个请求完成返回结果才会继续处理下面的通信。
张三同时给李四和王五发邮件,发送完成之后,不需要等待李四和王五读完邮件就可以去做其他的事。这个就是异步,简单的说就是:异步就是发送一个请求,但是不需要阻塞等待这个请求完成,这个请求完成之后,通过回调通知调用者这个请求的结果。
Handler可以直接New出来,也可以通过自定义类继承Handler实现。Handler创建的时候就会默认关联一个Looper对象,默认构造关联的是当前的Looper,但是可以通过set关联Looper。一个线程可以有多个Handler,但是只能有一个Looper。创建完成Handler之后,我们就可以进行接收和发送消息:
Handler发送消息:Handler提供的发送消息的方法有:
post(Runnable)
postAtTime(Runnable, long)
postDelayed(Runnable, long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message, long)
sendMessageDelayed(Message, long)
通过上面这些方法,Handler可以向MessageQueue中发送消息。msg.target就是Handler对象,这样可以保证Looper处理到该Message的时候可以获取到该Handler;post发出的Message的Callback是Runnable对象。
Handler读取消息:Handler读取消息主要通过两个方法dispatchMessage(Message msg)与handleMessage(Message msg)。其中handleMessage(Message msg)和Runnable对象的run方法由开发者实现逻辑,Handler读取消息有两个特点:
Handler可以在任意线程发送消息,这些消息会存放在MessageQueue中
Handler是在它关联的Looper中处理消息的,
Handler解决了Android开发中子线程不能更新UI的问题,Android中UI线程也是一个Looper线程,在主线程中创建的Handler会默认关联该Looper。Looper在Android中应用很多,Handler可以在主线程中创建,然后传递给工作线程,工作线程获取到主线程的Handler引用之后,在工作线程完成任务可以传递消息给主线程更新UI。下面我们通过例子体会一下Handler的使用吧:
3、Handler的使用
Handler可以在UI线程中创建,也可以在子线程中创建,不过,在子线程中创建需要我们自己去创建一个Looper对象,下面我们实现一下这两种使用:
3.1、在UI线程中使用
在UI线程中创建Handler不需要我们自己去创建一个Looper,所以使用非常简单。
public class ProgressDialogActivity extends AppCompatActivity {private Button btn_circle_progress;private Button btn_progress_01;private ProgressDialog progressDialog;private int maxProgress = 100;private int progress = 0;Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == 111) { progressDialog.setProgress(progress); } if (progress >= maxProgress) { progressDialog.dismiss(); } }};@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_progress_dialog); btn_circle_progress = (Button) findViewById(R.id.btn_circle_progress); btn_circle_progress.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //通过show方法创建一个进度条对话框 ProgressDialog.show(ProgressDialogActivity.this, "通讯中", "正在通讯,请稍候...", false, true); } }); btn_progress_01 = (Button) findViewById(R.id.btn_progress_01); btn_progress_01.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //通过new方法创建一个对话框 progressDialog = new ProgressDialog(ProgressDialogActivity.this); //设置进度条的最大值 progressDialog.setMax(maxProgress); //设置标题 progressDialog.setTitle("正在下载"); //设置提示信息内容 progressDialog.setMessage("安装包正在下载,马上就好..."); //设置是否允许点击进度条对话框外面取消对话框 progressDialog.setCancelable(false); //设置进度条的样式,这个为长方形 progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); //设置是否显示进度,false显示 progressDialog.setIndeterminate(false); //将进度条对话框show出来 progressDialog.show(); //新建一个线程更新进度 new Thread(new Runnable() { @Override public void run() { while (progress < maxProgress) { //模拟耗时方法 sleep(); progress += 2; handler.sendEmptyMessage(111); } } }).start(); } });}private void sleep() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }}}
通过Handler发送消息去更新进度条,实现效果如下:
3.2、在子线程中使用
在子线程中使用需要我们自己创建一个Looper,流程是:
直接调用Looper.prepare()方法就可以为当前线程创建Looper对象
创建Handler对象,重写handleMessage()方法处理消息
调用Looper.loop()方法启动Looper
下面是具体实现的例子:
这里实现的一个非常简单的例子,点击按钮,发送消息,显示一个吐司,下面是实例代码:
package com.example.webservicedemo;import android.os.Bundle;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.support.annotation.Nullable;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Toast;/** * Created by Devin on 2016/8/9. */public class SonHandlerActivity extends AppCompatActivity {private HandThread mThread;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_son); mThread = new HandThread(); mThread.start(); findViewById(R.id.btn_show_toast).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mThread.mHandler.sendEmptyMessage(0001); } });}class HandThread extends Thread { public Handler mHandler; @Override public void run() { super.run(); //调用prepare()方法,j为当前线程创建Looper对象 Looper.prepare(); //创建Handler对象 mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == 0001) { Toast.makeText(SonHandlerActivity.this, "显示一个吐司", Toast.LENGTH_SHORT).show(); } } }; //调用Looper.loop()启动Looper Looper.loop(); }}}
实现效果图:
关于Handler使用就简单介绍到这里,下面我们需要处理一个非常严重的问题:Handler内存泄漏问题
4、Handler内存泄漏问题
Java使用有向图机制,通过GC自动检查内存中的对象,当一个对象没有被任何引用指向的时候,GC会回收该对象,但是,当该对象有外部引用的时候,GC就不会回收。
Android中使用Handler很多都是内部类创建Handler,这样创建Handler对象会隐式地持有一个外部类对象(例如Activity)的引用,而Handler常常会有一个耗时的后台线程,这个后台线程执行完任务之后,通过消息机制通知Handler更新界面。但是有这样一种情况:在后台线程操作的时候,用户关闭了对应的Activity,但是,该后台线程持有Handler,Handler又持有Activity,这样就会导致该Activity不会被GC回收,这样就会导致内存泄漏。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。
内存泄漏会导致虚拟机占用内存过高,导致OOM(内存溢出),对于一个Android应用来说,这样容易导致程序占用内存超过系统限制,从而导致程序崩溃。
使用Handler内存泄漏的解决办法:
第一种方法:
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
第二种方法:
将Handler声明成静态,这样不会被外部持有。但是这样就会出现无法更新UI的问题,解决办法就是:在Handler中添加对Activity的弱引用,具体代码如下:
static class MyHandler extends Handler {WeakReference<Activity > mActivityReference;MyHandler(Activity activity) { mActivityReference= new WeakReference<Activity>(activity);}@Overridepublic void handleMessage(Message msg) { final Activity activity = mActivityReference.get(); if (activity != null) { mImageView.setImageBitmap(mBitmap); }}}
这样我们就可以操作Activity中的对象,Handler也不会被外部对象所持有,解决了非静态内部对象被外部对象持有导致的内存泄漏问题。
关于WeakReference弱引用:我们所说的弱引用,与强引用像对。GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不会出现了。
Handler就介绍到这里了
- Android学习笔记040之Handler
- android学习笔记之Handler
- Android学习笔记之Handler
- Android学习笔记之Handler(一)
- android学习笔记之handler(1)
- android学习笔记之handler(2)
- android学习笔记之handler初接触!
- Android 学习笔记 二十二 之Handler
- Android学习笔记之Handler内存泄露
- android学习笔记之handler简单实用
- Android学习笔记四十之Handler
- Android Handler学习笔记
- android handler学习笔记
- Android Handler学习笔记
- Android学习笔记----Handler
- android Handler 学习笔记
- android Handler学习笔记
- Android笔记之handler
- MFC + 线程访问窗口资源的问题(规则DLL)
- 基于Retinex的低照度增强算法
- Calendar set时间时,天数加1,月份的改变
- 现代操作系统/深入理解计算机系统:虚拟存储管理
- JSP XML 数据处理
- Android学习笔记040之Handler
- coreseek分词词表那些事
- 【47】2求1+2+3+...+n
- EventBus使用详解
- 访问者模式
- laravel路由404 openvpn客户端安装 apache版本查看 ls详细信息及隐藏文件 laravel项目迁移 2016.08.09回顾
- 解决#安卓手机更新软件后悔,如何回退版本#
- HDU1087 最大子序列和
- 全排列的一些总结