课程背景
主线程和子线程
首先需要了解,在我们的 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 常见发生消息的方法
- 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 ->{
}
}
}
}
}

}
打地鼠
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>
上面的代码可以写的更规范一点,时间有点晚了,明天再改写看看