Android Handler详解、使用(倒计时、验证码)

Android Handler详解、使用(倒计时、验证码)



一、为什么要使用Handler
当出现耗时操作,并需要根据耗时操作返回结果时:
当Android的一个程序开启的时候,他会开启一个主线程,也就是常说的UI线程,但是大家都知道不能在主线程中进行耗时操作,就是各种下载、IO操作、等等,如果时间过长那么会出现一个ANR无响应的对话框,提示等待或者关闭。所以我们把这些耗时的操作放入子线程中去执行。
我们将耗时操作放入子线程中执行的话又会出现一个问题,就是当我们执行耗时操作的时候又想对UI更新操作怎么办?
子线程没有办法对UI界面上的内容进行操作,如果操作,将抛出异常:CalledFromWrongThreadException
也就是说我们在Android中处理多线程要保证一下两点:
1.不要阻塞UI线程
2.不要在UI线程之外访问Android UI工具包(TextView.setText()这样的操作)
所以这时就需要一种机制:主线程可以发送“命令/任务”给子线程执行,然后子线程反馈执行结果;
那么为了实现子线程中操作UI界面,Android中引入了Handler消息传递机制,目的是打破对主线程的依赖性。

二、那么什么是Handler呢?

一个Handler允许你发送和处理消息(Message)以及与一个线程的消息队列相关的Runnable对象。每个Handler实例都和单个线程以及该线程的消息队列有关。当你创建了一个新Handler,它就会和创建它的线程/消息队列绑定,在那以后,它就会传递消息以及runnable对象给消息队列,然后执行它们。

需要使用Handler有两大主要的原因:
      (1)在将来的某个时间点调度处理消息和runnable对象;
      (2)将需要执行的操作放到其他线程之中,而不是自己的;

调度处理消息是通过调用post(Runnable), postAtTime(Runnable, long),postDelayed(Runnable, long), sendEmptyMessage(int), sendMessage(Message),sendMessageAtTime(Message, long)和sendMessageDelayed(Message,long)等方法完成的。其中的post版本的方法可以让你将Runnable对象放进消息队列;sendMessage版本的方法可以让你将一个包含有bundle对象的消息对象放进消息队列,然后交由handleMessage(Message)方法处理。(这个需要你复写Handler的handleMessage方法)

你必需要知道的:
若在主线程中实例化一个Handler对象,例如:     Handler mHandler = newHandler();
此时它并没有新派生一个线程来执行此Handler,而是将此Handler附加在主线程上,故此时若你在Handler中执行耗时操作的话,还是会弹出ANR对话框!


三、一个简单的示例,验证码倒计时的实现

先上一张图吧


1.创建一个Handler对象并实现其中的handleMessage方法
private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    break;
            }
        }
    };

2.在点击方法中开启线程循环发送消息
    //send方式发送验证码
    public void sendMessageClick(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
              for(int i = 59;i>=0;i--){
                  Message msg = handler.obtainMessage();
                  msg.arg1 = i;
                  handler.sendMessage(msg);
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
            }
        }).start();
    }
 
3.对handleMessage方法进行完善
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 0:
                    if(msg.arg1==0){
                        button_send.setText("点击发送验证码");
                        button_send.setClickable(true);
                    }else{
                        button_send.setText("请在("+msg.arg1+"秒)后再次点击按钮");
                        button_send.setClickable(false);
                    }

                    break;
            }
        }
    };
但是!上述代码并不算是一个很好的代码,因为没有考虑到Handler的内存泄露问题,那么这个内存泄露是如何产生的呢?又应该如何去解决。
因为Handler会持有一个外部类的引用,例如在Mainactivity中创建,那么会获取到Mainactivity的引用,因为Handler是他的内部类,内部类的对象要依赖于外部类。当你使用的时候,如果退出了Mainactivity那么Handler对象应该被销毁,但是当你退出的时候Handler还在工作的话,那么这个Mainactivity并不能被退出。也就是Handler执行后才会退出,依然占用内存,也就造成了内存泄露。
解决办法:
1.定义一个内部类时会默认拥有外部类对象的引用,所以建议使用内部类时最好定义为一个static静态内部类。(因为静态内部类相当于外部类,不依赖外部类对象)
2.创建一个自定义的Handler类并集成Handler
3.定义一个弱引用 定义当前对象
private static class MyHandler extends Handler{
        private final WeakReference<MainActivity> weakReference;
        public MyHandler(MainActivity activity) {
            weakReference = new WeakReference<MainActivity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity activity = weakReference.get();
            if(activity!=null){
                switch (msg.what) {
                    case 0:
                        if(msg.arg1==0){
                            button_send.setText("点击发送验证码");
                            button_send.setClickable(true);
                        }else{
                            button_send.setText("请在("+msg.arg1+"秒)后再次点击按钮");
                            button_send.setClickable(false);
                        }
                        break;
                }
            }
        }
    }
4.创建一个自定义Handler对象
private MyHandler myHandler = new MyHandler(this);

四、上述是使用send,那么如何使用Post呢?

1.最开始还是一样的方式

2.将Runnable对象发送到消息队列中,按照队列的机制按顺序执行不同的Runnable对象中的run方法。
    //post方式发送验证码
    public void sendMessagePostClick(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i = 59;i>=0;i--){
                    Message msg = myHandler.obtainMessage();
                    msg.arg1 = i;
                    final int finalI = i;
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    myHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            if(finalI ==0){
                                button_post.setText("点击发送验证码");
                                button_post.setClickable(true);
                            }else{
                                button_post.setText("请在("+ finalI +"秒)后再次点击按钮");
                                button_post.setClickable(false);
                            }
                        }
                    });
                }
            }
        }).start();
    }


五、如果在消息处理的时候有延时操作怎么办?
1.post
    //开启子线程的线程名字
    private HandlerThread handlerThread = new HandlerThread("myHandlerThread");
    private Handler mHandler = new Handler();
    public void downloadPostClick(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //第一种方式,在handler绑定在主线程中,其中不能做耗时操作
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            Thread.sleep(60000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Log.e("ceshi", "post线程id"+Thread.currentThread().getId());
                    }
                }) ;
/*                //开启了子线程,可以耗时操作了
                handlerThread.start();
                mHandler = new Handler(handlerThread.getLooper());
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Log.e("ceshi", "post线程id"+Thread.currentThread().getName());
                    }
                });*/
            }
        }).start();
    }
如果不开启子线程的话,就是在主线程中执行耗时操作了,所以开启一个HandlerThread在这个新建的线程中执行耗时操作。
2.send方式的话那么在处理的时候使用新的线程就好


六、原理
1.首先是基本的概念:
a.Message:消息对象,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。用于重复利用,避免大量创建消息对象,造成内存浪费
b.Handler:消息处理者,负责Message的发送及处理。使用Handler时,需要实handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。Handler类的主要作用:(有两个主要作用)1)、在工作线程中发送消息;2)、在主线程中获取、并处理消息。
c.MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message串联起来的,等待Looper的抽取。
d.Looper:消息泵(消息对象的处理者),不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。
e.Thread:线程,负责调度整个消息循环,即消息循环的执行场所。
首先Handler创建一个Message对象,存放如消息队列中,然后消息泵从队列中取出Message对象(FIFO原则)
,当取出对象后交送给Handler的消息处理者handleMessage(Message msg)去处理消息。其中Message Pool是消息池,当消息创建后如果在执行那么则创建新的,如果不在执行那么从消息池中直接调用就好。

附上验证码倒计时的源码:源码

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值