Handler 基本使用(1)

228 阅读5分钟

课程背景

主线程和子线程

首先需要了解,在我们的 andriod 开始会有一个主进程启动,主进程会开启一个主线程执行,即在 ActivityThread 类中有一个 public static void main(String[] args) 函数,从这里开始执行

主线程也叫 UI 线程,只有它能执行UI的更新

线程不安全

首先,线程安全指的是在多线程的情况下,同时操作某一块内存,不会存在冲突的情况,一般情况下会给该区域上锁,但是在这里若在对 Button 等UI组件进行更新的时候,若上锁,那么效率会很低,所以在Andriod中并不采用锁来保证安全,而是将所有UI线程的更新都统一到主线程中进行操作,这样的话就可以避免加锁而不冲突

消息循环机制

ActivityThread 的 main 方法中有 looper 死循环,不断的遍历 enqueue 取出消息执行。(消息指的是你点击事件等行为会被封转为消息放入enqueue中,等等)

应用场景

1、To schedule message and runnables to be executed as some point in the future 安排一些消息或者 runnable 行为在未来的某些点去执行(执行定时任务)

2、To enqueue an action to be performed on a different thread than your own 添加一些行为到不同的线程中去执行(线程和线程之间的处理)

使用说明

1、使用 Handler 去执行定时任务

class HandlerActivity : AppCompatActivity() {

    private val tag = "HandlerActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.handler_activity)

        val handler = Handler(Looper.getMainLooper(), object :Handler.Callback{
            override fun handleMessage(msg: Message): Boolean {
                Log.d(tag, System.currentTimeMillis().toString())
                Log.d(tag, msg.what.toString())
                return true
            }
        })
       
        Log.d(tag, System.currentTimeMillis().toString())
        // 可以调用delay延迟执行
        handler.sendEmptyMessage(1234)

    }
}

1、使用 Handler 进行线程间的交互,这里就是子线程发送消息给主线程执行,让主线程更新 UI

package com.study.studyforbook.handler

class HandlerActivity : AppCompatActivity() {

    private val tag = "HandlerActivity"
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.handler_activity)

        val handler = Handler(Looper.getMainLooper(), object :Handler.Callback{
            override fun handleMessage(msg: Message): Boolean {

                Log.d(tag, msg.what.toString())
                if(msg.what == 1234){
                    // 更新 UI
                    val textView = findViewById<TextView>(R.id.textView)
                    textView.text = "test"
                }
                return true
            }
        })


        findViewById<Button>(R.id.btn).setOnClickListener {
            Thread(object :Runnable{
                override fun run() {
                    handler.sendEmptyMessage(1234)
                }
            }).start()
        }
    }

 
}

Handler 常见发生消息的方法

image.png

  • what 一般作为标记
  • arg1 参数1 int
  • arg2 参数2 int
  • obj 参数3 object
  • replyTo 参数4 进程通讯相关
 handler.post(object : Runnable{
            override fun run() {
                Log.d(tag, "postDelayed1")
            }
        })
        
handler.postDelayed(object : Runnable{
    override fun run() {
        Log.d(tag, "postDelayed_4000")
    }
}, 4000)
val message = handler.obtainMessage()
    message.what = 123
    message.arg1 = 1234
    message.arg2 = 12345
    message.obj = this
handler.sendMessage(message)
handler.sendMessageDelayed(message, 2000)

简单案例( 解决handle内存泄漏问题 )

Handler 下载文件并更新进度条

package com.study.Handler

import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.util.Log
import android.widget.Button
import android.widget.ProgressBar
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    var handler: Handler? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        /**
         * 主线程 --》 start
         * 点击按键
         * 发起下载
         * 开启子线程做下载
         * 下载过程中通知主线程 |  --》 主线程更新进度条
         */
        findViewById<Button>(R.id.download).setOnClickListener {
            download("https://www.google.com.hk/imgres?q=图片&imgurl=https%3A%2F%2Fcdn.pixabay.com%2Fphoto%2F2017%2F08%2F30%2F17%2F26%2Fplease-2697951_1280.jpg&imgrefurl=https%3A%2F%2Fpixabay.com%2Fzh%2Fimages%2Fsearch%2F%25E5%259B%25BE%25E7%2589%2587%2F&docid=yqH-3bOc4O1mDM&tbnid=5ptdYrcVnN-RMM&vet=12ahUKEwjV9pqBxJWIAxU2GTQIHXPWMxQQM3oECHEQAA..i&w=1280&h=545&hcb=2&ved=2ahUKEwjV9pqBxJWIAxU2GTQIHXPWMxQQM3oECHEQAA")
        }
        handler = Handler(Looper.getMainLooper(), object :Handler.Callback{
            override fun handleMessage(msg: Message): Boolean {
                when(msg.what){
                    1001 ->{
                        val progressBar = findViewById<ProgressBar>(R.id.progress)
                        progressBar.progress = msg.obj as Int
                    }
                    1002 ->{
                        Log.d("MainActivity", "下载失败...")
                    }
                    else ->{

                    }
                }
                return true
            }
        })
    }

    private fun download(appUrl: String){
        Thread{
            var downSize = 0
            try {
                while (downSize < 100){
                    downSize += 10
                    // 发送更新ui
                    val message = Message.obtain()
                    message.obj = downSize / 100 * 100
                    message.what = 1001
                    handler?.sendMessage(message)
                }
            } catch (e: Exception) {
                // 处理异常的代码
                // 发送更新ui
                val message = Message.obtain()
                message.what = 1002
                handler?.sendMessage(message)
            }
        }.start()
    }
}

Handler 实现倒计时并优化

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/count"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:text="10"
        />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:id="@+id/btn"
        android:text="开始计时"/>
</LinearLayout>


package com.study.Handler

import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class CountdownActivity:AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.count_down_activity)
        var handler: Handler? = null
        handler = Handler(Looper.getMainLooper(), object :Handler.Callback{
            override fun handleMessage(msg: Message): Boolean {
                when(msg.what){
                    1010 ->{
                        findViewById<TextView>(R.id.count).text = (--msg.arg1).toString()
                        val message = Message.obtain()
                        message.what = 1010
                        message.arg1 = msg.arg1
                        handler?.sendMessageDelayed(message, 1000)
                    }
                    else ->{

                    }
                }
                return true
            }

        })
        findViewById<Button>(R.id.btn).setOnClickListener {
            val message = Message.obtain()
            message.what = 1010
            message.arg1 = 10
            handler.sendMessageDelayed(message, 1000)
        }

    }
    

但是上述代码会存在内存泄漏的问题,因为 handler 的 handleMessage 中持有 activity 的引用,而 handler 是会随着 message 被引用在 MainLoop 中的,是跟着整个应用程序的,而不是跟着mainActivity,生命周期比 MainActivity 长(这里不懂没关系,后面看了handler的原理之后就会理解了)

所以我们通过将引用改为弱应用的方式可以减少内存泄漏

package com.study.Handler

import android.app.Activity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import java.lang.ref.WeakReference

class CountdownActivity:AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.count_down_activity)
        var handler: Handler? = null
        handler = CountHandler(this, Looper.getMainLooper())
        findViewById<Button>(R.id.btn).setOnClickListener {
            val message = Message.obtain()
            message.what = 1010
            message.arg1 = 10
            handler.sendMessageDelayed(message, 1000)
        }
    }
   
    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacksAndMessages(null) // 清除所有消息
    }

    class CountHandler: Handler{

        var weakActivity: WeakReference<Activity>

        constructor(activity: Activity,looper: Looper):super(looper){
            weakActivity = WeakReference(activity)
        }

        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            weakActivity.get()?.let {
                when(msg.what){
                    1010 ->{
                        it.findViewById<TextView>(R.id.count).text = (--msg.arg1).toString()
                        val message = Message.obtain()
                        message.what = 1010
                        message.arg1 = msg.arg1
                        sendMessageDelayed(message, 1000)
                    }
                    else ->{

                    }
                }
            }
        }
    }

![image.png](https://p6-xtjj-sign.byteimg.com/tos-cn-i-73owjymdk6/886a95ba84f642d48a1fbc7bab55a6f6~tplv-73owjymdk6-jj-mark-v1:0:0:0:0:5o6Y6YeR5oqA5pyv56S-5Yy6IEAg5oiQ6ZW_55qE5aSn5Za1:q75.awebp?rk3s=f64ab15b&x-expires=1745451302&x-signature=L2MJNvajbSgaSHrXCoU3dpBEBnA%3D)
}

image.png

打地鼠

package com.study.Handler

import android.app.Activity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import java.lang.ref.WeakReference
import kotlin.random.Random

class DiglettActivity: AppCompatActivity() {

    lateinit var handler: DiglettHandler
    companion object{
        val position = arrayOf(
            intArrayOf(300, 400),
            intArrayOf(100, 200),
            intArrayOf(600, 800),
            intArrayOf(0, 0),
            intArrayOf(3, 900),
            intArrayOf(201, 53)
        )
        val delayTime = 2000L
        val gameTime = 10
        var chooseTime = 0
        var successNum = 0
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.diglett_activity)
        findViewById<ImageView>(R.id.img).setOnClickListener{
            successNum++
            findViewById<TextView>(R.id.answer).text = "获胜次数:${successNum} 尝试次数:${chooseTime}"
            findViewById<ImageView>(R.id.img).visibility = View.GONE
        }
        handler = DiglettHandler(Looper.getMainLooper(), this)
        val msg = Message.obtain()
        msg.what = 1001
        handler.sendMessageDelayed(msg, delayTime)
    }

    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacksAndMessages(null)
    }

    class DiglettHandler: Handler{

        lateinit var weakActivity: WeakReference<Activity>
        constructor(looper: Looper, activity: Activity) : super(looper) {
            this.weakActivity = WeakReference(activity)
        }

        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            val activity = weakActivity.get()
            if(activity == null) return
            when(msg.what){
                1001 ->{
                    // 改变地鼠的位置
                    if(chooseTime > gameTime){
                        return
                    }
                    val diglett = activity.findViewById<ImageView>(R.id.img)
                    // 展示
                    activity.findViewById<TextView>(R.id.answer).text = "获胜次数:${successNum} 尝试次数:${chooseTime}"

                    val random = Random.nextInt(0, position.size)
                    diglett.x = position[random][0].toFloat()
                    diglett.y = position[random][1].toFloat()
                    diglett.visibility = View.VISIBLE
                    val msg = Message.obtain()
                    msg.what = 1001
                    chooseTime++
                    sendMessageDelayed(msg, delayTime)
                }else->{

                }
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/img"
        android:id="@+id/img"
        android:visibility="gone"
        />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:id="@+id/click"
        android:layout_gravity="center"
        android:layout_centerHorizontal="true"
        android:text="游戏开始"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/click"
        android:id="@+id/answer"
        android:layout_centerHorizontal="true"
        android:text="结果" />

</RelativeLayout>

上面的代码可以写的更规范一点,时间有点晚了,明天再改写看看

image.png