现在kotlin成为android开发的主流开发语言,相对于java,可谓有过之而无不及,例如处理了空指针,简化了代码,使用精简的语法代替了相对繁琐的语法,采用协程代替了线程的使用,匿名函数更方便的处理逻辑.....单从这些方面就可以知道kotlin相对于java更精简,更安全,更方便,那么它是如何体现这些的呢?废话不多说,以我写的一个本地音乐播放器为例,从代码方面去更好的理解这些优势。
这个播放器是一个播放手机本地文件中的mp3的音乐文件的播放器,其中的主要的步骤是:
1:进入主页面,onCreate中循环遍历手机本地文件夹查找所有mp3文件
2:将获取到的mp3文件的路径放入MutableList<String>中,通过
MutableLiveData<MutableList<String>>的方式将数据返回给到activity
3:通过Recyclerview设置adapter将数据展示出来
4:通过MediaPlayer将选中文件播放出来
开发架构模式:MVVM(kotlin+DataBinding+LiveData+协程)
下面从代码方面分析以上四点各点的知识点:(下面的第一点对应上面的第一点,以此类推)
1:进入主页面,需要先动态获取文件权限
(1)申请文件读写权限:
/**
* 申请文件读写权限
*/
fun requestFilePermission() {
if (!checkPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) || !checkPermission(
Manifest.permission.READ_EXTERNAL_STORAGE)) {
activity.requestPermissions(
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
// Manifest.permission.MANAGE_EXTERNAL_STORAGE
), REQUEST_WRITE_EXTERNAL_STORAGE
)
} else {
onFilePermissionCallback(true)
}
}
(2) 通过回调确认是否获取动态权限:
/**
* 文件权限获取结果的回调
*/
fun onRequestResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
when (requestCode) {
REQUEST_WRITE_EXTERNAL_STORAGE -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//已经申请权限
onFilePermissionCallback(true)
} else {
//申请权限失败
onFilePermissionCallback(false)
}
}
}
}
注意此处获取到的文件权限结果是通过匿名函数
onFilePermissionCallback: (result: Boolean) -> Any
的方式(类似于java的回调接口)返回的,使用方式如下(举例):
//调用函数
fun a(b:Int,c:String,back:(d:Int)->Unit){
back(b)
}
//函数实现
val result=a(5,"daxiang"){
//此处为back返回的值为b,因此为5,可用it来操作
}
(3)获取到文件读写权限后,遍历手机本地文件夹,查询所有.mp3文件,如下:
/**
* 查找本地音乐文件
*/
fun findMusicFile(onMusicListCallback: (MutableList<String>) -> Unit) {
val rootUrl: String = Environment.getExternalStorageDirectory().path //+"/gx"
Log.d("dx", "rooturl=$rootUrl")
val rootFile: File = File(rootUrl)
mainScope.launch {
onMusicListCallback(withContext(Dispatchers.IO) {
musics.clear()
loopFile(rootFile)
musics
})
}
}
/**
* 循环遍历文件夹获取文件后缀为.mp3的文件
*/
private fun loopFile(file: File) {
val files = file.listFiles()
if (files != null) {
files.forEach {
if (it.isDirectory) {
Log.d("dx", "is Directory=${it.path}")
loopFile(it)
} else {
Log.d("dx", "is not Directory=${it.path}")
if (it.path.contains(".mp3")) {
Log.d("dx", "add success")
musics.add(it.path)
}
}
}
}
}
2:将获取到的文件数据返回给到activity
/**
* 监听音乐数据
*/
fun listenMusicData() {
musicViewModel.getMusicData().observe(this, Observer {
Log.d("dx", "mainactivity musicdata $it")
refreshData(it)
})
}
3:将数据通过adapter展示出来:
/**
* 刷新数据列表
*/
private fun refreshData(result: MutableList<String>) {
musicData=result
musicAdapter.refreshData(result,-1)
}
4:通过MediaPlayer播放对应选项的歌曲
override fun play(position: Int) {
// musicPlayer.release()
//判断是否点击播放按钮
if (position == -1) {
if (!musicPlayer.isPlaying) {
if (isPrepared) {
musicPlayer.start()
} else {
play(0)
}
}
return
}
var resultUrl = data[position]
Log.d("dx", "resultUrl=$resultUrl")
if (resultUrl.isEmpty()) {
return
}
if (musicPlayer.isPlaying && currentPosition == position) {
return
} else {
musicPlayer.reset()
}
musicPlayer.setDataSource(resultUrl)
musicPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC)
musicPlayer.prepareAsync()
musicPlayer.setOnPreparedListener(MediaPlayer.OnPreparedListener {
isPrepared = true
musicPlayer.start()
})
musicPlayer.setOnCompletionListener {
next()
}
currentPosition = position
onPositionCallback(position)
}
通过以上可以看出
1):匿名函数代替java接口回调方式(上面已讲)
2):协程的使用:
mainScope.launch {//协程作用域 onMusicListCallback(withContext(Dispatchers.IO) {//运行在IO线程中 musics.clear() loopFile(rootFile) musics //最后一行代表该匿名函数返回值 }) }
3):语法的简化:
很多,这里体现的是
files.forEach {...}代替了java的for循环,匿名函数代替了java接口回调,协程 代替了java线程,对象属性的调用可以直接使用而不需要通过get,set方法获取等等 (更多的语法使用优势需要大家去查看熟悉kotlin语法)
这些步骤我只贴了关键位置的代码,整个完整的代码我上传到了github上面,需要的可以使用
git copy下来查看,感谢。完整代码:https://github.com/daxiangzaici214703306/KtPlayerhttps://github.com/daxiangzaici214703306/KtPlayer