Android笔记(十二):结合Compose实现Handler机制处理多线程的通信

如果你不相信努力和时光,那么成果就会是第一个选择辜负你的。不要去否定你自己的过去,也不要用你的过去牵扯你现在的努力和对未来的展望。不是因为拥有希望你才去努力,而是去努力了,你才有可能看到希望的光芒。Android笔记(十二):结合Compose实现Handler机制处理多线程的通信,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

在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 清华大学出版社

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

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/195539.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!