Android Handler 消息机制

Android Handler机制是Android中处理多线程和UI更新的关键,它通过MessageQueue、Looper和Handler协同工作,实现线程间的通信。Handler主要用于推送Message或Runnable到消息队列,确保UI线程的安全更新。Handler的post和sendMessage方法底层逻辑一致,通过MessageQueue管理消息,而HandlerThread简化了在子线程中使用Handler的流程。内存泄漏是Handler使用中常见的问题,需谨慎处理以避免内存泄露。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【Android Handler 消息机制】

前言

在Android开发中,我们都知道不能在主线程中执行耗时的任务避免ANR

Android中主线程也叫UI线程,那么从名字上我们也知道主线程主要是用来创建、更新UI的,而其他耗时操作,比如网络访问,或者文件处理,多媒体处理等都需要在子线程中操作。
之所以在子线程中操作是为了保证UI的流畅程度,手机显示的刷新频率是60Hz,也就是一秒钟刷新60次,每16.67毫秒刷新一次,为了不丢帧,那么主线程处理代码最好不要超过16毫秒。

当子线程处理完数据后,为了防止UI处理逻辑的混乱,Android只允许主线程修改UI,那么这时候就需要Handler来充当子线程和主线程之间的桥梁了。Handler机制在Android多线程编程中可以说是不可或缺的角色。

消息机制概述

Android系统在设计的初期就已经考虑到了UI的更新问题,由于Android中的View是线程不安全的,然而程序中异步处理任务结束后更新UI元素也是必须的。这就造成了一个矛盾,最简单的解决方法肯定是给View加同步锁使其变成线程安全的。这样做不是不可以,只是会有两点坏处:

  1. 加锁会有导致效率底下
  2. 由于可以在多个地方更新UI,开发就必须很小心操作,开发起来就很麻烦,一不小心就出错了。

基于以上两个缺点,谷歌设置一个线程专门处理UI控件的更新,如果其他线程也需要对UI进行更新,必须把想做的告诉那个专门处理UI线程的家伙,让它帮你做。大家各有各的任务,井水不犯河水,各司其职。

消息机制简单概括:其他线程通过给特定线程发送消息,将某项专职的工作,交给这个特定的线程去做。比如说其他线程都把处理UI显示的工作通过发送消息交给UI线程去做。

实现原理呢,我是这么理解的,现在要做的主要工作就是切换线程去操作,怎么切换呢?把两条线程看作是两条并行的公路,如果要从一条公路转到另一条公路上,要怎么做呢?是不是只要找到两条公路交叉或者共用某个资源的地方,如果说交叉路口,比如说加油站。当然了,线程是不存在交叉的地方的,那么可以考虑他们公用资源的地方,不同的进程享用不同的内存空间,但是同一个进程的不同线程享用的是同一片内存空间,那让其他线程把要处理的消息放到这个特定的内存空间上,处理消息的线程来这个内存空间上来取消息去处理不就可以了吗。事实正是如此,在Android的消息机制中,扮演这个特定内存空间的对象就是MessageQueue对象,发送和处理的消息就是Message对象。其他的HandlerLooper都是为了配合线程切换用的。
其实不仅仅是线程之间,不同进程之间进行消息传递(IPC机制),也是这个思路,找到公用的一个资源点,文件系统啊,共享内存啊等等。

Handler

Handler的主要用途

  1. 推送未来某个时间点将要执行的Message或者Runnable到消息队列。
  2. 在子线程把需要在另一个线程执行的操作加入到消息队列中去。

废话不多说,通过举例来说明Handler的两个主要用途。

1. 推送未来某个时间点将要执行的Message或者Runnable到消息队列

实例:通过Handler配合Message或者Runnable实现倒计时

  • 首先看一下效果图

  • 方法一,通过Handler + Message的方式实现倒计时。代码如下:
public class MainActivity extends AppCompatActivity {
   
   
    private ActivityMainBinding mBinding;
    private Handler mHandler ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
   
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        //设置监听事件
        mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
   
   
            @Override
            public void onClick(View v) {
   
   
                //通过Handler + Message的方式实现倒计时
                for (int i = 1; i <= 10; i++) {
   
   
                    Message message = Message.obtain(mHandler);
                    message.what = 10 - i;
                    mHandler.sendMessageDelayed(message, 1000 * i); //通过延迟发送消息,每隔一秒发送一条消息
                }
            }
        });
        mHandler = new Handler() {
   
   
            @Override
            public void handleMessage(Message msg) {
   
   
                super.handleMessage(msg);
                mBinding.time.setText(msg.what + "");   //在handleMessage中处理消息队列中的消息
            }
        };
    }
}

通过这个小程序,大家可以了解到Handler的一个作用就是:在主线程中,可以通过Handler来处理一些有顺序的操作,让它们在固定的时间点被执行。

  • 方法二,通过Handler + Runnable的方式实现倒计时。代码如下:
public class MainActivity extends AppCompatActivity {
   
   
    private ActivityMainBinding mBinding;
    private Handler mHandler = new Handler();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
   
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        //设置监听事件
        mBinding.clickBtn.setOnClickListener(new View.OnClickListener() {
   
   
            @Override
            public void onClick(View v) {
   
   
                for (int i = 1; i <= 10; i++) {
   
   
                    final int fadedSecond = i;
                    //每延迟一秒,发送一个Runnable对象
                    mHandler.postDelayed(new Runnable() {
   
   
                        @Override
                        public void run() {
   
   
                            mBinding.time.setText((10 - fadedSecond) + "");
                        }
                    }, 1000 * i);
                }
            }
        });
    }
}

方法二也是通过代码让大家加深Handler处理有序事件的用途,之所以分开Runnable和Message两种方法来实现,是因为很多人都搞不清楚为什么Handler可以推送Runnable和Message两种对象。
其实,无论Handler将Runnable还是Message加入MessageQueue,最终都只是将Message加入到MessageQueue。Handler的post Runnable对象这个方法只是对post Message进行了一层封装,所以最终我们都是通过Handler推送了一个Message罢了。

2. 在子线程把需要在另一个线程执行的操作加入到消息队列中去

实例:通过Handler + Message来实现子线程加载图片,在UI线程显示图片
  • 效果图如下

  • 代码如下(布局代码也不放出来了)
public class ThreadActivity extends AppCompatActivity implements View.OnClickListener {
   
   
    private ActivityThreadBinding mBinding = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
   
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_thread);
        // 设置点击事件
        mBinding.clickBtn.setOnClickListener(this);
        mBinding.resetBtn.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
   
   
        switch (v.getId()) {
   
   
            // 响应load按钮
            case R.id.clickBtn:
                // 开启一个线程
                new Thread(new Runnable() {
   
   
                    @Override
                    public void run() {
   
   
                        // 在Runnable中进行网络读取操作,返回bitmap
                        final Bitmap bitmap = loadPicFromInternet();
                        // 在子线程中实例化Handler同样是可以的,只要在构造函数的参数中传入主线程的Looper即可
                        Handler handler = new Handler(Looper.getMainLooper());
                        // 通过Handler的post Runnable到UI线程的MessageQueue中去即可
                        handler.post(new Runnable() {
   
   
                            @Override
                            public void run() {
   
   
                                // 在MessageQueue出队该Runnable时进行的操作
                                mBinding.photo.setImageBitmap(bitmap);
                            }
                        });
                    }
                }).start();
                break;
            case R.id.resetBtn:
                mBinding.photo.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.mipmap.default_pic));
                break;
        }
    }

    /* HttpUrlConnection加载图片 */
    public Bitmap loadPicFromInternet() {
   
   
        Bitmap bitmap = null;
        int respondCode = 0;
        InputStream is = null;
        try {
   
   
            URL url = new URL("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=1421494343,3838991329&fm=23&gp=0.jpg");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            connection.setConnectTimeout(10 * 1000);
            connection.setReadTimeout(5 * 1000);
            connection.connect();
            respondCode = connection.getResponseCode();
            if (respondCode == 200) {
   
   
                is = connection.getInputStream();
                bitmap = BitmapFactory.decodeStream(is);
            }
        } catch (MalformedURLException e) {
   
   
            e.printStackTrace();
            Toast.makeText(getApplicationContext(), "访问失败", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
   
   
            e.printStackTrace();
        } finally {
   
   
            if (is != null) {
   
   
                try {
   
   
                    is.close();
                } catch (IOException e) {
   
   
                    e.printStackTrace();
                }
            }
        }
        return bitmap;
    }
}

Handler推送Message和Runnable的区别

首先我们看看post方法和sendMessage方法的源码:

    public final boolean post(Runnable r){
   
   
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    public final boolean sendMessage(Message msg) {
   
   
        return sendMessageDelayed(msg, 0);
    }

可见,两个方法都是通过调用sendMessageDelayed方法实现的,所以可以知道它们的底层逻辑是一致的。

但是,post方法的底层调用sendMessageDelayed的时候,却是通过getPostMessage®来将Runnable对象来转为Message,我们点进方getPostMessage()法可以看到:

    private static Message getPostMessage(Runnable r) {
   
   
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

其实,最终runnable最终也是转化为一个Message,而这个Message只有一个被赋值的成员变量,就是Runnable的回调函数,也就是说,这个Message在进入MessageQueue之后,它只是一个“动作”,即我们Runnbale的run方法里面的操作。

要知道,我们的Message类可是有很多参数的,所以你可以理解为它是一个非常丰富的JavaBean,可以看看它的成员变量:

  • public int what;
  • public int arg1;
  • public int arg2;
  • public Object obj;

那么讲到这里,大家也应该有所理解为什么Google工程师为什么会封装这两种方法,我总结如为:为了更方便开发者根据不同需要进行调用
当我们需要传输很多数据时,我们可以使用sendMessage来实现,因为通过给Message的不同成员变量赋值可以封装成数据非常丰富的对象,从而进行传输;
当我们只需要进行一个动作时,直接使用Runnable,在run方法中实现动作内容即可。当然我们也可以通过Message.obtain(Handler h, Runnable callback)来传入callback接口,但这样看起来就没有post(Ruannable callback)那么直观。

API

API是我们学习最好的文档

构造函数

  • Handler()
  • Handler(Handler.Callback callback):传入一个实现的Handler.Callback接口,接口只需要实现handleMessage方法。
  • Handler(Looper looper):将Handler关联到任意一个线程的Looper,在实现子线程之间通信可以用到。
  • Handler(Looper looper, Handler.Callback callback)

主要方法

  • void dispatchMessage (Message msg)
    一般情况下不会使用,因为它的底层实现其实是作为处理系统消息的一个方法,如果真要用,效果和sendMessage(Message m)效果一样。

    public void dispatchMessage(Message msg) {
         
         
        if (msg.callback != null) {
         
         
            // 如果有Runnbale,则直接执行它的run方法
            handleCallback(msg);
        } else {
         
         
            //如果有实现自己的callback接口
            if (mCallback != null) {
         
         
                //执行callback的handleMessage方法
                if (mCallback.handleMessage(msg)) {
         
         
                    return;
                }
            }
            //否则执行自身的handleMessage方法
            handleMessage(msg);
        }
    }
    private static void handleCallback(Message message) {
         
         
        message.callback.run();
    }
    
  • void dump (Printer pw, String prefix)
    主要Debug时使用的一个方法,dump函数只是使用了Printer对象进行了打印,打印出Handler以及Looper和Queue中的一些信息,源码如下:

    public final void dump(Printer pw, String prefix) {
         
         
        pw.println(prefix + this + " @ " + SystemClock.uptimeMillis());
        // 如果Looper为空,输出Looper没有初始化
        if (mLooper == null) {
         
         
            pw.println(prefix + "looper uninitialized");
        } else {
         
         
            // 否则调用Looper的dump方法,Looper的dump方法也是
            mLooper.dump(pw, prefix + "  ");
        }
    }
    

    通过测试用例大家会了解得更清晰:

          //测试代码
          Printer pw = new LogPrinter(Log.ERROR, "MyTag");
          mHandler.dump(pw, "prefix");
    

    结果:

  • Looper getLooper ()
    拿到Handler相关联的Looper

  • String getMessageName (Message message)
    获取Message的名字,默认名字为message.what的值。

  • void handleMessage (Message msg)
    处理消息。

  • boolean hasMessages (int what)
    判断是否有Message的what值为参数what。

  • boolean hasMessages (int what, Object object)
    判断是否有Message的what值为参数what,obj值为参数object。

  • Message obtainMessage (int what, Object obj)
    从消息池中拿到一个消息并赋值what和obj,其他重载函数同理。

  • boolean post (Runnable r)
    将Runnable对象加入MessageQueue。

  • boolean postAtFrontOfQueue (Runnable r)
    将Runnbale加入到消息队列的队首。但是官方不推荐这么做,因为很容易打乱队列顺序。

  • boolean postAtTime (Runnable r, Object token, long uptimeMillis)
    在某个时间点执行Runnable r。

  • boolean postDelayed (Runnable r, long delayMillis)
    当前时间延迟delayMillis个毫秒后执行Runnable r。

  • void removeCallbacks (Runnable r, Object token)
    移除MessageQueue中的所有Runnable对象。

  • void removeCallbacksAndMessages (Object token)
    移除MessageQueue中的所有Runnable和Message对象。

  • void removeMessages (int what)
    移除所有what值的Message对象。

  • boolean sendEmptyMessage (int what)
    直接拿到一个空的消息,并赋值what,然后发送到MessageQueue。

  • boolean sendMessageDelayed (Message msg, long delayMillis)
    在延迟delayMillis毫秒之后发送一个Message到MessageQueue。

Handler内存泄漏问题

在上面的例子中,为了展示方便,我都没有考虑内存泄漏的情况,但是在实际开发中,如果不考虑代码的安全性的话,尤其当一个项目到达了一定的规模之后,那么对于代码的维护和系统的调试都是非常困难的。而Handler的内存泄漏在Android中也是一个非常经典的案例。

典型错误的使用示例

public class LeakActivity extends AppCompatActivity {
   
   
    private int a = 10;
    //new Handler匿名内部类,会引用外部
    private Handler mHandler = new Handler() {
   
   
        @Override
        public void handleMessage(Message msg) {
   
   
            switch (msg.what) {
   
   
            case 0:
                a = 20;
                break;
            }
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        mHandler.sendEmptyMessageDelayed(0, 5000);
        //new Runnable也是匿名内部类,也会引用外部
        mHandler.postDelayed(new Runnable() {
   
   
            @Override
            public void run() {
   
   
                ...
            }
        }, 1000000);
    }
}

分析问题:

  1. 外部类Activity中定义了一个非静态内部类Handler非静态内部类默认持有对外部类的引用
    如果外部Activity突然关闭了,MessageQueue中的消息还没处理完,那么Handler就会一直持有对外部Activty的引用,垃圾回收器无法回收Activity,从而导致内存泄漏
    Handler的生命周期有可能与Activity的生命周期不一致,比如栗子中的sendEmptyMessageDelayed,在5000毫秒之后才发送消息,但是很有可能这时候Activity被返回了,这样会造成Handler比Activity还要长寿,这样会导致Activity发生暂时性的内存泄漏
  2. 如上代码,在postDelayed中,我们在参数中传入一个非静态内部类Runnable,这同样会造成内存泄漏
    假如此时关闭了Activity,那么垃圾回收器在接下来的1000000ms内都无法回收Activity,造成内存泄漏。

问题一:

为了解决这个问题,我们可以把Handler改为static的, 改成静态内部类后,对外部类的引用设为弱引用,因为在垃圾回收时,会自动将弱引用的对象回收。
但是这样会造成Handler无法访问Activity的非静态变量a。

private static Handler mHandler = new Handler() {
   
   
    @Override
    public void handleMessage(Message msg) {
   
   
        switch (msg.what) {
   
   
        case 0:
            //a = 20;   //不能访问得到
            break;
        }
    }
};

问题二:

通过把Activity作为Handler成员变量,在Handler构造的时候传进来即可。这时候我们不能使用匿名内部类了,要把Handler单独抽取成一个类,这样就可以访问Activity的非静态变量了。但是我们的问题又回来了,这时候Handler持有了Activity的强引用了,这样不就是回到我们的原点了吗?(内存泄漏问题依然没有解决

private static class MyHandler extends Handler {
   
   
    private LeakActivity mActivity;//外部类的强引用
    public MyHandler(LeakActivity activity) {
   
   
        mActivity = activity;
    }
    @Override
    public void handleMessage(Message msg) {
   
   
        super.handleMessage(msg);
        mActivity.a = 20;
    }
}

解决方案:

Activity通过弱引用来作为成员变量。虽然我们把Activity作为弱引用,但是Activity不一定就是会在GC的时候被回收,因为可能还有其他对象引用了Activity。在处理消息的时候就要注意了,当Activity回收或者正在finish的时候,就不能继续处理消息了。

public class LeakActivity extends AppCompatActivity {
   
   
    private int a = 10;
    private final MyHandler mHandler = new MyHandler(this);
    private static final Runnable mRunnable = new Runnable() {
   
   
        @Override
        public void run() {
   
   
            // 操作
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
   
   
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fourth);
        mHandler.postDelayed(mRunnable, 1000*10);
        finish();   
    }

    private static class MyHandler extends Handler {
   
   
        WeakReference<HandlerActivity> mWeakActivity;
        public MyHandler(HandlerActivity activity) {
   
   
            this.mWeakActivity = new WeakReference<HandlerActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
   
   
            super.handleMessage(msg);
	        //当使用弱引用的时候,会回收Activity吗?
	        //虽然用的是弱引用,但是并不代表不存在其他的对象没有引用Activity,因此不一定会被回收
	        LeakActivity activity = mActivityRef.get();//获取Activity
	        if (activity == null || activity.isFinishing()) {
   
   //当Activity回收或者正在finish的时候,不能继续处理消息
	            return;
	        }
	        mActivityRef.get().a = 20;
        }
    }
}

HandlerThread

思考一下,假如我们需要同时下载A和B,下载A需要6s,下载B需要5s,在它们下载完成后Toast信息出来即可,此时HandlerThread便是一种解决方式之一。那么HandlerThread到底是什么?

  • HandlerThread就是一种线程。
  • HandlerThread和普通的Thread之间的区别就是HandlerThread在创建的时候会提供自己该线程的Looper对象

我们在Actvity创建时系统会自动帮我们初始化好主线程的Looper,然后这个Looper就会管理主线程的消息队列。但是在我们创建子线程时,系统并不会帮我们创建子线程的Looper,需要我们自己手动创建,如下:

    new Thread(){
   
   
        @Override
        public void run() {
   
   
            super.run();
            Looper.prepare();
            Handler mHandler = new Handler(Looper.myLooper());
            Looper.loop();
        }
    }.start();

所以HandlerThread就在内部帮我们封装了Looper的创建过程,从源码可以看到,HandlerThread集成于Thread,然后覆写run方法,进行Looper的创建,从而通过getLooper方法暴露出该线程的Looper对象

/* HandlerThread 源码 */
public class HandlerThread extends Thread {
   
   
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    ...
    public HandlerThread(String name) {
   
   
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;//默认优先级
    }
    public HandlerThread(String name, int priority) {
   
   
        super(name);
        mPriority = priority;
    }
    @Override
    public void run() {
   
   
        mTid = Process.myTid(); //获取线程的tid
        Looper
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值