测试音乐列表的功能,在MusicActivity中可以获取音乐列表以及播放状态的数据
V层:MusicActivity,测试用来获取音乐列表,控制音乐播放状态
package com.example.jetpackbysob.musiclisttest1
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.jetpackbysob.R
import kotlinx.android.synthetic.main.activity_music_list.*
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/5/1 12:50
*/
class MusicActivity : AppCompatActivity() {
companion object {
private const val TAG = "MusicActivity"
}
private val mMusicPresenter by lazy {
MusicPresenter.instance
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_music_list)
initDataListener()
initViewListener()
}
override fun onDestroy() {
super.onDestroy()
}
/**
* 监听数据的变化
*/
private fun initDataListener() {
println("$TAG, cfx initDataListener Thread.currentThread().name = ${Thread.currentThread().name}")
mMusicPresenter.mMusicList.addListener {
println("$TAG, cfx initDataListener Thread.currentThread().name = ${Thread.currentThread().name}")
// 音乐列表数据变化
println("$TAG, cfx initDataListener mMusicList.size = ${it?.size}")
get_music_count.text = "共 ${it?.size} 首音乐"
}
mMusicPresenter.mLoadState.addListener {
// 播放状态的数据变化
println("$TAG, cfx initDataListener mLoadState = $it")
}
}
private fun initViewListener() {
get_music_list.setOnClickListener {
// 获取音乐列表
mMusicPresenter.getMusicList()
}
}
}
P层:处理获取音乐列表,控制播放状态相关逻辑
package com.example.jetpackbysob.musiclisttest1
import com.example.jetpackbysob.musiclisttest.domain.MusicBean
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/5/1 12:52
*/
class MusicPresenter {
companion object {
private const val TAG = "MusicPresenter"
val instance by lazy {
MusicPresenter()
}
}
enum class MusicLoadState {
LOADING, EMPTY, SUCCESS, ERROR
}
private val mMusicModel by lazy {
MusicModel()
}
val mMusicList = DataListenerContainer<List<MusicBean>>()
val mLoadState = DataListenerContainer<MusicLoadState>()
private val page = 1
private val size = 30
/**
* 获取音乐列表
*/
fun getMusicList() {
mLoadState.value = MusicLoadState.LOADING
// 从model层获取音乐列表,这个操作一般是异步操作,子线程执行
mMusicModel.loadMusicListByPage(page, size, object: MusicModel.OnMusicLoadResult {
override fun onSuccess(result: List<MusicBean>) {
// 加载音乐列表成功
mMusicList.value = result
mLoadState.value = if (result.isEmpty()) {
MusicLoadState.EMPTY
} else {
MusicLoadState.SUCCESS
}
println("$TAG getMusicList onSuccess 获取音乐列表成功")
}
override fun onError(msg: String, code: Int) {
// 加载音乐列表失败
mLoadState.value = MusicLoadState.ERROR
println("$TAG getMusicList onError msg: $msg code: $code")
}
})
}
}
M层:MusicModel,对音乐内容进行处理
package com.example.jetpackbysob.musiclisttest1
import com.example.jetpackbysob.musiclisttest.domain.MusicBean
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/5/1 12:52
*/
class MusicModel {
interface OnMusicLoadResult {
fun onSuccess(result: List<MusicBean>)
fun onError(msg: String, code: Int)
}
/**
* 获取音乐列表
*/
fun loadMusicListByPage(page: Int, size: Int, callback: OnMusicLoadResult) {
// 异步加载数据
val result = arrayListOf<MusicBean>()
Thread {
for (i in (0 until size)) {
result.add(
MusicBean(
name = "音乐名称:$i",
cover = "cover 封面:$i",
url = "url ==> $i"
)
)
}
// 数据加载完成
// 通知数据更新
callback.onSuccess(result)
}.start()
}
}
MusicBean:音乐实体类
package com.example.jetpackbysob.musiclisttest1.domain
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 22:17
*
* 歌曲的实体类对象
*/
data class MusicBean(
val name: String,
val cover: String,
val url: String
) {
}
这里和前文一样使用DataListenerContainer对数据进行监听
class DataListenerContainer<T> {
private val mBlocks = arrayListOf<(T?) -> Unit>()
// 数据
var value: T? = null
/**
* 当设置数据时,即表明数据发生变化
* 就进行通知更新
*/
set(value: T?) {
mBlocks.forEach { it.invoke(value) }
}
// 将匿名函数添加到数据容器中去
fun addListener(block: (T?) -> Unit) {
if (!mBlocks.contains(block)) {
mBlocks.add(block)
}
}
}
在MusicActivity中只对音乐列表的数据、播放状态的数据(mMusicList、mLoadState)进行监听
同前文一样,这里的书写仍然存在问题,一般情况下MusicModel中获取音乐数据是耗时操作,在异步线程执行,所以在P层处理的过程中,最终在V层监听mMusicList、mLoadState中返回后也处于异步线程了,而一般会在V层进行更新UI,所以这里会有应用奔溃的风险,那么如何处理这个问题呢?
可以改写DataListenerContainer,在set方法中进行判断是否是UI线程,如果是UI线程,才更新回调,不是UI线程就切换到UI线程,再进行回调
package com.example.jetpackbysob.musiclisttest
import android.os.Looper
/**
* Project_name:JetPackBySob
* Created by:ChenFuXu.
* Date: 2022/4/30 22:51
*
* 数据容器:监听数据的变化
* 这个类中添加对数据进行监听的功能
*/
class DataListenerContainer<T> {
private val mBlocks = arrayListOf<(T?) -> Unit>()
// 数据
var value: T? = null
/**
* 当设置数据时,即表明数据发生变化
* 就进行通知更新
*/
set(value: T?) {
// 判断当前是否是主线程,如果是主线程,继续执行
// 如果不是主线程,切换到主线程执行
if (Looper.getMainLooper().thread == Thread.currentThread()) {
mBlocks.forEach { it.invoke(value) }
} else {
// 切换到主线程执行
App.mHandler.post {
mBlocks.forEach { it.invoke(value) }
}
}
}
// 将匿名函数添加到数据容器中去
fun addListener(block: (T?) -> Unit) {
if (!mBlocks.contains(block)) {
mBlocks.add(block)
}
}
}
判断当前线程是否是UI线程的方法?
Looper.getMainLooper().thread == Thread.currentThread()
这样的话,MVP架构中,存在异步线程更新UI的缺陷也优化处理了