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就介绍到这里了

0 0
原创粉丝点击