Android Handler 的基本使用_android handler weakreference-程序员宅基地

技术标签: Handler  QtAndroid  Android  

1.前言

https://developer.android.google.cn/reference/android/os/Handler.html

Handler 是 Android 中线程通信的常用方式,文档如是说:

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread.

Handler 允许你发送和处理与线程的 MessageQueue 关联的 Message 和 Runnable 对象。 每个 Handler 实例都与一个线程和该线程的消息队列 (MessageQueue) 相关联。 当你创建一个新的 Handler 时,它会绑定到 Looper。 它将向 Looper 的消息队列传递消息 (Message) 和可运行对象 (Runnable),并在 Looper 的线程上执行它们。

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

Handler 有两个主要用途: (1) 安排消息和可运行对象在未来某个时刻执行; (2) 将要在与你自己的线程不同的线程上执行的操作排队。

Scheduling messages is accomplished with the post(Runnable), postAtTime(java.lang.Runnable, long), postDelayed(Runnable, Object, long), sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long), and sendMessageDelayed(Message, long) methods. The post versions allow you to enqueue Runnable objects to be called by the message queue when they are received; the sendMessage versions allow you to enqueue a Message object containing a bundle of data that will be processed by the Handler's handleMessage(Message) method (requiring that you implement a subclass of Handler).

消息的调度是通过 post(Runnable), postAtTime(java.lang.Runnable, long), postDelayed(Runnable, Object, long), sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long), and sendMessageDelayed(Message, long) 等方法来完成的。post 系列的方法允许你将 Runnable 对象放入队列,当消息队列处理该消息时 Runnable 被调用;sendMessage 系列的方法允许你将可包含数据的 Message 对象放入队列,消息会被传递到 Handler 的 handleMessage(Message) 接口进行处理,你需要实现该接口。

When posting or sending to a Handler, you can either allow the item to be processed as soon as the message queue is ready to do so, or specify a delay before it gets processed or absolute time for it to be processed. The latter two allow you to implement timeouts, ticks, and other timing-based behavior.

当 post 或 sendMessage 到 Handler 时,可以在消息队列准备好后立即处理该消息,也可以指定延时时间 (Delayed) 或者指定时间点 (AtTime) 处理该消息。 后两者可以让你实现超时、定时和其他基于时间的行为。

When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing the top-level application objects (activities, broadcast receivers, etc) and any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler. This is done by calling the same post or sendMessage methods as before, but from your new thread. The given Runnable or Message will then be scheduled in the Handler's message queue and processed when appropriate.

当为应用程序创建进程时,其主线程专用于运行一个消息队列,该消息队列负责管理顶层应用程序对象(Activity、BroadcastReceiver 等)及其创建的任何窗口。你可以创建自己的线程,并通过 Handler 与主应用程序线程进行通信。这只需要在新线程中调用 post 或 sendMessage 方法。 然后,给定的 Runnable 或 Message 将在 Handler 的消息队列中进行调度,并在适当的时候进行处理。

2.基本使用

2.1.基本流程

post 和 sendMessage 的基本使用,子线程向主线程发消息:

    // 创建 Handler 时关联一个 Looper 消息循环
    Handler handler = new Handler(Looper.getMainLooper()) {
        // 在 Looper 对应的线程处理该消息
        @Override
        public void handleMessage(Message msg) {
            // 根据 Message what 区分不同的消息
            switch (msg.what) {
                case 0: {
                    // 从 Message 中获取传递的参数数据进行处理
                    // do some ... ...
                }
                break;
            }
        }
    };

    void doing() {
        // 在线程中执行任务
        new Thread(new Runnable() {
            @Override
            public void run() {
                // do some ... ...
                // 任务完成 send 消息给 Handler 处理
                handler.sendEmptyMessage(0);
                // 或者 post 一个回调到 Handler 线程执行
                handler.post(new Runnable() {
                    // Run 会在 Handler Looper 线程执行
                    @Override
                    public void run() {
                        // do some ... ...
                    }
                });
            }
        }).start();
    }

2.2.Message 对象

创建 Message 的几种方式:

Message m1 = handler.obtainMessage(); //通过 Handler 实例获取
Message m2 = Message.obtain(); //通过 Message 获取
Message m3 = new Message(); //创建新的 Message 实例

其中,Handler 的 obtainMessage() 方法也是调用了 Message 的 obtain() 方法:

    public final Message obtainMessage() {
        return Message.obtain(this); // this Handler 作为 target 参数
    }

    public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;
        return m;
    }

obtain()从消息池拿一个 Message 对象,不足时 new 一个新的 Message 返回:

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

Message 除了 what 属性可以用来区分消息类型,还可以携带参数:

Message msg = new Message();
// what:int 消息类型
msg.what = 1;
// arg1:int 和 arg2:int 两个 int 作为便捷参数,替代 obj
msg.arg1 = 0;
msg.arg2 = 0;
// obj:Object 存储对象
msg.obj = "Message";
// 存储复杂数据
// setData 设置内部 data,读取用 getData
Bundle bd = new Bundle();
bd.putString("name", "Bundle");
bd.putInt("code", 123);
msg.setData(bd);

handler.sendMessage(msg);

2.3.向子线程发消息

Handler 需要关联一个 Looper 对象,主线程直接用 Looper.getMainLooper() 获取,子线程需要用 Looper.prepare() 创建 Looper,再用 Looper.loop() 开启消息循环。

    Handler handler;

    void test() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e(LogTag, "Looper in");
                // 准备 Looper,不存在则创建
                Looper.prepare();
                // 获取当前线程 Looper
                handler = new Handler(Looper.myLooper()) {
                    @Override
                    public void handleMessage(Message msg) {
                        // do some ... ...
                        Log.e(LogTag, "handleMessage " + msg.what);
                        // 退出消息循环
                        Looper.myLooper().quit();
                    }
                };
                // 开启消息循环
                Looper.loop();
                Log.e(LogTag, "Looper exit");
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000); // 延时,等 handler 初始化完
                    // 发送消息
                    Log.e(LogTag, "sendEmptyMessage");
                    handler.sendEmptyMessage(123);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }

也可以用封装了 Looper 操作的 HandlerThread:

    Handler handler;
    HandlerThread thread;

    void test() {
        thread = new HandlerThread("test thread");
        thread.start();
        handler = new Handler(thread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                // do some ... ...
                Log.e(LogTag, "handleMessage " + msg.what);
                // 退出消息循环
                thread.quit();
                // 或者 thread.quitSafely();
            }
        };
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000); // 延时,等 handler 初始化完
                    // 发送消息
                    Log.e(LogTag, "sendEmptyMessage");
                    handler.sendEmptyMessage(123);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }

3.内存泄漏 

界面 Activity finish 的时候,Handler 可能还被 MessageQueue 消息队列引用着。而前文 Handler 和 post 都用到了匿名内部类的写法:new Type(){},非静态的内部类和匿名内部类都会隐式持有外部类 Activity 的引用。Activity finish 不能立即回收,还要等 Handler 处理结束才能被回收,就造成了内存泄漏。

可以将 Handler 定义成静态内部类,静态内部类是不持有外部类的实例的。同时,为了能调用外部的实例方法,需要持有一个外部的弱引用。

public class MainActivity extends AppCompatActivity {

    private static class MyHandler extends Handler {

        // 弱引用持有 Activity, GC 回收时会被回收掉
        private WeakReference<MainActivity> weakReference;

        public MyHandler(MainActivity activity) {
            super(Looper.getMainLooper());
            this.weakReference = new WeakReference(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = weakReference.get();
            super.handleMessage(msg);
            if (null == activity) {
                return;
            }
            // do some ... ...
        }
    }

    private MyHandler handler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // do some ... ...

        handler = new MyHandler(this);
        new Thread(new Runnable() {
            @Override
            public void run() {
                handler.sendEmptyMessage(123);
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        // 移除所有回调及消息
        handler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }
}

4.参考

安卓文档:Handler  |  Android Developers (google.cn)

参考博客:Android——Handler详解_android handler_Yawn__的博客-程序员宅基地

参考博客:Android - Handler_android handler looper_Jomurphys的博客-程序员宅基地

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/gongjianbo1992/article/details/132100484

智能推荐

python中文显示不出来_解决Python词云库wordcloud不显示中文的问题-程序员宅基地

文章浏览阅读2.6k次。解决Python词云库wordcloud不显示中文的问题2018-11-25背景:wordcloud是基于Python开发的词云生成库,功能强大使用简单。github地址:https://github.com/amueller/word_cloudwordcloud默认是不支持显示中文的,中文会被显示成方框。安装:安装命令:pip install wordcloud解决:经过测试发现不支持显示中文..._词云python代码无法输出文字

台式计算机cpu允许温度,玩游戏cpu温度多少正常(台式电脑夏季CPU一般温度多少)...-程序员宅基地

文章浏览阅读1.1w次。随着炎热夏季的到来,当玩游戏正爽的时候,电脑突然死机了,自动关机了,是不是有想给主机一脚的冲动呢?这个很大的原因是因为CPU温度过高导致的。很多新手玩家可能都有一个疑虑,cpu温度多少以下正常?有些说是60,有些说是70,到底多高CPU温度不会死机呢?首先我们先看看如何查看CPU的温度。下载鲁大师并安装,运行鲁大师软件,即可进入软件界面,并点击温度管理,即可看到电脑各个硬件的温度。鲁大师一般情况下..._台式机玩游戏温度多少正常

小白自学Python日记 Day2-打印打印打印!_puthon打印任务收获-程序员宅基地

文章浏览阅读243次。Day2-打印打印打印!我终于更新了!(哭腔)一、 最简单的打印最最简单的打印语句: print(“打印内容”)注意:python是全英的,符号记得是半角下面是我写的例子:然后进入power shell ,注意:你需要使用cd来进入你保存的例子的文件夹,保存时名字应该取为xxx.py我终于知道为什么文件夹取名都建议取英文了,因为进入的时候是真的很麻烦!如果你没有进入正确的文件夹..._puthon打印任务收获

Docker安装:Errors during downloading metadata for repository ‘appstream‘:_"cenerrors during download metadata for repository-程序员宅基地

文章浏览阅读1k次。centos8问题参考CentOS 8 EOL如何切换源? - 云服务器 ECS - 阿里云_"cenerrors during download metadata for repository \"appstream"

尚硅谷_谷粒学苑-微服务+全栈在线教育实战项目之旅_基于微服务的在线教育平台尚硅谷-程序员宅基地

文章浏览阅读2.7k次,点赞3次,收藏11次。SpringBoot+Maven+MabatisPlusmaven在新建springboot项目引入RELEASE版本出错maven在新建springboot项目引入RELEASE版本出错maven详解maven就是通过pom.xml中的配置,就能够从仓库获取到想要的jar包。仓库分为:本地仓库、第三方仓库(私服)、中央仓库springframework.boot:spring-boot-starter-parent:2.2.1.RELEASE’ not found若出现jar包下载不了只有两_基于微服务的在线教育平台尚硅谷

随便推点

网络学习第六天(路由器、VLAN)_路由和vlan-程序员宅基地

文章浏览阅读316次。路由的概念路由器它称之为网关设备。路由器就是用于连接不同网络的设备路由器是位于OSI模型的第三层。路由器通过路由决定数据的转发。网关的背景:当时每家计算机厂商,用于交换数据的通信程序(协议)和数据描述格式各不相同。因此,就把用于相互转换这些协议和格式的计算机称为网关。路由器与三层交换器的对比路由协议对比路由器的作用:1.路由寻址2.实现不同网络之间相连的功能3.通过路由决定数据的转发,转发策略称为 路由选择。VLAN相关技术什么是VLAN?中文名称叫:虚拟局域网。虚_路由和vlan

设置div背景颜色透明度,内部元素不透明_div设置透明度,里面的内容不透明-程序员宅基地

文章浏览阅读2.8w次,点赞6次,收藏22次。设置div背景颜色透明度,内部元素不透明:.demo{  background-color:rgba(255,255,255,0.15) } 错误方式:.demo{ background-color:#5CACEE;opacity:0.75;} 这样会导致div里面的元素内容和背景颜色一起变透明只针对谷歌浏览器的测试_div设置透明度,里面的内容不透明

Discuz!代码大全-程序员宅基地

文章浏览阅读563次。1.[ u]文字:在文字的位置可以任意加入您需要的字符,显示为下划线效果。2.[ align=center]文字:在文字的位置可以任意加入您需要的字符,center位置center表示居中,left表示居左,right表示居右。5.[ color=red]文字:输入您的颜色代码,在标签的中间插入文字可以实现文字颜色改变。6.[ SIZE=数字]文字:输入您的字体大小,在标签的中间插入文..._discuzcode 大全

iOS NSTimer定时器-程序员宅基地

文章浏览阅读2.6k次。iOS中定时器有三种,分别是NSTimer、CADisplayLink、dispatch_source,下面就分别对这三种计时器进行说明。一、NSTimerNSTimer这种定时器用的比较多,但是特别需要注意释放问题,如果处理不好很容易引起循环引用问题,造成内存泄漏。1.1 NSTimer的创建NSTimer有两种创建方法。方法一:这种方法虽然创建了NSTimer,但是定时器却没有起作用。这种方式创建的NSTimer,需要加入到NSRunLoop中,有NSRunLoop的驱动才会让定时器跑起来。_ios nstimer

Linux常用命令_ls-lmore-程序员宅基地

文章浏览阅读4.8k次,点赞17次,收藏51次。Linux的命令有几百个,对程序员来说,常用的并不多,考虑各位是初学者,先学习本章节前15个命令就可以了,其它的命令以后用到的时候再学习。1、开机 物理机服务器,按下电源开关,就像windows开机一样。 在VMware中点击“开启此虚拟机”。2、登录 启动完成后,输入用户名和密码,一般情况下,不要用root用户..._ls-lmore

MySQL基础命令_mysql -u user-程序员宅基地

文章浏览阅读4.1k次。1.登录MYSQL系统命令打开DOS命令框shengfen,以管理员的身份运行命令1:mysql -u usernae -p password命令2:mysql -u username -p password -h 需要连接的mysql主机名(localhost本地主机名)或是mysql的ip地址(默认为:127.0.0.1)-P 端口号(默认:3306端口)使用其中任意一个就OK,输入命令后DOS命令框得到mysql>就说明已经进入了mysql系统2. 查看mysql当中的._mysql -u user

推荐文章

热门文章

相关标签