Use Kotlin coroutine to create a timing tool class

The pain of Java callbacks

Android was commonly developed using Java a few years ago. Now after Google announced "Kotlin First", coupled with the rising number of Kotlin Users, the transition to kt has begun. In the Java language, one point that is widely complained about is that asynchronous operations need to be set up through callbacks, and special attention must be paid to thread switching. If you are not careful, you will get:

Only the original thread that created a view hierarchy can touch its views

For example, when I asked for a pop-up window to update the button status with a countdown, I had to initialize a countdown tool class, then set up a callback, write logic in the callback, and finally call start. Just like the following, a bunch of override callbacks are generated during the setting process, which is extremely unsightly.

      CountDownUtils count = new CountDownUtils(5, 1000);
      count.setTimerCallBack(new CountDownUtils.OnTimerCallBack() {
    
    
            @Override
            public void onStart() {
    
    
                btn_close.setClickable(false);
                btn_close.getBackground().setAlpha(88);
                btn_close.setTextColor(Color.argb(80, 0xff, 0xff, 0xff));
                btn_close.setText("关闭 5s");
            }

            @Override
            public void onTick(int times) {
    
    
                btn_close.setText("关闭 " + times + "s");
            }

            @Override
            public void onFinish() {
    
    
                btn_close.getBackground().setAlpha(255);
                btn_close.setTextColor(Color.argb(255, 0xff, 0xff, 0xff));
                btn_close.setClickable(true);
                btn_close.setText("关闭");
            }
      });
      count.start();

Later, RxJava, a powerful asynchronous library, came out, but its use was too complicated, and various operators had relatively high learning costs. This tool was not needed to make a simple countdown. After learning Kotlin, I started to design a simple countdown tool class myself.

Several features of Kotlin used in tool classes

Singleton class declaration

In the past, there was a common writing method for singleton tool classes in Java, which was usually the getInstance synchronization method, ensuring that the singleton was returned at the acquisition point:

public class BSingleton {
    
    
    
    private static BSingleton bSingleton;

    private BSingleton() {
    
    
    }

    public synchronized static BSingleton getbSingleton(){
    
    
        if(bSingleton == null){
    
    
            bSingleton = new BSingleton();
        }
        return bSingleton;
    }
}

Kotlin thoughtfully optimizes the declaration of singleton classes, directly using the object keyword to declare a singleton class. The internal fun functions are all static functions by default, and can be called directly from the outside:

object BSingleton{
    
    

}

Default function parameters

When encountering a method with the same name but different parameters in Java, a bunch of overloaded functions are generally set up to provide calls. When called, the one with the fewest parameters is actually called. Kotlin has added a default parameter design. When a function is declared, a default value can be given to the parameter. It does not need to be passed when calling. The default value will be used directly when the function is executed.

fun printSomething(text:String = "default"){
    
        
    Log.i("MYTAG", text)
}

// 调用时不写参数就是使用的text默认值"default"
printSomething()printSomething("my special text")

This can effectively eliminate the writing of overloaded methods. In addition, the use of nullable types can be added. With the elvis symbol, ?:when the parameter is empty, it can prevent control null pointer errors.

Functional parametric Lambda expression

It may be a little confusing with the previous concept. Functional parameters can actually be understood as a kind of callback, and this function using functional parameters is also called a higher-order function.

The definition of higher-order function : A function is a higher-order function if its parameter type is a function or its return value type is a function. Kotlin supports passing functions as arguments without the need to build objects to wrap functions. The most typical example is when setting a click callback for a button, setOnClickListener is a high-order function, such as the following writing:binding.btnFastclick.setOnClickListener { infoLog("test") }

Let's look at another simple use, the getStringLenth function, which passes in a String object a, and a "function object" b that gets the length of the string. This b is not actually an object, but is actually implemented by an anonymous inner class. .

val lenth = getStringLenth("Android") {
    
        it.length}

/** * 将另一个函数当作参数的函数称为高阶函数 */fun getStringLenth(str: String, getLenth: (String) -> Int): Int {
    
        return getLenth(str)}

It can be seen that when defining, the type of the second parameter is (String) -> Int)that it is a function that receives String type and returns Int type. At the call site, when the last parameter is a functional parameter, it can be written as a lambda expression, as shown above.

Coroutines are simple to use

Coroutines are a design concept, and the virtual threads added by Java in JDK21 also have this design concept. A convenient design that switches code tasks on different threads, supports users to switch scopes freely, and writes code in a synchronous manner. Give an example that is common to the entire network. The IO thread obtains network data and displays it on the interface:

// viewModel或者controller里获取数据逻辑
// 使用suspend限制在协程里使用;withContext切换调度器,指定在IO线程执行下面的任务
suspend fun getUserName() = withContext(Dispatchers.IO) {
    
        debugLog("thread name: ${Thread.currentThread().name}")    ServiceCreator.createService<UserService>()        .getUserName("2cd1e3c5ee3cda5a")        .execute()        .body()}
// Activity调用处
override fun onCreate(savedInstanceState: Bundle?){
    
    
// 最直接的声明方法,CoroutineScope(Dispatchers.Main),在主线程执行下面的逻辑
    CoroutineScope(Dispatchers.Main).launch {
    
    
        // 相当于get这一半是在IO线程执行,拿到结果后的变量赋值这一半操作由调度器自动切换到主线程来执行了
        val userName = mViewModel.getUserName()        infoLog("userName: $userName")        binding.tvUserName.text = userName
    }
}

The above is to set up a suspend function to obtain network data. It can be understood as a function that needs to wait for a period of time before it is completed. It can only be called in a coroutine or other suspending functions.

If called elsewhere, a compile error will be reported:Suspend function 'XXXXXX' should be called only from a coroutine or another suspend function

If there is a delay operation in the coroutine, you can directly call the top-level function delay(). The top-level function is the static function in Java, which can be written directly in the KT file and does not belong to any class. The simple timing class implemented later is to use delay(1000L) directly to implement the 1s interval tick operation. Moreover, delay is a non-blocking suspension function. Even if the main thread delays for a long time, ANR will not occur. If we don't use delay(), but use Thread.sleep(), then the coroutine will not respond to cancellation, but will continue to execute until the end of the loop. This is because Thread.sleep() is a blocking function, it does not check the cancellation status of the coroutine, nor does it throw any exception. Therefore, we should try to avoid using blocking functions in coroutines and instead use suspending functions.

Implement CountDownUtil

CountDownUtil has only two functions, one is start and the other is cancel.

Among them, five parameters need to be passed in the start function, of which the total duration and step (ms) need to be divisible, otherwise an IllegalArgumentException will be thrown. Three additional parameters:

onStart: () -> Unit = {},

onTick: (currentTime: Int) -> Unit = {},

onFinish: () -> Unit = {}

These three are functional parameters, which trigger the onStart call at the beginning, the onTick call at the step, and the onFinish call at the end. And the default values ​​are all empty. When calling, you can pass none or set some.

In addition, in order to cancel the initialized new operation, a map is maintained in the tool class, and the id is used to correspond to the coroutine that performs the timing operation. When the timing needs to be canceled, the id query is written as an object to perform the cancellation operation.

Note that there is another pitfall when using Map, that is, when accessing a non-existent key, it will not report an error, but will only return a null value. If we enter the wrong ID, the coroutine will not be canceled and no error will be reported, which is very embarrassing. So manually add an id check and throw a RuntimeException when the element is not in the map.

object CountDownUtil {
    
        private val coroutingMap = mutableMapOf<Int, CoroutineScope>()    /**     * totalTime总时长,单位s     * interval步进,单位ms,默认值1000ms     * onStart和onFinish可空     */    fun start(        coroutineId: Int,        totalTime: Long,        interval: Long = 1000,        onStart: () -> Unit = {
    
    },        onTick: (currentTime: Int) -> Unit = {
    
    },        onFinish: () -> Unit = {
    
    }    ) {
    
            // 整除校验        if ((totalTime * 1000 % interval).toInt() != 0) {            throw IllegalArgumentException("CountDownUtil: remainder is not 0")        }        // 和id一起加入map,方便后续定点cancel        // 如果你看过协程的官方文档或视频。你应该会知道Job和SupervisorJob的一个区别是,Job的子协程发生异常被取消会同时取消Job的其它子协程,而SupervisorJob不会。        val countDownCoroutine = CoroutineScope(            Dispatchers.Main + SupervisorJob()        )        coroutingMap[coroutineId] = countDownCoroutine        // 开始计时        countDownCoroutine.launch(CoroutineExceptionHandler { _, e ->            e.message?.let { errorLog(it) }        }) {            // 开始            onStart()            // 循环触发onTick            repeat((totalTime * 1000 / interval).toInt()) {                delay(interval)                onTick((totalTime * 1000 / interval - (it + 1)).toInt())            }            // 循环结束,触发onFinish            onFinish()        }    }    /**     * 以ID标识,取消协程,停止计时     */    fun cancel(coroutineId: Int) {        if (!coroutingMap.contains(coroutineId)) throw RuntimeException("Can't find your Id in the Coroutine map")        coroutingMap[coroutineId]?.cancel()        coroutingMap.remove(coroutineId)    }}

When calling:

CountDownUtil.start(12355, 5, 1000,    
onStart = {
    
     infoLog("CountDown  start") },    
onTick = {
    
     infoLog("CountDown  tick: $it") },    
onFinish = {
    
     infoLog("CountDown  finish ") })

// 延时3s取消测试
sleep(3000L)
// 测试id不存在
CountDownUtil.cancel(123) // java.lang.RuntimeException: Can't find your Id in the Coroutine map

At this point, our timing tool class is complete. At the function call point, we use lambda expressions to set the different operations of start, in progress, and end, eliminating the traditional callback mode and making it more friendly to developers.

If only one finish operation is needed, the writing method will be more elegant:

CountDownUtil.start(12355, 5, 1000) {
    
     infoLog("CountDown finish ") }

at last

If you want to become an architect or want to break through the 20-30K salary range, then don't be limited to coding and business, you must be able to select and expand, and improve your programming thinking. In addition, good career planning is also very important, and learning habits are important, but the most important thing is to be able to persevere. Any plan that cannot be implemented consistently is empty talk.

If you have no direction, here is a set of "Advanced Notes on the Eight Modules of Android" written by a senior architect at Alibaba to help you systematically organize messy, scattered, and fragmented knowledge, so that you can systematically and efficiently Master various knowledge points of Android development.
img
Compared with the fragmented content we usually read, the knowledge points in this note are more systematic, easier to understand and remember, and are strictly arranged according to the knowledge system.

Welcome everyone to support with one click and three links. If you need the information in the article, just scan the CSDN official certification WeChat card at the end of the article to get it for free↓↓↓ (There is also a small bonus of ChatGPT robot at the end of the article, don’t miss it)

PS: There is also a ChatGPT robot in the group, which can answer everyone’s work or technical questions.

picture

Guess you like

Origin blog.csdn.net/weixin_43440181/article/details/135309823