Handler、HandlerThread理解

Handler、HandlerThread理解Handler在android线程编程中非常常见。线程中的handler使用原理:每个线程只有一个Looper来管理消息队列,handler在使用的时候需要绑定到对应的Looper上。Handler给自己绑定的Looper不断的发送消息,Looper来做死循环来不断读取MessageQueue队列中的消息,发送给handler来进行处理。 Android的UI是运行在主线程中,主线程是用MainL…

大家好,又见面了,我是你们的朋友全栈君。

Handler、HandlerThread理解

Handler在android线程编程中非常常见。


线程中的handler使用原理:

每个线程只有一个Looper来管理消息队列,handler在使用的时候需要绑定到对应的Looper上。Handler给自己绑定的Looper不断的发送消息,Looper来做死循环来不断读取MessageQueue队列中的消息,发送给handler来进行处理。

 

Android的UI是运行在主线程中,主线程是用MainLooper来管理,循环读取MessageQueue队列中消息的,如果创建Handler对象new Handler( )时构造的时候参数没有指定绑定的Looper,默认是和主线程的Looper绑定在一起。Handler发送和处理消息是默认在主线程中进行的。

 

应用中和UI是在主线程中进行绘制的,为了保证用户和UI交互的流畅,软件中常常耗时的动作,如网络的操作、IO的读取、数据的处理等单独的放在子线程中去处理。比如通过异步的获取数据,获取完成后通过使用主线程的handler来发送msg给主线程的MainLooper队列来通知主线程再进行UI刷新。

 

Android原生的HandlerThread为我们提供了一种思路。


HandlerThread实质是Android封装的一个Thread。

1、new一个HandlerThread对象,实质是创建线程,然后必须通过start()方法把此线程运行起来;

2、new 一个Handler对象,在构造Handler对象的时候,把此thread的Looper作为参数传递给我们构造的Handler对象;这样我们创建的Handler对象就和此thread线程的Looper绑定起来;

3、此时我们创建的Handler对象就可以给thread的MessageQueue队列发送消息和处理消息了,处理消息是运行在子线程中,可以做耗时的操作,不会阻塞UI线程。等在子线程做完耗时的动作获取完数据后就可以通过主线程的handler发消息给主线程来更新当前UI界面;

4、用完后,需要来停止此thread的Looper循环,防止内存泄露。

参考实例如下:
1、创建需要的变量

private BtSwitchHandler btSwitchHandler;
private HandlerThread checkBtSwitchThread;
private long checkBtSwitchDelayTime = 45 * 1000;
private static final int MSG_CHECK_BT_SWITCH = 0x20;

2、开始构造HandlerThread对象和构造Handler对象,并发送Message消息

private void checkBtSwitchIsOpenThread() {
    L.d(TAG, "checkBtSwitchIsOpenThread start");
    if (checkBtSwitchThread == null)
        checkBtSwitchThread = new HandlerThread("checkBtSwitch");
    checkBtSwitchThread.start();
    if (btSwitchHandler == null)
        btSwitchHandler = new BtSwitchHandler(checkBtSwitchThread.getLooper());
    btSwitchHandler.removeCallbacksAndMessages(null);
    btSwitchHandler.sendEmptyMessageDelayed(MSG_CHECK_BT_SWITCH, checkBtSwitchDelayTime);
}

3、创建内部类Handler类处理消息,运行在子线程

private class BtSwitchHandler extends Handler {
    public BtSwitchHandler(Looper looper) {
        super(looper);
    }
    @Override
    public void handleMessage(Message msg) {
        if (msg.what == MSG_CHECK_BT_SWITCH)
        {
            if (mBluetooth.isEnabled())
            {
                L.d(TAG, "bt open success");
            } else
            {
                L.d(TAG, "bt switch open 45s timeout, show failer view");
                onekeystate = ONEKEYSTATE.ERROR;
                Message errMsg = mHandler.obtainMessage(MSG_ERROR);
                mHandler.sendMessage(errMsg);
            }
            stopBtSwitchThread();
        }
        super.handleMessage(msg);
    }
}

4、注销HandlerThread,销毁loop循环

private void stopBtSwitchThread() {
    if (checkBtSwitchThread != null)
    {
        Looper btSwitchLooper = checkBtSwitchThread.getLooper();
        if (btSwitchLooper != null)
        {
            btSwitchLooper.quit();
        }
    }
    checkBtSwitchThread = null;
    btSwitchHandler = null;
}

2018年6月8日 补充如下:

最近在使用过程中遇到几个问题,觉得再进一步理解一下:

1、Handler.post(Runnable r)时,看大家都在讲run函数处理运行在UI主线程中。

(此种用法一般不常使用,一般适合于你不想利用handler发msg来处理更新UI时,可以这样简单的在主线程直接去更新一下UI,相当于将Runnable直接放到了主线程的Looper中进行直接处理)

参考blog:

点击打开链接: handler.post方法的终极最直观的理解与解释

点击打开链接: 从Handler.post(Runnable r)再一次梳理Android的消息机制(以及handler的内存泄露)

new Handler().post(new Runnable()
{
    @Override
    public void run()
    {
        Log.d("zws", "isMainThread:" + (Looper.myLooper() == Looper.getMainLooper())) ;
        ssid.setText("");
    }
});

但我在使用过程中发现有个handler对象的post运行run函数的时候看日志发现并没有运行在主线程,很基本原理相悖,比较诡异,查了好久并没有发现有在其它地方有重置此handler对象的looper。

而在我们创建handler对象的时候,必须指定handler绑定的线程的looper。

如果在主线程创建handler对象,没有指定主线程looper时,系统会帮我们默认指定,将主线程的looper绑定到handler对象;

如果在非主线程中创建handler对象,没有指定线程的looper时,直接会报错,程序会退出,报错没有looper,必须先执行looper.prepare( ),或者需要将线程的looper传给handler对象来进行创建。

请教别人后,说handler必须有对应线程的looper来和它绑定,线程中的looper会不断读取MessageQueue队列中的msg来处理,绑定looper后handler才能具有和收发能力,否则原理是讲不通的。

我发现的handler的post的run函数运行在子线程,确定肯定是此handler对象创建时它的looper参数在创建的时候被在子线程中设置了,looper参数传的子线程的looper才会出现这种现象。

 Log.d(“zws”, “new myloop ? ” + (Looper.myLooper().toString()));

Log.d(“zws”, “new isMainLoop ? ” + (Looper.myLooper() == Looper.getMainLooper()));

 Log.d(“zws”, “thread name? ” + Thread.currentThread().getName());

加打印后,可以直接将创建handler时的looper打印处理,发现是在子线程中的一处插件进行设置的。验证了此想法是正确的,所以post的run函数才会运行在子线程。否则,一般都运行在主线程。

2、thread和handler、HandlerThread

thread就是一般的线程,可以通过起一个线程来执行做事情,一般适合于不和UI相关的事情,耗时的事情。

new Thread(new Runnable() 
{
    @Override
    public void run() 
    {
        
    }
}).start();

handler用来线程间的通信,和更新UI相关

a、可以在子线程中来利用handler来给主线程发消息来通知主线程更新UI;

b、利用handler来在主线程延时执行一段程序。

public class TestHandler extends Handler
{
    private static TestHandler ourInstance = new TestHandler();
    private TestRunnable mTestRunnable = null;
    private static final long DELAY_TIME = 30 * 1000;

    public static TestHandler getInstance()
    {
        return ourInstance;
    }

    private TestHandler()
    {

    }

    public void startTest()
    {
        if (mTestRunnable != null)
        {
            stopTest();
        }
        mTestRunnable = new TestRunnable();
        this.postDelayed(mTestRunnable, DELAY_TIME);
    }

    public void stopTest()
    {
        if (mTestRunnable == null)
        {
            return;
        }
        this.removeCallbacks(mTestRunnable);
        mTestRunnable = null;
    }

    private class TestRunnable implements Runnable
    {

        @Override
        public void run()
        {
            Log.d("zws", "TestRunnale ....");
        }
    }
}

利用handler延时开启和关闭线程,只需要做下面操作:
TestHandler.getInstance().startTest();  开启线程
TestHandler.getInstance().stopTest();  关闭线程

参考blog:

点击打开链接: android利用Handler开启线程和关闭线程

HandlerThread:那么现在我们要是想子线程与子线程之间的通信要怎么做呢?当然说到底也是用Handler+Thread来完成(不推荐,需要自己操作Looper),Google官方很贴心的帮我们封装好了一个类,那就是刚才说到的:HandlerThread

参考blog,讲的非常清楚:

点击打开链接: Thread、Handler和HandlerThread关系何在?


有些理解不太到位,后续继续补充。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/159170.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 固态硬盘数据如何恢复「建议收藏」

    固态硬盘数据如何恢复「建议收藏」虽说很多朋友都曾使用过硬盘,但是对于硬盘的一些定义,你们却不一定真正的了解,例如固态硬盘是什么?这个概念,你能说的出来吗?我想未必吧!好吧,在说咱们的正题之前,咱们先就固态硬盘这个概念解释一下。  所谓固态硬盘,其实就是一种硬盘的类别,它在使用方法和性能上与一般的硬盘完全一样,龙腾recuva数据恢复软件但是不得不说,这种固态硬盘的稳定性更好,就这一点也让固态硬盘的档次也上升了好几倍,因此,这

  • 解决idea一直处于Process Running问题

    解决idea一直处于Process Running问题

  • vs2010中出现lnk2019和lnk1120错误

    vs2010中出现lnk2019和lnk1120错误非常令人难受的是,今天在配置Cocos2d-x的时候,配置完突然出现这个问题,我以为VS2010给我弄崩了,吓得我瞬间慌了不过研究了好几篇文章才发现这个一点关系都没有。 下面说一下到底出了什么问题:创建项目的时候选错了!!!!创建项目的时候选错了!!!!创建项目的时候选错了!!!! 不要怀疑,真的很有可能是项目选错了!我看了别人发了一堆什么长篇大论,作为小白…

  • .NETFramework类库命名空间 – asp.net 教程-「建议收藏」

    .NETFramework类库命名空间 – asp.net 教程-「建议收藏」.NETFramework类库

    .NETFramework类库是一个由Microsoft.NETFrameworkSDK中包含的类、接口和值类型组成的库。该库提供对系统功能的访问,是建立.NETFramework应用程序、组件和控件的基础。 命名空间
    .NETFramework类库提供下列命名空间:Microsoft.Aspnet.Snapin
    包含使Microsoft®InternetInformationServer管理

  • JMeter下载安装及入门教程

    JMeter下载安装及入门教程目录JMeter介绍准备工作JMeter下载及环境配置下载环境配置JMeter中遇到的乱码问题JMeter介绍(参考:http://www.importnew.com/13876.html)JMeter使用了不同技术和协议,是一款可以进行配置和执行负载测试、性能测试和压力测试的工具。负载测试、性能测试和压力测试概念: 负载测试:这类测试使系统或者应用程序在…

  • 快速制作机房3D效果图教程「建议收藏」

    快速制作机房3D效果图教程「建议收藏」作者:广州麦景科技有限公司林鲁刚 原文接随着信息网络技术的不断发展,大量数据中心的建设,机房监控软件已经成为了机房管理者重要的管理工具,机房监控软件也从无到有,从2D到3D,从静态到三维动态的改进。不多说,直接上图↓以前是这样的现在是这样的或者这样的(麦景数据中心可视化管理平台)现在教大家如何画好一张机房效果图,所用软件有↓前期准备资料

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号