android开发,使用kotlin学习消息机制Handler

简介: android开发,使用kotlin学习消息机制Handler



Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程。

1.消息机制原理的解释:

在主线程里创建一个Handler,然后在分线程中引用这个Handler来发送Message对象给MessageQueue,循环器Looper从MessageQueue里面取出一个需要处理的Message,交给Handler处理,一般是进行UI处理。处理完之后,Message就没有太大的用处,Looper清理Message,让Message回到默认状态。

2.Android的消息机制概述

1.Handler的背景(三个常见问题)

(1)Android为什么要提供Handler?

这是因为Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,那么程序就会抛出异常,但是Android又建议不要在主线程中进行耗时操作,否则会导致线程无法响应即ANR,比如我们需要从服务器拉取一些信息并将其显示在UI上,这个时候必须在子线程中进行拉取工作,拉取完毕后,不能在子线程上直接访问UI,这时候通过Handler就可以将访问UI的操作切换到主线程去执行。

(2)系统为什么不允许在子线程中访问UI呢?

这个因为Android的UI控件并不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。

(3)为什么系统不对UI控件的访问加上锁机制呢?

缺点有两个:

  • 加上锁机制会让UI访问的逻辑变得复杂
  • 锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。

鉴于这两个缺点,最简单且高效的方法就是采用单线程模型来处理UI操作。

Handler的工作原理的解释:

子线程默认没有Looper的,如果需要使用Handler,那么在Handler创建时就需要为线程创建Looper,利用Looper来构建内部的消息循环系统,如果当前线程没有Looper,那么就会报错。Handler创建完毕之后,Handler通过post方法把一个Runnable传到Looper中去处理,或者通过send方法发送消息,Looper会调用MessageQueue的enqueueMessage方法将消息发入消息队列中,然后Looper不断循环发现需要处理的消息之后,就会调用消息中的Runnable或者或者Handler的handleMessage方法,这样一来,Handler中的业务逻辑就被切除到创建Handler所在的线程中去执行。

3.消息机制的分析

1.了解Message

可理解为线程间通讯的数据单元,可通过message携带需要的数据

  创建Message的方法

val message:Message= Message()
val message1:Message=Message.obtain()//它利用了Message中消息池(sPool)

  封装数据:

int what:标识

int arg1:保存int数据

int arg2:保存int数据

Object obj:保存任意时刻的数据

Long when :记录应该被处理的时间值,若为即时消息,时间值=发送时间,若为延时消息,时间值=发送时间+延迟时间

Handler target:用来处理消息的Handler对象,就是发送消息的Handler

Runnable callback:用来处理消息的回调器

Message next:指向下一个Message用来形成一个链表

Message sPool:用来缓存处理过的Message,以便复用

2.了解Handler

Handler是Message的处理器,同时也负责消息的发送和移除的工作

(1)Handler的构造方法

Android API 30以上,使用Handler()方法时会显示删除线,并提示相关的方法已经被弃用,不建议使用。如图所示:

但是安卓不是弃用Handler这个类,而只是弃用Handler的两个构造方法:

Handler()
Handler(callback:Handler.Callback)

安卓建议采用如下方法来解决

1.使用Executor

2.明确指定Looper

3.使用Looper.getMainLooper()定位并使用主线程的Looper

4.如果又想在其他线程,又想要不出bug,请使用Handler(looper:Looper)或者Handler(Looper.myLooper())这两个构造方法。

(2) Handler导致的内存泄漏问题

在Android中最常用的一种内存泄漏是Handler导致的泄漏,原因:

(1)在Activity被摧毁时,延迟消息还没发出,Handler可能有未执行完或者正在执行的Message,MessagesQueue就会持有这个消息的引用,导致Handler持有Activity的引用,进而导致GC无法回收Activity。

(2)Handler中有还没执行完的Message,还在运行,而运行中的子线程不会被回收,所以就导致了内存泄漏。

解决方法:

1.在Activity的onDestroy()方法中,清空Handler中的未执行或者正在执行的Message和Callbacks

override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacksAndMessages(null)
    }

2.static+弱引用

class  MyHandler(activity: HandlerActivity):Handler(Looper.getMainLooper()){
        private val myWeakReference:WeakReference<HandlerActivity> = WeakReference(activity)
        override fun handleMessage(msg: Message) {//处理消息的回调方法
            myWeakReference.get()?.run {
            ...
        }
      }
 }

(3)Handler的常用方法:

  1. 发送即时消息:sendMessage(msg  Message)
  2. 发送延时消息:sendMessageDelayed(msg Message,delayMillis Long)
  3. 处理方法:handleMessage(msg Message)(回调方法)
  4. 移除还未处理的消息:removeMessages(what int)

部分源码:

由下面的源码可以看出,这些方法的本质是调用了queue.enqueueMessage(msg,uptimeMillis)方法。

sendMessage(Message msg)
    ->sendMessageDelayed(msg,0)
sendEmptyMessage(int what)
    ->sendEmptyMessageDelayed(what,0)
sendEmptyMessageDelayed(what,0)//发送不带数据的消息
     ->sendMessageDelayed(msg,delayMillis)
sendMessageDelayed(Message msg,long delayMillis)
     ->sendMessageAtTime(msg,SystemClock.uptimeMillis()+delayMillis)
sendMessageAtTime(Message msg,long uptimeMillis)
     ->enaueueMessage(queue,msg,uptimeMillis)//将消息添加到消息队列中
enaueueMessage(MessageQueue queue,Message msg,long uptimeMillis)
     ->queue.enqueueMessage(msg,uptimeMillis)//调用消息队列保存消息对象
removeMessage(int what)//移除消息
     ->mQueue.removeMessage(this,what,null)//调用消息队列移除它内部的指定what消息
handleMessage(Message msg)//处理消息的回调方法

3.消息队列的工作原理

  • MessageQueue消息队列,负责入队和出队,储存Handler发送的消息,它是一个按Message的when的排序的优先队列。
  • 虽然MessageQueue叫消息队列,但是它内部是用链表来实现的。

MessageQueue主要包含两个操作:插入和读取,读取操作本身会伴随删除操作,插入和读取对应的方法分别为enqueueMessage和next:

boolean enqueueMessage(Message msg, long when):往消息队列中插入一条消息
Message next() :消息队列中取出一条消息并将其从消息队列中移除

enqueueMessage和 next方法的部分源码:

enqueueMessage的主要操作就是单链表的插入操作

boolean enqueueMessage(Message msg, long when) {//将Messages插入消息队列
    ...
   msg.when = when;//指定消息应该被处理的时间
   ...
   for (;;) {//将当前消息对象保存到消息队列中的一个合适的位置
            prev = p;
            p = p.next;
            if (p == null || when < p.when) {
                break;
            }
            if (needWake && p.isAsynchronous()) {
                 needWake = false;
             }
    }//最终的结果是:消息队列是按when来排序的
    ...
    nativeWake(mPtr);//通过本地方法实现对处理等待状态的底层线程
    ...
}

next方法是一个无限循环的方法。如果消息队列中没有消息,那么next方法可以阻塞在这里,

当有新的消息到来时,next方法会返回这条消息并将其中单链表中移除。

Message next() {//取出一个合适的Message对象,可能不会立刻返回
    ...
    nativePollOnce(ptr, nextPollTimeoutMillis);//本地方法,会导致可能处理等待状态,但不会阻塞主线程
    ...
    Message msg = mMessages;//取出消息队列中的第一个消息
    ...
    return msg;//返回
    ...
}

4.Looper的工作原理

Looper在Android的消息机制中扮演着消息循环的角色。

Looper为一个线程开启一个消息循环,创建MessageQueue,负责循环取出Message Queue里面的当前需要处理的Message,也就是说,它会一直不停地从MessageQueue中查看是否会有新消息,如果有新消息就会交给对应的Handler进行处理,处理完后,将Message缓存到消息池中以备复用,否则就一直阻塞在那里,Looper退出后,Handler发送消息会失败,线程会立刻终止。

常用方法:

Looper.prepare():为当前线程创建一个Looper。

Looper.loop():开启消息循环.

Looper.getMainLooper():获取主线程的Looper。

Looper.quit()直接推迟Looper

Looper.quitSafely()设定一个退出标记,然后把消息队列中的已有消息处理完毕才安全地退出。

如何为一个线程创建Looper?

thread {
            Looper.prepare()
            val  handler:Handler=MyHandler(this)
            Looper.loop()
        }

loop方法的部分源码:

public static void loop() {
    final Looper me = myLooper();//得到looper对象
    ...
    for (;;) {//无限循环
         ...
         Message msg = me.mQueue.next(); // 从消息队列中取出消息
         ...
         msg.target.dispatchMessage(msg);//调用Handler去分发并处理消息
         ...
         msg.recycleUnchecked();//回收利用Message
         ...
    }
}

4.Handler使用(DEMO)

功能描述:

1.初始时

显示10,可以通过点击按钮改变其值

2.点击自动增加

每隔一秒上面的文本数值增加1,但最大显示20并作出提示

3.点击自动减少

每隔一秒上面的文本数值减少1,但是最小显示1并作出提示

4.点击暂停

上面的数值文本不再变化

效果图:

代码如下:

class HandlerActivity : AppCompatActivity() {
    lateinit var handler: Handler
    lateinit var number:TextView
    lateinit var increase:Button
    lateinit var decrease:Button
    lateinit var pause:Button
    //static+弱引用
    class  MyHandler(activity: HandlerActivity):Handler(Looper.getMainLooper()){
        private val myWeakReference:WeakReference<HandlerActivity> = WeakReference(activity)
        override fun handleMessage(msg: Message) {//处理消息的回调方法
            myWeakReference.get()?.run {
                var numberint:Int=Integer.parseInt(number.text.toString())
                when(msg.what){
                    1->{
                        if(numberint==20){
                            Toast.makeText(this,"已经达到最大值",Toast.LENGTH_SHORT).show()
                             return
                        }
                        numberint++
                        number.text= numberint.toString()
                        handler.sendEmptyMessageDelayed(1,1000)
                    }
                    2->{
                        if(numberint==1){
                            Toast.makeText(this,"已经达到最小值",Toast.LENGTH_SHORT).show()
                            return
                        }
                        numberint--
                        number.text=numberint.toString()
                        handler.sendEmptyMessageDelayed(2,1000)
                    }
                    else -> {}
                }
            }
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_handler)
        number=findViewById(R.id.number)
        increase=findViewById(R.id.increase)
        decrease=findViewById(R.id.decrease)
        pause=findViewById(R.id.pause)
        handler=MyHandler(this)
        increase.setOnClickListener {
            increase.isEnabled=false
            decrease.isEnabled=true
            pause.isEnabled=true
            handler.removeMessages(2)
            handler.sendEmptyMessage(1)
        }
        decrease.setOnClickListener {
            increase.isEnabled=true
            decrease.isEnabled=false
            pause.isEnabled=true
            handler.removeMessages(1)
            handler.sendEmptyMessage(2)
        }
        pause.setOnClickListener {
            increase.isEnabled=true
            decrease.isEnabled=true
            pause.isEnabled=false
            handler.removeMessages(1)
            handler.removeMessages(2)
        }
    }
    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacksAndMessages(null)
    }
}
目录
打赏
0
0
0
0
2
分享
相关文章
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
41 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
167 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
49 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
escrcpy:【技术党必看】Android开发,Escrcpy 让你无线投屏新体验!图形界面掌控 Android,30-120fps 超流畅!🔥
escrcpy 是一款基于 Scrcpy 的开源项目,使用 Electron 构建,提供图形化界面来显示和控制 Android 设备。它支持 USB 和 Wi-Fi 连接,帧率可达 30-120fps,延迟低至 35-70ms,启动迅速且画质清晰。escrcpy 拥有丰富的功能,包括自动化任务、多设备管理、反向网络共享、批量操作等,无需注册账号或广告干扰。适用于游戏直播、办公协作和教育演示等多种场景,是一款轻量级、高性能的 Android 控制工具。
Kotlin学习探索-1
前言: 今天是2018年10月24日,也就是1024节,在这里首先祝福大家节日快乐,祝福各位研发朋友开心快乐、身体健康 1024节日快乐 本篇文章是《Kotlin学习探索》系列的第一篇,本篇文章主要学习的知识点有: Kotlin在Android Studio上的环境搭建 Kotlin在Android Studio上的应用和可视化直观对比 Kotlin在Android Studio上的环境搭建: Android Studio 从 3.0(preview)新版本开始就已经内置安装了 Kotlin 插件。
1320 0
Android面试之5个Kotlin深度面试题:协程、密封类和高阶函数
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。文章详细解析了Kotlin中的协程、扩展函数、高阶函数、密封类及`inline`和`reified`关键字在Android开发中的应用,帮助读者更好地理解和使用这些特性。
86 1
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
196 1
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
**Kotlin中的`by lazy`和`lateinit`都是延迟初始化技术。`by lazy`用于只读属性,线程安全,首次访问时初始化;`lateinit`用于可变属性,需手动初始化,非线程安全。`by lazy`支持线程安全模式选择,而`lateinit`适用于构造函数后初始化。选择依赖于属性特性和使用场景。**
213 5
Android经典面试题之Kotlin延迟初始化的by lazy和lateinit有什么区别?
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
94 4
Android经典面试题之Kotlin中常见作用域函数
**Kotlin作用域函数概览**: `let`, `run`, `with`, `apply`, `also`. `let`安全调用并返回结果; `run`在上下文中执行代码并返回结果; `with`执行代码块,返回结果; `apply`配置对象后返回自身; `also`附加操作后返回自身
79 8

热门文章

最新文章

  • 1
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    16
  • 2
    Android历史版本与APK文件结构
    12
  • 3
    【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
    31
  • 4
    当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
    2
  • 5
    APP-国内主流安卓商店-应用市场-鸿蒙商店上架之必备前提·全国公安安全信息评估报告如何申请-需要安全评估报告的资料是哪些-优雅草卓伊凡全程操作
    12
  • 6
    【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    3
  • 7
    【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
    3
  • 8
    【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
    2
  • 9
    Cellebrite UFED 4PC 7.71 (Windows) - Android 和 iOS 移动设备取证软件
    9
  • 10
    【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
    1
  • AI助理

    你好,我是AI助理

    可以解答问题、推荐解决方案等