Android Handler 消息机制

1,519 阅读4分钟
1、先来了解下下面的几个重要的组件:
(1) Handler:收发消息工具。
(2) Message:Hander接收和处理消息对象。
(3) looper: 每一个线程拥有一个looper,创建消息队列,循环取队列里的消息进行分发。
(4) MessageQueue,消息队列,采用先进先出的方法俩管理Message对象,程序创建Looper对象 时,会在它的构造器中创建MessageQueue对象 。




2、在子线程中创建Handler:
方法1:在new Handler()前后加上 Looper.prepare()和 Looper.loop();
//子线程创建handler,需要创建looper
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                @SuppressLint("HandlerLeak")
                Handler handler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                    }
                };
                Looper.loop();
            }
        }).start();

方法二:通过主线程的Looper.getMainLooper()来实现

 //将主线程的looper传进来
        new Thread(new Runnable() {
            @Override
            public void run() {
                Handler handler = new Handler(Looper.getMainLooper()) {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                    }
                };
            }
        }).start();
3、handler之内存泄漏:
造成泄露原因:
(1)Handler 的生命周期与Activity 不一致:
  • 当Android应用启动的时候,会先创建一个UI主线程的Looper对象,Looper实现了一个简单的消息队列,一个一个的处理里面的Message对象。主线程Looper对象在整个应用生命周期中存在。
  • 当在主线程中初始化Handler时,该Handler和Looper的消息队列关联(没有关联会报错的)。发送到消息队列的Message会引用发送该消息的Handler对象,这样系统可以调用 Handler#handleMessage(Message) 来分发处理该消息。
(2)handler 引用 Activity 阻止了GC对Acivity的回收
  • 在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。
  • 如果外部类是Activity,则会引起Activity泄露 。当Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。

如何避免内存泄露:
(1)通过程序逻辑来进行保护
在Activity销毁时,将消息从消息队列中移除
removeMessages(int what)
removeCallbacks(Runnable r)
removeCallbacksAndMessages(null);
(2)将Handler声明为静态类 (见如下代码)

 private static class ActivityHandler extends Handler {
        private final WeakReference<AsyncActivity> mActivity;

        public ActivityHandler(AsyncActivity activity) {
            this.mActivity = new WeakReference(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            if (this.mActivity != null) {
                AsyncActivity activity = this.mActivity.get();
                if (activity != null && !activity.isFinishing()) {
                    activity.handleMessage(msg);
                }
            }
        }
    }

    public void handleMessage(Message msg) {
    }

4、Looper能创建多个实例嘛?能自己创建Looper实例嘛?

Looper只能创建一个实例,可以自己调用Looper.prepare()方法创建实例。
先来看Looper的prepare()方法,调用多次prepare方法创建实例,会抛出异常。

  public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

ThreadLocal类的特性:ThreadLocal是线程内部的数据存储类,当使用ThreadLocal维护变量的时候,它会为每个使用该变量的线程提供一个独立的变量副本,这个变量副本是该线程独有,不受其他线程影响。

Looper的主要作用是与当前线程形成一种绑定的关系,同时创建一个MessageQueue,这样保证一个线程只能持有一个Looper和MessageQueue,同时Looper使得MessageQueue循环起来。

5、handler,asyncTask有什么区别?
Android的AsyncTask比Handler更轻量级一些(只是代码上轻量一些,而实际上要比handler更耗资源),适用于简单的异步处理。

AsyncTask的重写方法:
  • doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。
  • onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回
  • onProgressUpdate(Progress…) 可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。
  • onPreExecute() 这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。
  • onCancelled() 用户调用取消时,要做的操作。

使用AsyncTask类,以下是几条必须遵守的准则:
  • Task的实例必须在UI thread中创建;
  • execute方法必须在UI thread中调用;
  • 不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法;
  • 该task只能被执行一次,否则多次调用时将会出现异常;

AsyncTask优缺点
优点:简单,快捷, 过程可控
缺点:在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来,最大并发数不超过5。

Handler优缺点:
优点:结构清晰,功能定义明确,对于多个后台任务时,简单,清晰。
缺点:在单个后台异步处理时,显得代码过多,结构过于复杂(相对性)。