Kotlin项目实战之手机影音---处理mv界面条目点击事件、视频播放处理、响应应用外视频播放请求

本文主要是介绍Kotlin项目实战之手机影音---处理mv界面条目点击事件、视频播放处理、响应应用外视频播放请求,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言:

接着上一次Kotlin项目实战之手机影音---加载mv界面区域数据、mv界面viewpager适配、tablayout适配、mv每一个界面列表显示的功能继续往下学习,在上一次由于在网上找的数据接口挂了,重新又找了一个能用的接口网易云音乐 NodeJS 版 API,这里回忆一下具体用法,不然项目启动时看不到数据很受打击:

1、首先启动node服务器:

进入到官方的源码,然后启动既可:

2、更改本机的ip:

此时要注意了,由于是运行在手机上,不是在电脑上,在APP的访问地址域名是不能用localhost的,需要改成本机的ip地址,也就是改它:

由于本机ip地址是会随着网络变化经常变的,所以在学习时一定要注意它的变化,及时进行更新调整。

处理mv界面条目点击事件:

效果预览:

接下来则处理MV列表的点击播放功能,预期的效果是:

具体实现:

1、给Adapter增加点击事件:

这块都比较熟了,由于我们列表是使用的RecylerView来实现的,它不像ListView一样有现成的API可以监听列表的点击,需要给Item View进行事件监听,然后再自己定义接口回调到界面上,具体做法就是到Adapter中:

它里面的onBindViewHolder()中进行rootView的事件监听:

其中这里复习一下Kotlin的语法,为啥这里可以使用大括号?

关于这块可以参考Kotlin项目实战之手机影音---基类抽取、欢迎界面、抽取startactivityandfinish、主界面布局 - cexo - 博客园之前的详细说明,好接下来则需要定义一个回调方法,这里用两种方式对比着学习。

传统回调实现:

这个就不过多解释了,先定义个接口,然后在里面进行调用既可:

很顺其自然对吧,但是对于Kotlin来说回调其实可以更加简便,所以下面来看一下Kotlin对于回调可以如何来定义?

Kotlin回调实现:

在我们传统定义一个回调时其方法是必须定义在一个类当中的对吧?

但是在Kotlin中就不一样了,函数和类都是一等公民,函数可以独立存在的,所以咱们可以更加简便的来定义回调方法了,如下:

然后使用时如下:

还有另外一种调用方式:

其实熟悉java8也有类似的效果,也是将函数提升为一等公民了。

空安全问题: 

在继续往下编写之前,突然想到个东东,就是前几天看我的csdn的博客上有个网友在一篇Flutter的文章Flutter项目实操---资讯、发布动弹_webor2006的博客-CSDN博客中提了个问题:

其中Flutter也有空安全机制,跟Kotlin类似,这里针对这个问题借着这块代码也来稍加说明一下,其实也就是几种情况,一种是变量为空则加个?既可:

另外如果你在使用变量时,这样用可能会报错对吧:

此时,你必须按要求来处理,第一种是你认为该变量不可能为空,此时可以这样用:

但是此时要注意了,这是你自认为的,如果真的变量为空那么程序肯定就崩溃了,所以平常用它时一定要自己来确保空的问题,另外还有一种比较友好的写法就是我们目前所使用的:

再配合着let【关于let扩展函数的使用可以参考博文阅读密码验证 - 博客园】,也就是如果listener为空,那么它里面的方法是不会执行的,这就保证了一个空安全判断的问题了,而对于Flutter的空安全几乎类似,可能语法有些些不同,度娘一下也很容易理解,这里就顺便回答一下该网友的提问了~~

2、MvPagerFragment来注册点击事件回调监听:

这里运行看一下能否正常的监听到点击的条目:

条目点击跳转到播放界面:

准备Activity:

使用anko库实现Intent的跳转的问题说明:

接下来咱们可以处理一下界面的跳转,通常我们直接使用startActivity来进行跳转:

这里扩展个知识吧,其实可以用一个开源的库来简化调转代码,叫anko,地址为Issues · Kotlin/anko · GitHub,不过打开你会发现,官网已经提示该库已经被废弃了:

其实不影响使用,又可以当作自己一个知识面的扩展,假如你在某个项目中会碰到呢,所以这里还是用一下它,对于anko库它其实包含以下几个应用:

都是来帮我们简化平常的一些调用的,这里定位到Intents:

可能有人会说了,这么一个简单的代码也有必要使用三方库么?怎么说呢,三方库的产生都是有目的的,要不是为了性能,要不就是为了代码更加简洁方便提高咱们的开发效率,我觉得三方库不管你项目有没有用到,可以认识一下,反正现在是学习,多多扩展眼界总是好的,至于要不要用到你的项目中,这块就根据自己的意愿来了,好,既然要用它,则需要添加依赖到工程中,其实在之前Kotlin项目实战之手机影音---项目介绍、项目启动 - cexo - 博客园已经添加进工程了: 

下面直接用一下,你会发现用不了:

这是因为该库只对support的Fragment进行了方法扩展,对于Koltin来说要想达到一个封装通用的作法都是采用对系统类进行一个方法扩展,可以看一下这个startActivity的实现:

所以,结论就是还是采用传统的方式来进行Activity的跳转吧。。那,既然没用了你还写出来干嘛?一是扩宽自己的知识面,二是知道该库存在的问题,三是重新提一下它,因为它还是有使用场景的,比如目前toast的还是可以用的:

为啥,因为它是基于Context进行的系统扩展:

对于Kotlin来说扩展方法这个技巧一定要学会,在平常开发中你基于一些类的扩展可以大大提高开发效率。

所以下面代码还是用传统方式来进行跳转,很显然跳转是需要将当前点击的实体传过去的,但是对于咱们目前的item bean来说有很多在播放界面用不到的属性:

所以,为了传递的简洁,这里再封装一个新的Bean用来进行数据传递,如下:

package com.kotlin.musicplayer.modelimport android.os.Parcel
import android.os.Parcelable/*** 传递给视频播放界面的bean类*/
data class VideoPlayBean(var id: Int, var title: String?, var url: String?) : Parcelable {constructor(parcel: Parcel) : this(parcel.readInt(),parcel.readString(),parcel.readString())override fun writeToParcel(parcel: Parcel, flags: Int) {parcel.writeInt(id)parcel.writeString(title)parcel.writeString(url)}override fun describeContents(): Int {return 0}companion object CREATOR : Parcelable.Creator<VideoPlayBean> {override fun createFromParcel(parcel: Parcel): VideoPlayBean {return VideoPlayBean(parcel)}override fun newArray(size: Int): Array<VideoPlayBean?> {return arrayOfNulls(size)}}}

然后跳转代码如下:

其中mv的地址写死了http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4,本身网上找的API数据不全,这里能正常播就成,不纠结是不是真实有效。

然后在播放界面就可以进行参数接收了,这里打印一下,看参数接收是否一切正常?

运行:

视频播放处理:

接下来就是处理视频播放了,通常也是基于一些三方的框架来傻瓜式的集成,世面上有多少视频播放的开源框架,目前我司项目中使用的是GitHub - CarGuo/GSYVideoPlayer: 视频播放器这款,还是很火的,而这里学习采用另一款https://github.com/Jzvd/JZVideo,节操播入器,具体集成这里就不多说明了,直接按照官网来集成既可,下面将其集成到咱们工程中。

1、添加库依赖:

2、添加布局:

4、设置视频地址、标题:

5、生命周期控制:

6、运行:

接下来运行看一下,发现报错了。。

e: /Users/xiongwei/.gradle/caches/transforms-2/files-2.1/978bdf9bc4b3844ec46f4a1babbe02fe/jetified-jiaozivideoplayer-7.7.0-api.jar!/META-INF/jiaozivideoplayer_release.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.5.1, expected version is 1.1.16.

而且在类上IDE有个提示:

网上搜了一下Error:Kotlin: Module was compiled with an incompatible version of Kotlin. The binary version of its_丧尸爱吃辣的博客-CSDN博客,有说重启一下kotlin插件既可:

发现不好使。。于是在这贴子的评论处又找到一个新的解决方案:Flutter Android 打包报错:Module was compiled with an incompatible version of Kotlin. The binary versi... - 简书,也就是更新一下Kotlin的版本,目前项目用的版本为:

改为:

嗯,貌似可以运行了,此时看一下效果:

此时点击播放时,发现APP崩溃了。。

又度娘一下[Android] java.lang.ClassCastException: Bootstrap method returned null问题处理_SecularBird的专栏-CSDN博客,原来是没有指定jdk的版本为1.8,gradle中指定一下:

再运行看一下,不报错了,但是提示视频播放不了,是因为视频的地址有问题,这里在网上又换了一个地址:用来测试的在线小视频url地址_Mencre的博客-CSDN博客_视频url,视频地址为:https://v-cdn.zjol.com.cn/280443.mp4,具体视频源这里可以自行找找,很有可能之后就不能用了,再运行:

响应应用外视频播放请求:

效果:

对于一款视频播放器,肯定是要支持本地视频打开时可以用咱们的软件来进行播放对吧,效果如下:

关于这块的实现其实也不难,也就是对于Intent的进行一个处理,下面具体来实现一下。

实现:

1、配置intent-filter:

<intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="http" /><data android:scheme="https" /><data android:mimeType="video/mp4" /><data android:mimeType="video/3gp" /><data android:mimeType="video/3gpp" /><data android:mimeType="video/3gpp2" />
</intent-filter>

关于这个fitler可以网上找一下,此时咱们本地找一个视频,打开就可以在列表中出现咱们的应用了:

 当然目前还打不开,因为还没有做相关数据的处理。

2、处理应用外的本地视频请求数据:

这里其实可以先来打印一下本地视频打开来自intent的视频地址:

2021-10-16 05:16:00.004 19194-19194/com.kotlin.musicplayer I/System.out: data=content://com.android.fileexplorer.myprovider/external_files/280443.mp4

 而要访问sdcard内容,肯定需要加权限:

接下来处理播放逻辑:

发现播不了。。为啥呢?因为我手机是9.0的,而在Android7.0以后对于sdcard上的路径都是以content开头的,关于这块可以参考Android7.0sdcard文件访问问题_divaid的博客-CSDN博客,对于我本地的视频路径应该是/storage/emulated/0/280443.mp4,而目前从intent中读取的是content://com.android.fileexplorer.myprovider/external_files/280443.mp4,很明显在7.0以上手机上需要进行处理一下,需要根据content的路径来查找真实的sdcard路径,而方法我是在网上搜到的android uri 解析获取文件真实路径(兼容7.0+)_JokAr-CSDN博客_android uri 获取文件路径,这里将其封装成一个工具方法便于之后在其它界面也可以使用:

而对于工具方法一般都会将类设计成单例的对吧,在Kotlin中怎么来弄呢?代码如下:

package com.kotlin.musicplayer.utilsimport android.content.ContentResolver
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.MediaStore
import android.text.TextUtils
import java.io.*object FileUtil {fun getFileFromUri(uri: Uri?, context: Context?): File? {return if (uri == null) {null} else when (uri.getScheme()) {"content" -> getFileFromContentUri(uri, context)"file" -> File(uri.getPath())else -> null}}/*** Gets the corresponding path to a file from the given content:// URI** @param contentUri The content:// URI to find the file path from* @param context    Context* @return the file path as a string*/private fun getFileFromContentUri(contentUri: Uri?, context: Context?): File? {if (contentUri == null) {return null}var file: File? = nullvar filePath: String? = nullval fileName: Stringval filePathColumn =arrayOf(MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME)val contentResolver: ContentResolver? = context?.getContentResolver()val cursor: Cursor? = contentResolver?.query(contentUri, filePathColumn, null,null, null)if (cursor != null) {cursor.moveToFirst()try {filePath = cursor.getString(cursor.getColumnIndex(filePathColumn[0]))} catch (e: Exception) {}fileName = cursor.getString(cursor.getColumnIndex(filePathColumn[1]))cursor.close()if (!TextUtils.isEmpty(filePath)) {file = File(filePath)}if (!file!!.exists() || file.length() <= 0 || TextUtils.isEmpty(filePath)) {filePath = getPathFromInputStreamUri(context, contentUri, fileName)}if (!TextUtils.isEmpty(filePath)) {file = File(filePath)}}return file}/*** 用流拷贝文件一份到自己APP目录下** @param context* @param uri* @param fileName* @return*/fun getPathFromInputStreamUri(context: Context?, uri: Uri, fileName: String): String? {var inputStream: InputStream? = nullvar filePath: String? = nullif (uri.authority != null) {try {inputStream = context?.contentResolver?.openInputStream(uri)val file = createTemporalFileFrom(context, inputStream, fileName)filePath = file!!.path} catch (e: java.lang.Exception) {} finally {try {if (inputStream != null) {inputStream.close()}} catch (e: java.lang.Exception) {}}}return filePath}@Throws(IOException::class)private fun createTemporalFileFrom(context: Context?,inputStream: InputStream?,fileName: String): File? {var targetFile: File? = nullif (inputStream != null) {var read: Intval buffer = ByteArray(8 * 1024)//自己定义拷贝文件路径targetFile = File(context?.getCacheDir(), fileName)if (targetFile.exists()) {targetFile.delete()}val outputStream: OutputStream = FileOutputStream(targetFile)while (inputStream.read(buffer).also { read = it } != -1) {outputStream.write(buffer, 0, read)}outputStream.flush()try {outputStream.close()} catch (e: IOException) {e.printStackTrace()}}return targetFile}
}

一个object声明就可以了,为啥?其实将它可以转换成java类就明白了:

然后再来修改一下咱们的视频处理代码:

此时就可以来运行看一下效果了,如果你是6.0以上手机,你会发现会报sdcard权限问题:

这是因为对于sdcard权限在6.0以后是需要我们主动申请才行的,这里为了方便,先手动进应用详情中打开它:

因为这块在之后会专门处理的,这里先略过,另外关于动态权限申请框架的搭建可以参考我之前写过的这篇Android9.0动态运行时权限源码分析及封装改造<一>-----运行时权限名词解释、权限检测源码分析 - cexo - 博客园,完整的记录了整个申请过程。好,接下来看一下最终效果:

3、处理应用外的网络视频请求数据:

对于应用外的视频应该咱们播放器还支持网络的对吧,所以接下来处理一下。

1、先新建一个module准备网络视频跳转:

这里应该是在另一个app中来跳到咱们这款播放器app中对吧,这里以新建module的形式来准备这个测试跳转app:

然后搞个在线视频的测试入口,点击则跳转一下,具体如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"tools:context=".MainActivity"><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:onClick="onclick"android:text="打开网络视频" /></RelativeLayout>

好,此时运行在手机上:

2、处理外部网络视频的播放逻辑:

其处理也比较简单:

3、运行:

最后咱们运行看一下:

播放器下面增加ViewPager滑动效果:

效果:

对于这个视频播放界面下面还空出一截对吧,接下来则需要来完善它,效果也很简单:

也就是一个tab滑动切换的效果,由于API目前网上也没找到比较合适的,这里就是占个位,具体内容这里就忽略了,其实就是一些视频的简介之类的,纯展示用的,比较简单,这里快速过一下。

实现:

1、布局准备:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><cn.jzvd.JzvdStdandroid:id="@+id/jz_video"android:layout_width="match_parent"android:layout_height="200dp" /><RadioGroupandroid:id="@+id/rg"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="20dp"android:orientation="horizontal"><RadioButtonandroid:id="@+id/rb1"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:background="@drawable/mv_description"android:button="@null"android:checked="true" /><RadioButtonandroid:id="@+id/rb2"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:background="@drawable/mv_comment"android:button="@null" /><RadioButtonandroid:id="@+id/rb3"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:background="@drawable/mv_relative"android:button="@null" /></RadioGroup><androidx.viewpager.widget.ViewPagerandroid:id="@+id/viewPager"android:layout_width="match_parent"android:layout_height="match_parent" />
</LinearLayout>

其中RadioButton有三个背景资源,如下:

mv_comment.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:drawable="@mipmap/player_comment_p" android:state_checked="true"/><item android:drawable="@mipmap/player_comment"/>
</selector>

mv_description.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:drawable="@mipmap/player_mv_p" android:state_checked="true"/><item android:drawable="@mipmap/player_mv"/>
</selector>

mv_relative.xml:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"><item android:drawable="@mipmap/player_relative_mv_p" android:state_checked="true"/><item android:drawable="@mipmap/player_relative_mv"/>
</selector>

涉及到的图片如下:

player_comment.png:

player_comment_p.png:

player_mv.png:

player_mv_p.png:

player_relative_mv.png:

player_relative_mv_p.png:

2、 准备Fragment:

由于就是占一个位,这里用一个Fragment既可,如下:

3、处理切换逻辑:

这块直接把代码贴出来了,都比较熟了,Kotlin语法也比较简单:

package com.kotlin.musicplayer.ui.activityimport androidx.viewpager.widget.ViewPager
import cn.jzvd.Jzvd
import com.kotlin.musicplayer.R
import com.kotlin.musicplayer.adapter.VideoPagerAdapter
import com.kotlin.musicplayer.base.BaseActivity
import com.kotlin.musicplayer.model.VideoPlayBean
import com.kotlin.musicplayer.utils.FileUtil
import kotlinx.android.synthetic.main.activity_video_player.*/*** 视频播放详情界面*/
class VideoPlayerActivity : BaseActivity() {override fun getLayoutId(): Int {return R.layout.activity_video_player}override fun initData() {super.initData()val data = intent.dataprintln("data=$data")if (data == null) {//应用内视频处理val videoPlayBean = intent.getParcelableExtra<VideoPlayBean>("item")jz_video.setUp(videoPlayBean.url, videoPlayBean.title)} else {//应用外视频处理if (data.toString().startsWith("http")) {//网络视频jz_video.setUp(data.toString(), data.toString())} else {//本地视频val filePath = FileUtil.getFileFromUri(data, this)?.absolutePathjz_video.setUp(filePath, filePath)}}}override fun onBackPressed() {if (Jzvd.backPress()) {return}super.onBackPressed()}override fun onPause() {super.onPause()Jzvd.releaseAllVideos()}override fun initListeners() {//适配viewpagerviewPager.adapter = VideoPagerAdapter(supportFragmentManager)//radiogroup选中监听rg.setOnCheckedChangeListener { radioGroup, i ->when (i) {R.id.rb1 -> viewPager.setCurrentItem(0)R.id.rb2 -> viewPager.setCurrentItem(1)R.id.rb3 -> viewPager.setCurrentItem(2)}}//viewpager选中状态监听viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {/*** 滑动状态改变的回调*/override fun onPageScrollStateChanged(state: Int) {}/*** 滑动回调*/override fun onPageScrolled(position: Int,positionOffset: Float,positionOffsetPixels: Int) {}/*** 选中状态改变回调*/override fun onPageSelected(position: Int) {when (position) {0 -> rg.check(R.id.rb1)1 -> rg.check(R.id.rb2)2 -> rg.check(R.id.rb3)}}})}
}

4、运行:

关注个人公众号,获得实时推送

这篇关于Kotlin项目实战之手机影音---处理mv界面条目点击事件、视频播放处理、响应应用外视频播放请求的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/798012

相关文章

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.

Python+PyQt5实现多屏幕协同播放功能

《Python+PyQt5实现多屏幕协同播放功能》在现代会议展示、数字广告、展览展示等场景中,多屏幕协同播放已成为刚需,下面我们就来看看如何利用Python和PyQt5开发一套功能强大的跨屏播控系统吧... 目录一、项目概述:突破传统播放限制二、核心技术解析2.1 多屏管理机制2.2 播放引擎设计2.3 专

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2

一文详解SpringBoot响应压缩功能的配置与优化

《一文详解SpringBoot响应压缩功能的配置与优化》SpringBoot的响应压缩功能基于智能协商机制,需同时满足很多条件,本文主要为大家详细介绍了SpringBoot响应压缩功能的配置与优化,需... 目录一、核心工作机制1.1 自动协商触发条件1.2 压缩处理流程二、配置方案详解2.1 基础YAML

一文教你如何将maven项目转成web项目

《一文教你如何将maven项目转成web项目》在软件开发过程中,有时我们需要将一个普通的Maven项目转换为Web项目,以便能够部署到Web容器中运行,本文将详细介绍如何通过简单的步骤完成这一转换过程... 目录准备工作步骤一:修改​​pom.XML​​1.1 添加​​packaging​​标签1.2 添加

tomcat多实例部署的项目实践

《tomcat多实例部署的项目实践》Tomcat多实例是指在一台设备上运行多个Tomcat服务,这些Tomcat相互独立,本文主要介绍了tomcat多实例部署的项目实践,具有一定的参考价值,感兴趣的可... 目录1.创建项目目录,测试文China编程件2js.创建实例的安装目录3.准备实例的配置文件4.编辑实例的

python+opencv处理颜色之将目标颜色转换实例代码

《python+opencv处理颜色之将目标颜色转换实例代码》OpenCV是一个的跨平台计算机视觉库,可以运行在Linux、Windows和MacOS操作系统上,:本文主要介绍python+ope... 目录下面是代码+ 效果 + 解释转HSV: 关于颜色总是要转HSV的掩膜再标注总结 目标:将红色的部分滤

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

Python实现自动化接收与处理手机验证码

《Python实现自动化接收与处理手机验证码》在移动互联网时代,短信验证码已成为身份验证、账号注册等环节的重要安全手段,本文将介绍如何利用Python实现验证码的自动接收,识别与转发,需要的可以参考下... 目录引言一、准备工作1.1 硬件与软件需求1.2 环境配置二、核心功能实现2.1 短信监听与获取2.