Handler类有两个主要作用:1、计划在未来某个时间运行一个功能或者发送一个消息;2、在一个不同的线程里执行你所要的操作,然后将结果或者通知传回。

       听起来应该就是Android的多线程机制。多线程用new Thread()不就行了吗?但你会发现如果你要直接用new Thread新开一个线程来改变当前的app界面,它会报错,对于UI方面的修改,android并不允许你这么用多线程。因为它不愿意把多线程协作的设计工作全部交给android app程序员去做,大概是觉得你太次了肯定做不好吧。要不然就是为了让你开发方便一些,不用考虑太多线程间协作的设计工作。

Handler实现方法

使用handler实现,修改java代码Main2Activity.java如下:

package com.mly.panhouye.handlerdemo;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
/**
* Handler:
* 1 处理的消息对象就是Message,理解为要传递的消息数据的封装对象
* Message what : 标记,用来区分多个消息
* Message arg1,arg2 : 用来传递int类型的数据
* Message obj : 可以传递任何类型的对象(Object)
*/
public class Main2Activity extends AppCompatActivity {
public static final int UPDATE = 0x1;
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
tv = (TextView) findViewById(R.id.tv);
begin();//开启倒计时并跳转页面的方法
}
//消息处理者,创建一个Handler的子类对象,目的是重写Handler的处理消息的方法(handleMessage())
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case UPDATE:
tv.setText(String.valueOf(msg.arg1));
break;
}
}
};

public void begin(){
new Thread(new Runnable() {
@Override
public void run() {
for (int i=5;i>0;i--){
Message msg = new Message();
msg.what = UPDATE;
msg.arg1 = i;
handler.sendMessage(msg);
try {
Thread.sleep(1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印log
Log.i("tag",Main2Activity.this+"-"+ i);
}
//计时结束后跳转到其他界面
startActivity(new Intent(Main2Activity.this,Main3Activity.class));
//添加finish方法在任务栈中销毁倒计时界面,使新开界面在回退时直接退出而不是再次返回该界面
finish();
}
}).start();
}

@Override
protected void onDestroy() {
super.onDestroy();
//log打印用于测试activity销毁
Log.i("tag","destory");
}
}

3. Handler实现原理

使用Handler方式进行异步消息处理主要由Message,Handler,MessageQueue,Looper四部分组成:

(1)Message,线程之间传递的消息,用于不同线程之间的数据交互。Message中的what字段用来标记区分多个消息,arg1、arg2 字段用来传递int类型的数据,obj可以传递任意类型的字段。

(2)Handler,用于发送和处理消息。其中的sendMessage()用来发送消息,handleMessage()用于消息处理,进行相应的UI操作。

(3)MessageQueue,消息队列(先进先出),用于存放Handler发送的消息,一个线程只有一个消息队列。

(4)Looper,可以理解为消息队列的管理者,当发现MessageQueue中存在消息,Looper就会将消息传递到handleMessage()方法中,同样,一个线程只有一个Looper。

Handler实现原理如下图:

java handler属于什么模块 handler类_java handler属于什么模块

结合上文的的代码示例以及上图的实现流程,要使用Handler实现异步消息处理,首先我们需要在主线程中创建Handler对象并重写handleMessage()方法,然后当子线程中需要进行UI操作时,就创建一个Message对象,并通过Handlerr将这条消息发送出去。之后这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler的handleMessage()方法中。由于Halldler是在主线程中创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,从而实现子线程通过Handler机制实现UI线程操作的目的。

4. Handler内存泄漏分析

4.1 Handler内存泄漏问题的引出:

上面的Handler实现代码中,其实在Android Studio中会提示以下问题:

java handler属于什么模块 handler类_java handler属于什么模块_02

大致意思就是应该让Handler类为静态的,不然就会产生内存泄漏。 原因也说的很清楚,Handler被声明为一个非静态内部类或者匿名类可能会阻止外部类的垃圾回收(大家可以了解下Android的gc回收机制)。过多的内存泄漏使程序占用的内存超出系统限制,导致OOM(内存溢出),程序出错。

4.2 防止Handler引起内存泄漏:

方法一:通过程序逻辑进行保护:

(1)在关闭Activity时停掉对应的后台线程。线程停止就相当于切断了Handle和外部链接的线,Activity自然会在合适的时候被回收。

(2)如果Handler是被delay的Message持有了引用,那就使用Handler的removeCallbacks()方法将消息对象从消息队列移除即可。

方法二:将Handler声明为静态类,静态类不持有外部类的对象,所以Activity可以被随意回收。此处使用了弱引用WeakReference,也就是说当在内存不足时,系统会销毁弱/回收引用引用的对象,从而达到优化内存的目的。优化后代码如下:

package com.mly.panhouye.handlerdemo;

import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
import java.lang.ref.WeakReference;

public class Main4Activity extends AppCompatActivity {
    public static final int UPDATE = 0x1;
    TextView tv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);
        tv = (TextView) findViewById(R.id.tv);
        begin();//开启倒计时并跳转页面的方法
    }
    //Handler静态内部类
    private static class MyHandler extends Handler {
        //弱引用
        WeakReference<Main4Activity> weakReference;
        public MyHandler(Main4Activity activity) {
            weakReference = new WeakReference<Main4Activity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            Main4Activity activity = weakReference.get();
            if (activity != null) {
                activity.tv.setText(String.valueOf(msg.arg1));
            }
        }
    }
    private MyHandler handler = new MyHandler(this);
    public void begin() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                    for (int i = 5; i > 0; i--) {
                        Message msg = new Message();
                        msg.what = UPDATE;
                        msg.arg1 = i;
                        handler.sendMessage(msg);
                        try {
                            Thread.sleep(1000);//休眠1秒
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        Log.i("tag", Main4Activity.this + "-" + i);
                    }
                    //计时结束后跳转到其他界面
                    startActivity(new Intent(Main4Activity.this, Main3Activity.class));
                    //添加finish方法在任务栈中销毁倒计时界面,使新开界面在回退时直接退出而不是再次返回该界面
                    finish();
                }
        }).start();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        handler.removeCallbacksAndMessages(null);
        Log.i("tag", "destory");
    }
}

5.小结

本次使用handler实现倒计时页面跳转的效果实现,只是向大家简单介绍handler的使用方法以及注意事项,但依然存在bug,如果倒计时未完成时退出activity,子线程依然会在后台运行直至完成跳转,效果以及log日志如下:

java handler属于什么模块 handler类_android开发_03

java handler属于什么模块 handler类_android开发_04

针对这种情况,我处理起来比较麻烦,需要在销毁倒计时activity时,同时终止线程,我试了很多方法,未能实现。

其实要实现倒计时闪屏效果,可以使用Android中有个countDownTimer类来实现,后面会做简单的实现介绍。