在Android应用中常常结合多线程处理多个任务。不可避免,多个线程之间需要数据通信。Hanlder消息处理机制是异步处理的方式之一。通过Handler机制可以实现在不同的线程之间的通信。
一、主线程和工作线程
1.主线程
一个Android的移动应用启动时会单独启动一个进程。这个进程中可以存在多个线程。但是这么多线程中有且仅有一个主线程,即UI线程。
Android应用程序运行时创建UI主线程,它主要是负责控制UI界面的显示、更新和控件交互。
2.工作线程
在一个应用中会定义多个线程来完成不同的任务,分担主线程的责任,避免主线程阻塞。这些自定义的线程为工作线程。值得注意地是,一旦主线程阻塞超过规定的时间,会出现ANR问题,道中程序中断运行。
3.线程的定义
Kotlin采用下列方式定义并启动线程
thread{
//线程体
}
下列定义了一个简单的计时器,通过定义工作线程,修改显示时间的状态,达到更新界面,动态显示计时的目的。
@Composable
fun MainScreen(){
var timer by remember{mutableStateOf(0)}
var running by remember{mutableStateOf(true)}
var display by remember{mutableStateOf("计时器")}
Column(modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center){
Text(text = display,fontSize = 30.sp)
Row(horizontalArrangement = Arrangement.Center){
TextButton(onClick = {
running = true
thread{
while(running){
Thread.sleep(1000)
timer += 1
display = "${timer} 秒"
}
}
}){
Icon(imageVector = Icons.Filled.PlayArrow,contentDescription = "play")
Text("开始")
}
TextButton(onClick = {
running = false
}){
Icon(imageVector = Icons.Default.Close,contentDescription = "pause")
Text("暂停")
}
TextButton(onClick = {
running = false
timer = 0
}){
Icon(imageVector = Icons.Filled.Refresh,contentDescription = "refresh")
Text("停止")
}
}
}
}
运行结果如下所示:
二、Handler处理机制概述
Handler机制中有如下:
Looper是一个循环者。在线程内部定义,每个线程只能定义一个Looper。Looper内部封装了消息队列MessageQueue。
Message表示的是消息也称为任务。有时将Runnable线程体对象封装成Message对象来处理其中的任务。Handler对象发送、接受并进行处理消息Message。
MessageQueue是消息队列。当产生一个消息时,关联Looper的Handler对象会将消息Message对象发送给MessageQueue消息队列。Looper对消息队列进行管理和控制,控制消息Message进出消息队列。
在《Android移动应用开发(微课版)》P170的图5-3很好地对Handler机制实现主线程和工作线程的通信。
三、Compose组件中使用Handler机制
将通过动态显示图片来观察Handler机制对不同线程之间的通信。
1.定义数据层
class ImageRepository(private val handler: Handler){
val imageList = listOf(
R.mipmap.scene1,
R.mipmap.scene2,
R.mipmap.scene3,
R.mipmap.scene4,
R.mipmap.scene5)
fun requestImage(imageId: MutableState<Int>){
thread{
//定义工作线程
while(imageId.value in 0 until imageList.size){
Thread.sleep(1000)
var message = Message.obtain()
message.what= 0x123
message.arg1 = imageList[imageId.value]
imageId.value = imageId.value+1
//发送数据
handler.sendMessage(message)
}
}
}
}
2.定义界面
@Composable
fun MainScreen(imageState: MutableState<Int>, imageRepository: ImageRepository){
var currentSelected = remember{mutableStateOf(0)}
Column(modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center){
if(imageState.value!=0)
Image(painter=painterResource(imageState.value),contentDescription="image")
else{
Text("等待加载图片")
}
Row{
for(i in 0 until imageRepository.imageList.size){
RadioButton(selected = currentSelected.value-1==i,
onClick = {
currentSelected.value = i
})
}
}
Row{
Button(onClick = {
imageRepository.requestImage(currentSelected)
}){
Text("动态显示")
}
Button(onClick = {
imageState.value = 0
currentSelected.value = 0
}){
Text("重置")
}
}
}
}
3.定义主活动
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
//图片列表的序号状态
val imageState = remember{mutableStateOf(0)}
//定义Handler对象
val handler=object: Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
if(msg.what == 0x123){
//接受数据,并修改状态值
imageState.value = msg.arg1
}
}
}
//定义图片仓库
val imageRepository = ImageRepository(handler)
ForCourseTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
MainScreen(imageState=imageState,imageRepository = imageRepository)
}
}
}
}
}
运行结果如下图所示:
四、显示在线图片的应用实例
本例结合Compose+ViewModel+Handler实现在线图片的显示应用,运行效果如下显示:
上述运行效果的采用的风景图片来源于“https://so1.360tres.com/t01166f28ff8d9dc33e.jpg”,特此说明。如有侵权,可以删除。
1.项目模块配置
在模块对应的build.gradle.kt中设置:
//viewModel implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2" //使用coil显示在线的图片 implementation("io.coil-kt:coil-compose:2.4.0")
在项目的AndroidManifest.xml增加使用Internet的许可
<uses-permission android:name="android.permission.INTERNET" />
2.项目架构
在这里:
数据层:定义数据存储和要访问处理的数据;
ViewModel层:是视图模型层,承担业务逻辑的定义和更新界面的任务。ViewModel调用数据层的数据,经过业务处理得到新的数据,用这些数据去修改UI层的界面;
UI层:界面层,可以观察VIewModel内存储状态数据的变化,根据ViewModel提供的数据,更新界面。
3.数据层
class ImageRepository (val handler: Handler){
//如果需要测试,请自行在网络中查找图片资源,下列的网址均为不存在,只为示例而已。
val imageLst:List<String> = listOf(
"https://somesite.com/1.jpg",
"https://somesite.com/2.jpg",
"https://somesite.com/3.jpg",
"https://somesite.com/4.jpg",
"https://somesite.com/5.jpg",
"https://somesite.com/6.jpg"
)
/**
* 根据列表的索引号获取图像的URL
* @param imageId Int
*/
fun requestImage(){
//自定义工作线程
thread {
for(imageId in 0 until imageLst.size){
Thread.sleep(1000)
var message = Message.obtain()
message.what = 0x123
message.arg1 = imageId+1
message.obj = imageLst[imageId]
handler.sendMessage(message)
}
}
}
}
4.视图模型层
class LoadImageViewModel(val imageRepository: ImageRepository): ViewModel() {
val _currentImageId = MutableStateFlow<Int>(0)
val currentImageId = _currentImageId.asStateFlow()
val _currentImage= MutableStateFlow<String>("")
val currentImage = _currentImage.asStateFlow()
/**
* 修改图片索引
*/
fun changeImageIndex(){
_currentImageId.value =(_currentImageId.value+1)%imageRepository.imageLst.size
}
/**
* Change image index
* 修改图片索引
*/
fun changeImageIndex(index:Int){
_currentImageId.value = index%imageRepository.imageLst.size
}
/**
* 修改当前的图片链接
*/
fun changeImage(url:String){
_currentImage.value = url
}
/**
* Request image
* 请求图片
*/
fun requestImage(){
imageRepository.requestImage()
}
}
5.定义界面
@Composable
fun ImageScreen(viewModel:LoadImageViewModel,repository:ImageRepository){
//数据层中保存的在线图片列表
val images = repository.imageLst
//获取当前图片状态
val imageURLState = viewModel.currentImage.collectAsState()
//获取当前图片索引
val imageIdState = viewModel.currentImageId.collectAsState()
Box(modifier = Modifier
.fillMaxSize()
.padding(20.dp)
.background(Color.Black),
contentAlignment = Alignment.Center){
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center){
if(!imageURLState.value.isBlank()) {
AsyncImage(modifier= Modifier
.width(400.dp)
.height(400.dp)
.border(BorderStroke(1.dp, Color.Blue)),
model = imageURLState.value,
contentDescription = null)
}
else
Text(modifier = Modifier
.width(300.dp)
.height(500.dp),
text = "等待加载图片",fontSize = 20.sp,textAlign = TextAlign.Center)
if(imageURLState.value.isNotBlank())
Row(horizontalArrangement = Arrangement.Center) {
for (i in 0 until images.size) {
RadioButton(
colors = RadioButtonDefaults.colors(selectedColor = Color.Green),
selected = i==imageIdState.value,
onClick =null)
}
}
Row(modifier = Modifier
.fillMaxWidth()
.padding(end = 10.dp), horizontalArrangement = Arrangement.Center){
TextButton(colors = ButtonDefaults.buttonColors(contentColor = Color.Blue, containerColor = Color.LightGray),
onClick={
//请求图片
viewModel.requestImage()
}){
Text("动态显示图片",fontSize=16.sp)
}
TextButton(colors = ButtonDefaults.buttonColors(contentColor = Color.Blue,
containerColor = Color.LightGray),onClick={
viewModel.changeImageIndex(0)
}){
Text("重置",fontSize = 16.sp)
}
}
}
}
}
6.主活动
class MainActivity : ComponentActivity() {
lateinit var viewModel:LoadImageViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
//定义Handler对象
val handler = object: Handler(Looper.getMainLooper()){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
if(msg.what == 0x123){
//修改图片
viewModel.changeImage(msg.obj as String)
//修改图片索引
viewModel.changeImageIndex(msg.arg1)
}
}
}
val imageRepository = ImageRepository(handler)
viewModel = LoadImageViewModel(imageRepository)
Ch06_DemoTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
//加载界面
ImageScreen(viewModel = viewModel, repository = imageRepository )
}
}
}
}
}
五、结合线程池和HandlerCompact实现Handler机制的多线程通信
在上述的显示在线图片的应用中,应用还可以优化。通过线程池和HandlerCompact获得的Handler,可以让多线程的通信的代码会更加简单。
1.在应用中定义线程池
在上述的代码中每次都是创建一个新的线程,这样会造成较多的问题:
(1)浪费资源,因为有些线程的资源没有及时回收,导致占用资源
(2)并发线程的管理困难
其实可以结合线程池来实现多线程的实现。线程池可以很好管控多个线程,可以提高响应速度,降低系统资源的消耗。而且线程池使用方便。
因为线程池往往在多处使用,因此,将线程池的处理放置在Application中,代码如下:
class MyApplication : Application() {
//获取可用核心的数量,可供 Java 虚拟机使用的处理器数
private val NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors()
//创建线程体队列
private val workQueue: BlockingQueue<Runnable> = LinkedBlockingQueue<Runnable>()
//设置闲时线程的在终端线程前的等待时间
private val KEEP_ALIVE_TIME = 1L
//设置时间单位为秒
private val KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS
//创建一个线程池管理器
val threadPoolExecutor:ThreadPoolExecutor = ThreadPoolExecutor(
NUMBER_OF_CORES, //初始化线程池的大小
NUMBER_OF_CORES, //最大线程池的个数
KEEP_ALIVE_TIME,
KEEP_ALIVE_TIME_UNIT,
workQueue
)
}
为了实现利用Application完成初始化的功能,需要在AndroidManifest.xml中增加android:name的配置,配置的内容如下:
<application
android:name=“.MyApplication”
…>
2.定义数据层
(1)定义结果类
Result类限制了Sucess和Error两种情况,Result.Success可以返回对象,Result.Error返回要抛出的异常。
sealed class Result<out R>{
data class Success<out T>(val data:T):Result<T>()
data class Error(val exception:Exception):Result<Nothing>()
}
(2)定义图像仓库
/**
* 定义图形仓库
* @property executor Executor
* @property resultHandler Handler
* @property imageLst List<String>
* @constructor
*/
class ImageRepository(private val executor: Executor,
private val resultHandler: Handler) {
val imageLst:List<String> = listOf(
"https://somesite.com/1.jpg",
"https://somesite.com/2.jpg",
"https://somesite.com/3.jpg",
"https://somesite.com/4.jpg",
"https://somesite.com/5.jpg",
"https://somesite.com/6.jpg"
)
/**
* 请求加载数据
* @param imageId Int 在图片列表中的序号
* @param callBack Function1<Result<String>, Unit> 回调
*/
fun loadImageById(imageId:Int,
callBack:(Result<String>)->Unit){
//线程池中创建一个新线程并执行
executor.execute{
try{
//按照列表索引请求图片资源
val successResult = loadSynImageById(imageId)
//与主线程通信
resultHandler.post{
callBack(successResult)
}
}catch(e:Exception){
val errorResult = Result.Error(e)
resultHandler.post{
callBack(errorResult)
}
}
}
}
private fun loadSynImageById(imageId:Int):Result<String>{
if (imageId >= imageLst.size && imageId < 0)
return Result.Error(Exception("图片索引存在问题"))
return Result.Success(imageLst[imageId])
}
}
3.定义ViewModel
class LoadImageViewModel(val imageRepository: ImageRepository): ViewModel() {
val _currentImageId = MutableStateFlow<Int>(0)
val _currentImage= MutableStateFlow<String>("")
val currentImage = _currentImage.asStateFlow()
/**
* Change image indxe
* 修改图片索引
*/
fun changeImageIndex(){
_currentImageId.value =(_currentImageId.value+1)%imageRepository.imageLst.size
}
/**
* Request image
* 请求图片
*/
fun requestImage(){
imageRepository.loadImageById(_currentImageId.value){it:Result<String>->
when(it){
//获取成功,修改在线图片的url
is Result.Success<String>-> _currentImage.value = it.data
//获取失败,提供失败的描述信息
else->_currentImage.value = "加载在线图片资源失败"
}
}
}
}
4.定义界面
@Composable
fun ImageScreen(viewModel:LoadImageViewModel){
//获取当前图片状态
val imageURLState = viewModel.currentImage.collectAsState()
//获取当前图片索引
val imageIdState = viewModel.currentImageId.collectAsState()
Box(modifier = Modifier
.fillMaxSize()
.padding(20.dp)
.background(Color.Black),
contentAlignment = Alignment.Center){
Column(horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center){
if(!imageURLState.value.isBlank()) {
AsyncImage(modifier= Modifier
.width(400.dp)
.height(400.dp)
.border(BorderStroke(1.dp, Color.Blue)),
model = imageURLState.value,
contentDescription = null)
}
else
Text(modifier = Modifier
.width(300.dp)
.height(500.dp),
text = "等待加载图片",fontSize = 20.sp,textAlign = TextAlign.Center)
Row(modifier = Modifier
.fillMaxWidth()
.padding(end = 10.dp), horizontalArrangement = Arrangement.Center){
TextButton(colors = ButtonDefaults.buttonColors(contentColor = Color.Blue, containerColor = Color.LightGray),
onClick={
//请求图片
imageViewModel.requestImage()
//修改索引
imageViewModel.changeImageIndex()
}){
Text("动态显示图片",fontSize=16.sp)
}
}
}
}
}
5.定义主活动
在主活动中,既也在主线程中,通过HandlerCompat这个访问Handler特性的帮助类,调用createAsync方法不但获取一个Handler对象,而且不必服从于同步的障碍。通过获得的Handler对象调用post方法来发布消息Message或将Runnable(线程体)对象。
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
//创建应用对象
val application = application as MyApplication
//创建处理器
val handler = HandlerCompat.createAsync(Looper.getMainLooper())
//创建图形仓库
val respository = ImageRepository(executor = application.threadPoolExecutor,
resultHandler = handler)
val imageViewModel = LoadImageViewModel(respository)
Ch06_DemoTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
ImageScreen(imageViewModel = imageViewModel)
}
}
}
}
}
参考文献
陈轶 《Android移动应用开发(微课版)》 P166-P170 清华大学出版社