Android Notes (12): Combined with Compose to implement the Handler mechanism to handle multi-threaded communication

In Android applications, multi-threading is often combined to handle multiple tasks. Inevitably, data communication is required between multiple threads. The Hanlder message processing mechanism is one of the methods of asynchronous processing. Communication between different threads can be achieved through the Handler mechanism.

1. Main thread and worker thread

1. Main thread

An Android mobile application starts a separate process when it is started. Multiple threads can exist in this process. But there is only one main thread among so many threads, which is the UI thread.

The UI main thread is created when the Android application is running, which is mainly responsible for controlling the display, update and control interaction of the UI interface.

2.Worker thread

In an application, multiple threads will be defined to complete different tasks, share the responsibilities of the main thread, and avoid blocking the main thread. These custom threads are worker threads. It is worth noting that once the main thread is blocked for more than the specified time, an ANR problem will occur and the program in the pipeline will be interrupted.

3. Definition of thread

Kotlin defines and starts threads in the following way

thread{ //Long body }

The following defines a simple timer. By defining a working thread and modifying the status of the displayed time, the purpose of updating the interface and dynamically displaying the timing is achieved.

@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("停止")
            }
        }
    }
}

The running results are as follows:
Insert image description here

2. Overview of Handler processing mechanism

Insert image description here
The Handler mechanism includes the following:

Looper is a looper. Defined inside the thread, each thread can only define one Looper. Looper internally encapsulates the message queue MessageQueue.
Message represents a message, also called a task. Sometimes the Runnable thread body object is encapsulated into a Message object to handle the tasks in it. The Handler object sends, receives and processes messages.
MessageQueue is a message queue. When a message is generated, the Handler object associated with the Looper will send the message Message object to the MessageQueue message queue. Looper manages and controls the message queue and controls messages entering and exiting the message queue.

Figure 5-3 on page 170 of "Android Mobile Application Development (Micro Course Edition)" nicely implements the communication between the main thread and the worker thread through the Handler mechanism.

3. Use Handler mechanism in Compose component

We will observe the communication between different threads by the Handler mechanism by dynamically displaying pictures.

1. Define the data layer

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. Define the interface

@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. Define the main activity

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)
                }
            }
        }
    }
}

The running results are shown in the figure below:

Insert image description here

4. Application examples for displaying online images

This example combines Compose+ViewModel+Handler to implement an online image display application. The running effect is as follows:
Insert image description here
The scenery image used in the above running effect comes from "https://so1 .360tres.com/t01166f28ff8d9dc33e.jpg”, hereby explain. If there is any infringement, it can be deleted.

1. Project module configuration

Set in build.gradle.kt corresponding to the module:

  //viewModel
  implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2"
  //使用coil显示在线的图片
  implementation("io.coil-kt:coil-compose:2.4.0")

Add permission to use the Internet in the project's AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />

2. Project structure

Insert image description here
Here:
Data layer: defines data storage and data to be accessed and processed;
ViewModel layer: is the view model layer, responsible for business logic The task of defining and updating the interface. ViewModel calls the data of the data layer, obtains new data through business processing, and uses these data to modify the interface of the UI layer;
UI layer: Interface layer, you can observe changes in the state data stored in VIewModel. Update the interface based on the data provided by ViewModel.

3. Data layer

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. View model layer

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. Define the interface

@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. Main activity

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 )
                }
            }
        }
    }
}

5. Combine thread pool and HandlerCompact to implement multi-thread communication of Handler mechanism

In the above-mentioned application for displaying online pictures, the application can also be optimized. The Handler obtained through the thread pool and HandlerCompact can make multi-threaded communication code simpler.

1. Define a thread pool in the application

In the above code, a new thread is created every time, which will cause more problems:
(1) Waste of resources, because some thread resources are not available in time Recycling, resulting in occupied resources
(2) Difficulties in managing concurrent threads
In fact, multi-threading can be achieved by combining thread pools. The thread pool can well control multiple threads, improve response speed, and reduce system resource consumption. And the thread pool is easy to use.
Because the thread pool is often used in multiple places, the processing of the thread pool is placed in the Application. The code is as follows:

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
    )
}

In order to implement the function of using Application to complete initialization, it is necessary to add the configuration of android:name in AndroidManifest.xml. The configuration content is as follows:

<application
android:name=“.MyApplication”
…>

2. Define the data layer

(1) Define the result class
The Result class limits the two situations of Sucess and Error. Result.Success can return objects, and Result.Error returns the exception to be thrown.

sealed class Result<out R>{
    
    
    data class Success<out T>(val data:T):Result<T>()
    data class Error(val exception:Exception):Result<Nothing>()
}

(2) Define image warehouse

/**
 * 定义图形仓库
 * @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. Define 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. Define the interface

@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. Define the main activity

In the main activity, as well as in the main thread, through HandlerCompat, a helper class that accesses Handler characteristics, calling the createAsync method not only obtains a Handler object, but also does not have to obey synchronization obstacles. Call the post method through the obtained Handler object to publish the message Message or the Runnable (thread body) object.

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)
                }
            }
        }
    }
}

references

Chen Yi "Android Mobile Application Development (Micro Course Edition)" P166-P170 Tsinghua University Press

Guess you like

Origin blog.csdn.net/userhu2012/article/details/134269225