Android系统编程入门系列之服务Service齐头并进多线程任务
在上篇文章中初步了解了Android系统的四大组件之一的服务Service,在服务内可以执行无用户交互的耗时操作任务,但是包括之前关于界面系列文章在内,生命周期方法都是在主线程内被系统回调的。如果直接在生命周期方法中执行耗时操作,同样可能会在主线程5s内无响应而触发系统对应用程序的ANR异常。为了解决这个问题,就需要使用多线程开发来执行耗时任务,在任务执行结束后将结果返回到主线程中响应。
什么是线程呢?每个应用程序在初始化时,默认运行在以其包名命名的进程中,同一进程中的内存是可以共享使用的。而每个进程在创建时,都会随之创建一个java.lang.Thread实例的线程,用以执行Android系统对当前应用程序的生命周期方法回调,也就是通常意义上的UI界面绘制等任务,这也就是所谓的主线程。而每个进程在主线程中,可以继续创建多个线程,理论上只要硬件内存支持,线程是可以无限创建的。这些新创建的线程,被称为子线程。子线程中可以执行耗时任务,以此使得主线程中的界面任务保持与用户的及时响应。
值得注意的是,子线程中是不允许执行更新界面相关操作,必须切换回主线程绘制界面。
任务创建
新任务类需要实现java.lang.Runnable接口,在实现的run()方法中处理需要操作的任务。
任务执行
在开发中启动新任务的方式主要有三种,其一是直接创建最基础的线程类,单独管理并执行耗时任务;其二是创建一个由一堆线程组成的线程池,将耗时任务放进去执行,剩下的由线程池管理;其三则是使用成熟的并发库,根据不同的并发库创建及启动任务的方式也将不仅限于Runnable类型的实例。另外在Android R即API30以前,还可以使用android.os.AsyncTask创建异步任务,但是该方式在API30之后已废弃,故不推荐使用。下面将简单介绍以上三种主流方式。
单独线程
通常使用Thread(Runnable target)构造方法创建子线程,参数 target 作为要执行的任务对象。
之后在需要执行任务的位置调用子线程对象的start()方法启动运行该线程即可。
由于创建的线程是依托于某个界面Activity或服务Service的一个组件,所以当该组件的生命周期方法销毁后,其中创建的子线程也就销毁了。所以子线程必须要在被销毁之前调用interrupt()方法中断运行并释放其占用的资源,以防止发生内存泄漏等问题。
线程池
通过线程池管理类java.util.concurrent.Executors的newCachedThreadPool()等系列静态方法,可以直接创建java.util.concurrent.Executor接口定义的线程池类实例化对象。
之后在需要执行任务的位置调用线程池对象的execute(Runnable command)方法即可执行一次任务。
线程池类的优点在于当该组件的生命周期方法销毁后,该线程池及其中的线程都会被强制销毁,不需要手动管理。
并发库
关于Android系统的多线程开发,目前已有多个成熟的并发库可以直接使用,包括基于Java的RxJava、基于Kotlin的协程等,然而他们的底层原理都是与上述类似的。至于如何使用现有的多线程开发库,将在后续文章中详细介绍。
任务间通信(多线程通信)
由于不同任务是运行在不同线程中的,所以任务间通信实际上也是线程间的通信。这主要通过android.os.Handler类来实现的。说到通信的话就是一方发送内容和另一方接收内容的过程,Android系统将要通信的内容封装为android.os.Message类,其中有 int arg1和int arg2两个属性储存简单的数值内容、Object obj属性存储任意类型的对象、int what属性可以标记区分不同的Message类型。
通信接收线程
在需要处理通信内容的线程中,创建Handler实例化对象。
可以使用Handler(Looper looper)构造方法或者createAsync(Looper looper)静态方法,创建处理任意内容的实例化对象。其中的参数 looper 标记当前Handler对象中的处理操作是在哪个线程,如果是主线程可以使用Looper.getMainLooper()静态方法获取android.os.Looper对象,如果是子线程,可以在子线程的run()方法中使用Looper.myLooper()静态方法获取当前线程的Looper对象。
或者使用Handler(Looper looper, Handler.Callback callback)构造方法或者createAsync(Looper looper, Handler.Callback callback)静态方法,创建需要接收Message消息处理的实例化对象。参数 looper 同样是标记当前Handler对象中的处理操作是在哪个线程。参数 callback 是android.os.Handler.Callback接口实现的实例化对象,其中实现的handleMessage(Message msg)方法可以接收并处理通信的结果。这里的参数 msg 就是收到的Message消息内容。
如果是通过Looper.myLooper()静态方法获取的Looper对象,也就是在子线程中处理通信结果的话,在创建Handler对象前后还要特别调用两个方法。
在上面初始化Handler对象之前,必须在子线程中先调用Looper.prepare()静态方法以初始化Looper对象,以此保证在调用Looper.myLooper()方法时获取到的对象非空。
以及在初始化Handler对象之后,必须在当前子线程中及时调用Looper.loop()静态方法以准备Message消息队列供当前子线程使用。
通信发送线程
在需要发送通信内容的线程中,需要首先接收到上文创建的Handler实例化对象。
在切换线程处理的位置,调用Handler对象的post(Runnable r)系列方法,参数 r 就是要在Handler对象所在线程中处理的Runnable任务对象。
或者在需要发送消息的位置,调用Handler对象的obtainMessage()系列方法,可以获取到空闲可以使用的Message消息对象,将要发送的消息体内容赋值给Message对象的不同属性。最后再调用Handler对象的sendMessage(Message msg)系列方法,将消息体Message对象发送即可。
Android系统编程入门系列之服务Service齐头并进多线程任务的更多相关文章
- Android系统编程入门系列之服务Service中的进程间通信
在上篇文章以线程间的通信方式Handler类结尾,服务Service还支持的进程间通信,又是具体怎么实现的呢?这就要用到加载服务一文中提到的AIDL语言规范了. AIDL是 Android Inter ...
- Android系统编程入门系列之加载界面Activity
上回说到应用初始化加载及其生命周期,在Android系统调用Applicaiton.onCreate()之后,继续创建并加载清单文件中注册的首个界面即主Activity,也可称之为入口界面.主Acti ...
- Android系统编程入门系列之硬件交互——通信硬件电信SIM卡
现在的SIM卡通常具备基站定位.语音通话.短信消息.网络流量这四大功能,而在移动端是无法对SIM卡使用基站定位功能的,所以这里只介绍移动端如何使用SIM卡实现语音通话.短信消息.数据流量三个功能. 语 ...
- Android系统编程入门系列之加载服务Service
之前几篇文章简单梳理了在Android系统的四大组件之一,最主要的界面Activity中,使应用程序与用户进行交互响应的相关知识点,那对于应用程序中不需要与用户交互的逻辑,又要用到哪些内容呢?本文开始 ...
- Android系统编程入门系列之应用环境及开发环境介绍
作为移动端操作系统,目前最新的Android 11.0已经发展的比较完善了,现在也到了系统的整理一番的时间,接下来的系列文章将以Android开发者为中心,争取用归纳总结的态度对初级入门者所应 ...
- Android系统编程入门系列之广播接收者BroadcastReceiver实现进程间通信
在前边几篇关于Android系统两个重要组件的介绍中,界面Activity负责应用程序与用户的交互,服务Service负责应用程序内部线程间的交互或两个应用程序进程之间的数据交互.看上去这两大组件就能 ...
- Android系统编程入门系列之应用间数据共享ContentProvider
内容提供者ContentProvider与前文的界面Activity.服务Service.广播接收者BroadcastReveiver,并列称为Android的四大组件,均是需要自定义子类继承上述组件 ...
- Android系统编程入门系列之硬件交互——通信硬件Bluetooth
通信硬件NFC的文章,虽然可以在Android系统中通过非直接接触的形式与支持NFC硬件的设备通信,但是也只能交互一些简短的标签内容,对大量的持续性数据,却并不能很好的支持.因此针对这个弊端,可以考虑 ...
- Android系统编程入门系列之清单文件
在上一篇文章中已经提到,Android系统加载应用程序之后,首先会读取该应用程序的AndroidManifest.xml清单文件,之后根据该清单文件加载后边的东西.所以要开发应用程序,自然要先知道清单 ...
随机推荐
- 徒手从零实现 uTools 系列(三)- 屏幕取色和截屏
前言 为了进一步提高开发工作效率,最近我们基于 electron 开发了一款媲美 uTools 的开源工具箱 rubick.该工具箱不仅仅开源,最重要的是可以使用 uTools 生态内所有开源插件!这 ...
- JAVA并发(8)-ThreadPoolExecutor的讲解
很久前(2020-10-23),就有想法学习线程池并输出博客,但是写着写着感觉看不懂了,就不了了之了.现在重拾起,重新写一下(学习一下). 线程池的优点也是老生常谈的东西了 减少线程创建的开销(任务数 ...
- <c:out>标签不能正确输出value中的值
问题: 我打算在jsp中输出request中的值,它的key为username, <c:out value="${requestScope.username}"/> 但 ...
- mysql 索引介绍与运用
索引 (1)什么是索引? 是一种提升查询速度的 特殊的存储结构. 它包含了对数据表里的记录的指针,类似于字典的目录. 当我们添加索引时会单独创建一张表来去存储和管理索引,索引比原数据大,会占用更多的资 ...
- 【笔记】Python编程 从入门到实践 第二版(基础部分)
1 字符串相关函数 .title() # 将字符串每个单词的首字母大写 .upper() #不改变字符串变量的值 .lower() #不改变字符串变量的值 f"{var} ,字符串" ...
- PYTHON 转化函数
ord(c)#字符转ASCII码值,10进制:自变量只能是一个字符 chr(a)#通过ASCII码值得到对应的字符 bin()函数:将整数(十 等进制)转化为二进制 bool():将指定参数转化为bo ...
- python 函数定义自变量的写法及调用
import pandas as pd #函数定义时指明自变量,指明自变量的类型,指定自变量的默认值 #函数定义时,可以通过"自变量名称=常量"的方式指定自变量的默认值,调用时可以 ...
- Python之手把手教你用JS逆向爬取网易云40万+评论并用stylecloud炫酷词云进行情感分析
本文借鉴了@平胸小仙女的知乎回复 https://www.zhihu.com/question/36081767 写在前面: 文章有点长,操作有点复杂,需要代码的直接去文末即可.想要学习的需要有点耐心 ...
- File类与常用IO流第八章——缓冲流
第八章.缓冲流 缓冲流概述 缓冲流,也叫高效流,是对4个基本的FileXxx流的增强.按照数据类型分为4类: 输入缓冲流 输出缓冲流 字节缓冲流 BufferedInputStream Buffe ...
- easyui-textbox使用value设置默认值失效
1,使用easyu-textbox的value参数设置默认值失效,easyui-textbox放到dialog弹框中,当dialog的closed为false时,也就是打开dialog时,设置的val ...