diff --git a/app/src/main/java/com/example/musicplayer/event/AlbumCollectionEvent.java b/app/src/main/java/com/example/musicplayer/event/AlbumCollectionEvent.java index 115b1c4..5c78301 100644 --- a/app/src/main/java/com/example/musicplayer/event/AlbumCollectionEvent.java +++ b/app/src/main/java/com/example/musicplayer/event/AlbumCollectionEvent.java @@ -1,12 +1,26 @@ package com.example.musicplayer.event; +// 该类的文档注释,提供了类的作者、创建时间和类的基本描述信息 /** *
  *     author : 残渊
  *     time   : 2019/07/19
  *     desc   : 收藏专辑事件
+ * 
+ *     该类表示一个与收藏专辑相关的事件。
+ *     它可以用于在音乐播放器应用中,当用户执行收藏专辑操作时,触发相关的业务逻辑。
+ *     例如,通知界面更新收藏状态,或者与后端服务器进行数据同步,告知服务器用户已收藏了某张专辑。
+ *     该类目前是一个空类,可能会在后续开发中添加相关属性和方法,
+ *     以携带收藏事件的具体信息,如收藏的专辑ID、收藏的时间等。
  * 
*/ - +// 定义一个名为 AlbumCollectionEvent 的公共类 public class AlbumCollectionEvent { -} + // 目前该类为空,没有任何成员变量和方法,可在后续开发中添加。 + // 例如,可能会添加以下成员变量: + // private int albumId; 用于存储收藏的专辑的唯一标识符 + // private Date collectionTime; 用于存储用户收藏该专辑的时间 + // 可能会添加以下方法: + // public void notifyUI() { } 用于通知用户界面更新收藏状态 + // public void syncWithServer() { } 用于将收藏信息同步到服务器 +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/event/DownloadEvent.java b/app/src/main/java/com/example/musicplayer/event/DownloadEvent.java index 92055c0..209997b 100644 --- a/app/src/main/java/com/example/musicplayer/event/DownloadEvent.java +++ b/app/src/main/java/com/example/musicplayer/event/DownloadEvent.java @@ -1,5 +1,6 @@ package com.example.musicplayer.event; +// 导入 DownloadInfo 类,因为 DownloadEvent 类中使用了该类 import com.example.musicplayer.entiy.DownloadInfo; /** @@ -7,42 +8,61 @@ import com.example.musicplayer.entiy.DownloadInfo; * author : 残渊 * time : 2019/09/16 * desc : + * + * 这个类是一个下载事件类,用于表示音乐播放器应用中的下载相关事件。 + * 它可以存储下载的状态信息,下载信息对象,以及在列表中的位置信息。 + * 可以在下载开始、下载中、下载完成、下载失败等不同阶段创建不同的 DownloadEvent 对象, + * 并将其传递给其他组件,以实现下载事件的通知和处理。 * */ - public class DownloadEvent { - private int downloadStatus;//下载的状态 + // 存储下载的状态,可用于表示下载是否开始、正在进行、完成、失败等不同状态 + private int downloadStatus; + // 存储下载信息,如文件大小、下载速度、已下载量等 private DownloadInfo downloadInfo; + // 存储在下载列表或队列中的位置,方便进行位置相关的操作,比如更新 UI 中的下载进度条位置 private int position; + // 构造函数,仅接收下载状态作为参数 public DownloadEvent(int status){ + // 将传入的状态存储到 downloadStatus 成员变量中 downloadStatus = status; } + // 构造函数,接收下载状态和 DownloadInfo 对象作为参数 public DownloadEvent(int status, DownloadInfo downloadInfo){ + // 将传入的状态存储到 downloadStatus 成员变量中 downloadStatus = status; + // 将传入的 DownloadInfo 对象存储到 downloadInfo 成员变量中 this.downloadInfo = downloadInfo; } + // 构造函数,接收下载状态和位置作为参数 public DownloadEvent(int status,int position){ + // 将传入的状态存储到 downloadStatus 成员变量中 downloadStatus = status; + // 将传入的位置存储到 position 成员变量中 this.position = position; } + // 获取位置的方法 public int getPosition() { return position; } + // 设置位置的方法 public void setPosition(int position) { this.position = position; } + // 获取下载状态的方法 public int getDownloadStatus() { return downloadStatus; } + // 获取下载信息的方法 public DownloadInfo getDownloadInfo() { return downloadInfo; } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/event/OnlineSongChangeEvent.java b/app/src/main/java/com/example/musicplayer/event/OnlineSongChangeEvent.java index f845dbb..74b6708 100644 --- a/app/src/main/java/com/example/musicplayer/event/OnlineSongChangeEvent.java +++ b/app/src/main/java/com/example/musicplayer/event/OnlineSongChangeEvent.java @@ -5,8 +5,16 @@ package com.example.musicplayer.event; * author : 残渊 * time : 2019/07/19 * desc : 网络歌曲切换事件 + * + * 该类表示一个网络歌曲切换的事件类。 + * 当用户在音乐播放器中切换网络歌曲时,可创建该类的对象,用于触发相应的事件处理逻辑。 + * 例如,可以通知用户界面更新歌曲信息,如歌曲名称、歌手、专辑封面等, + * 也可以通知音频播放组件切换播放的歌曲源, + * 或者通知后台服务记录用户的播放历史和歌曲切换行为。 + * 目前该类为空,后续可根据需求添加成员变量和方法。 * */ - public class OnlineSongChangeEvent { -} + // 目前该类为空,可在后续开发中添加成员变量和方法。 + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/event/OnlineSongErrorEvent.java b/app/src/main/java/com/example/musicplayer/event/OnlineSongErrorEvent.java index 17b223f..289921c 100644 --- a/app/src/main/java/com/example/musicplayer/event/OnlineSongErrorEvent.java +++ b/app/src/main/java/com/example/musicplayer/event/OnlineSongErrorEvent.java @@ -5,8 +5,15 @@ package com.example.musicplayer.event; * author : 残渊 * time : 2019/07/20 * desc : 网络歌曲错误 + * + * 这个类是一个事件类,用于表示在音乐播放器中播放网络歌曲时出现的错误事件。 + * 当网络歌曲播放过程中发生错误,例如网络连接中断、歌曲资源不存在、服务器错误等情况时, + * 可以创建该类的对象,以触发相应的错误处理逻辑。 + * 可能会被用来通知用户界面显示错误消息,记录错误日志, + * 或者尝试重新连接网络或获取歌曲资源等操作。 + * 目前该类为空,后续可以根据具体的错误处理需求添加成员变量和方法。 * */ - public class OnlineSongErrorEvent { -} + // 目前该类为空,可在后续开发中添加成员变量和方法。 +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/event/SongAlbumEvent.java b/app/src/main/java/com/example/musicplayer/event/SongAlbumEvent.java index 507f9ed..0e1c1d7 100644 --- a/app/src/main/java/com/example/musicplayer/event/SongAlbumEvent.java +++ b/app/src/main/java/com/example/musicplayer/event/SongAlbumEvent.java @@ -5,8 +5,15 @@ package com.example.musicplayer.event; * author : 残渊 * time : 2019/07/19 * desc : 播放专辑歌曲事件 + * + * 该类用于表示在音乐播放器中播放专辑歌曲时的事件。 + * 当用户选择播放专辑中的歌曲时,可以创建该类的对象,以触发相应的播放事件处理逻辑。 + * 它可能会被用来通知用户界面更新播放界面,例如显示当前播放的歌曲信息、专辑封面等, + * 也可以通知音频播放组件开始播放专辑歌曲, + * 或者与后台服务交互,记录播放历史、更新用户偏好等。 + * 目前该类为空,后续可根据需要添加成员变量和方法。 * */ - public class SongAlbumEvent { -} + // 目前该类为空,可在后续开发中添加成员变量和方法。 +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/event/SongCollectionEvent.java b/app/src/main/java/com/example/musicplayer/event/SongCollectionEvent.java index 1fc2f69..e56b403 100644 --- a/app/src/main/java/com/example/musicplayer/event/SongCollectionEvent.java +++ b/app/src/main/java/com/example/musicplayer/event/SongCollectionEvent.java @@ -5,16 +5,26 @@ package com.example.musicplayer.event; * author : 残渊 * time : 2019/07/19 * desc : 收藏歌曲事件 + * + * 该类用于表示在音乐播放器中收藏歌曲的事件。 + * 当用户对一首歌曲进行收藏操作时,可创建该类的对象。 + * 它可以存储歌曲的收藏状态,并提供方法来获取和设置该状态。 + * 该类可用于触发与歌曲收藏相关的业务逻辑,如更新用户界面显示, + * 或者将收藏状态同步到服务器或本地存储中。 * */ - public class SongCollectionEvent { + // 存储歌曲的收藏状态,true 表示已收藏,false 表示未收藏 private boolean isLove; + + // 构造函数,接收一个布尔值表示收藏状态 public SongCollectionEvent(boolean isLove){ + // 将传入的收藏状态存储到 isLove 成员变量中 this.isLove = isLove; } + // 获取歌曲的收藏状态的方法 public boolean isLove() { return isLove; } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/event/SongDownloadedEvent.java b/app/src/main/java/com/example/musicplayer/event/SongDownloadedEvent.java index 727a44b..0d9c528 100644 --- a/app/src/main/java/com/example/musicplayer/event/SongDownloadedEvent.java +++ b/app/src/main/java/com/example/musicplayer/event/SongDownloadedEvent.java @@ -5,8 +5,16 @@ package com.example.musicplayer.event; * author : 残渊 * time : 2019/09/18 * desc : 已下载歌曲改变的消息 + * + * 该类表示已下载歌曲发生改变的事件。 + * 当音乐播放器中的已下载歌曲列表发生变化时,比如新增了一首已下载歌曲, + * 或者删除了一首已下载歌曲,可创建该类的对象,以触发相应的事件处理逻辑。 + * 此事件可以用于通知用户界面更新已下载歌曲列表的显示, + * 也可以通知其他相关组件,如数据库管理器更新存储信息, + * 或者通知播放列表管理器更新可播放歌曲列表。 + * 目前该类为空,后续可根据需求添加成员变量和方法。 * */ - public class SongDownloadedEvent { + // 该类目前为空,后续可根据具体的需求添加成员变量和方法。 } diff --git a/app/src/main/java/com/example/musicplayer/event/SongHistoryEvent.java b/app/src/main/java/com/example/musicplayer/event/SongHistoryEvent.java index 03bd647..897028d 100644 --- a/app/src/main/java/com/example/musicplayer/event/SongHistoryEvent.java +++ b/app/src/main/java/com/example/musicplayer/event/SongHistoryEvent.java @@ -5,8 +5,15 @@ package com.example.musicplayer.event; * author : 残渊 * time : 2019/07/19 * desc : 最近播放歌曲事件 + * + * 该类表示一个最近播放歌曲的事件。 + * 当用户播放一首歌曲时,可创建该类的对象,用于触发最近播放歌曲的相关事件处理逻辑。 + * 可以利用此事件来更新用户界面,显示最近播放歌曲列表, + * 或者通知存储服务更新最近播放歌曲的存储信息, + * 还可以用来向用户推荐与最近播放歌曲相似的歌曲等。 + * 目前该类为空,后续可根据需求添加成员变量和方法。 * */ - public class SongHistoryEvent { + // 目前该类为空,后续可根据具体需求添加成员变量和方法。 } diff --git a/app/src/main/java/com/example/musicplayer/event/SongListNumEvent.java b/app/src/main/java/com/example/musicplayer/event/SongListNumEvent.java index 79e2ec1..d70a1a7 100644 --- a/app/src/main/java/com/example/musicplayer/event/SongListNumEvent.java +++ b/app/src/main/java/com/example/musicplayer/event/SongListNumEvent.java @@ -5,20 +5,31 @@ package com.example.musicplayer.event; * author : 残渊 * time : 2019/09/20 * desc : 歌曲列表数量变化消息 + * + * 该类用于表示歌曲列表数量发生变化的事件。 + * 当歌曲列表中的歌曲数量增加或减少时,可以创建该类的对象, + * 并将该对象传递给相关组件,以触发相应的处理逻辑。 + * 其中的 "type" 成员变量可用于表示歌曲列表数量变化的类型, + * 例如,正数表示增加,负数表示减少,不同的绝对值可能表示不同的操作量。 + * 该类提供了获取和设置 "type" 成员变量的方法,方便其他组件操作。 * */ - public class SongListNumEvent { + // 存储歌曲列表数量变化的类型,可用于表示数量的增减及变化量 private int type; + + // 构造函数,接收一个整数表示歌曲列表数量变化的类型,并将其存储在 type 变量中 public SongListNumEvent(int type){ this.type = type; } + // 获取歌曲列表数量变化类型的方法 public int getType() { return type; } + // 设置歌曲列表数量变化类型的方法 public void setType(int type) { this.type = type; } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/event/SongLocalEvent.java b/app/src/main/java/com/example/musicplayer/event/SongLocalEvent.java index 1b7921e..00acd4e 100644 --- a/app/src/main/java/com/example/musicplayer/event/SongLocalEvent.java +++ b/app/src/main/java/com/example/musicplayer/event/SongLocalEvent.java @@ -5,8 +5,16 @@ package com.example.musicplayer.event; * author : 残渊 * time : 2019/07/19 * desc : 本地歌曲改变事件 + * + * 该类用于表示本地歌曲发生改变的事件。 + * 当用户对本地歌曲库进行操作,如添加、删除或修改本地歌曲时, + * 可以创建该类的对象,以触发相应的事件处理逻辑。 + * 它可用于通知用户界面更新本地歌曲列表的显示, + * 或者通知文件管理系统更新存储信息, + * 也可以用于通知播放列表管理器更新可播放歌曲列表。 + * 目前该类为空,后续可根据需求添加成员变量和方法。 * */ - public class SongLocalEvent { -} + // 目前该类为空,后续可根据具体需求添加成员变量和方法。 +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/event/SongLocalSizeChangeEvent.java b/app/src/main/java/com/example/musicplayer/event/SongLocalSizeChangeEvent.java index 739edaa..749fe08 100644 --- a/app/src/main/java/com/example/musicplayer/event/SongLocalSizeChangeEvent.java +++ b/app/src/main/java/com/example/musicplayer/event/SongLocalSizeChangeEvent.java @@ -5,8 +5,15 @@ package com.example.musicplayer.event; * author : 残渊 * time : 2019/07/19 * desc : 导入本地音乐事件 + * + * 该类表示一个导入本地音乐的事件。 + * 当用户将本地音乐文件导入到音乐播放器中时,可创建该类的对象, + * 用于触发相关的事件处理逻辑。它可以用来通知用户界面更新音乐列表, + * 也可以通知存储系统更新本地音乐存储信息, + * 或者通知后台服务进行导入后的后续处理,例如索引构建或信息更新等。 + * 目前该类为空,后续可根据需要添加成员变量和方法。 * */ - public class SongLocalSizeChangeEvent { -} + // 该类目前为空,可在后续开发中添加成员变量和方法。 +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/event/SongStatusEvent.java b/app/src/main/java/com/example/musicplayer/event/SongStatusEvent.java index 0111f38..da7e7cd 100644 --- a/app/src/main/java/com/example/musicplayer/event/SongStatusEvent.java +++ b/app/src/main/java/com/example/musicplayer/event/SongStatusEvent.java @@ -5,17 +5,28 @@ package com.example.musicplayer.event; * author : 残渊 * time : 2019/07/19 * desc : 歌曲播放事件 + * + * 该类用于表示歌曲播放过程中的事件,主要是歌曲的播放状态。 + * 当歌曲的播放状态发生变化时,例如开始播放、暂停播放、停止播放等,可以创建该类的对象。 + * 它包含一个 songStatus 成员变量,用于存储歌曲的当前播放状态, + * 并提供了构造函数来初始化该状态,同时提供了一个获取歌曲播放状态的方法。 + * 此事件类可以用于通知用户界面更新播放状态的显示, + * 或者通知其他相关组件,如音频播放组件调整播放操作, + * 还可以用于记录播放状态日志等。 * */ - public class SongStatusEvent { + // 存储歌曲的播放状态,不同的整数值可以表示不同的播放状态, + // 例如:0 表示停止,1 表示正在播放,2 表示暂停等,具体可根据应用需求定义 private int songStatus; + // 构造函数,接收一个整数表示歌曲的播放状态,并将其存储在 songStatus 变量中 public SongStatusEvent(int songStatus){ this.songStatus = songStatus; } + // 获取歌曲播放状态的方法 public int getSongStatus() { return songStatus; } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/model/DataModel.java b/app/src/main/java/com/example/musicplayer/model/DataModel.java index b2b882e..2cfcf0c 100644 --- a/app/src/main/java/com/example/musicplayer/model/DataModel.java +++ b/app/src/main/java/com/example/musicplayer/model/DataModel.java @@ -25,92 +25,164 @@ import io.reactivex.Observable; * author : 残渊 * time : 2019/07/15 * desc : 数据操作集合接口类 + * + * 该类实现了 NetworkHelper、DbHelper 和 PreferencesHelper 接口, + * 作为数据操作的集合接口类,将网络操作、数据库操作和偏好设置操作整合在一起。 + * 它通过组合 NetworkHelperImpl、DbHelperImpl 和 PreferencesHelperImpl 类的实例, + * 实现了具体的数据操作功能,包括网络请求、数据库操作和播放模式的存储与获取。 * */ - -public class DataModel implements NetworkHelper, DbHelper,PreferencesHelper { +public class DataModel implements NetworkHelper, DbHelper, PreferencesHelper { + // 声明一个 NetworkHelperImpl 类型的私有成员变量 mNetworkHelper,用于存储网络操作的实现类对象 private NetworkHelperImpl mNetworkHelper; + // 声明一个 DbHelperImpl 类型的私有成员变量 mDbHelper,用于存储数据库操作的实现类对象 private DbHelperImpl mDbHelper; + // 声明一个 PreferencesHelperImpl 类型的私有成员变量 mPreferencesHelper,用于存储偏好设置操作的实现类对象 private PreferencesHelperImpl mPreferencesHelper; - public DataModel(NetworkHelperImpl networkHelper,DbHelperImpl dbHelper,PreferencesHelperImpl preferencesHelper){ + // 构造函数,用于创建 DataModel 对象 + // 参数 networkHelper:NetworkHelperImpl 类型的对象,提供网络操作的具体实现 + // 参数 dbHelper:DbHelperImpl 类型的对象,提供数据库操作的具体实现 + // 参数 preferencesHelper:PreferencesHelperImpl 类型的对象,提供偏好设置操作的具体实现 + public DataModel(NetworkHelperImpl networkHelper, DbHelperImpl dbHelper, PreferencesHelperImpl preferencesHelper) { + // 将传入的网络操作实现类对象存储在 mNetworkHelper 成员变量中 mNetworkHelper = networkHelper; + // 将传入的数据库操作实现类对象存储在 mDbHelper 成员变量中 mDbHelper = dbHelper; + // 将传入的偏好设置操作实现类对象存储在 mPreferencesHelper 成员变量中 mPreferencesHelper = preferencesHelper; } + // 实现 NetworkHelper 接口中的 getAlbumSong 方法,用于获取专辑歌曲信息 + // 参数 id:专辑歌曲的唯一标识符,用于指定要获取的专辑歌曲 + // 返回值:一个 Observable 对象,用于异步获取专辑歌曲信息 @Override public Observable getAlbumSong(String id) { + // 调用 mNetworkHelper 的 getAlbumSong 方法来执行获取专辑歌曲的操作 return mNetworkHelper.getAlbumSong(id); } + // 实现 NetworkHelper 接口中的 search 方法,用于搜索歌曲 + // 参数 seek:搜索关键字,用于指定搜索的内容 + // 参数 offset:搜索结果的偏移量,用于分页等操作 + // 返回值:一个 Observable 对象,用于异步获取搜索歌曲的结果 @Override public Observable search(String seek, int offset) { + // 调用 mNetworkHelper 的 search 方法来执行歌曲搜索操作 return mNetworkHelper.search(seek, offset); } + // 实现 NetworkHelper 接口中的 searchAlbum 方法,用于搜索专辑 + // 参数 seek:搜索关键字,用于指定搜索的内容 + // 参数 offset:搜索结果的偏移量,用于分页等操作 + // 返回值:一个 Observable 对象,用于异步获取搜索专辑的结果 @Override public Observable searchAlbum(String seek, int offset) { + // 调用 mNetworkHelper 的 searchAlbum 方法来执行专辑搜索操作 return mNetworkHelper.searchAlbum(seek, offset); } + // 实现 NetworkHelper 接口中的 getLrc 方法,用于获取歌词 + // 参数 seek:搜索关键字,用于指定搜索的歌词内容 + // 返回值:一个 Observable 对象,用于异步获取歌词的结果 @Override public Observable getLrc(String seek) { + // 调用 mNetworkHelper 的 getLrc 方法来执行获取歌词的操作 return mNetworkHelper.getLrc(seek); } + // 实现 NetworkHelper 接口中的 getOnlineSongLrc 方法,用于获取网络歌曲的歌词 + // 参数 songId:歌曲的唯一标识符,用于指定要获取歌词的网络歌曲 + // 返回值:一个 Observable 对象,用于异步获取网络歌曲歌词的结果 @Override public Observable getOnlineSongLrc(String songId) { + // 调用 mNetworkHelper 的 getOnlineSongLrc 方法来执行获取网络歌曲歌词的操作 return mNetworkHelper.getOnlineSongLrc(songId); } + // 实现 NetworkHelper 接口中的 getSingerImg 方法,用于获取歌手头像 + // 参数 singer:歌手的名称,用于指定要获取头像的歌手 + // 返回值:一个 Observable 对象,用于异步获取歌手头像的结果 @Override public Observable getSingerImg(String singer) { + // 调用 mNetworkHelper 的 getSingerImg 方法来执行获取歌手头像的操作 return mNetworkHelper.getSingerImg(singer); } + // 实现 NetworkHelper 接口中的 getSongUrl 方法,用于获取歌曲的播放地址 + // 参数 data:与播放地址相关的数据,可能包含歌曲的相关信息 + // 返回值:一个 Observable 对象,用于异步获取歌曲播放地址的结果 @Override public Observable getSongUrl(String data) { + // 调用 mNetworkHelper 的 getSongUrl 方法来执行获取歌曲播放地址的操作 return mNetworkHelper.getSongUrl(data); } + // 实现 DbHelper 接口中的 insertAllAlbumSong 方法,用于将专辑歌曲列表插入数据库 + // 参数 songList:包含专辑歌曲信息的列表,其中元素类型为 AlbumSong.DataBean.ListBean @Override public void insertAllAlbumSong(List songList) { - mDbHelper.insertAllAlbumSong(songList); + // 调用 mDbHelper 的 insertAllAlbumSong 方法将专辑歌曲列表插入数据库 + mDbHelper.insertAllAlbumSong(songList); } + // 实现 DbHelper 接口中的 getLocalMp3Info 方法,用于获取本地 MP3 信息 + // 返回值:一个 List 对象,存储本地歌曲信息 @Override public List getLocalMp3Info() { + // 调用 mDbHelper 的 getLocalMp3Info 方法来获取本地歌曲信息 return mDbHelper.getLocalMp3Info(); } + // 实现 DbHelper 接口中的 saveSong 方法,用于保存本地歌曲到数据库 + // 参数 localSongs:要保存的本地歌曲列表,其中元素类型为 LocalSong + // 返回值:一个 boolean 值,表示保存操作是否成功 @Override public boolean saveSong(List localSongs) { + // 调用 mDbHelper 的 saveSong 方法来保存本地歌曲 return mDbHelper.saveSong(localSongs); } + // 实现 DbHelper 接口中的 queryLove 方法,用于查询歌曲是否被收藏 + // 参数 songId:歌曲的唯一标识符,用于指定要查询的歌曲 + // 返回值:一个 boolean 值,表示歌曲是否被收藏 @Override public boolean queryLove(String songId) { + // 调用 mDbHelper 的 queryLove 方法来查询歌曲是否被收藏 return mDbHelper.queryLove(songId); } + // 实现 DbHelper 接口中的 saveToLove 方法,用于将歌曲收藏到数据库 + // 参数 song:要收藏的歌曲对象,类型为 Song + // 返回值:一个 boolean 值,表示收藏操作是否成功 @Override public boolean saveToLove(Song song) { + // 调用 mDbHelper 的 saveToLove 方法来执行收藏歌曲的操作 return mDbHelper.saveToLove(song); } + // 实现 DbHelper 接口中的 deleteFromLove 方法,用于从收藏中删除歌曲 + // 参数 songId:歌曲的唯一标识符,用于指定要删除收藏的歌曲 + // 返回值:一个 boolean 值,表示删除操作是否成功 @Override public boolean deleteFromLove(String songId) { + // 调用 mDbHelper 的 deleteFromLove 方法来执行从收藏中删除歌曲的操作 return mDbHelper.deleteFromLove(songId); } + // 实现 PreferencesHelper 接口中的 setPlayMode 方法,用于设置播放模式 + // 参数 mode:表示播放模式的整数,可用于设置不同的播放模式,如顺序播放、随机播放等 @Override public void setPlayMode(int mode) { + // 调用 mPreferencesHelper 的 setPlayMode 方法来设置播放模式 mPreferencesHelper.setPlayMode(mode); } + // 实现 PreferencesHelper 接口中的 getPlayMode 方法,用于获取播放模式 + // 返回值:一个整数,表示当前的播放模式 @Override public int getPlayMode() { + // 调用 mPreferencesHelper 的 getPlayMode 方法来获取播放模式 return mPreferencesHelper.getPlayMode(); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/model/db/DbHelper.java b/app/src/main/java/com/example/musicplayer/model/db/DbHelper.java index 19da7e2..e0dbbf9 100644 --- a/app/src/main/java/com/example/musicplayer/model/db/DbHelper.java +++ b/app/src/main/java/com/example/musicplayer/model/db/DbHelper.java @@ -12,20 +12,72 @@ import java.util.List; * author : 残渊 * time : 2019/07/16 * desc : 数据库操作接口 + * + * 该接口定义了一系列数据库操作的方法,是数据库操作的抽象层, + * 具体的数据库操作实现类需要实现该接口。 + * 这些方法涵盖了不同的数据库操作,如插入、查询、更新和删除等, + * 主要用于音乐播放器应用中的歌曲和专辑信息的数据库操作。 * */ - public interface DbHelper { /** - * 将所有搜索专辑列表中的歌曲都保存到网络歌曲数据库中 - * @param songList 专辑列表 + * 将所有搜索专辑列表中的歌曲都保存到网络歌曲数据库中 + * + * @param songList 专辑列表,包含要保存的歌曲信息,歌曲列表中的元素为 AlbumSong.DataBean.ListBean 类型 + * 该方法用于将专辑中的歌曲存储到网络歌曲数据库中,可用于将用户搜索到的专辑歌曲保存到数据库中, + * 以便后续使用,例如在没有网络连接时也能播放这些歌曲,或者进行歌曲信息的持久化存储。 */ void insertAllAlbumSong(List songList); - List getLocalMp3Info(); //得到本地列表 - boolean saveSong(List localSongs);//将本地音乐放到数据库中 - boolean queryLove(String songId);//从数据库查找是否为收藏歌曲 - boolean saveToLove(Song song);//收藏歌曲 - boolean deleteFromLove(String songId);//取消收藏歌曲 + /** + * 得到本地列表 + * 该方法用于从数据库中获取本地歌曲的信息列表,返回的列表包含 LocalSong 类型的元素, + * 可以在需要显示本地歌曲列表或对本地歌曲进行操作时调用此方法, + * 如在用户打开本地音乐列表页面时,调用该方法获取本地歌曲列表信息。 + * @return 存储本地歌曲信息的列表 + */ + List getLocalMp3Info(); -} + /** + * 将本地音乐放到数据库中 + * + * @param localSongs 要保存的本地歌曲列表,列表中的元素为 LocalSong 类型 + * 该方法用于将本地歌曲存储到数据库中,可用于将用户添加的新的本地歌曲保存到数据库, + * 以便对本地歌曲进行管理,如更新本地歌曲信息、备份歌曲信息等。 + * @return 操作是否成功的布尔值,如果存储成功返回 true,否则返回 false + */ + boolean saveSong(List localSongs); + + + /** + * 从数据库查找是否为收藏歌曲 + * + * @param songId 歌曲的唯一标识符,用于查找对应的歌曲 + * 该方法用于根据歌曲的唯一标识符在数据库中查找该歌曲是否为收藏歌曲, + * 可在用户点击歌曲的收藏按钮时调用此方法,以判断该歌曲是否已经被收藏。 + * @return 查找结果的布尔值,如果歌曲是收藏歌曲返回 true,否则返回 false + */ + boolean queryLove(String songId); + + + /** + * 收藏歌曲 + * + * @param song 要收藏的歌曲对象,为 Song 类型 + * 该方法用于将指定的歌曲收藏到数据库中,可在用户点击收藏按钮且歌曲尚未被收藏时调用, + * 例如用户决定收藏一首正在播放的歌曲,可调用该方法将其收藏。 + * @return 操作是否成功的布尔值,如果收藏成功返回 true,否则返回 false + */ + boolean saveToLove(Song song); + + + /** + * 取消收藏歌曲 + * + * @param songId 要取消收藏的歌曲的唯一标识符 + * 该方法用于根据歌曲的唯一标识符将歌曲从收藏列表中移除, + * 可在用户取消收藏歌曲时调用,以更新数据库中的收藏状态。 + * @return 操作是否成功的布尔值,如果取消收藏成功返回 true,否则返回 false + */ + boolean deleteFromLove(String songId); +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/model/db/DbHelperImpl.java b/app/src/main/java/com/example/musicplayer/model/db/DbHelperImpl.java index 6ea4511..512a9e5 100644 --- a/app/src/main/java/com/example/musicplayer/model/db/DbHelperImpl.java +++ b/app/src/main/java/com/example/musicplayer/model/db/DbHelperImpl.java @@ -22,137 +22,206 @@ import java.util.List; * author : 残渊 * time : 2019/07/16 * desc : 数据库操作类 + * + * 该类实现了 DbHelper 接口,提供了具体的数据库操作实现。 + * 它使用 LitePal 作为数据库操作框架,处理各种音乐相关信息的存储、查询、更新和删除操作, + * 包括网络歌曲、本地歌曲以及收藏歌曲的操作。 * */ - public class DbHelperImpl implements DbHelper { @Override public void insertAllAlbumSong(List songList) { + // 先删除 OnlineSong 表中的所有数据 LitePal.deleteAll(OnlineSong.class); + // 遍历歌曲列表,将每首歌曲的信息存储到 OnlineSong 表中 for (int i = 0; i < songList.size(); i++) { AlbumSong.DataBean.ListBean song = songList.get(i); OnlineSong onlineSong = new OnlineSong(); + // 设置歌曲的 ID,从 1 开始递增 onlineSong.setId(i + 1); + // 设置歌曲名称 onlineSong.setName(song.getSongname()); + // 设置歌手,取第一个歌手的名字 onlineSong.setSinger(song.getSinger().get(0).getName()); + // 设置歌曲的唯一标识符 onlineSong.setSongId(song.getSongmid()); + // 设置歌曲时长 onlineSong.setDuration(song.getInterval()); - onlineSong.setPic(Api.ALBUM_PIC + song.getAlbummid()+Api.JPG); + // 设置歌曲封面图片的 URL + onlineSong.setPic(Api.ALBUM_PIC + song.getAlbummid() + Api.JPG); + // 暂时设置歌曲的 URL 和歌词为 null onlineSong.setUrl(null); onlineSong.setLrc(null); + // 保存歌曲信息到数据库 onlineSong.save(); } } @Override public List getLocalMp3Info() { + // 创建一个用于存储本地歌曲信息的列表 List mp3InfoList = new ArrayList<>(); - getFromDownloadFile(mp3InfoList); //从下载列表中读取歌曲文件 + // 从下载列表中读取歌曲文件 + getFromDownloadFile(mp3InfoList); + // 查询系统媒体库中的音频文件 Cursor cursor = App.getContext().getContentResolver().query( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); + // 遍历查询结果 for (int i = 0; i < cursor.getCount(); i++) { cursor.moveToNext(); LocalSong mp3Info = new LocalSong(); + // 获取音乐标题 String title = cursor.getString((cursor - .getColumnIndex(MediaStore.Audio.Media.TITLE)));//音乐标题 + .getColumnIndex(MediaStore.Audio.Media.TITLE))); + // 获取艺术家 String artist = cursor.getString(cursor - .getColumnIndex(MediaStore.Audio.Media.ARTIST));//艺术家 + .getColumnIndex(MediaStore.Audio.Media.ARTIST)); + // 获取时长 long duration = cursor.getLong(cursor - .getColumnIndex(MediaStore.Audio.Media.DURATION));//时长 + .getColumnIndex(MediaStore.Audio.Media.DURATION)); + // 获取文件大小 long size = cursor.getLong(cursor - .getColumnIndex(MediaStore.Audio.Media.SIZE)); //文件大小 + .getColumnIndex(MediaStore.Audio.Media.SIZE)); + // 获取文件路径 String url = cursor.getString(cursor - .getColumnIndex(MediaStore.Audio.Media.DATA)); //文件路径 + .getColumnIndex(MediaStore.Audio.Media.DATA)); + // 获取是否为音乐的标记 int isMusic = cursor.getInt(cursor - .getColumnIndex(MediaStore.Audio.Media.IS_MUSIC));//是否为音乐 - if (isMusic != 0) {//只把音乐添加到集合当中 + .getColumnIndex(MediaStore.Audio.Media.IS_MUSIC)); + // 只添加音乐文件到集合中 + if (isMusic!= 0) { + // 对于文件大小大于 800KB 的文件进行处理 if (size > 1000 * 800) { - // 注释部分是切割标题,分离出歌曲名和歌手 (本地媒体库读取的歌曲信息不规范) + // 对于本地媒体库读取的歌曲信息进行规范处理,分离歌曲名和歌手 if (title.contains("-")) { String[] str = title.split("-"); artist = str[0]; title = str[1]; } + // 设置歌曲名称 mp3Info.setName(title.trim()); + // 设置歌手 mp3Info.setSinger(artist); + // 设置时长(单位为秒) mp3Info.setDuration(duration / 1000); + // 设置文件路径 mp3Info.setUrl(url); - mp3Info.setSongId(i+""); + // 设置歌曲的唯一标识符 + mp3Info.setSongId(i + ""); + // 将歌曲信息添加到列表中 mp3InfoList.add(mp3Info); } } } + // 关闭游标 cursor.close(); + // 返回存储本地歌曲信息的列表 return mp3InfoList; } @Override public boolean saveSong(List localSongs) { + // 先删除 LocalSong 表中的所有数据 LitePal.deleteAll(LocalSong.class); + // 遍历要保存的本地歌曲列表,将歌曲信息存储到数据库中 for (LocalSong localSong : localSongs) { LocalSong song = new LocalSong(); + // 设置歌曲名称 song.setName(localSong.getName()); + // 设置歌手 song.setSinger(localSong.getSinger()); + // 设置文件路径 song.setUrl(localSong.getUrl()); + // 设置歌曲的唯一标识符 song.setSongId(localSong.getSongId()); + // 设置时长 song.setDuration(localSong.getDuration()); - if(!song.save()) return false; + // 保存歌曲信息,如果保存失败返回 false + if (!song.save()) return false; } + // 保存成功返回 true return true; } @Override public boolean queryLove(String songId) { - List love=LitePal.where("songId=?",songId).find(Love.class); - return love.size() != 0; + // 根据歌曲的唯一标识符查询收藏歌曲列表 + List love = LitePal.where("songId=?", songId).find(Love.class); + // 如果查询结果不为空,表示歌曲已被收藏,返回 true,否则返回 false + return love.size()!= 0; } @Override public boolean saveToLove(Song song) { - Love love =new Love(); + // 创建一个新的收藏歌曲对象 + Love love = new Love(); + // 设置歌曲名称 love.setName(song.getSongName()); + // 设置歌手 love.setSinger(song.getSinger()); + // 设置文件路径 love.setUrl(song.getUrl()); + // 设置歌曲封面图片的 URL love.setPic(song.getImgUrl()); + // 设置时长 love.setDuration(song.getDuration()); + // 设置歌曲的唯一标识符 love.setSongId(song.getSongId()); + // 设置是否为在线歌曲 love.setOnline(song.isOnline()); + // 设置歌曲的 QQ 标识符 love.setQqId(song.getQqId()); + // 设置歌曲的媒体标识符 love.setMediaId(song.getMediaId()); + // 设置是否已下载 love.setDownload(song.isDownload()); + // 保存收藏歌曲信息,保存成功返回 true,否则返回 false return love.save(); } @Override public boolean deleteFromLove(String songId) { - return LitePal.deleteAll(Love.class,"songId=?",songId) !=0; + // 根据歌曲的唯一标识符删除收藏歌曲,删除成功返回 true,否则返回 false + return LitePal.deleteAll(Love.class, "songId=?", songId)!= 0; } - //从下载列表中读取文件 - private void getFromDownloadFile(List songList){ + // 从下载列表中读取文件的方法 + private void getFromDownloadFile(List songList) { + // 获取存储歌曲的目录 File file = new File(Api.STORAGE_SONG_FILE); - if(!file.exists()) { + // 如果目录不存在,则创建目录 + if (!file.exists()) { file.mkdirs(); return; } - + // 获取目录下的所有文件 File[] subFile = file.listFiles(); + // 遍历文件列表 for (File value : subFile) { String songFileName = value.getName(); + // 提取歌曲文件名(不包含后缀) String songFile = songFileName.substring(0, songFileName.lastIndexOf(".")); + // 分割文件名,包含歌手、歌曲名、时长、歌曲唯一标识符和文件大小 String[] songValue = songFile.split("-"); + // 获取文件大小 long size = Long.valueOf(songValue[4]); - //如果文件的大小不等于实际大小,则表示该歌曲还未下载完成,被人为暂停,故跳过该歌曲,不加入到已下载集合 - if(size != value.length()) continue; - LocalSong song =new LocalSong(); + // 如果文件大小不等于实际大小,说明歌曲未下载完成,跳过该歌曲 + if (size!= value.length()) continue; + LocalSong song = new LocalSong(); + // 设置歌手 song.setSinger(songValue[0]); + // 设置歌曲名称 song.setName(songValue[1]); + // 设置时长 song.setDuration(Long.valueOf(songValue[2])); + // 设置歌曲的唯一标识符 song.setSongId(songValue[3]); + // 设置文件路径 song.setUrl(Api.STORAGE_SONG_FILE + songFileName); + // 将歌曲信息添加到歌曲列表中 songList.add(song); } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/model/https/NetworkHelper.java b/app/src/main/java/com/example/musicplayer/model/https/NetworkHelper.java index ce3667d..848d9bf 100644 --- a/app/src/main/java/com/example/musicplayer/model/https/NetworkHelper.java +++ b/app/src/main/java/com/example/musicplayer/model/https/NetworkHelper.java @@ -16,16 +16,90 @@ import io.reactivex.Observable; * author : 残渊 * time : 2019/07/15 * desc : 网络操作接口集合 + * + * 该接口定义了音乐播放器应用中所需的各种网络操作接口, + * 利用 RxJava 的 Observable 作为返回类型,实现异步的网络请求和响应处理。 + * 包括搜索歌曲、搜索专辑、获取专辑歌曲、获取歌词、获取歌手头像以及获取歌曲播放地址等操作。 + * 这些接口为上层业务逻辑提供了与网络服务交互的抽象,具体的网络请求实现需要在实现类中完成。 * */ - public interface NetworkHelper { - Observable getAlbumSong(String id); //得到专辑 - Observable search(String seek, int offset); //搜索歌曲 - Observable searchAlbum(String seek,int offset);//搜索照片 - Observable getLrc(String seek);//获取歌词 - Observable getOnlineSongLrc(String songId);//获取网络歌曲的歌词 - Observable getSingerImg(String singer);//获取歌手头像 - Observable getSongUrl(String data);//获取播放地址 -} + /** + * 得到专辑 + * + * @param id 专辑的唯一标识符 + * + * 该方法返回一个 Observable,用于异步获取专辑歌曲信息, + * 可以根据提供的专辑唯一标识符向服务器请求专辑的详细信息, + * 例如歌曲列表、专辑封面等信息。 + */ + Observable getAlbumSong(String id); + + /** + * 搜索歌曲 + * + * @param seek 搜索关键字 + * @param offset 搜索结果的偏移量,用于分页 + * + * 该方法返回一个 Observable,用于异步搜索歌曲, + * 根据搜索关键字和偏移量向服务器请求歌曲信息, + * 可用于搜索不同歌曲,并根据偏移量获取不同页的搜索结果。 + */ + Observable search(String seek, int offset); + + /** + * 搜索照片(此处可能是文档注释错误,推测应该是搜索专辑) + * + * @param seek 搜索关键字 + * @param offset 搜索结果的偏移量,用于分页 + * + * 该方法返回一个 Observable,用于异步搜索专辑, + * 根据搜索关键字和偏移量向服务器请求专辑信息, + * 可用于搜索不同的专辑,并根据偏移量获取不同页的搜索结果。 + */ + Observable searchAlbum(String seek,int offset); + + /** + * 获取歌词 + * + * @param seek 搜索关键字 + * + * 该方法返回一个 Observable,用于异步搜索歌词, + * 根据搜索关键字向服务器请求歌词信息, + * 可用于查找歌曲的歌词。 + */ + Observable getLrc(String seek); + + /** + * 获取网络歌曲的歌词 + * + * @param songId 歌曲的唯一标识符 + * + * 该方法返回一个 Observable,用于异步获取网络歌曲的歌词, + * 根据歌曲的唯一标识符向服务器请求歌词, + * 可用于在播放网络歌曲时获取其对应的歌词。 + */ + Observable getOnlineSongLrc(String songId); + + /** + * 获取歌手头像 + * + * @param singer 歌手名称 + * + * 该方法返回一个 Observable,用于异步获取歌手头像, + * 根据歌手名称向服务器请求头像信息, + * 可用于显示歌手的头像。 + */ + Observable getSingerImg(String singer); + /** + * 获取播放地址 + * + * @param data 与播放地址相关的数据,可能包含歌曲的相关信息 + * + * 该方法返回一个 Observable,用于异步获取歌曲的播放地址, + * 根据传入的数据向服务器请求播放地址, + * 可用于获取歌曲的播放链接,以便进行播放操作。 + */ + Observable getSongUrl(String data); +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/model/https/NetworkHelperImpl.java b/app/src/main/java/com/example/musicplayer/model/https/NetworkHelperImpl.java index bfd85eb..6c84188 100644 --- a/app/src/main/java/com/example/musicplayer/model/https/NetworkHelperImpl.java +++ b/app/src/main/java/com/example/musicplayer/model/https/NetworkHelperImpl.java @@ -17,48 +17,62 @@ import io.reactivex.Observable; * author : 残渊 * time : 2019/07/15 * desc : 网络操作实现类 + * + * 该类实现了 NetworkHelper 接口,作为网络操作的具体实现类。 + * 它依赖于 RetrofitService 来执行实际的网络请求操作, + * 通过调用 RetrofitService 中的方法来完成各种网络操作, + * 如搜索歌曲、搜索专辑、获取歌词、获取歌手头像、获取歌曲播放地址等。 + * 利用 RxJava 的 Observable 来处理异步网络请求的结果。 * */ - public class NetworkHelperImpl implements NetworkHelper { + // 存储 RetrofitService 实例,用于执行网络请求 private RetrofitService mRetrofitService; + // 构造函数,接收 RetrofitService 实例并存储 public NetworkHelperImpl(RetrofitService retrofitService){ mRetrofitService = retrofitService; } + // 获取专辑歌曲的方法,调用 RetrofitService 的 getAlbumSong 方法 @Override public Observable getAlbumSong(String id) { return mRetrofitService.getAlbumSong(id); } + // 搜索歌曲的方法,调用 RetrofitService 的 search 方法 @Override public Observable search(String seek, int offset) { return mRetrofitService.search(seek, offset); } + // 搜索专辑的方法,调用 RetrofitService 的 searchAlbum 方法 @Override public Observable searchAlbum(String seek, int offset) { return mRetrofitService.searchAlbum(seek, offset); } + // 获取歌词的方法,调用 RetrofitService 的 getLrc 方法 @Override public Observable getLrc(String seek) { return mRetrofitService.getLrc(seek); } + // 获取网络歌曲歌词的方法,调用 RetrofitService 的 getOnlineSongLrc 方法 @Override public Observable getOnlineSongLrc(String songId) { return mRetrofitService.getOnlineSongLrc(songId); } + // 获取歌手头像的方法,调用 RetrofitService 的 getSingerImg 方法 @Override public Observable getSingerImg(String singer) { return mRetrofitService.getSingerImg(singer); } + // 获取歌曲播放地址的方法,调用 RetrofitService 的 getSongUrl 方法 @Override public Observable getSongUrl(String data) { return mRetrofitService.getSongUrl(data); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/model/https/RetrofitFactory.java b/app/src/main/java/com/example/musicplayer/model/https/RetrofitFactory.java index bad01e6..f7d9732 100644 --- a/app/src/main/java/com/example/musicplayer/model/https/RetrofitFactory.java +++ b/app/src/main/java/com/example/musicplayer/model/https/RetrofitFactory.java @@ -18,83 +18,114 @@ import retrofit2.converter.gson.GsonConverterFactory; *
  *     author : 残渊
  *     time   : 2019/07/15
- *     desc   : 封装Retrofit,方便外部调用
+ *     desc   : 封装 Retrofit,方便外部调用
+ * 
+ *     该类是一个 Retrofit 的工厂类,用于创建和配置 Retrofit 实例,
+ *     并提供了不同类型的 Retrofit 实例以满足不同的网络请求需求,
+ *     如通用请求、歌手照片请求和歌曲播放地址请求等。
+ *     它使用 OkHttpClient 进行网络请求的底层配置,
+ *     并使用 Gson 进行 JSON 数据的解析和转换,
+ *     同时结合 RxJava2 来处理异步请求结果。
  * 
*/ - public class RetrofitFactory { + // 存储 OkHttpClient 实例,用于网络请求 private static OkHttpClient sOkHttpClient; + // 存储通用的 Retrofit 实例 private static Retrofit sRetrofit; + // 存储用于歌曲播放地址请求的 Retrofit 实例 private static Retrofit songUrlRetrofit; + // 存储用于歌手照片请求的 Retrofit 实例 private static Retrofit sSingerPicRetrofit; - // 创建网络请求Observable + // 创建网络请求 Observable 的方法,使用通用的 Retrofit 实例 public static RetrofitService createRequest() { return getRetrofit().create(RetrofitService.class); } + // 创建用于歌手相关请求的网络请求 Observable 的方法,使用歌手照片的 Retrofit 实例 public static RetrofitService createRequestOfSinger() { return getRetrofitOfSinger().create(RetrofitService.class); } - public static RetrofitService createRequestOfSongUrl(){ + // 创建用于歌曲播放地址请求的网络请求 Observable 的方法,使用歌曲播放地址的 Retrofit 实例 + public static RetrofitService createRequestOfSongUrl() { return getRetrofitOfSongUrl().create(RetrofitService.class); } - // 配置Retrofit + + // 配置通用的 Retrofit 实例 private synchronized static Retrofit getRetrofit() { if (sRetrofit == null) { sRetrofit = new Retrofit.Builder() - .baseUrl(Api.FIDDLER_BASE_QQ_URL) // 对应服务端的host - .client(getOkHttpClient()) - .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create())) // 这里还结合了Gson - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 把Retrofit请求转化成RxJava的Observable - .build(); + // 设置基础 URL,对应服务端的 host + .baseUrl(Api.FIDDLER_BASE_QQ_URL) + // 使用 OkHttpClient 进行网络请求 + .client(getOkHttpClient()) + // 使用 Gson 进行 JSON 数据的解析和转换,使用了宽松模式 + .addConverterFactory(GsonConverterFactory.create(new GsonBuilder().setLenient().create())) + // 将 Retrofit 请求转化成 RxJava 的 Observable,以便使用 RxJava 处理异步请求结果 + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build(); } return sRetrofit; } - // 获取歌手照片 + // 配置用于歌手照片请求的 Retrofit 实例 private synchronized static Retrofit getRetrofitOfSinger() { if (sSingerPicRetrofit == null) { sSingerPicRetrofit = new Retrofit.Builder() - .baseUrl(Api.SINGER_PIC_BASE_URL) // 对应服务端的host - .client(getOkHttpClient()) - .addConverterFactory(GsonConverterFactory.create()) // 这里还结合了Gson - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 把Retrofit请求转化成RxJava的Observable - .build(); + // 设置基础 URL,对应服务端的 host + .baseUrl(Api.SINGER_PIC_BASE_URL) + // 使用 OkHttpClient 进行网络请求 + .client(getOkHttpClient()) + // 使用 Gson 进行 JSON 数据的解析和转换 + .addConverterFactory(GsonConverterFactory.create()) + // 将 Retrofit 请求转化成 RxJava 的 Observable + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build(); } return sSingerPicRetrofit; } - //得到播放地址 + // 配置用于歌曲播放地址请求的 Retrofit 实例 private synchronized static Retrofit getRetrofitOfSongUrl() { if (songUrlRetrofit == null) { songUrlRetrofit = new Retrofit.Builder() - .baseUrl(Api.FIDDLER_BASE_SONG_URL) // 对应服务端的host - .client(getOkHttpClient()) - .addConverterFactory(GsonConverterFactory.create()) // 这里还结合了Gson - .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) // 把Retrofit请求转化成RxJava的Observable - .build(); + // 设置基础 URL,对应服务端的 host + .baseUrl(Api.FIDDLER_BASE_SONG_URL) + // 使用 OkHttpClient 进行网络请求 + .client(getOkHttpClient()) + // 使用 Gson 进行 JSON 数据的解析和转换 + .addConverterFactory(GsonConverterFactory.create()) + // 将 Retrofit 请求转化成 RxJava 的 Observable + .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) + .build(); } return songUrlRetrofit; } - //配置OkHttp + // 配置 OkHttpClient 实例 private synchronized static OkHttpClient getOkHttpClient() { if (sOkHttpClient == null) { + // 创建一个 HttpLoggingInterceptor 用于打印网络请求日志 HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(message -> { - //打印retrofit日志 + // 打印 retrofit 日志 Log.i("RetrofitLog","retrofitBack = "+message); }); + // 设置日志级别为 BODY,会打印请求和响应的详细信息 loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); sOkHttpClient = new OkHttpClient.Builder() - .connectTimeout(100, TimeUnit.SECONDS) - .readTimeout(100,TimeUnit.SECONDS) - .writeTimeout(100,TimeUnit.SECONDS) - //.addInterceptor(loggingInterceptor) 日志 - .build(); + // 设置连接超时时间 + .connectTimeout(100, TimeUnit.SECONDS) + // 设置读取超时时间 + .readTimeout(100,TimeUnit.SECONDS) + // 设置写入超时时间 + .writeTimeout(100,TimeUnit.SECONDS) + // 可以添加日志拦截器,这里被注释掉了 + //.addInterceptor(loggingInterceptor) + .build(); } return sOkHttpClient; } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/model/https/api/RetrofitService.java b/app/src/main/java/com/example/musicplayer/model/https/api/RetrofitService.java index 18292ff..578f560 100644 --- a/app/src/main/java/com/example/musicplayer/model/https/api/RetrofitService.java +++ b/app/src/main/java/com/example/musicplayer/model/https/api/RetrofitService.java @@ -22,43 +22,73 @@ import retrofit2.http.Query; * author : 残渊 * time : 2019/07/15 * desc : + * + * 该接口定义了使用 Retrofit 进行网络请求的服务方法, + * 主要用于音乐播放器应用中与服务器的各种交互操作, + * 包括搜索歌曲、搜索专辑、获取专辑歌曲、获取歌曲播放地址、获取歌词和获取歌手照片等操作。 + * 每个方法对应一个特定的网络 API 端点,并使用 RxJava 的 Observable 来处理异步网络请求的结果。 * */ - public interface RetrofitService { /** - * 搜索歌曲:https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=2&n=2&w=周杰伦&format=json + * 搜索歌曲:https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=2&n=2&w=周杰伦&format=json + * + * @param seek 搜索关键词 + * @param offset 搜索结果的偏移量(页数) + * + * 该方法使用 @GET 注解和 Api.SEARCH_SONG 作为请求的 URL, + * 通过 @Query 注解添加查询参数 "w"(搜索关键词)和 "p"(偏移量), + * 返回一个 Observable,用于接收搜索歌曲的结果。 */ @GET(Api.SEARCH_SONG) Observable search(@Query("w") String seek, @Query("p")int offset); /** * 搜索专辑:https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=1&n=2&w=林宥嘉&format=json&t=8 + * * @param seek 搜索关键字 * @param offset 页数 + * + * 该方法使用 @GET 注解和 Api.SEARCH_ALBUM 作为请求的 URL, + * 通过 @Query 注解添加查询参数 "w"(搜索关键词)和 "p"(偏移量), + * 返回一个 Observable,用于接收搜索专辑的结果。 */ @GET(Api.SEARCH_ALBUM) Observable searchAlbum(@Query("w") String seek, @Query("p")int offset); /** * 专辑详细:https://c.y.qq.com/v8/fcg-bin/fcg_v8_album_info_cp.fcg?albummid=004YodY33zsWTT&format=json - * @param id 专辑mid + * + * @param id 专辑的 mid 标识符 + * + * 该方法使用 @GET 注解和 Api.ALBUM_DETAIL 作为请求的 URL, + * 通过 @Query 注解添加查询参数 "albummid"(专辑的 mid), + * 返回一个 Observable,用于接收专辑歌曲的结果。 */ @GET(Api.ALBUM_DETAIL) Observable getAlbumSong(@Query("albummid")String id); /** - * 得到歌曲的播放地址,变化的只有songmid,即{}所示 + * 得到歌曲的播放地址,变化的只有 songmid,即{}所示 * https://u.y.qq.com/cgi-bin/musicu.fcg?format=json&data=%7B%22req_0%22%3A%7B%22module%22%3A%22vkey.GetVkeyServer%22%2C%22method%22%3A%22CgiGetVkey%22%2C%22param%22%3A%7B%22guid%22%3A%22358840384%22%2C%22 + * songmid%22%3A%5B%22{003wFozn3V3Ra0} + * %22%5D%2C%22songtype%22%3A%5B0%5D%2C%22uin%22%3A%221443481947%22%2C%22loginflag%22%3A1%2C%22platform%22%3A%2220%22%7D%7D%2C%22comm%22%3A%7B%22uin%22%3A%221443481947%22%2C%22format%22%3A%22json%22%2C%22ct%22%3A24%2C%22cv%22%3A0%7D%7D + * + * 该方法使用 @GET 注解和 Api.SONG_URL 作为请求的 URL, + * 通过 @Query 注解添加查询参数 "data"(编码为 true), + * 返回一个 Observable,用于接收歌曲播放地址的结果。 */ @GET(Api.SONG_URL) Observable getSongUrl(@Query(value = "data",encoded = true) String data); /** - * 根据songmid获取歌词:https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=000wocYU11tSzS&format=json&nobase64=1 - * headers中的Referer是qq用来防盗链的 + * 根据 songmid 获取歌词:https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg?songmid=000wocYU11tSzS&format=json&nobase64=1 + * headers 中的 Referer 是 qq 用来防盗链的 + * + * 该方法使用 @GET 注解和 Api.ONLINE_SONG_LRC 作为请求的 URL, + * 通过 @Headers 注解添加请求头 Api.HEADER_REFERER 用于防盗链, + * 通过 @Query 注解添加查询参数 "songmid"(歌曲的 mid), + * 返回一个 Observable,用于接收在线歌曲歌词的结果。 */ @Headers(Api.HEADER_REFERER) @GET(Api.ONLINE_SONG_LRC) @@ -66,18 +96,29 @@ public interface RetrofitService { /** * 搜索歌词:https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=1&n=1&w=说谎&format=json&t=7 + * * @param seek 关键词 + * + * 该方法使用 @GET 注解和 Api.SONG_LRC 作为请求的 URL, + * 通过 @Query 注解添加查询参数 "w"(搜索关键词), + * 返回一个 Observable,用于接收搜索歌词的结果。 */ @GET(Api.SONG_LRC) Observable getLrc(@Query("w") String seek); /** * 得到歌手照片,主要用于本地音乐:http://music.163.com/api/search/get/web?s=刘瑞琦&type=100 + * * @param singer 歌手名字 + * + * 该方法使用 @POST 注解和 Api.SINGER_PIC 作为请求的 URL, + * 通过 @Headers 注解添加请求头 Api.HEADER_USER_AGENT, + * 通过 @FormUrlEncoded 注解表示使用表单编码, + * 通过 @Field 注解添加表单字段 "s"(歌手名字), + * 返回一个 Observable,用于接收歌手照片的结果。 */ @Headers(Api.HEADER_USER_AGENT) @POST(Api.SINGER_PIC) @FormUrlEncoded Observable getSingerImg(@Field("s")String singer); - -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/model/prefs/PreferencesHelper.java b/app/src/main/java/com/example/musicplayer/model/prefs/PreferencesHelper.java index c893864..a8cefc7 100644 --- a/app/src/main/java/com/example/musicplayer/model/prefs/PreferencesHelper.java +++ b/app/src/main/java/com/example/musicplayer/model/prefs/PreferencesHelper.java @@ -5,10 +5,18 @@ package com.example.musicplayer.model.prefs; * author : 残渊 * time : 2019/09/09 * desc : 存储状态抽象类 + * + * 该接口定义了存储播放状态的抽象方法, + * 作为存储状态的抽象层,具体的存储实现类需要实现该接口。 + * 包含设置播放状态和获取播放状态的方法, + * 可用于保存和读取音乐播放器的播放模式信息, + * 例如顺序播放、随机播放、单曲循环等模式。 * */ - public interface PreferencesHelper { - void setPlayMode(int mode); //保存播放状态 - int getPlayMode();//得到播放状态 -} + // 保存播放状态的方法,接收一个整数表示播放模式 + void setPlayMode(int mode); + + // 获取播放状态的方法,返回一个整数表示播放模式 + int getPlayMode(); +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/model/prefs/PreferencesHelperImpl.java b/app/src/main/java/com/example/musicplayer/model/prefs/PreferencesHelperImpl.java index e5b84cb..f40aecd 100644 --- a/app/src/main/java/com/example/musicplayer/model/prefs/PreferencesHelperImpl.java +++ b/app/src/main/java/com/example/musicplayer/model/prefs/PreferencesHelperImpl.java @@ -11,23 +11,33 @@ import com.example.musicplayer.app.Constant; * author : 残渊 * time : 2019/09/09 * desc : 保存状态的实现类 + * + * 该类实现了 PreferencesHelper 接口,使用 SharedPreferences 来存储和获取播放模式信息。 + * 它通过 App.getContext() 获取应用上下文,并使用 Constant.SHARED_PREFERENCES_NAME 作为 SharedPreferences 的名称, + * 实现了保存播放模式和获取播放模式的具体逻辑。 * */ - public class PreferencesHelperImpl implements PreferencesHelper{ + // 存储 SharedPreferences 实例,用于存储播放模式等信息 private SharedPreferences mPreferences; + // 构造函数,获取 SharedPreferences 实例 public PreferencesHelperImpl(){ + // 获取 SharedPreferences 实例,使用应用上下文和预定义的名称,且为私有模式 mPreferences = App.getContext().getSharedPreferences(Constant.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); } + // 实现接口中的设置播放模式方法,将播放模式存储到 SharedPreferences 中 @Override public void setPlayMode(int mode) { + // 编辑 SharedPreferences 并存储播放模式,使用 Constant.PREFS_PLAY_MODE 作为键,mode 作为值,并使用 apply 方法异步保存 mPreferences.edit().putInt(Constant.PREFS_PLAY_MODE,mode).apply(); } + // 实现接口中的获取播放模式方法,从 SharedPreferences 中获取播放模式 @Override public int getPlayMode() { + // 从 SharedPreferences 中获取播放模式,使用 Constant.PREFS_PLAY_MODE 作为键,若不存在则默认返回 0 return mPreferences.getInt(Constant.PREFS_PLAY_MODE,0); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/presenter/AlbumSongPresenter.java b/app/src/main/java/com/example/musicplayer/presenter/AlbumSongPresenter.java index d1b5251..be51030 100644 --- a/app/src/main/java/com/example/musicplayer/presenter/AlbumSongPresenter.java +++ b/app/src/main/java/com/example/musicplayer/presenter/AlbumSongPresenter.java @@ -19,32 +19,53 @@ import static com.example.musicplayer.view.search.AlbumSongFragment.ALBUM_SONG; /** * Created by 残渊 on 2018/11/27. + * + *
+ * 该类是一个专辑歌曲的 Presenter 类,它继承自 BasePresenter 并实现了 IAlbumSongContract.Presenter 接口。
+ * 主要负责处理与专辑歌曲相关的业务逻辑,如获取专辑详情和插入所有专辑歌曲。
+ * 它使用 RxJava 进行异步操作,将网络请求调度到 IO 线程,将结果处理调度到主线程。
+ * 并通过 BaseObserver 对网络请求结果进行观察和处理,根据不同情况更新视图状态。
+ * 
*/ - public class AlbumSongPresenter extends BasePresenter implements IAlbumSongContract.Presenter { + // 用于日志输出的 TAG 常量,方便在日志中识别该类 private final static String TAG = "AlbumSongPresenter"; + // 实现 IAlbumSongContract.Presenter 接口中的 getAlbumDetail 方法,用于获取专辑的详细信息 @Override public void getAlbumDetail(String id, int type) { + // 使用 addRxSubscribe 方法添加 RxJava 的订阅操作 addRxSubscribe( + // 调用 mModel 的 getAlbumSong 方法获取专辑歌曲信息,mModel 可能是在 BasePresenter 中声明的对象 mModel.getAlbumSong(id) - .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) - .subscribeWith(new BaseObserver(mView) { + // 将网络请求操作调度到 IO 线程,避免阻塞主线程 + .subscribeOn(Schedulers.io()) + // 将结果处理调度到主线程,方便更新 UI + .observeOn(AndroidSchedulers.mainThread()) + // 使用 BaseObserver 对结果进行观察和处理 + .subscribeWith(new BaseObserver(mView) { + // 在网络请求开始时调用,用于显示加载状态 @Override public void onStart() { mView.showLoading(); } + // 当请求成功获取到结果时调用 @Override public void onNext(AlbumSong albumSong) { super.onNext(albumSong); + // 隐藏加载状态 mView.hideLoading(); + // 如果获取的结果代码为 0,表示请求成功 if (albumSong.getCode() == 0) { + // 根据类型进行不同的处理 if (type == ALBUM_SONG) { + // 调用 insertAllAlbumSong 方法插入所有专辑歌曲 insertAllAlbumSong(albumSong.getData().getList()); } else { + // 调用视图的 showAlbumMessage 方法显示专辑信息 mView.showAlbumMessage( albumSong.getData().getName(), albumSong.getData().getLan(), @@ -53,18 +74,25 @@ public class AlbumSongPresenter extends BasePresenter i albumSong.getData().getDesc()); } } else { + // 如果请求不成功,显示专辑歌曲错误 mView.showAlbumSongError(); } } + // 当请求发生错误时调用 @Override public void onError(Throwable e) { + // 打印错误栈信息 e.printStackTrace(); + // 输出错误信息到日志 Log.d(TAG, "onError: " + e.toString()); + // 隐藏加载状态 mView.hideLoading(); + // 如果是网络异常且类型为 ALBUM_SONG,则显示网络错误 if (e instanceof UnknownHostException && type == ALBUM_SONG) { mView.showNetError(); } else { + // 否则显示专辑歌曲错误 mView.showAlbumSongError(); } } @@ -72,9 +100,12 @@ public class AlbumSongPresenter extends BasePresenter i ); } + // 实现 IAlbumSongContract.Presenter 接口中的 insertAllAlbumSong 方法,用于插入所有专辑歌曲 @Override public void insertAllAlbumSong(List songList) { + // 调用 mModel 的 insertAllAlbumSong 方法将歌曲列表插入数据库,mModel 可能是在 BasePresenter 中声明的对象 mModel.insertAllAlbumSong(songList); + // 调用视图的 setAlbumSongList 方法更新视图的专辑歌曲列表 mView.setAlbumSongList(songList); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/presenter/LocalPresenter.java b/app/src/main/java/com/example/musicplayer/presenter/LocalPresenter.java index 143c89d..f8087a3 100644 --- a/app/src/main/java/com/example/musicplayer/presenter/LocalPresenter.java +++ b/app/src/main/java/com/example/musicplayer/presenter/LocalPresenter.java @@ -14,27 +14,42 @@ import java.util.List; /** * Created by 残渊 on 2018/10/17. + * + *
+ * 该类是一个本地音乐的 Presenter 类,继承自 BasePresenter 并实现了 ILocalContract.Presenter 接口。
+ * 主要负责处理本地音乐的相关业务逻辑,如获取本地 MP3 信息和保存本地歌曲。
+ * 它通过调用 mModel 的方法进行数据操作,并使用 EventBus 发送事件,
+ * 同时根据不同的操作结果更新视图或显示相应的信息。
+ * 
*/ - public class LocalPresenter extends BasePresenter implements ILocalContract.Presenter { + // 实现 ILocalContract.Presenter 接口中的 getLocalMp3Info 方法,用于获取本地 MP3 信息 @Override public void getLocalMp3Info() { + // 调用 mModel 的 getLocalMp3Info 方法获取本地歌曲列表,mModel 可能是在 BasePresenter 中声明的对象 List localSongList = mModel.getLocalMp3Info(); + // 如果获取到的本地歌曲列表大小为 0,表示没有本地歌曲 if(localSongList.size() == 0){ + // 调用视图的 showErrorView 方法显示错误视图 mView.showErrorView(); }else { + // 否则调用 saveSong 方法保存本地歌曲 saveSong(localSongList); } - } + // 实现 ILocalContract.Presenter 接口中的 saveSong 方法,用于保存本地歌曲 @Override public void saveSong(List localSongs) { + // 调用 mModel 的 saveSong 方法保存本地歌曲,mModel 可能是在 BasePresenter 中声明的对象 if(mModel.saveSong(localSongs)) { + // 如果保存成功,使用 EventBus 发送一个 SongListNumEvent 事件,类型为 Constant.LIST_TYPE_LOCAL EventBus.getDefault().post(new SongListNumEvent(Constant.LIST_TYPE_LOCAL)); + // 调用视图的 showToast 方法显示成功导入本地音乐的提示 mView.showToast("成功导入本地音乐"); + // 调用视图的 showMusicList 方法显示音乐列表 mView.showMusicList(localSongs); } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/presenter/PlayPresenter.java b/app/src/main/java/com/example/musicplayer/presenter/PlayPresenter.java index 86f74ac..1773ac1 100644 --- a/app/src/main/java/com/example/musicplayer/presenter/PlayPresenter.java +++ b/app/src/main/java/com/example/musicplayer/presenter/PlayPresenter.java @@ -21,134 +21,191 @@ import io.reactivex.schedulers.Schedulers; /** * Created by 残渊 on 2018/10/26. + * + *
+ * 该类是一个播放相关的 Presenter 类,继承自 BasePresenter 并实现了 IPlayContract.Presenter 接口。
+ * 主要负责处理播放相关的业务逻辑,如获取歌手图片、获取歌词、搜索歌曲、获取歌曲 ID、设置播放模式、查询和操作收藏歌曲等。
+ * 它使用 RxJava 进行异步操作,将网络请求调度到 IO 线程,将结果处理调度到主线程。
+ * 通过 RetrofitFactory 进行网络请求,使用 BaseObserver 处理请求结果和异常。
+ * 
*/ - public class PlayPresenter extends BasePresenter implements IPlayContract.Presenter { + // 实现 IPlayContract.Presenter 接口中的 getSingerImg 方法,用于获取歌手图片 @Override public void getSingerImg(String singer, String song, long duration) { addRxSubscribe( + // 通过 RetrofitFactory 创建请求服务并调用 getSingerImg 方法获取歌手图片 RetrofitFactory.createRequestOfSinger().getSingerImg(singer) - .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) - .doOnNext(singerImg -> mView.setSingerImg(singerImg.getResult().getArtists().get(0).getImg1v1Url())) - .doOnError(SingerImg -> mView.showToast("获取不到歌手图片")) - .observeOn(Schedulers.io()) - .flatMap((Function>) singerImg -> RetrofitFactory.createRequest().search(song,1)) - .observeOn(AndroidSchedulers.mainThread()) - .subscribeWith(new BaseObserver(mView) { + // 将网络请求操作调度到 IO 线程 + .subscribeOn(Schedulers.io()) + // 将结果处理调度到主线程 + .observeOn(AndroidSchedulers.mainThread()) + // 当获取到歌手图片时,设置歌手图片到视图 + .doOnNext(singerImg -> mView.setSingerImg(singerImg.getResult().getArtists().get(0).getImg1v1Url())) + // 当获取歌手图片出现错误时,显示错误信息 + .doOnError(SingerImg -> mView.showToast("获取不到歌手图片")) + // 再次将后续操作调度到 IO 线程 + .observeOn(Schedulers.io()) + // 将获取歌手图片的操作结果转换为搜索歌曲的操作 + .flatMap((Function>) singerImg -> RetrofitFactory.createRequest().search(song, 1)) + // 将后续操作调度到主线程 + .observeOn(AndroidSchedulers.mainThread()) + // 使用 BaseObserver 对搜索歌曲的结果进行观察和处理 + .subscribeWith(new BaseObserver(mView) { + // 当搜索歌曲成功时 @Override public void onNext(SearchSong value) { super.onNext(value); if (value.getCode() == 0) { - matchLrc(value.getData().getSong().getList(),duration); + // 调用 matchLrc 方法匹配歌词 + matchLrc(value.getData().getSong().getList(), duration); } else { + // 当搜索结果异常时,显示获取歌词错误 mView.getLrcError(null); } } + // 当搜索歌曲出现错误时 @Override public void onError(Throwable e) { super.onError(e); + // 显示获取歌词错误 mView.getLrcError(null); } }) ); } + // 实现 IPlayContract.Presenter 接口中的 getLrc 方法,用于获取歌词 @Override - public void getLrc(String songId,int type) { + public void getLrc(String songId, int type) { + // 调用 mModel 的 getOnlineSongLrc 方法获取在线歌曲歌词,mModel 可能是在 BasePresenter 中声明的对象 mModel.getOnlineSongLrc(songId) - .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) - .subscribeWith(new BaseObserver(mView,false,false){ + // 将网络请求操作调度到 IO 线程 + .subscribeOn(Schedulers.io()) + // 将结果处理调度到主线程 + .observeOn(AndroidSchedulers.mainThread()) + // 使用 BaseObserver 对获取歌词的结果进行观察和处理 + .subscribeWith(new BaseObserver(mView, false, false) { + // 当获取歌词成功时 @Override - public void onNext(OnlineSongLrc onlineSongLrc){ - if(onlineSongLrc.getCode() == 0){ + public void onNext(OnlineSongLrc onlineSongLrc) { + if (onlineSongLrc.getCode() == 0) { String lrc = onlineSongLrc.getLyric(); - //如果是本地音乐,就将歌词保存起来 - if(type == Constant.SONG_LOCAL) mView.saveLrc(lrc); + // 如果是本地音乐,保存歌词 + if (type == Constant.SONG_LOCAL) mView.saveLrc(lrc); + // 显示歌词 mView.showLrc(lrc); - }else { + } else { + // 当获取歌词失败时,显示获取歌词错误 mView.getLrcError(null); } } }); } + // 实现 IPlayContract.Presenter 接口中的 getSongId 方法,用于获取歌曲 ID @Override public void getSongId(String song, long duration) { addRxSubscribe( + // 调用 mModel 的 search 方法搜索歌曲 mModel.search(song, 1) - .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) - .subscribeWith(new BaseObserver(mView, true, true) { + // 将网络请求操作调度到 IO 线程 + .subscribeOn(Schedulers.io()) + // 将结果处理调度到主线程 + .observeOn(AndroidSchedulers.mainThread()) + // 使用 BaseObserver 对搜索结果进行观察和处理 + .subscribeWith(new BaseObserver(mView, true, true) { + // 当搜索成功时 @Override public void onNext(SearchSong searchSong) { super.onNext(searchSong); if (searchSong.getCode() == 0) { - matchSong(searchSong.getData().getSong().getList(),duration); + // 调用 matchSong 方法匹配歌曲 + matchSong(searchSong.getData().getSong().getList(), duration); } else { + // 当搜索结果异常时,显示获取歌词错误 mView.getLrcError(null); } } - })); + }); } + // 实现 IPlayContract.Presenter 接口中的 setPlayMode 方法,用于设置播放模式 @Override public void setPlayMode(int mode) { + // 调用 mModel 的 setPlayMode 方法设置播放模式,mModel 可能是在 BasePresenter 中声明的对象 mModel.setPlayMode(mode); } + // 实现 IPlayContract.Presenter 接口中的 getPlayMode 方法,用于获取播放模式 @Override public int getPlayMode() { + // 调用 mModel 的 getPlayMode 方法获取播放模式,mModel 可能是在 BasePresenter 中声明的对象 return mModel.getPlayMode(); } + // 实现 IPlayContract.Presenter 接口中的 queryLove 方法,用于查询歌曲是否被收藏 @Override public void queryLove(String songId) { + // 调用 mView 的 showLove 方法显示歌曲是否被收藏,mView 是 IPlayContract.View 类型的视图对象 mView.showLove(mModel.queryLove(songId)); } + // 实现 IPlayContract.Presenter 接口中的 saveToLove 方法,用于将歌曲保存到收藏 @Override public void saveToLove(Song song) { - if(mModel.saveToLove(song)){ + // 调用 mModel 的 saveToLove 方法保存歌曲到收藏,mModel 可能是在 BasePresenter 中声明的对象 + if (mModel.saveToLove(song)) { + // 当保存成功时,调用 mView 的 saveToLoveSuccess 方法显示保存成功信息 mView.saveToLoveSuccess(); } } + // 实现 IPlayContract.Presenter 接口中的 deleteFromLove 方法,用于从收藏中删除歌曲 @Override public void deleteFromLove(String songId) { - if(mModel.deleteFromLove(songId)){ + // 调用 mModel 的 deleteFromLove 方法从收藏中删除歌曲,mModel 可能是在 BasePresenter 中声明的对象 + if (mModel.deleteFromLove(songId)) { + // 当删除成功时,调用 mView 的 sendUpdateCollection 方法更新收藏列表 mView.sendUpdateCollection(); } } - //匹配歌词 - private void matchLrc(List listBeans,long duration){ + // 匹配歌词的私有方法 + private void matchLrc(List listBeans, long duration) { boolean isFind = false; - for(SearchSong.DataBean.SongBean.ListBean listBean:listBeans){ - if(duration == listBean.getInterval()){ + // 遍历搜索歌曲结果列表 + for (SearchSong.DataBean.SongBean.ListBean listBean : listBeans) { + // 如果歌曲时长匹配,设置找到标志并设置歌曲 ID + if (duration == listBean.getInterval()) { isFind = true; mView.setLocalSongId(listBean.getSongmid()); } } - //如果找不到歌曲id就传输找不到歌曲的消息 - if(!isFind) { + // 如果未找到匹配的歌曲,显示找不到歌曲 ID 的错误信息 + if (!isFind) { mView.getLrcError(Constant.SONG_ID_UNFIND); } } - private void matchSong(List listBeans,long duration){ + // 匹配歌曲的私有方法 + private void matchSong(List listBeans, long duration) { boolean isFind = false; - for(SearchSong.DataBean.SongBean.ListBean listBean:listBeans){ - if(duration == listBean.getInterval()){ + // 遍历搜索歌曲结果列表 + for (SearchSong.DataBean.SongBean.ListBean listBean : listBeans) { + // 如果歌曲时长匹配,设置找到标志并获取歌曲成功 + if (duration == listBean.getInterval()) { isFind = true; mView.getSongIdSuccess(listBean.getSongmid()); } } - //如果找不到歌曲id就传输找不到歌曲的消息 - if(!isFind) { + // 如果未找到匹配的歌曲,显示找不到歌曲 ID 的错误信息 + if (!isFind) { mView.getLrcError(Constant.SONG_ID_UNFIND); } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/presenter/SearchContentPresenter.java b/app/src/main/java/com/example/musicplayer/presenter/SearchContentPresenter.java index b8c2721..6222969 100644 --- a/app/src/main/java/com/example/musicplayer/presenter/SearchContentPresenter.java +++ b/app/src/main/java/com/example/musicplayer/presenter/SearchContentPresenter.java @@ -30,22 +30,37 @@ import okhttp3.Response; /** * Created by 残渊 on 2018/11/21. + * + *
+ * 该类是一个搜索内容的 Presenter 类,继承自 BasePresenter 并实现了 ISearchContentContract.Presenter 接口。
+ * 主要负责处理搜索相关的业务逻辑,包括搜索歌曲、搜索更多歌曲、搜索专辑、搜索更多专辑以及获取歌曲播放地址等操作。
+ * 它使用 RxJava 进行异步操作,将网络请求调度到 IO 线程,将结果处理调度到主线程,
+ * 并通过 BaseObserver 处理请求结果和异常,根据不同的结果更新视图。
+ * 
*/ - public class SearchContentPresenter extends BasePresenter implements ISearchContentContract.Presenter { + // 用于日志输出的 TAG 常量,方便在日志中识别该类 private static final String TAG = "SearchContentPresenter"; + // 实现 ISearchContentContract.Presenter 接口中的 search 方法,用于搜索歌曲 @Override public void search(String seek, int offset) { addRxSubscribe( + // 调用 mModel 的 search 方法进行搜索,mModel 可能是在 BasePresenter 中声明的对象 mModel.search(seek, offset) - .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) - .subscribeWith(new BaseObserver(mView, true, true) { + // 将网络请求操作调度到 IO 线程 + .subscribeOn(Schedulers.io()) + // 将结果处理调度到主线程 + .observeOn(AndroidSchedulers.mainThread()) + // 使用 BaseObserver 对搜索结果进行观察和处理 + .subscribeWith(new BaseObserver(mView, true, true) { + // 当搜索结果成功时 @Override public void onNext(SearchSong searchSong) { super.onNext(searchSong); if (searchSong.getCode() == 0) { + // 将搜索结果中的歌曲列表设置到视图中 mView.setSongsList((ArrayList) searchSong.getData().getSong().getList()); } @@ -53,12 +68,19 @@ public class SearchContentPresenter extends BasePresenter(mView, false, true) { + // 将网络请求操作调度到 IO 线程 + .subscribeOn(Schedulers.io()) + // 将结果处理调度到主线程 + .observeOn(AndroidSchedulers.mainThread()) + // 使用 BaseObserver 对搜索结果进行观察和处理 + .subscribeWith(new BaseObserver(mView, false, true) { + // 当搜索结果成功时 @Override public void onNext(SearchSong searchSong) { super.onNext(searchSong); @@ -66,90 +88,125 @@ public class SearchContentPresenter extends BasePresenter songListBeans = (ArrayList) searchSong.getData().getSong().getList(); if (songListBeans.size() == 0) { + // 当搜索结果为空时,显示搜索更多错误 mView.searchMoreError(); } else { + // 当搜索结果不为空时,显示搜索更多成功并传递结果列表 mView.searchMoreSuccess(songListBeans); } } else { + // 当搜索结果异常时,显示搜索更多错误 mView.searchMoreError(); } } + // 当搜索出现错误时 @Override - public void onError(Throwable e){ + public void onError(Throwable e) { super.onError(e); + // 显示搜索更多的网络错误 mView.showSearcherMoreNetworkError(); } })); } + // 实现 ISearchContentContract.Presenter 接口中的 searchAlbum 方法,用于搜索专辑 @Override public void searchAlbum(String seek, int offset) { addRxSubscribe( + // 调用 mModel 的 searchAlbum 方法进行专辑搜索,mModel 可能是在 BasePresenter 中声明的对象 mModel.searchAlbum(seek, offset) - .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) - .subscribeWith(new BaseObserver(mView, true, true) { + // 将网络请求操作调度到 IO 线程 + .subscribeOn(Schedulers.io()) + // 将结果处理调度到主线程 + .observeOn(AndroidSchedulers.mainThread()) + // 使用 BaseObserver 对搜索专辑结果进行观察和处理 + .subscribeWith(new BaseObserver(mView, true, true) { + // 当搜索专辑结果成功时 @Override public void onNext(Album album) { super.onNext(album); if (album.getCode() == 0) { + // 当搜索专辑结果正常时,显示搜索专辑成功并传递结果列表 mView.searchAlbumSuccess(album.getData().getAlbum().getList()); } else { + // 当搜索专辑结果异常时,显示搜索专辑错误 mView.searchAlbumError(); } } })); } + // 实现 ISearchContentContract.Presenter 接口中的 searchAlbumMore 方法,用于搜索更多专辑 @Override public void searchAlbumMore(String seek, int offset) { addRxSubscribe( + // 调用 mModel 的 searchAlbum 方法进行专辑搜索,mModel 可能是在 BasePresenter 中声明的对象 mModel.searchAlbum(seek, offset) - .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) - .subscribeWith(new BaseObserver(mView, false, true) { + // 将网络请求操作调度到 IO 线程 + .subscribeOn(Schedulers.io()) + // 将结果处理调度到主线程 + .observeOn(AndroidSchedulers.mainThread()) + // 使用 BaseObserver 对搜索专辑结果进行观察和处理 + .subscribeWith(new BaseObserver(mView, false, true) { + // 当搜索专辑结果成功时 @Override public void onNext(Album album) { super.onNext(album); if (album.getCode() == 0) { + // 当搜索专辑结果正常时,显示搜索更多专辑成功并传递结果列表 mView.searchAlbumMoreSuccess(album.getData().getAlbum().getList()); } else { + // 当搜索专辑结果异常时,显示搜索更多错误 mView.searchMoreError(); } } + + // 当搜索专辑出现错误时 @Override - public void onError(Throwable e){ + public void onError(Throwable e) { super.onError(e); + // 显示搜索更多的网络错误 mView.showSearcherMoreNetworkError(); } })); } + // 实现 ISearchContentContract.Presenter 中的 getSongUrl 方法,用于获取歌曲播放地址 @Override public void getSongUrl(Song song) { + // 输出日志,显示正在获取歌曲播放地址及相关数据 Log.d(TAG, "getSongUrl: "+Api.SONG_URL_DATA_LEFT+song.getSongId()+Api.SONG_URL_DATA_RIGHT); addRxSubscribe( + // 通过 RetrofitFactory 创建请求服务并调用 getSongUrl 方法获取歌曲播放地址 RetrofitFactory.createRequestOfSongUrl().getSongUrl(Api.SONG_URL_DATA_LEFT+song.getSongId()+Api.SONG_URL_DATA_RIGHT) - .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) - .subscribeWith(new BaseObserver(mView,false,false){ + // 将网络请求操作调度到 IO 线程 + .subscribeOn(Schedulers.io()) + // 将结果处理调度到主线程 + .observeOn(AndroidSchedulers.mainThread()) + // 使用 BaseObserver 对获取歌曲播放地址的结果进行观察和处理 + .subscribeWith(new BaseObserver(mView,false,false){ + // 当获取歌曲播放地址成功时 @Override - public void onNext(SongUrl songUrl){ + public void onNext(SongUrl songUrl) { super.onNext(songUrl); - if(songUrl.getCode() == 0){ + if (songUrl.getCode() == 0) { String sip = songUrl.getReq_0().getData().getSip().get(0); String purl = songUrl.getReq_0().getData().getMidurlinfo().get(0).getPurl(); - if(purl.equals("")){ + if (purl.equals("")) { + // 当歌曲无版权时,显示提示信息 mView.showToast("该歌曲暂时没有版权,搜搜其它歌曲吧"); - }else { - mView.getSongUrlSuccess(song,sip+purl); + } else { + // 当获取播放地址成功时,调用视图的 getSongUrlSuccess 方法 + mView.getSongUrlSuccess(song, sip + purl); } - - }else { + } else { + // 当获取歌曲播放地址失败时,显示错误信息 mView.showToast(songUrl.getCode()+":获取不到歌曲播放地址"); } } }) ); } - -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/service/DownloadService.java b/app/src/main/java/com/example/musicplayer/service/DownloadService.java index 6b03333..d5fe506 100644 --- a/app/src/main/java/com/example/musicplayer/service/DownloadService.java +++ b/app/src/main/java/com/example/musicplayer/service/DownloadService.java @@ -8,7 +8,6 @@ import android.app.Service; import android.content.Intent; import android.graphics.BitmapFactory; import android.os.Binder; -import android.os.Build; import android.os.IBinder; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; @@ -46,164 +45,225 @@ import static com.example.musicplayer.app.Constant.TYPE_DOWNLOADING; *
  *     author : 残渊
  *     time   : 2019/04/08
- *     desc   : 下载服务,保证DownloadTask在后台运行
+ *     desc   : 下载服务,保证 DownloadTask 在后台运行
+ * 
+ *     该类是一个服务类,用于处理歌曲的下载操作。
+ *     它使用 DownloadTask 进行下载任务的执行,通过 DownloadListener 监听下载进度和状态,
+ *     并使用 EventBus 进行事件的发布和订阅,以更新 UI 或通知其他部分下载的状态。
+ *     同时使用 LitePal 进行数据库操作,存储和更新下载信息。
+ *     还使用 Notification 进行通知的创建和更新,让用户了解下载的状态。
  * 
*/ - public class DownloadService extends Service { + // 日志标签 private static final String TAG = "DownloadService"; + // 存储当前的下载任务 private DownloadTask downloadTask; + // 存储下载的 URL private String downloadUrl; + // 自定义的 Binder 对象,用于与客户端通信 private DownloadBinder downloadBinder = new DownloadBinder(); - private LinkedList downloadQueue = new LinkedList<>();//等待队列 - private int position = 0;//下载歌曲在下载歌曲列表的位置 + // 存储下载等待队列,使用 LinkedList 方便在队列头部和尾部操作 + private LinkedList downloadQueue = new LinkedList<>(); + // 下载歌曲在下载歌曲列表中的位置 + private int position = 0; + // 下载监听器,监听下载进度和状态 private DownloadListener listener = new DownloadListener() { + // 下载进度更新时调用 @Override public void onProgress(DownloadInfo downloadInfo) { + // 更新下载信息的状态为正在下载 downloadInfo.setStatus(Constant.DOWNLOAD_ING); - EventBus.getDefault().post(new DownloadEvent(TYPE_DOWNLOADING, downloadInfo)); //通知下载模块 - if(downloadInfo.getProgress()!=100){ - getNotificationManager().notify(1, getNotification("正在下载: "+downloadInfo.getSongName(), downloadInfo.getProgress())); - }else { - if(downloadQueue.isEmpty()) getNotificationManager().notify(1, getNotification("下载成功",-1)); + // 发布下载事件,通知下载模块 + EventBus.getDefault().post(new DownloadEvent(TYPE_DOWNLOADING, downloadInfo)); + if (downloadInfo.getProgress()!= 100) { + // 更新通知显示正在下载及进度 + getNotificationManager().notify(1, getNotification("正在下载: " + downloadInfo.getSongName(), downloadInfo.getProgress())); + } else { + if (downloadQueue.isEmpty()) { + // 当下载队列空且下载完成时,更新通知为下载成功 + getNotificationManager().notify(1, getNotification("下载成功", -1)); + } } - } + // 下载成功时调用 @Override public void onSuccess() { downloadTask = null; + // 从下载队列中取出已完成的下载信息 DownloadInfo downloadInfo = downloadQueue.poll(); - operateDb(downloadInfo); //操作数据库 - start();//下载队列中的其它歌曲 - //下载成功通知前台服务通知关闭,并创建一个下载成功的通知 + // 操作数据库,可能是更新下载信息等操作 + operateDb(downloadInfo); + // 开始下载队列中的下一个歌曲 + start(); + // 停止前台服务通知,并创建一个下载成功的通知 stopForeground(true); - if(downloadQueue.isEmpty()) getNotificationManager().notify(1, getNotification("下载成功",-1)); + if (downloadQueue.isEmpty()) { + getNotificationManager().notify(1, getNotification("下载成功", -1)); + } } + // 下载完成时调用 @Override public void onDownloaded() { downloadTask = null; + // 显示下载完成的 Toast 提示 CommonUtil.showToast(DownloadService.this, "已下载"); } + // 下载失败时调用 @Override public void onFailed() { downloadTask = null; - - //下载失败通知前台服务通知关闭,并创建一个下载失败的通知 + // 停止前台服务通知,并创建一个下载失败的通知 stopForeground(true); - getNotificationManager().notify(1, getNotification("下载失败",-1)); + getNotificationManager().notify(1, getNotification("下载失败", -1)); + // 显示下载失败的 Toast 提示 Toast.makeText(DownloadService.this, "下载失败", Toast.LENGTH_SHORT).show(); } + // 下载暂停时调用 @Override public void onPaused() { downloadTask = null; - DownloadInfo downloadInfo=downloadQueue.poll();//从下载列表中移除该歌曲 + // 从下载队列中移除暂停的歌曲 + DownloadInfo downloadInfo = downloadQueue.poll(); + // 更新数据库中暂停的歌曲状态 updateDbOfPause(downloadInfo.getSongId()); - getNotificationManager().notify(1, getNotification("下载已暂停:"+downloadInfo.getSongName(), -1)); - start();//下载下载列表中的歌曲 + // 更新通知为下载已暂停 + getNotificationManager().notify(1, getNotification("下载已暂停:" + downloadInfo.getSongName(), -1)); + // 开始下载队列中的下一个歌曲 + start(); + // 更新下载信息状态为暂停,并发布暂停事件 downloadInfo.setStatus(Constant.DOWNLOAD_PAUSED); - EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_PAUSED, downloadInfo)); //下载暂停 + EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_PAUSED, downloadInfo)); + // 显示下载已暂停的 Toast 提示 Toast.makeText(DownloadService.this, "下载已暂停", Toast.LENGTH_SHORT).show(); } + // 下载取消时调用 @Override public void onCanceled() { downloadTask = null; + // 停止前台服务通知 stopForeground(true); + // 显示下载已取消的 Toast 提示 Toast.makeText(DownloadService.this, "下载已取消", Toast.LENGTH_SHORT).show(); } }; + // 服务绑定方法,返回 Binder 对象 @Nullable @Override public IBinder onBind(Intent intent) { return downloadBinder; } + // 自定义的 Binder 类,用于与客户端通信 public class DownloadBinder extends Binder { + // 开始下载方法 public void startDownload(DownloadInfo song) { try { - postDownloadEvent(song);//通知正在下载界面 + // 通知正在下载界面 + postDownloadEvent(song); } catch (Exception e) { e.printStackTrace(); } - if (downloadTask != null) { + if (downloadTask!= null) { + // 已存在下载任务时的提示 CommonUtil.showToast(DownloadService.this, "已经加入下载队列"); } else { + // 开始下载的提示 CommonUtil.showToast(DownloadService.this, "开始下载"); start(); } } + // 暂停下载方法 public void pauseDownload(String songId) { - //暂停的歌曲是否为当前下载的歌曲 - if (downloadTask != null &&downloadQueue.peek().getSongId().equals(songId)) { + // 判断暂停的歌曲是否为当前正在下载的歌曲 + if (downloadTask!= null && downloadQueue.peek().getSongId().equals(songId)) { + // 暂停当前下载任务 downloadTask.pauseDownload(); - }else {//暂停的歌曲是下载队列的歌曲 - //将该歌曲从下载队列中移除 + } else { + // 暂停的歌曲在下载队列中 for (int i = 0; i < downloadQueue.size(); i++) { DownloadInfo downloadInfo = downloadQueue.get(i); if (downloadInfo.getSongId().equals(songId)) { + // 从下载队列中移除该歌曲 downloadQueue.remove(i); + // 更新数据库中该歌曲的暂停状态 updateDbOfPause(downloadInfo.getSongId()); + // 更新下载信息状态为暂停,并发布暂停事件 downloadInfo.setStatus(Constant.DOWNLOAD_PAUSED); - EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_PAUSED, downloadInfo)); //下载暂停 + EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_PAUSED, downloadInfo)); } } } } + // 取消下载方法 public void cancelDownload(DownloadInfo song) { String songId = song.getSongId(); - //如果该歌曲正在下载,则需要将downloadTask置为null - if (downloadTask != null && downloadQueue.peek().getSongId().equals(songId)) { + // 如果该歌曲正在下载,取消下载任务 + if (downloadTask!= null && downloadQueue.peek().getSongId().equals(songId)) { downloadTask.cancelDownload(); } - //将该歌曲从下载队列中移除 + // 将该歌曲从下载队列中移除 for (int i = 0; i < downloadQueue.size(); i++) { DownloadInfo downloadInfo = downloadQueue.get(i); if (downloadInfo.getSongId().equals(songId)) downloadQueue.remove(i); } + // 更新数据库 updateDb(songId); + // 删除数据库中的该歌曲信息 deleteDb(songId); - //取消下载需要将文件删除并将通知关闭 - if (song.getUrl() != null) { - checkoutFile(song,song.getUrl()); //实际文件长度 + // 取消下载需要将文件删除并关闭通知 + if (song.getUrl()!= null) { + checkoutFile(song, song.getUrl()); } - //通知正在下载列表 + // 发布下载取消事件 EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_CANCELED)); - } - } + + // 开始下载的方法 private void start() { - if (downloadTask == null && !downloadQueue.isEmpty()) { + if (downloadTask == null &&!downloadQueue.isEmpty()) { + // 从下载队列中取出要下载的歌曲信息 DownloadInfo downloadInfo = downloadQueue.peek(); + // 从数据库中查找该歌曲信息 List songList = - LitePal.where("songId = ?",downloadInfo.getSongId()).find(DownloadInfo.class); + LitePal.where("songId =?", downloadInfo.getSongId()).find(DownloadInfo.class); DownloadInfo currentDownloadInfo = songList.get(0); + // 更新歌曲状态为准备下载 currentDownloadInfo.setStatus(Constant.DOWNLOAD_READY); - EventBus.getDefault().post(new DownloadEvent(TYPE_DOWNLOADING,currentDownloadInfo)); + // 发布下载事件 + EventBus.getDefault().post(new DownloadEvent(TYPE_DOWNLOADING, currentDownloadInfo)); downloadUrl = currentDownloadInfo.getUrl(); + // 创建新的下载任务并执行 downloadTask = new DownloadTask(listener); downloadTask.execute(currentDownloadInfo); - getNotificationManager().notify(1, getNotification("正在下载:"+downloadInfo.getSongName(), 0)); + // 更新通知为正在下载 + getNotificationManager().notify(1, getNotification("正在下载:" + downloadInfo.getSongName(), 0)); } } + // 获取通知管理器 private NotificationManager getNotificationManager() { return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); } - private Notification getNotification(String title,int progress) { + // 创建通知的方法 + private Notification getNotification(String title, int progress) { + // 创建跳转到主界面的意图 Intent intent = new Intent(this, MainActivity.class); PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // 创建通知通道 String id = "channel_001"; String name = "下载通知"; NotificationChannel mChannel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW); @@ -212,107 +272,132 @@ public class DownloadService extends Service { builder.setSmallIcon(R.mipmap.icon); builder.setContentIntent(pi); builder.setContentTitle(title); - if(progress>0){ - builder.setContentText(progress +"%"); + if (progress > 0) { + builder.setContentText(progress + "%"); builder.setProgress(100, progress, false); } return builder.build(); } else { + // 兼容旧版本的通知创建 NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "default"); builder.setSmallIcon(R.mipmap.icon); builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.icon)); builder.setContentIntent(pi); builder.setContentTitle(title); - if(progress>0){ - builder.setContentText(progress +"%"); + if (progress > 0) { + builder.setContentText(progress + "%"); builder.setProgress(100, progress, false); } return builder.build(); } } + // 操作数据库的方法,可能是下载成功后的操作 private void operateDb(DownloadInfo downloadInfo) { updateDb(downloadInfo.getSongId()); deleteDb(downloadInfo.getSongId()); - EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_SUCCESS));//通知已下载列表 - EventBus.getDefault().post(new SongListNumEvent(Constant.LIST_TYPE_DOWNLOAD)); //通知主界面的下载个数需要改变 + // 发布下载成功事件 + EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_SUCCESS)); + // 发布歌曲列表数量改变事件 + EventBus.getDefault().post(new SongListNumEvent(Constant.LIST_TYPE_DOWNLOAD)); } - //更新数据库中歌曲列表的位置,即下载完成歌曲后的位置都要减去1; + // 更新数据库中歌曲列表的位置 private void updateDb(String songId) { - long id = LitePal.select("id").where("songId = ?", songId).find(DownloadInfo.class).get(0).getId(); - List songIdList = LitePal.where("id > ?", id + "").find(DownloadInfo.class); + // 查找该歌曲在数据库中的 ID + long id = LitePal.select("id").where("songId =?", songId).find(DownloadInfo.class).get(0).getId(); + // 查找该歌曲之后的歌曲列表 + List songIdList = LitePal.where("id >?", id + "").find(DownloadInfo.class); for (DownloadInfo song : songIdList) { + // 更新位置,将后续歌曲位置减 1 song.setPosition(song.getPosition() - 1); song.save(); } } - //暂停时更新列表歌曲状态 - private void updateDbOfPause(String songId){ + // 暂停时更新数据库中歌曲的状态 + private void updateDbOfPause(String songId) { List statusList = - LitePal.where("songId = ?",songId).find(DownloadInfo.class,true); + LitePal.where("songId =?", songId).find(DownloadInfo.class, true); DownloadInfo downloadInfo = statusList.get(0); downloadInfo.setStatus(Constant.DOWNLOAD_PAUSED); downloadInfo.save(); } - //下载完成时要删除下载歌曲表中的数据以及关联表中的数据 + // 下载完成时删除数据库中的数据 private void deleteDb(String songId) { - LitePal.deleteAll(DownloadInfo.class, "songId=?", songId);//删除已下载歌曲的相关列 + // 删除已下载歌曲的相关列 + LitePal.deleteAll(DownloadInfo.class, "songId=?", songId); } + // 发布下载事件 private void postDownloadEvent(DownloadInfo downloadInfo) { - //如果需要下载的表中有该条歌曲,则添加到下载队列后跳过 + // 检查数据库中是否已存在该歌曲的下载信息 List downloadInfoList = - LitePal.where("songId = ?",downloadInfo.getSongId()).find(DownloadInfo.class,true); - if (downloadInfoList.size() != 0){ + LitePal.where("songId =?", downloadInfo.getSongId()).find(DownloadInfo.class, true); + if (downloadInfoList.size()!= 0) { DownloadInfo historyDownloadInfo = downloadInfoList.get(0); + // 更新状态为等待 historyDownloadInfo.setStatus(Constant.DOWNLOAD_WAIT); historyDownloadInfo.save(); - EventBus.getDefault().post(new DownloadEvent(Constant.DOWNLOAD_PAUSED,historyDownloadInfo)); + // 发布暂停事件 + EventBus.getDefault().post(new DownloadEvent(Constant.DOWNLOAD_PAUSED, historyDownloadInfo)); + // 将歌曲添加到下载队列 downloadQueue.offer(historyDownloadInfo); return; } - + // 设置歌曲在下载列表中的位置 position = LitePal.findAll(DownloadInfo.class).size(); downloadInfo.setPosition(position); - downloadInfo.setStatus(Constant.DOWNLOAD_WAIT); //等待 + downloadInfo.setStatus(Constant.DOWNLOAD_WAIT); downloadInfo.save(); - downloadQueue.offer(downloadInfo);//将歌曲放到等待队列中 + // 将歌曲添加到等待队列 + downloadQueue.offer(downloadInfo); + // 发布添加下载事件 EventBus.getDefault().post(new DownloadEvent(Constant.TYPE_DOWNLOAD_ADD)); } - //获取歌曲实际大小,然后判断是否存在于文件中 - public void checkoutFile(DownloadInfo song, String downloadUrl){ - OkHttpClient client = new OkHttpClient(); - Request request = new Request.Builder() - .url(downloadUrl) - .build(); - Call call= client.newCall(request); - call.enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - if(response.isSuccessful()){ - long size = response.body().contentLength(); - String fileName = DownloadUtil.getSaveSongFile(song.getSinger(),song.getSongName(),song.getDuration(),song.getSongId(),size); - File downloadFile = new File(Api.STORAGE_SONG_FILE); - String directory = String.valueOf(downloadFile); - File file = new File(fileName, directory); - if (file.exists()) { - file.delete(); - } - getNotificationManager().cancel(1); - stopForeground(true); - } - } - }); - } + // 获取歌曲实际大小,并判断文件是否存在,可能用于取消下载时的清理 +public void checkoutFile(DownloadInfo song, String downloadUrl) { + // 创建 OkHttpClient 实例,用于网络请求 + OkHttpClient client = new OkHttpClient(); + // 构建请求对象,指定请求的 URL + Request request = new Request.Builder() + .url(downloadUrl) + .build(); + // 创建一个 Call 对象,用于执行请求 + Call call = client.newCall(request); + // 将请求加入请求队列,并设置回调 + call.enqueue(new Callback() { + // 请求失败时调用 + @Override + public void onFailure(Call call, IOException e) { + // 此处暂时未处理请求失败的情况,可以根据需求添加相应的处理逻辑 + } -} + // 请求成功时调用 + @Override + public void onResponse(Call call, Response response) throws IOException { + if (response.isSuccessful()) { + // 获取响应体的内容长度,即文件的实际大小 + long size = response.body().contentLength(); + // 根据歌曲信息和文件大小生成保存歌曲的文件名 + String fileName = DownloadUtil.getSaveSongFile(song.getSinger(), song.getSongName(), song.getDuration(), song.getSongId(), size); + // 获取存储歌曲文件的目录 + File downloadFile = new File(Api.STORAGE_SONG_FILE); + String directory = String.valueOf(downloadFile); + // 构建文件对象 + File file = new File(fileName, directory); + if (file.exists()) { + // 如果文件存在,删除文件 + file.delete(); + } + // 取消通知 + getNotificationManager().cancel(1); + // 停止服务的前台运行状态 + stopForeground(true); + } + } + }); +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/service/PlayerService.java b/app/src/main/java/com/example/musicplayer/service/PlayerService.java index e8d52b2..bcadeea 100644 --- a/app/src/main/java/com/example/musicplayer/service/PlayerService.java +++ b/app/src/main/java/com/example/musicplayer/service/PlayerService.java @@ -4,7 +4,6 @@ import android.annotation.SuppressLint; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; -import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.graphics.BitmapFactory; @@ -54,29 +53,45 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -@SuppressLint("NewApi") +// 抑制新 API 警告 +@SuppressLint("NewApi") public class PlayerService extends Service { - - private static final String TAG = "PlayerService"; - private final int NOTIFICATION_ID = 98; - private PlayStatusBinder mPlayStatusBinder = new PlayStatusBinder(); - private MediaPlayer mediaPlayer = new MediaPlayer(); //媒体播放器对象 - private boolean isPause; //暂停状态 - private boolean isPlaying; //是否播放 - private List mLocalSongList; - private List mSongList; - private List mLoveList; - private List mHistoryList; - private List mDownloadList; - private int mCurrent; - private int mListType; - private int mPlayMode = Constant.PLAY_ORDER; //播放模式,默认为顺序播放 - + // 日志标记 + private static final String TAG = "PlayerService"; + // 通知的唯一标识 + private final int NOTIFICATION_ID = 98; + // 自定义的 Binder 类,用于在服务和客户端之间通信 + private PlayStatusBinder mPlayStatusBinder = new PlayStatusBinder(); + // 媒体播放器对象,用于播放音乐 + private MediaPlayer mediaPlayer = new MediaPlayer(); + // 暂停状态标记 + private boolean isPause; + // 是否正在播放标记 + private boolean isPlaying; + // 本地歌曲列表 + private List mLocalSongList; + // 在线歌曲列表 + private List mSongList; + // 收藏歌曲列表 + private List mLoveList; + // 历史播放歌曲列表 + private List mHistoryList; + // 下载歌曲列表 + private List mDownloadList; + // 当前歌曲的索引 + private int mCurrent; + // 列表类型(在线、本地、收藏、历史、下载) + private int mListType; + // 播放模式,默认为顺序播放 + private int mPlayMode = Constant.PLAY_ORDER; @Override public void onCreate() { - Log.d(TAG, "onCreate: true"); - mListType = FileUtil.getSong().getListType(); + // 输出日志,服务创建 + Log.d(TAG, "onCreate: true"); + // 获取存储的歌曲列表类型 + mListType = FileUtil.getSong().getListType(); + // 根据不同的列表类型从数据库中获取相应的歌曲列表 if (mListType == Constant.LIST_TYPE_ONLINE) { mSongList = LitePal.findAll(OnlineSong.class); } else if (mListType == Constant.LIST_TYPE_LOCAL) { @@ -85,546 +100,787 @@ public class PlayerService extends Service { mLoveList = LitePal.findAll(Love.class); } else if (mListType == Constant.LIST_TYPE_HISTORY) { mHistoryList = orderHistoryList(LitePal.findAll(HistorySong.class)); - //保证最近播放列表一开始总是第一个 + // 保证最近播放列表一开始总是第一个 Song song = FileUtil.getSong(); song.setPosition(0); FileUtil.saveSong(song); - }else if(mListType == Constant.LIST_TYPE_DOWNLOAD){ + } else if (mListType == Constant.LIST_TYPE_DOWNLOAD) { mDownloadList = orderDownloadList(DownloadUtil.getSongFromFile(Api.STORAGE_SONG_FILE)); } - - //开启前台服务 - startForeground(NOTIFICATION_ID,getNotification("随心跳动,开启你的音乐旅程!")); + // 开启前台服务并显示通知 + startForeground(NOTIFICATION_ID, getNotification("随心跳动,开启你的音乐旅程!")); } @Override public IBinder onBind(Intent arg0) { - Log.d(TAG, "onBind: jsyjst"); + // 输出日志,服务绑定 + Log.d(TAG, "onBind: jsyjst"); + // 设置媒体播放器播放完成的监听器 mediaPlayer.setOnCompletionListener(mp -> { - EventBus.getDefault().post(new SongStatusEvent(Constant.SONG_PAUSE));//暂停广播 + // 发送歌曲暂停事件 + EventBus.getDefault().post(new SongStatusEvent(Constant.SONG_PAUSE)); mCurrent = FileUtil.getSong().getPosition(); - //将歌曲的信息保存起来 - if (mListType == Constant.LIST_TYPE_LOCAL) { - mCurrent=getNextCurrent(mCurrent, mPlayMode, mLocalSongList.size()); //根据播放模式来播放下一曲 - saveLocalSongInfo(mCurrent); - } else if (mListType == Constant.LIST_TYPE_ONLINE) { - mCurrent=getNextCurrent(mCurrent, mPlayMode, mSongList.size());//根据播放模式来播放下一曲 - saveOnlineSongInfo(mCurrent); - } else if (mListType == Constant.LIST_TYPE_LOVE) { - mCurrent=getNextCurrent(mCurrent, mPlayMode, mLoveList.size());//根据播放模式来播放下一曲 - saveLoveInfo(mCurrent); - } else if(mListType == Constant.LIST_TYPE_HISTORY){ - mCurrent=getNextCurrent(mCurrent, mPlayMode, mHistoryList.size());//根据播放模式来播放下一曲 - saveHistoryInfo(mCurrent); - }else if(mListType == Constant.LIST_TYPE_DOWNLOAD){ - mCurrent=getNextCurrent(mCurrent, mPlayMode, mDownloadList.size());//根据播放模式来播放下一曲 - saveDownloadInfo(mCurrent); - } - if(mListType!=0) { - mPlayStatusBinder.play(mListType); - }else { - mPlayStatusBinder.stop(); - } - }); - /** - * MediaPlayer切歌进入setOnCompletionListener的问题 - * 因为直接切歌会发生错误,所以增加错误监听器。返回true。就不会回调onCompletion方法了。 - */ - mediaPlayer.setOnErrorListener((mp, what, extra) -> true); - return mPlayStatusBinder; + // 根据播放模式播放下一曲并保存歌曲信息 + if (mListType == Constant.LIST_TYPE_LOCAL) { + // 如果列表类型是本地歌曲列表 + mCurrent = getNextCurrent(mCurrent, mPlayMode, mLocalSongList.size()); + // 根据当前播放位置、播放模式和本地歌曲列表的长度,调用 getNextCurrent 方法获取下一首歌曲的位置 + saveLocalSongInfo(mCurrent); + // 保存本地歌曲列表中对应位置的歌曲信息,将当前播放位置、歌曲名称、歌手等信息存储到文件中 + } else if (mListType == Constant.LIST_TYPE_ONLINE) { + // 如果列表类型是在线歌曲列表 + mCurrent = getNextCurrent(mCurrent, mPlayMode, mSongList.size()); + // 根据当前播放位置、播放模式和在线歌曲列表的长度,调用 getNextCurrent 方法获取下一首歌曲的位置 + saveOnlineSongInfo(mCurrent); + // 保存在线歌曲列表中对应位置的歌曲信息,将当前播放位置、歌曲名称、歌手等信息存储到文件中 + } else if (mListType == Constant.LIST_TYPE_LOVE) { + // 如果列表类型是收藏歌曲列表 + mCurrent = getNextCurrent(mCurrent, mPlayMode, mLoveList.size()); + // 根据当前播放位置、播放模式和收藏歌曲列表的长度,调用 getNextCurrent 方法获取下一首歌曲的位置 + saveLoveInfo(mCurrent); + // 保存收藏歌曲列表中对应位置的歌曲信息,将当前播放位置、歌曲名称、歌手等信息存储到文件中 + } else if (mListType == Constant.LIST_TYPE_HISTORY) { + // 如果列表类型是历史歌曲列表 + mCurrent = getNextCurrent(mCurrent, mPlayMode, mHistoryList.size()); + // 根据当前播放位置、播放模式和历史歌曲列表的长度,调用 getNextCurrent 方法获取下一首歌曲的位置 + saveHistoryInfo(mCurrent); + // 保存历史歌曲列表中对应位置的歌曲信息,将当前播放位置、歌曲名称、歌手等信息存储到文件中 + } else if (mListType == Constant.LIST_TYPE_DOWNLOAD) { + // 如果列表类型是下载歌曲列表 + mCurrent = getNextCurrent(mCurrent, mPlayMode, mDownloadList.size()); + // 根据当前播放位置、播放模式和下载歌曲列表的长度,调用 getNextCurrent 方法获取下一首歌曲的位置 + saveDownloadInfo(mCurrent); + // 保存下载歌曲列表中对应位置的歌曲信息,将当前播放位置、歌曲名称、歌手等信息存储到文件中 + } + if (mListType!= 0) { + // 如果列表类型不为 0 + mPlayStatusBinder.play(mListType); + // 调用 PlayStatusBinder 的 play 方法,根据当前列表类型播放歌曲 + } else { + mPlayStatusBinder.stop(); + // 否则调用 PlayStatusBinder 的 stop 方法,停止播放 +} + // 设置媒体播放器错误监听器,防止直接切歌时发生错误 + mediaPlayer.setOnErrorListener((mp, what, extra) -> true); + return mPlayStatusBinder; } - - public class PlayStatusBinder extends Binder{ - - public void setPlayMode(int mode){ + public class PlayStatusBinder extends Binder { + // 设置播放模式 + public void setPlayMode(int mode) { mPlayMode = mode; } - + // 获取历史播放列表 public void getHistoryList() { mHistoryList = orderHistoryList(LitePal.findAll(HistorySong.class)); - //保证最近播放列表一开始总是第一个 + // 保证最近播放列表一开始总是第一个 Song song = FileUtil.getSong(); song.setPosition(0); FileUtil.saveSong(song); } - /** - * 播放音乐 - * - * @param - */ - + // 播放音乐 public void play(int listType) { try { mListType = listType; + // 根据不同的列表类型更新列表并发送相应的事件 if (mListType == Constant.LIST_TYPE_ONLINE) { - mSongList = LitePal.findAll(OnlineSong.class); - EventBus.getDefault().post(new SongAlbumEvent()); - } else if (mListType == Constant.LIST_TYPE_LOCAL) { - mLocalSongList = LitePal.findAll(LocalSong.class); - EventBus.getDefault().post(new SongLocalEvent()); //发送本地歌曲改变事件 - } else if (mListType == Constant.LIST_TYPE_LOVE) { - mLoveList = orderList(LitePal.findAll(Love.class)); - EventBus.getDefault().post(new SongCollectionEvent(true));//发送歌曲改变事件 - } else if (mListType == Constant.LIST_TYPE_HISTORY) { - EventBus.getDefault().post(new SongHistoryEvent()); //发送随机歌曲改变事件 - }else if(mListType == Constant.LIST_TYPE_DOWNLOAD){ - mDownloadList =orderDownloadList(DownloadUtil.getSongFromFile(Api.STORAGE_SONG_FILE)); - EventBus.getDefault().post(new SongDownloadedEvent()); //发送下载歌曲改变的消息 - } - mCurrent = FileUtil.getSong().getPosition(); - mediaPlayer.reset();//把各项参数恢复到初始状态 - if (mListType == Constant.LIST_TYPE_LOCAL) { - mediaPlayer.setDataSource(mLocalSongList.get(mCurrent).getUrl()); - startPlay(); - } else if (mListType == Constant.LIST_TYPE_ONLINE) { - getSongUrl(mSongList.get(mCurrent).getSongId()); - } else if (mListType == Constant.LIST_TYPE_LOVE) { - mediaPlayer.setDataSource(mLoveList.get(mCurrent).getUrl()); - startPlay(); - } else if(mListType == Constant.LIST_TYPE_HISTORY){ - mediaPlayer.setDataSource(mHistoryList.get(mCurrent).getUrl()); - startPlay(); - }else if(mListType == Constant.LIST_TYPE_DOWNLOAD){ - Log.d(TAG, "play: "+mDownloadList.get(mCurrent).getUrl()); - mediaPlayer.setDataSource(mDownloadList.get(mCurrent).getUrl()); - startPlay(); - } - } catch (Exception e) { - e.printStackTrace(); - } - } + // 如果列表类型是在线歌曲列表 + mSongList = LitePal.findAll(OnlineSong.class); + // 使用 LitePal 从数据库中查找所有的 OnlineSong 类型的歌曲,并将结果存储在 mSongList 中 + EventBus.getDefault().post(new SongAlbumEvent()); + // 通过 EventBus 发送 SongAlbumEvent 事件,可能用于通知其他组件更新 UI 或执行相应操作 + } else if (mListType == Constant.LIST_TYPE_LOCAL) { + // 如果列表类型是本地歌曲列表 + mLocalSongList = LitePal.findAll(LocalSong.class); + // 使用 LitePal 从数据库中查找所有的 LocalSong 类型的歌曲,并将结果存储在 mLocalSongList 中 + EventBus.getDefault().post(new SongLocalEvent()); + // 通过 EventBus 发送 SongLocalEvent 事件,可能用于通知其他组件更新 UI 或执行相应操作 + } else if (mListType == Constant.LIST_TYPE_LOVE) { + // 如果列表类型是收藏歌曲列表 + mLoveList = orderList(LitePal.findAll(Love.class)); + // 先使用 LitePal 从数据库中查找所有的 Love 类型的歌曲,然后调用 orderList 方法对结果进行排序,将排序后的列表存储在 mLoveList 中 + EventBus.getDefault().post(new SongCollectionEvent(true)); + // 通过 EventBus 发送 SongCollectionEvent 事件,可能用于通知其他组件更新 UI 或执行相应操作 + } else if (mListType == Constant.LIST_TYPE_HISTORY) { + // 如果列表类型是历史歌曲列表 + EventBus.getDefault().post(new SongHistoryEvent()); + // 通过 EventBus 发送 SongHistoryEvent 事件,可能用于通知其他组件更新 UI 或执行相应操作 + } else if (mListType == Constant.LIST_TYPE_DOWNLOAD) { + // 如果列表类型是下载歌曲列表 + mDownloadList = orderDownloadList(DownloadUtil.getSongFromFile(Api.STORAGE_SONG_FILE)); + // 通过 DownloadUtil 从文件中获取下载歌曲列表,并调用 orderDownloadList 方法对结果进行排序,将排序后的列表存储在 mDownloadList 中 + EventBus.getDefault().post(new SongDownloadedEvent()); + // 通过 EventBus 发送 SongDownloadedEvent 事件,可能用于通知其他组件更新 UI 或执行相应操作 + } + mCurrent = FileUtil.getSong().getPosition(); + // 从 FileUtil 中获取当前歌曲的位置信息,并存储在 mCurrent 中 + // 重置媒体播放器状态 + mediaPlayer.reset(); + // 根据列表类型设置数据源并开始播放 + if (mListType == Constant.LIST_TYPE_LOCAL) { + // 如果列表类型是本地歌曲列表 + mediaPlayer.setDataSource(mLocalSongList.get(mCurrent).getUrl()); + // 从 mLocalSongList 中获取当前歌曲的 URL,并将其设置为媒体播放器 mediaPlayer 的数据源 + startPlay(); + // 调用 startPlay 方法开始播放音乐 + } else if (mListType == Constant.LIST_TYPE_ONLINE) { + // 如果列表类型是在线歌曲列表 + getSongUrl(mSongList.get(mCurrent).getSongId()); + // 获取当前在线歌曲的歌曲 ID,然后调用 getSongUrl 方法通过网络请求获取该歌曲的播放 URL + } else if (mListType == Constant.LIST_TYPE_LOVE) { + // 如果列表类型是收藏歌曲列表 + mediaPlayer.setDataSource(mLoveList.get(mCurrent).getUrl()); + // 从 mLoveList 中获取当前歌曲的 URL,并将其设置为媒体播放器 mediaPlayer 的数据源 + startPlay(); + // 调用 startPlay 方法开始播放音乐 + } else if (mListType == Constant.LIST_TYPE_HISTORY) { + // 如果列表类型是历史歌曲列表 + mediaPlayer.setDataSource(mHistoryList.get(mCurrent).getUrl()); + // 从 mHistoryList 中获取当前歌曲的 URL,并将其设置为媒体播放器 mediaPlayer 的数据源 + startPlay(); + // 调用 startPlay 方法开始播放音乐 + } else if (mListType == Constant.LIST_TYPE_DOWNLOAD) { + // 如果列表类型是下载歌曲列表 + Log.d(TAG, "play: " + mDownloadList.get(mCurrent).getUrl()); + // 打印当前下载歌曲的 URL 到日志 + mediaPlayer.setDataSource(mDownloadList.get(mCurrent).getUrl()); + // 从 mDownloadList 中获取当前歌曲的 URL,并将其设置为媒体播放器 mediaPlayer 的数据源 + startPlay(); + // 调用 startPlay 方法开始播放音乐 + } + } catch (Exception e) { + e.printStackTrace(); +} - //播放搜索歌曲 + // 播放搜索歌曲 public void playOnline() { try { mediaPlayer.reset(); + // 设置数据源,准备并开始播放 mediaPlayer.setDataSource(FileUtil.getSong().getUrl()); mediaPlayer.prepare(); isPlaying = true; - saveToHistoryTable(); + // 保存到历史播放表 + saveToHistoryTable(); mediaPlayer.start(); - EventBus.getDefault().post(new OnlineSongChangeEvent()); //发送网络歌曲改变事件 + // 发送在线歌曲改变事件和歌曲状态改变事件 + EventBus.getDefault().post(new OnlineSongChangeEvent()); EventBus.getDefault().post(new SongStatusEvent(Constant.SONG_CHANGE)); - //改变通知栏歌曲 + // 改变通知栏歌曲信息 Song song = FileUtil.getSong(); getNotificationManager().notify(NOTIFICATION_ID, - getNotification(song.getSongName()+" - "+song.getSinger())); + getNotification(song.getSongName() + " - " + song.getSinger())); } catch (Exception e) { - EventBus.getDefault().post(new OnlineSongErrorEvent()); + // 发送在线歌曲错误事件 + EventBus.getDefault().post(new OnlineSongErrorEvent()); e.printStackTrace(); } - } - - /** - * 暂停音乐 - */ - + // 暂停音乐 public void pause() { - if (mediaPlayer != null && mediaPlayer.isPlaying()) { + if (mediaPlayer!= null && mediaPlayer.isPlaying()) { isPlaying = false; mediaPlayer.pause(); isPause = true; - EventBus.getDefault().post(new SongStatusEvent(Constant.SONG_PAUSE));//发送暂停的广播给主活动 - } + // 发送暂停广播 + EventBus.getDefault().post(new SongStatusEvent(Constant.SONG_PAUSE)); + } } + // 恢复播放 public void resume() { if (isPause) { mediaPlayer.start(); isPlaying = true; isPause = false; - EventBus.getDefault().post(new SongStatusEvent(Constant.SONG_RESUME)); + // 发送恢复播放广播 + EventBus.getDefault().post(new SongStatusEvent(Constant.SONG_RESUME)); } } - + // 播放下一首歌曲 public void next() { EventBus.getDefault().post(new SongStatusEvent(Constant.SONG_RESUME)); mCurrent = FileUtil.getSong().getPosition(); + // 根据播放模式获取下一首歌曲的索引并保存歌曲信息 if (mListType == Constant.LIST_TYPE_LOCAL) { - mCurrent=getNextCurrent(mCurrent, mPlayMode, mLocalSongList.size()); //根据播放模式来播放下一曲 - saveLocalSongInfo(mCurrent); - } else if (mListType == Constant.LIST_TYPE_ONLINE) { - mCurrent=getNextCurrent(mCurrent, mPlayMode, mSongList.size());//根据播放模式来播放下一曲 - saveOnlineSongInfo(mCurrent); - } else if (mListType == Constant.LIST_TYPE_LOVE) { - mCurrent=getNextCurrent(mCurrent, mPlayMode, mLoveList.size());//根据播放模式来播放下一曲 - saveLoveInfo(mCurrent); - } else if(mListType == Constant.LIST_TYPE_HISTORY){ - mCurrent=getNextCurrent(mCurrent, mPlayMode, mHistoryList.size());//根据播放模式来播放下一曲 - saveHistoryInfo(mCurrent); - }else if(mListType == Constant.LIST_TYPE_DOWNLOAD){ - mCurrent=getNextCurrent(mCurrent, mPlayMode, mDownloadList.size());//根据播放模式来播放下一曲 - saveDownloadInfo(mCurrent); - } - if(mListType!=0) mPlayStatusBinder.play(mListType); - } - - public void last() { - EventBus.getDefault().post(new SongStatusEvent(Constant.SONG_RESUME));//暂停广播 - mCurrent = FileUtil.getSong().getPosition(); - if (mListType == Constant.LIST_TYPE_LOCAL) { - mCurrent = getLastCurrent(mCurrent,mPlayMode,mLocalSongList.size()); - saveLocalSongInfo(mCurrent); - } else if (mListType == Constant.LIST_TYPE_ONLINE) { - mCurrent = getLastCurrent(mCurrent,mPlayMode,mSongList.size()); - saveOnlineSongInfo(mCurrent); - } else if (mListType == Constant.LIST_TYPE_LOVE) { - mCurrent = getLastCurrent(mCurrent,mPlayMode,mLoveList.size()); - saveLoveInfo(mCurrent); - } else if(mListType == Constant.LIST_TYPE_HISTORY){ - mCurrent = getLastCurrent(mCurrent,mPlayMode,mHistoryList.size()); - saveHistoryInfo(mCurrent); - } else if(mListType == Constant.LIST_TYPE_DOWNLOAD){ - mCurrent = getLastCurrent(mCurrent,mPlayMode,mDownloadList.size()); - saveDownloadInfo(mCurrent); - } - if(mListType!=0) mPlayStatusBinder.play(mListType); - } - - /** - * 停止音乐 - */ + // 如果当前歌曲列表类型是本地歌曲列表 + mCurrent = getNextCurrent(mCurrent, mPlayMode, mLocalSongList.size()); + // 调用 getNextCurrent 方法,根据当前歌曲位置、播放模式和本地歌曲列表的大小来计算下一首歌曲的位置 + // 此方法会根据播放模式(顺序、随机等)计算下一首歌曲的位置 + saveLocalSongInfo(mCurrent); + // 调用 saveLocalSongInfo 方法,将本地歌曲列表中当前位置(mCurrent)的歌曲信息保存起来 + // 可能会保存歌曲的位置、名称、歌手、时长、URL 等信息 + } else if (mListType == Constant.LIST_TYPE_ONLINE) { + // 如果当前歌曲列表类型是在线歌曲列表 + mCurrent = getNextCurrent(mCurrent, mPlayMode, mSongList.size()); + // 同样调用 getNextCurrent 方法,根据当前歌曲位置、播放模式和在线歌曲列表的大小计算下一首歌曲的位置 + saveOnlineSongInfo(mCurrent); + // 调用 saveOnlineSongInfo 方法,将在线歌曲列表中当前位置(mCurrent)的歌曲信息保存起来 + } else if (mListType == Constant.LIST_TYPE_LOVE) { + // 如果当前歌曲列表类型是收藏歌曲列表 + mCurrent = getNextCurrent(mCurrent, mPlayMode, mLoveList.size()); + // 调用 getNextCurrent 方法,根据当前歌曲位置、播放模式和收藏歌曲列表的大小计算下一首歌曲的位置 + saveLoveInfo(mCurrent); + // 调用 saveLoveInfo 方法,将收藏歌曲列表中当前位置(mCurrent)的歌曲信息保存起来 + } else if (mListType == Constant.LIST_TYPE_HISTORY) { + // 如果当前歌曲列表类型是历史歌曲列表 + mCurrent = getNextCurrent(mCurrent, mPlayMode, mHistoryList.size()); + // 调用 getNextCurrent 方法,根据当前歌曲位置、播放模式和历史歌曲列表的大小计算下一首歌曲的位置 + saveHistoryInfo(mCurrent); + // 调用 saveHistoryInfo 方法,将历史歌曲列表中当前位置(mCurrent)的歌曲信息保存起来 + } else if (mListType == Constant.LIST_TYPE_DOWNLOAD) { + // 如果当前歌曲列表类型是下载歌曲列表 + mCurrent = getNextCurrent(mCurrent, mPlayMode, mDownloadList.size()); + // 调用 getNextCurrent 方法,根据当前歌曲位置、播放模式和下载歌曲列表的大小计算下一首歌曲的位置 + saveDownloadInfo(mCurrent); + // 调用 saveDownloadInfo 方法,将下载歌曲列表中当前位置(mCurrent)的歌曲信息保存起来 + } + if (mListType!= 0) mPlayStatusBinder.play(mListType); + // 如果 mListType 不为 0,调用 mPlayStatusBinder 的 play 方法,根据当前列表类型开始播放歌曲 + // 这意味着当列表类型有效时,会继续播放下一首歌曲,否则不进行播放操作 + + // 播放上一首歌曲 + public void last() { + // 发送歌曲状态恢复事件,可能用于通知其他组件(如 UI 界面)歌曲状态将发生变化 + EventBus.getDefault().post(new SongStatusEvent(Constant.SONG_RESUME)); + // 从 FileUtil 中获取当前歌曲的位置,并存储在 mCurrent 中 + mCurrent = FileUtil.getSong().getPosition(); + // 根据播放模式获取上一首歌曲的索引并保存歌曲信息 + if (mListType == Constant.LIST_TYPE_LOCAL) { + // 如果是本地歌曲列表 + mCurrent = getLastCurrent(mCurrent, mPlayMode, mLocalSongList.size()); + // 调用 getLastCurrent 方法,根据当前歌曲的位置、播放模式和本地歌曲列表的大小计算上一首歌曲的位置 + // 不同的播放模式(如顺序、随机等)会影响计算结果 + saveLocalSongInfo(mCurrent); + // 调用 saveLocalSongInfo 方法,将本地歌曲列表中对应上一首歌曲的信息保存起来 + // 可能会保存歌曲的位置、名称、歌手、时长、URL 等信息 + } else if (mListType == Constant.LIST_TYPE_ONLINE) { + // 如果是在线歌曲列表 + mCurrent = getLastCurrent(mCurrent, mPlayMode, mSongList.size()); + // 调用 getLastCurrent 方法,根据当前歌曲的位置、播放模式和在线歌曲列表的大小计算上一首歌曲的位置 + saveOnlineSongInfo(mCurrent); + // 调用 saveOnlineSongInfo 方法,将在线歌曲列表中对应上一首歌曲的信息保存起来 + } else if (mListType == Constant.LIST_TYPE_LOVE) { + // 如果是收藏歌曲列表 + mCurrent = getLastCurrent(mCurrent, mPlayMode, mLoveList.size()); + // 调用 getLastCurrent 方法,根据当前歌曲的位置、播放模式和收藏歌曲列表的大小计算上一首歌曲的位置 + saveLoveInfo(mCurrent); + // 调用 saveLoveInfo 方法,将收藏歌曲列表中对应上一首歌曲的信息保存起来 + } else if (mListType == Constant.LIST_TYPE_HISTORY) { + // 如果是历史歌曲列表 + mCurrent = getLastCurrent(mCurrent, mPlayMode, mHistoryList.size()); + // 调用 getLastCurrent 方法,根据当前歌曲的位置、播放模式和历史歌曲列表的大小计算上一首歌曲的位置 + saveHistoryInfo(mCurrent); + // 调用 saveHistoryInfo 方法,将历史歌曲列表中对应上一首歌曲的信息保存起来 + } else if (mListType == Constant.LIST_TYPE_DOWNLOAD) { + // 如果是下载歌曲列表 + mCurrent = getLastCurrent(mCurrent, mPlayMode, mDownloadList.size()); + // 调用 getLastCurrent 方法,根据当前歌曲的位置、播放模式和下载歌曲列表的大小计算上一首歌曲的位置 + saveDownloadInfo(mCurrent); + // 调用 saveDownloadInfo 方法,将下载歌曲列表中对应上一首歌曲的信息保存起来 + } + if (mListType!= 0) mPlayStatusBinder.play(mListType); + // 如果 mListType 不为 0,调用 mPlayStatusBinder 的 play 方法,根据当前列表类型播放上一首歌曲 + // 这样可以确保当列表类型有效时,会开始播放上一首歌曲 +} + // 停止音乐 public void stop() { - if (mediaPlayer != null) { - isPlaying = false; - mediaPlayer.stop(); - try { - mediaPlayer.prepare(); // 在调用stop后如果需要再次通过start进行播放,需要之前调用prepare函数 - } catch (Exception e) { - e.printStackTrace(); - } - - - } - + // 检查 mediaPlayer 是否不为空 + if (mediaPlayer!= null) { + // 将 isPlaying 标志设置为 false,表示音乐停止播放 + isPlaying = false; + // 调用 mediaPlayer 的 stop 方法停止播放音乐 + mediaPlayer.stop(); + try { + // 调用 stop 后,如果要再次播放,需要调用 prepare 函数将 MediaPlayer 恢复到准备状态 + mediaPlayer.prepare(); + } catch (Exception e) { + // 打印异常堆栈信息 + e.printStackTrace(); } + } +} - public boolean isPlaying() { - - return isPlaying; - } - - public MediaPlayer getMediaPlayer() { - - return mediaPlayer; - } - public PlayerService getPlayerService(){ - return PlayerService.this; - } +// 判断是否正在播放 +public boolean isPlaying() { + // 返回 isPlaying 的值,用于外部判断当前是否正在播放音乐 + return isPlaying; +} - public long getCurrentTime() { - return mediaPlayer.getCurrentPosition() / 1000; - } - } +// 获取媒体播放器对象 +public MediaPlayer getMediaPlayer() { + // 返回媒体播放器对象,以便外部对其进行操作 + return mediaPlayer; +} +// 获取 PlayerService 实例 +public PlayerService getPlayerService() { + // 返回 PlayerService 自身的实例,可用于外部调用该服务的其他方法或访问其属性 + return PlayerService.this; +} - @Override - public void onDestroy() { - Log.d(TAG, "onDestroy: 服务被销毁了"); - if (mediaPlayer != null) { - mediaPlayer.stop(); - mediaPlayer.release(); - } - stopForeground(true); - } +// 获取当前播放时间(秒) +public long getCurrentTime() { + // 获取当前播放位置(毫秒),并将其转换为秒 + return mediaPlayer.getCurrentPosition() / 1000; +} +} - @Override - public boolean onUnbind(Intent intent) { - Log.d(TAG, "onUnbind: jsyjst"); - return true; - } - //保存本地音乐列表的信息 - private void saveLocalSongInfo(int current) { - //将歌曲的信息保存起来 - mLocalSongList = LitePal.findAll(LocalSong.class); - Song song = new Song(); - LocalSong localSong = mLocalSongList.get(current); - song.setPosition(current); - song.setSongName(localSong.getName()); - song.setSinger(localSong.getSinger()); - song.setDuration(localSong.getDuration()); - song.setUrl(localSong.getUrl()); - song.setImgUrl(localSong.getPic()); - song.setSongId(localSong.getSongId()); - song.setQqId(localSong.getQqId()); - song.setOnline(false); - song.setListType(Constant.LIST_TYPE_LOCAL); - FileUtil.saveSong(song); +@Override +public void onDestroy() { + // 输出日志,表明服务正在被销毁 + Log.d(TAG, "onDestroy: 服务被销毁了"); + if (mediaPlayer!= null) { + // 停止媒体播放器 + mediaPlayer.stop(); + // 释放媒体播放器资源,避免资源泄漏 + mediaPlayer.release(); } + // 停止前台服务,隐藏通知等相关操作 + stopForeground(true); +} - //保存网络专辑列表的信息 - private void saveOnlineSongInfo(int current) { - mSongList = LitePal.findAll(OnlineSong.class); - Song song = new Song(); - song.setPosition(current); - song.setSongId(mSongList.get(current).getSongId()); - song.setSongName(mSongList.get(current).getName()); - song.setSinger(mSongList.get(current).getSinger()); - song.setDuration(mSongList.get(current).getDuration()); - song.setUrl(mSongList.get(current).getUrl()); - song.setImgUrl(mSongList.get(current).getPic()); - song.setOnline(true); - song.setListType(Constant.LIST_TYPE_ONLINE); - song.setMediaId(mSongList.get(current).getMediaId()); - FileUtil.saveSong(song); - } +@Override +public boolean onUnbind(Intent intent) { + // 输出日志,表明服务正在解绑 + Log.d(TAG, "onUnbind: jsyjst"); + // 返回 true,表示服务可以在所有客户端都解绑后自动销毁(如果没有其他绑定) + return true; +} - //保存我的收藏的列表的信息 - private void saveLoveInfo(int current) { - mLoveList = orderList(LitePal.findAll(Love.class)); - Love love = mLoveList.get(current); - Song song = new Song(); - song.setPosition(current); - song.setSongId(love.getSongId()); - song.setQqId(love.getQqId()); - song.setSongName(love.getName()); - song.setSinger(love.getSinger()); - song.setUrl(love.getUrl()); - song.setImgUrl(love.getPic()); - song.setListType(Constant.LIST_TYPE_LOVE); - song.setOnline(love.isOnline()); - song.setDuration(love.getDuration()); - song.setMediaId(love.getMediaId()); - song.setDownload(love.isDownload()); - FileUtil.saveSong(song); - } + // 保存本地音乐列表的信息 +private void saveLocalSongInfo(int current) { + // 从数据库中查找所有的 LocalSong 并存储在 mLocalSongList 中 + mLocalSongList = LitePal.findAll(LocalSong.class); + // 创建一个新的 Song 对象 + Song song = new Song(); + // 从 mLocalSongList 中根据当前索引获取 LocalSong 对象 + LocalSong localSong = mLocalSongList.get(current); + // 设置 Song 对象的位置信息 + song.setPosition(current); + // 设置 Song 对象的歌曲名称,从 localSong 中获取 + song.setSongName(localSong.getName()); + // 设置 Song 对象的歌手信息,从 localSong 中获取 + song.setSinger(localSong.getSinger()); + // 设置 Song 对象的歌曲时长,从 localSong 中获取 + song.setDuration(localSong.getDuration()); + // 设置 Song 对象的歌曲 URL,从 localSong 中获取 + song.setUrl(localSong.getUrl()); + // 设置 Song 对象的图片 URL,从 localSong 中获取 + song.setImgUrl(localSong.getPic()); + // 设置 Song 对象的歌曲 ID,从 localSong 中获取 + song.setSongId(localSong.getSongId()); + // 设置 Song 对象的 QQ 歌曲 ID,从 localSong 中获取 + song.setQqId(localSong.getQqId()); + // 设置 Song 对象为非在线歌曲 + song.setOnline(false); + // 设置 Song 对象的列表类型为本地列表类型 + song.setListType(Constant.LIST_TYPE_LOCAL); + // 使用 FileUtil 保存 Song 对象 + FileUtil.saveSong(song); +} +// 保存网络专辑列表的信息 +private void saveOnlineSongInfo(int current) { + // 从数据库中查找所有的 OnlineSong 并存储在 mSongList 中 + mSongList = LitePal.findAll(OnlineSong.class); + // 创建一个新的 Song 对象 + Song song = new Song(); + // 设置 Song 对象的位置信息 + song.setPosition(current); + // 设置 Song 对象的歌曲 ID,从 mSongList 中根据当前索引获取的 OnlineSong 对象中获取 + song.setSongId(mSongList.get(current).getSongId()); + // 设置 Song 对象的歌曲名称,从 mSongList 中根据当前索引获取的 OnlineSong 对象中获取 + song.setSongName(mSongList.get(current).getName()); + // 设置 Song 对象的歌手信息,从 mSongList 中根据当前索引获取的 OnlineSong 对象中获取 + song.setSinger(mSongList.get(current).getSinger()); + // 设置 Song 对象的歌曲时长,从 mSongList 中根据当前索引获取的 OnlineSong 对象中获取 + song.setDuration(mSongList.get(current).getDuration()); + // 设置 Song 对象的歌曲 URL,从 mSongList 中根据当前索引获取的 OnlineSong 对象中获取 + song.setUrl(mSongList.get(current).getUrl()); + // 设置 Song 对象的图片 URL,从 mSongList 中根据当前索引获取的 OnlineSong 对象中获取 + song.setImgUrl(mSongList.get(current).getPic()); + // 设置 Song 对象为在线歌曲 + song.setOnline(true); + // 设置 Song 对象的列表类型为在线列表类型 + song.setListType(Constant.LIST_TYPE_ONLINE); + // 设置 Song 对象的媒体 ID,从 mSongList 中根据当前索引获取的 OnlineSong 对象中获取 + song.setMediaId(mSongList.get(current).getMediaId()); + // 使用 FileUtil 保存 Song 对象 + FileUtil.saveSong(song); +} - //保存下载列表的信息 - private void saveDownloadInfo(int current){ - DownloadSong downloadSong = mDownloadList.get(current); - Song song = new Song(); - song.setPosition(current); - song.setSongId(downloadSong.getSongId()); - song.setSongName(downloadSong.getName()); - song.setSinger(downloadSong.getSinger()); - song.setUrl(downloadSong.getUrl()); - song.setImgUrl(downloadSong.getPic()); - song.setListType(Constant.LIST_TYPE_DOWNLOAD); - song.setOnline(false); - song.setDuration(downloadSong.getDuration()); - song.setMediaId(downloadSong.getMediaId()); - song.setDownload(true); - FileUtil.saveSong(song); - } +// 保存收藏列表的信息 +private void saveLoveInfo(int current) { + // 从数据库中查找所有的 Love 并存储在 mLoveList 中,同时对列表进行排序 + mLoveList = orderList(LitePal.findAll(Love.class)); + // 从 mLoveList 中根据当前索引获取 Love 对象 + Love love = mLoveList.get(current); + // 创建一个新的 Song 对象 + Song song = new Song(); + // 设置 Song 对象的位置信息 + song.setPosition(current); + // 设置 Song 对象的歌曲 ID,从 love 中获取 + song.setSongId(love.getSongId()); + // 设置 Song 对象的 QQ 歌曲 ID,从 love 中获取 + song.setQqId(love.getQqId()); + // 设置 Song 对象的歌曲名称,从 love 中获取 + song.setSongName(love.getName()); + // 设置 Song 对象的歌手信息,从 love 中获取 + song.setSinger(love.getSinger()); + // 设置 Song 对象的歌曲 URL,从 love 中获取 + song.setUrl(love.getUrl()); + // 设置 Song 对象的图片 URL,从 love 中获取 + song.setImgUrl(love.getPic()); + // 设置 Song 对象的列表类型为收藏列表类型 + song.setListType(Constant.LIST_TYPE_LOVE); + // 设置 Song 对象的在线状态,从 love 中获取 + song.setOnline(love.isOnline()); + // 设置 Song 对象的歌曲时长,从 love 中获取 + song.setDuration(love.getDuration()); + // 设置 Song 对象的媒体 ID,从 love 中获取 + song.setMediaId(love.getMediaId()); + // 设置 Song 对象的下载状态,从 love 中获取 + song.setDownload(love.isDownload()); + // 使用 FileUtil 保存 Song 对象 + FileUtil.saveSong(song); +} - //保存我的收藏的列表的信息 - private void saveHistoryInfo(int current) { - HistorySong historySong = mHistoryList.get(current); - Song song = new Song(); - song.setPosition(current); - song.setSongId(historySong.getSongId()); - song.setQqId(historySong.getQqId()); - song.setSongName(historySong.getName()); - song.setSinger(historySong.getSinger()); - song.setUrl(historySong.getUrl()); - song.setImgUrl(historySong.getPic()); - song.setListType(Constant.LIST_TYPE_HISTORY); - song.setOnline(historySong.isOnline()); - song.setDuration(historySong.getDuration()); - song.setMediaId(historySong.getMediaId()); - song.setDownload(historySong.isDownload()); - FileUtil.saveSong(song); - } + // 保存下载列表的信息 +private void saveDownloadInfo(int current) { + // 从 mDownloadList 中根据当前索引获取 DownloadSong 对象 + DownloadSong downloadSong = mDownloadList.get(current); + // 创建一个新的 Song 对象 + Song song = new Song(); + // 设置 Song 对象的位置信息 + song.setPosition(current); + // 设置 Song 对象的歌曲 ID,从 downloadSong 中获取 + song.setSongId(downloadSong.getSongId()); + // 设置 Song 对象的歌曲名称,从 downloadSong 中获取 + song.setSongName(downloadSong.getName()); + // 设置 Song 对象的歌手信息,从 downloadSong 中获取 + song.setSinger(downloadSong.getSinger()); + // 设置 Song 对象的歌曲 URL,从 downloadSong 中获取 + song.setUrl(downloadSong.getUrl()); + // 设置 Song 对象的图片 URL,从 downloadSong 中获取 + song.setImgUrl(downloadSong.getPic()); + // 设置 Song 对象的列表类型为下载列表类型 + song.setListType(Constant.LIST_TYPE_DOWNLOAD); + // 设置 Song 对象为非在线歌曲 + song.setOnline(false); + // 设置 Song 对象的歌曲时长,从 downloadSong 中获取 + song.setDuration(downloadSong.getDuration()); + // 设置 Song 对象的媒体 ID,从 downloadSong 中获取 + song.setMediaId(downloadSong.getMediaId()); + // 设置 Song 对象的下载状态为已下载 + song.setDownload(true); + // 使用 FileUtil 保存 Song 对象 + FileUtil.saveSong(song); +} - //将歌曲保存到最近播放的数据库中 - private void saveToHistoryTable() { +// 保存历史播放列表的信息 +private void saveHistoryInfo(int current) { + // 从 mHistoryList 中根据当前索引获取 HistorySong 对象 + HistorySong historySong = mHistoryList.get(current); + // 创建一个新的 Song 对象 + Song song = new Song(); + // 设置 Song 对象的位置信息 + song.setPosition(current); + // 设置 Song 对象的歌曲 ID,从 historySong 中获取 + song.setSongId(historySong.getSongId()); + // 设置 Song 对象的 QQ 歌曲 ID,从 historySong 中获取 + song.setQqId(historySong.getQqId()); + // 设置 Song 对象的歌曲名称,从 historySong 中获取 + song.setSongName(historySong.getName()); + // 设置 Song 对象的歌手信息,从 historySong 中获取 + song.setSinger(historySong.getSinger()); + // 设置 Song 对象的歌曲 URL,从 historySong 中获取 + song.setUrl(historySong.getUrl()); + // 设置 Song 对象的图片 URL,从 historySong 中获取 + song.setImgUrl(historySong.getPic()); + // 设置 Song 对象的列表类型为历史播放列表类型 + song.setListType(Constant.LIST_TYPE_HISTORY); + // 设置 Song 对象的在线状态,从 historySong 中获取 + song.setOnline(historySong.isOnline()); + // 设置 Song 对象的歌曲时长,从 historySong 中获取 + song.setDuration(historySong.getDuration()); + // 设置 Song 对象的媒体 ID,从 historySong 中获取 + song.setMediaId(historySong.getMediaId()); + // 设置 Song 对象的下载状态,从 historySong 中获取 + song.setDownload(historySong.isDownload()); + // 使用 FileUtil 保存 Song 对象 + FileUtil.saveSong(song); +} - final Song song = FileUtil.getSong(); - LitePal.where("songId=?", song.getSongId()).findAsync(HistorySong.class) - .listen(new FindMultiCallback() { - @Override - public void onFinish(List list) { - if (list.size() == 1) { - LitePal.deleteAll(HistorySong.class, "songId=?", song.getSongId()); - } - final HistorySong history = new HistorySong(); - history.setSongId(song.getSongId()); - history.setQqId(song.getQqId()); - history.setName(song.getSongName()); - history.setSinger(song.getSinger()); - history.setUrl(song.getUrl()); - history.setPic(song.getImgUrl()); - history.setOnline(song.isOnline()); - history.setDuration(song.getDuration()); - history.setMediaId(song.getMediaId()); - history.setDownload(song.isDownload()); - history.saveAsync().listen(new SaveCallback() { - @Override - public void onFinish(boolean success) { - if (success) { - //告诉主界面最近播放的数目需要改变 - EventBus.getDefault().post(new SongListNumEvent(Constant.LIST_TYPE_HISTORY)); - if (LitePal.findAll(HistorySong.class).size() > Constant.HISTORY_MAX_SIZE) { - LitePal.delete(HistorySong.class, LitePal.findFirst(HistorySong.class).getId()); - } +// 将歌曲保存到最近播放的数据库中 +private void saveToHistoryTable() { + // 从 FileUtil 中获取当前的 Song 对象 + final Song song = FileUtil.getSong(); + // 使用 LitePal 异步查找 songId 匹配的 HistorySong 列表 + LitePal.where("songId=?", song.getSongId()).findAsync(HistorySong.class) + .listen(new FindMultiCallback() { + @Override + public void onFinish(List list) { + // 如果找到的列表大小为 1,表示该歌曲已经存在于历史播放列表中 + if (list.size() == 1) { + // 删除已存在的历史记录,避免重复添加 + LitePal.deleteAll(HistorySong.class, "songId=?", song.getSongId()); + } + // 创建一个新的 HistorySong 对象 + final HistorySong history = new HistorySong(); + // 设置 HistorySong 对象的歌曲 ID,从 song 中获取 + history.setSongId(song.getSongId()); + // 设置 HistorySong 对象的 QQ 歌曲 ID,从 song 中获取 + history.setQqId(song.getQqId()); + // 设置 HistorySong 对象的歌曲名称,从 song 中获取 + history.setName(song.getSongName()); + // 设置 HistorySong 对象的歌手信息,从 song 中获取 + history.setSinger(song.getSinger()); + // 设置 HistorySong 对象的歌曲 URL,从 song 中获取 + history.setUrl(song.getUrl()); + // 设置 HistorySong 对象的图片 URL,从 song 中获取 + history.setPic(song.getImgUrl()); + // 设置 HistorySong 对象的在线状态,从 song 中获取 + history.setOnline(song.isOnline()); + // 设置 HistorySong 对象的歌曲时长,从 song 中获取 + history.setDuration(song.getDuration()); + // 设置 HistorySong 对象的媒体 ID,从 song 中获取 + history.setMediaId(song.getMediaId()); + // 设置 HistorySong 对象的下载状态,从 song 中获取 + history.setDownload(song.isDownload()); + // 异步保存 HistorySong 对象 + history.saveAsync().listen(new SaveCallback() { + @Override + public void onFinish(boolean success) { + if (success) { + // 发送 SongListNumEvent 事件,通知主界面最近播放的数目需要改变 + EventBus.getDefault().post(new SongListNumEvent(Constant.LIST_TYPE_HISTORY)); + // 如果历史播放列表的大小超过了最大历史记录数 + if (LitePal.findAll(HistorySong.class).size() > Constant.HISTORY_MAX_SIZE) { + // 删除最早的历史记录,保持列表长度不超过最大长度 + LitePal.delete(HistorySong.class, LitePal.findFirst(HistorySong.class).getId()); } } - }); - - } - }); - - } + } + }); + } + }); +} - //对数据库进行倒叙排序 - private List orderList(List tempList) { - List loveList = new ArrayList<>(); - loveList.clear(); - for (int i = tempList.size() - 1; i >= 0; i--) { - loveList.add(tempList.get(i)); - } - return loveList; + // 对收藏列表进行倒序排序 +private List orderList(List tempList) { + // 创建一个新的 Love 类型的列表 loveList + List loveList = new ArrayList<>(); + // 清空 loveList,确保列表为空 + loveList.clear(); + // 从 tempList 的最后一个元素开始,依次将元素添加到 loveList 的开头,实现倒序排序 + for (int i = tempList.size() - 1; i >= 0; i--) { + loveList.add(tempList.get(i)); } + // 返回倒序排序后的 loveList + return loveList; +} - private List orderDownloadList(List tempList) { - List downloadSongList = new ArrayList<>(); - downloadSongList.clear(); - for (int i = tempList.size() - 1; i >= 0; i--) { - downloadSongList.add(tempList.get(i)); - } - return downloadSongList; +// 对下载列表进行倒序排序 +private List orderDownloadList(List tempList) { + // 创建一个新的 DownloadSong 类型的列表 downloadSongList + List downloadSongList = new ArrayList<>(); + // 清空 downloadSongList,确保列表为空 + downloadSongList.clear(); + // 从 tempList 的最后一个元素开始,依次将元素添加到 downloadSongList 的开头,实现倒序排序 + for (int i = tempList.size() - 1; i >= 0; i--) { + downloadSongList.add(tempList.get(i)); } + // 返回倒序排序后的 downloadSongList + return downloadSongList; +} - private List orderHistoryList(List tempList) { - List historySongList = new ArrayList<>(); - historySongList.clear(); - for (int i = tempList.size() - 1; i >= 0; i--) { - historySongList.add(tempList.get(i)); - } - return historySongList; +// 对历史播放列表进行倒序排序 +private List orderHistoryList(List tempList) { + // 创建一个新的 HistorySong 类型的列表 historySongList + List historySongList = new ArrayList<>(); + // 清空 historySongList,确保列表为空 + historySongList.clear(); + // 从 tempList 的最后一个元素开始,依次将元素添加到 historySongList 的开头,实现倒序排序 + for (int i = tempList.size() - 1; i >= 0; i--) { + historySongList.add(tempList.get(i)); } + // 返回倒序排序后的 historySongList + return historySongList; +} - //网络请求获取播放地址 - private void getSongUrl(String songId) { - Log.d(TAG, "getSongUrl: "+Api.SONG_URL_DATA_LEFT + songId + Api.SONG_URL_DATA_RIGHT); - RetrofitFactory.createRequestOfSongUrl().getSongUrl(Api.SONG_URL_DATA_LEFT + songId + Api.SONG_URL_DATA_RIGHT) - .subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) - .subscribeWith(new Observer() { - @Override - public void onSubscribe(Disposable disposable) { - - } +// 网络请求获取播放地址 +private void getSongUrl(String songId) { + // 打印日志,显示正在请求的歌曲的 ID 信息 + Log.d(TAG, "getSongUrl: " + Api.SONG_URL_DATA_LEFT + songId + Api.SONG_URL_DATA_RIGHT); + // 使用 RetrofitFactory 创建一个网络请求,请求歌曲的播放地址 + RetrofitFactory.createRequestOfSongUrl().getSongUrl(Api.SONG_URL_DATA_LEFT + songId + Api.SONG_URL_DATA_RIGHT) + .subscribeOn(Schedulers.io()) + // 指定网络请求在 IO 线程执行 + .observeOn(AndroidSchedulers.mainThread()) + // 指定结果处理在主线程执行 + .subscribeWith(new Observer() { + @Override + public void onSubscribe(Disposable disposable) { + // 当订阅开始时,可进行一些初始化操作,但这里为空 + } - @Override - public void onNext(SongUrl songUrl) { - if (songUrl.getCode() == 0) { - String sip = songUrl.getReq_0().getData().getSip().get(0); - String purl = songUrl.getReq_0().getData().getMidurlinfo().get(0).getPurl(); - if(purl.equals("")) { - CommonUtil.showToast(PlayerService.this,"该歌曲暂时没有版权,试试搜索其它歌曲吧"); - return; - } - Song song = FileUtil.getSong(); - assert song != null; - song.setUrl(sip + purl); - FileUtil.saveSong(song); - try { - mediaPlayer.setDataSource(sip + purl); - Log.d(TAG, "onNext:jsyjst ="+sip+purl); - startPlay(); - } catch (IOException e) { - e.printStackTrace(); - } - } else { - Log.d(TAG, "onNext:" + songUrl.getCode() + ":获取不到歌曲播放地址"); + @Override + public void onNext(SongUrl songUrl) { + // 当网络请求成功获取到 SongUrl 对象时 + if (songUrl.getCode() == 0) { + // 获取歌曲的 sip 和 purl 信息 + String sip = songUrl.getReq_0().getData().getSip().get(0); + String purl = songUrl.getReq_0().getData().getMidurlinfo().get(0).getPurl(); + // 如果 purl 为空,表示歌曲无版权 + if (purl.equals("")) { + // 显示提示信息,让用户尝试搜索其他歌曲 + CommonUtil.showToast(PlayerService.this, "该歌曲暂时没有版权,试试搜索其它歌曲吧"); + return; } + // 从 FileUtil 中获取当前的 Song 对象 + Song song = FileUtil.getSong(); + assert song!= null; + // 设置 Song 对象的 URL 为 sip + purl + song.setUrl(sip + purl); + // 保存更新后的 Song 对象 + FileUtil.saveSong(song); + try { + // 将媒体播放器的数据源设置为 sip + purl + mediaPlayer.setDataSource(sip + purl); + // 打印日志,显示正在设置数据源 + Log.d(TAG, "onNext:jsyjst =" + sip + purl); + // 调用 startPlay 方法开始播放歌曲 + startPlay(); + } catch (IOException e) { + // 打印异常信息 + e.printStackTrace(); + } + } else { + // 当获取的歌曲 URL 的 code 不为 0,打印错误信息 + Log.d(TAG, "onNext:" + songUrl.getCode() + ":获取不到歌曲播放地址"); } + } - @Override - public void onError(Throwable throwable) { - Log.d(TAG, "onError: "+throwable.toString()); - } - - @Override - public void onComplete() { - - } - }); - - } + @Override + public void onError(Throwable throwable) { + // 当网络请求出现错误时,打印错误信息 + Log.d(TAG, "onError: " + throwable.toString()); + } - //开始播放 - private void startPlay() throws IOException { - mediaPlayer.prepare(); //进行缓冲 - isPlaying = true; - mediaPlayer.start(); - saveToHistoryTable(); - EventBus.getDefault().post(new SongStatusEvent(Constant.SONG_CHANGE));//发送所有歌曲改变事件 - EventBus.getDefault().post(new OnlineSongChangeEvent()); //发送网络歌曲改变事件 - //改变通知栏歌曲 - Song song = FileUtil.getSong(); - getNotificationManager().notify(NOTIFICATION_ID, - getNotification(song.getSongName()+" - "+song.getSinger())); - } + @Override + public void onComplete() { + // 当网络请求完成时,可进行一些清理或后续操作,但这里为空 + } + }); +} + // 开始播放 +private void startPlay() throws IOException { + // 调用 mediaPlayer 的 prepare 方法,准备播放歌曲,可能会进行缓冲等操作 + mediaPlayer.prepare(); + // 将 isPlaying 标记为 true,表示正在播放 + isPlaying = true; + // 调用 mediaPlayer 的 start 方法,开始播放歌曲 + mediaPlayer.start(); + // 调用 saveToHistoryTable 方法将歌曲信息保存到历史播放表 + saveToHistoryTable(); + // 发送 SongStatusEvent 事件,通知歌曲状态发生改变,可能用于更新 UI 等 + EventBus.getDefault().post(new SongStatusEvent(Constant.SONG_CHANGE)); + // 发送 OnlineSongChangeEvent 事件,可能用于更新在线歌曲相关的 UI 或逻辑 + EventBus.getDefault().post(new OnlineSongChangeEvent()); + // 从 FileUtil 中获取当前歌曲对象 + Song song = FileUtil.getSong(); + // 获取通知管理器并更新通知,使用歌曲的名称和歌手信息更新通知栏 + getNotificationManager().notify(NOTIFICATION_ID, + getNotification(song.getSongName() + " - " + song.getSinger())); +} - //根据播放模式得到下一首歌曲的位置 - private int getNextCurrent(int current, int playMode, int len) { - int res; - if (playMode == Constant.PLAY_ORDER) { - res = (current + 1) % len; - } else if (playMode == Constant.PLAY_RANDOM) { - res = (current + (int) (Math.random() * len)) % len; - } else { - res = current; - } - return res; - } - //根据播放模式得到上一首歌曲的位置 - private int getLastCurrent(int current, int playMode, int len) { - int res; - if (playMode == Constant.PLAY_ORDER) { - res = current - 1 == -1 ? len-1 : current - 1; - } else if (playMode == Constant.PLAY_RANDOM) { - res = (current + (int) (Math.random() * len)) % len; - } else { - res = current; - } - return res; - } - //开启前台服务 - private NotificationManager getNotificationManager() { - return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); +// 根据播放模式得到下一首歌曲的位置 +private int getNextCurrent(int current, int playMode, int len) { + int res; + // 如果是顺序播放模式 + if (playMode == Constant.PLAY_ORDER) { + // 下一首歌曲的位置是当前位置加 1 对列表长度取模,实现循环播放 + res = (current + 1) % len; + } else if (playMode == Constant.PLAY_RANDOM) { + // 如果是随机播放模式 + // 生成一个随机数乘以列表长度并取模,得到随机位置 + res = (current + (int) (Math.random() * len)) % len; + } else { + // 否则(可能是单曲循环等模式),位置不变 + res = current; } + return res; +} - - - - //设置通知栏标题 - private Notification getNotification(String title) { - Intent intent = new Intent(this, MainActivity.class); - PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - String id = "play"; - String name = "播放歌曲"; - NotificationChannel mChannel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW); - getNotificationManager().createNotificationChannel(mChannel); - Notification.Builder builder = new Notification.Builder(this, id); - builder.setSmallIcon(R.mipmap.icon); - builder.setContentIntent(pi); - builder.setContentTitle(title); - return builder.build(); - } else { - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "play"); - builder.setSmallIcon(R.mipmap.icon); - builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.icon)); - builder.setContentIntent(pi); - builder.setContentTitle(title); - return builder.build(); - } - +// 根据播放模式得到上一首歌曲的位置 +private int getLastCurrent(int current, int playMode, int len) { + int res; + // 如果是顺序播放模式 + if (playMode == Constant.PLAY_ORDER) { + // 上一首歌曲的位置是当前位置减 1,如果减到 -1 则为列表的最后一个元素,实现循环播放 + res = current - 1 == -1? len - 1 : current - 1; + } else if (playMode == Constant.PLAY_RANDOM) { + // 如果是随机播放模式 + // 生成一个随机数乘以列表长度并取模,得到随机位置 + res = (current + (int) (Math.random() * len)) % len; + } else { + // 否则(可能是单曲循环等模式),位置不变 + res = current; } + return res; +} +// 获取通知管理器 +private NotificationManager getNotificationManager() { + // 调用 getSystemService 方法获取系统的通知管理器 + return (NotificationManager) getSystemService(NOTIFICATION_SERVICE); } +// 设置通知栏标题 +private Notification getNotification(String title) { + // 创建一个 Intent,点击通知时跳转到 MainActivity + Intent intent = new Intent(this, MainActivity.class); + // 创建一个 PendingIntent,用于点击通知时启动 MainActivity + PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + String id = "play"; + String name = "播放歌曲"; + // 创建一个通知通道,适用于 Android 8.0 及以上版本 + NotificationChannel mChannel = new NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW); + // 在通知管理器中创建通知通道 + getNotificationManager().createNotificationChannel(mChannel); + // 创建一个通知构建器 + Notification.Builder builder = new Notification.Builder(this, id); + // 设置通知的小图标 + builder.setSmallIcon(R.mipmap.icon); + // 设置点击通知的 PendingIntent + builder.setContentIntent(pi); + // 设置通知的标题 + builder.setContentTitle(title); + // 构建并返回通知对象 + return builder.build(); + } else { + // 对于 Android 8.0 以下版本,使用 NotificationCompat.Builder 创建通知 + NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "play"); + // 设置通知的小图标 + builder.setSmallIcon(R.mipmap.icon); + // 设置通知的大图标 + builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.icon)); + // 设置点击通知的 PendingIntent + builder.setContentIntent(pi); + // 设置通知的标题 + builder.setContentTitle(title); + // 构建并返回通知对象 + return builder.build(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/util/CommonUtil.java b/app/src/main/java/com/example/musicplayer/util/CommonUtil.java index 6c8ff7f..e94a68e 100644 --- a/app/src/main/java/com/example/musicplayer/util/CommonUtil.java +++ b/app/src/main/java/com/example/musicplayer/util/CommonUtil.java @@ -28,164 +28,173 @@ import com.example.musicplayer.app.App; /** * Created by 残渊 on 2018/10/26. + * 通用的工具类,包含多个实用方法,可在整个应用中使用 */ - -//通用的工具类 public class CommonUtil { + // 存储 Toast 实例,避免重复创建 private static Toast toast; + // 控制活动的状态栏显示或隐藏 public static void hideStatusBar(Activity activity, boolean isHide) { - View decorView = activity.getWindow().getDecorView(); + // 获取活动的根视图 + View decorView = activity.getWindow().getDecorView(); if (isHide) { if (Build.VERSION.SDK_INT >= 22) { - + // 隐藏状态栏并使布局扩展到状态栏区域,状态栏透明 decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | - View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - activity.getWindow().setStatusBarColor(Color.TRANSPARENT); + View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + activity.getWindow().setStatusBarColor(Color.TRANSPARENT); } } else { - decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); - activity.getWindow().setStatusBarColor(App.getContext().getResources().getColor(R.color.actionBarColor)); + // 显示状态栏并设置状态栏颜色 + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + activity.getWindow().setStatusBarColor(App.getContext().getResources().getColor(R.color.actionBarColor)); } } - // + // 显示 Toast 消息 @SuppressLint("ShowToast") public static void showToast(Context context, String message) { if (toast == null) { - toast = Toast.makeText(context, message, Toast.LENGTH_SHORT); + // 创建一个新的 Toast 实例 + toast = Toast.makeText(context, message, Toast.LENGTH_SHORT); } else { - toast.setText(message); + // 更新 Toast 的消息内容 + toast.setText(message); } - toast.show(); + // 显示 Toast + toast.show(); } - /** - * 得到屏幕的宽度 - */ + // 获取屏幕的宽度 public static int getScreenWidth(Context context) { if (null == context) { return 0; } - return context.getResources().getDisplayMetrics().widthPixels; + // 获取屏幕的宽度像素值 + return context.getResources().getDisplayMetrics().widthPixels; } - /** - * 得到屏幕的高度 - */ - + // 获取屏幕的高度 public static int getScreenHeight(Context context) { if (null == context) { return 0; } - return context.getResources().getDisplayMetrics().heightPixels; + // 获取屏幕的高度像素值 + return context.getResources().getDisplayMetrics().heightPixels; } - /** - * EditText获取焦点并显示软键盘 - */ - //弹出软键盘 + // 使 EditText 获取焦点并显示软键盘 public static void showKeyboard(EditText editText, Context context) { - //其中editText为dialog中的输入框的 EditText - if (editText != null) { - //设置可获得焦点 - editText.setFocusable(true); - editText.setFocusableInTouchMode(true); - //请求获得焦点 - editText.requestFocus(); - //调用系统输入法 - InputMethodManager inputManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - inputManager.toggleSoftInput(0,InputMethodManager.HIDE_NOT_ALWAYS); + if (editText!= null) { + // 使 EditText 可获得焦点 + editText.setFocusable(true); + editText.setFocusableInTouchMode(true); + // 请求获得焦点 + editText.requestFocus(); + // 获取输入法管理器 + InputMethodManager inputManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + // 显示软键盘 + inputManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS); } } - /** - * 关闭软键盘 - * - * @param mEditText 输入框 - * @param context 上下文 - */ + // 关闭软键盘 public static void closeKeybord(EditText mEditText, Context context) { - mEditText.clearFocus(); - InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); + // 清除 EditText 的焦点 + mEditText.clearFocus(); + // 获取输入法管理器 + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + // 隐藏软键盘 + imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); } - /** - * 使指定的字符串显示不同的颜色 - */ + // 使指定字符串在 TextView 中显示不同颜色 public static void showStringColor(String appointStr, String originalStr, TextView textView) { - originalStr = originalStr.replaceAll(appointStr, "" + appointStr + ""); - textView.setText(Html.fromHtml(originalStr)); + // 将原始字符串中的指定字符串替换为带有颜色标记的 HTML 字符串 + originalStr = originalStr.replaceAll(appointStr, "" + appointStr + ""); + // 使用 Html.fromHtml 将 HTML 字符串转换为 Spanned 并设置到 TextView 中 + textView.setText(Html.fromHtml(originalStr)); } - //获取状态栏高度 + // 获取状态栏的高度 public static int getStatusHeightPx(Activity act) { int height = 0; - int resourceId = act.getResources().getIdentifier("status_bar_height", "dimen", "android"); + // 获取状态栏高度资源的标识符 + int resourceId = act.getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { - height = act.getResources().getDimensionPixelSize(resourceId); + // 获取状态栏的高度像素值 + height = act.getResources().getDimensionPixelSize(resourceId); } return height; } - //高斯模糊 + + // 生成前景模糊的 Drawable public static Drawable getForegroundDrawable(Bitmap bitmap) { - /*得到屏幕的宽高比,以便按比例切割图片一部分*/ + // 计算屏幕宽高比 final float widthHeightSize = (float) (DisplayUtil.getScreenWidth(App.getContext()) - * 1.0 / DisplayUtil.getScreenHeight(App.getContext()) * 1.0); - - int cropBitmapWidth = (int) (widthHeightSize * bitmap.getHeight()); - int cropBitmapWidthX = (int) ((bitmap.getWidth() - cropBitmapWidth) / 2.0); - - /*切割部分图片*/ + * 1.0 / DisplayUtil.getScreenHeight(App.getContext()) * 1.0); + // 计算裁剪图片的宽度 + int cropBitmapWidth = (int) (widthHeightSize * bitmap.getHeight()); + int cropBitmapWidthX = (int) ((bitmap.getWidth() - cropBitmapWidth) / 2.0); + // 裁剪部分图片 Bitmap cropBitmap = Bitmap.createBitmap(bitmap, cropBitmapWidthX, 0, cropBitmapWidth, - bitmap.getHeight()); - /*缩小图片*/ + bitmap.getHeight()); + // 缩小图片 Bitmap scaleBitmap = Bitmap.createScaledBitmap(cropBitmap, bitmap.getWidth() / 50, bitmap - .getHeight() / 50, false); - /*模糊化*/ - final Bitmap blurBitmap = FastBlurUtil.doBlur(scaleBitmap, 3, true); - - final Drawable foregroundDrawable = new BitmapDrawable(blurBitmap); - /*加入灰色遮罩层,避免图片过亮影响其他控件*/ - - return foregroundDrawable; + .getHeight() / 50, false); + // 对缩小后的图片进行模糊处理 + final Bitmap blurBitmap = FastBlurUtil.doBlur(scaleBitmap, 3, true); + // 创建模糊后的 Drawable + final Drawable foregroundDrawable = new BitmapDrawable(blurBitmap); + // 可添加灰色遮罩层(未实现) + return foregroundDrawable; } - public static Bitmap getImgBitmap(Context context,String imgUrl){ + // 使用 Glide 加载图片并获取其 Bitmap + public static Bitmap getImgBitmap(Context context, String imgUrl) { SimpleTarget target = new SimpleTarget(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL) { @Override public void onResourceReady(@Nullable Drawable resource, Transition transition) { - Bitmap bitmap = ((BitmapDrawable) resource).getBitmap(); + // 当资源加载完成时,将 Drawable 转换为 Bitmap + Bitmap bitmap = ((BitmapDrawable) resource).getBitmap(); } }; + // 使用 Glide 加载图片,设置占位符和错误图片 Glide.with(context) - .load(imgUrl) - .apply(RequestOptions.placeholderOf(R.drawable.welcome)) - .apply(RequestOptions.errorOf(R.drawable.welcome)) - .into(target); - return null; + .load(imgUrl) + .apply(RequestOptions.placeholderOf(R.drawable.welcome)) + .apply(RequestOptions.errorOf(R.drawable.welcome)) + .into(target); + // 此方法始终返回 null,可能需要修改 + return null; } - public static void setImgWithGlide(Context context,String imgUrl,ImageView view){ + + // 使用 Glide 加载图片到 ImageView + public static void setImgWithGlide(Context context, String imgUrl, ImageView view) { + // 使用 Glide 加载图片,设置占位符和错误图片 Glide.with(context) - .load(imgUrl) - .apply(RequestOptions.placeholderOf(R.drawable.welcome)) - .apply(RequestOptions.errorOf(R.drawable.love)) - .into(view); + .load(imgUrl) + .apply(RequestOptions.placeholderOf(R.drawable.welcome)) + .apply(RequestOptions.errorOf(R.drawable.love)) + .into(view); } - public static void setSingerImg (Context context, String singer, ImageView view) { - if(singer.contains("/")){ - String[] s=singer.split("/"); - singer=s[0]; - } - singer=singer.trim(); - String imgUrl = Api.STORAGE_IMG_FILE + singer + ".jpg"; + // 根据歌手信息加载歌手图片到 ImageView + public static void setSingerImg(Context context, String singer, ImageView view) { + if (singer.contains("/")) { + // 处理歌手名字中包含斜杠的情况 + String[] s = singer.split("/"); + singer = s[0]; + } + singer = singer.trim(); + // 构建歌手图片的 URL + String imgUrl = Api.STORAGE_IMG_FILE + singer + ".jpg"; + // 使用 Glide 加载图片,设置占位符和错误图片 Glide.with(context) - .load(imgUrl) - .apply(RequestOptions.placeholderOf(R.drawable.welcome)) - .apply(RequestOptions.errorOf(R.drawable.welcome)) - .into(view); + .load(imgUrl) + .apply(RequestOptions.placeholderOf(R.drawable.welcome)) + .apply(RequestOptions.errorOf(R.drawable.welcome)) + .into(view); } - -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/util/DisplayUtil.java b/app/src/main/java/com/example/musicplayer/util/DisplayUtil.java index 9db8e66..260f5fb 100644 --- a/app/src/main/java/com/example/musicplayer/util/DisplayUtil.java +++ b/app/src/main/java/com/example/musicplayer/util/DisplayUtil.java @@ -4,18 +4,18 @@ import android.content.Context; /** * Created by 残渊 on 2018/10/27. + * 该类包含了一些与显示相关的常量和方法,主要用于处理与屏幕尺寸相关的比例计算和屏幕尺寸获取 */ - public class DisplayUtil { - /*手柄起始角度*/ + /* 手柄起始角度,可能用于旋转动画等操作,初始角度为 -30 度 */ public static final float ROTATION_INIT_NEEDLE = -30; - /*截图屏幕宽高*/ + /* 基准屏幕的宽度和高度,作为比例计算的基础 */ private static final float BASE_SCREEN_WIDTH = (float) 1080.0; private static final float BASE_SCREEN_HEIGHT = (float) 1920.0; - /*唱针宽高、距离等比例*/ + /* 唱针的各种比例相关的常量,可能用于布局调整或动画计算 */ public static final float SCALE_NEEDLE_WIDTH = (float) (276.0 / BASE_SCREEN_WIDTH); public static final float SCALE_NEEDLE_MARGIN_LEFT = (float) (500.0 / BASE_SCREEN_WIDTH); public static final float SCALE_NEEDLE_PIVOT_X = (float) (43.0 / BASE_SCREEN_WIDTH); @@ -23,20 +23,20 @@ public class DisplayUtil { public static final float SCALE_NEEDLE_HEIGHT = (float) (413.0 / BASE_SCREEN_HEIGHT); public static final float SCALE_NEEDLE_MARGIN_TOP = (float) (43.0 / BASE_SCREEN_HEIGHT); - /*唱盘比例*/ + /* 唱盘的比例,可能用于布局调整,确定唱盘的大小和位置 */ public static final float SCALE_DISC_SIZE = (float) (813.0 / BASE_SCREEN_WIDTH); public static final float SCALE_DISC_MARGIN_TOP = (float) (190 / BASE_SCREEN_HEIGHT); - /*专辑图片比例*/ + /* 专辑图片的比例,可能用于布局调整,确定专辑图片的大小 */ public static final float SCALE_MUSIC_PIC_SIZE = (float) (533.0 / BASE_SCREEN_WIDTH); - /*设备屏幕宽度*/ + /* 获取设备屏幕的宽度,以像素为单位 */ public static int getScreenWidth(Context context) { return context.getResources().getDisplayMetrics().widthPixels; } - /*设备屏幕高度*/ + /* 获取设备屏幕的高度,以像素为单位 */ public static int getScreenHeight(Context context) { return context.getResources().getDisplayMetrics().heightPixels; } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/util/DownloadUtil.java b/app/src/main/java/com/example/musicplayer/util/DownloadUtil.java index 457c486..f27fd81 100644 --- a/app/src/main/java/com/example/musicplayer/util/DownloadUtil.java +++ b/app/src/main/java/com/example/musicplayer/util/DownloadUtil.java @@ -18,64 +18,90 @@ import static com.example.musicplayer.app.Api.STORAGE_SONG_FILE; * desc : 下载相关工具 * */ - public class DownloadUtil { /** - * - * @param fileName 文件跟目录 - * @return 得到已下载的歌曲 + * 从文件中获取已下载的歌曲列表 + * @param fileName 文件根目录 + * @return 包含已下载歌曲的列表 */ - public static final List getSongFromFile(String fileName){ - //将.m4a截取掉得到singer-songName-duration-songId-size - List res = new ArrayList<>(); - File file=new File(fileName); - if(!file.exists()){ - file.mkdirs(); - return res; + public static final List getSongFromFile(String fileName) { + // 存储已下载歌曲的列表 + List res = new ArrayList<>(); + // 创建文件对象 + File file = new File(fileName); + // 如果文件不存在,创建该目录 + if (!file.exists()) { + file.mkdirs(); + return res; } - File[] subFile = file.listFiles(); - for (File value : subFile) { - String songFileName = value.getName(); - String songFile = songFileName.substring(0, songFileName.lastIndexOf(".")); - String[] songValue = songFile.split("-"); - long size = Long.valueOf(songValue[4]); - //如果文件的大小不等于实际大小,则表示该歌曲还未下载完成,被人为暂停,故跳过该歌曲,不加入到已下载集合 - if(size != value.length()) continue; - DownloadSong downloadSong = new DownloadSong(); - downloadSong.setSinger(songValue[0]); - downloadSong.setName(songValue[1]); - downloadSong.setDuration(Long.valueOf(songValue[2])); - downloadSong.setSongId(songValue[3]); - downloadSong.setUrl(fileName + songFileName); - res.add(downloadSong); + // 获取目录下的所有文件 + File[] subFile = file.listFiles(); + for (File value : subFile) { + // 获取文件名 + String songFileName = value.getName(); + // 去掉文件后缀名 + String songFile = songFileName.substring(0, songFileName.lastIndexOf(".")); + // 将文件名按 - 分割 + String[] songValue = songFile.split("-"); + // 获取文件大小 + long size = Long.valueOf(songValue[4]); + // 如果文件大小不等于实际文件大小,说明歌曲未下载完成,跳过该文件 + if (size!= value.length()) continue; + // 创建 DownloadSong 对象 + DownloadSong downloadSong = new DownloadSong(); + // 设置歌手信息 + downloadSong.setSinger(songValue[0]); + // 设置歌曲名称 + downloadSong.setName(songValue[1]); + // 设置歌曲时长 + downloadSong.setDuration(Long.valueOf(songValue[2])); + // 设置歌曲 ID + downloadSong.setSongId(songValue[3]); + // 设置歌曲的 URL + downloadSong.setUrl(fileName + songFileName); + // 将 DownloadSong 对象添加到结果列表中 + res.add(downloadSong); } - return res; + return res; } - public static final boolean isExistOfDownloadSong(String songId){ - //将.m4a截取掉得到singer-songName-duration-songId-size - File file=new File(STORAGE_SONG_FILE); - if(!file.exists()){ - file.mkdirs(); - return false; + /** + * 判断歌曲是否已下载完成 + * @param songId 歌曲的 ID + * @return 歌曲是否已下载完成 + */ + public static final boolean isExistOfDownloadSong(String songId) { + // 创建文件对象,使用 STORAGE_SONG_FILE 作为文件根目录 + File file = new File(STORAGE_SONG_FILE); + // 如果文件不存在,创建该目录 + if (!file.exists()) { + file.mkdirs(); + return false; } - File[] subFile = file.listFiles(); - for (File value : subFile) { - String songFileName = value.getName(); - String songFile = songFileName.substring(0, songFileName.lastIndexOf(".")); - String[] songValue = songFile.split("-"); - //如果文件的大小不等于实际大小,则表示该歌曲还未下载完成,被人为暂停,故跳过该歌曲,不加入到已下载集合 - if(songValue[3].equals(songId)) { - long size = Long.valueOf(songValue[4]); - return size == value.length(); + // 获取目录下的所有文件 + File[] subFile = file.listFiles(); + for (File value : subFile) { + // 获取文件名 + String songFileName = value.getName(); + // 去掉文件后缀名 + String songFile = songFileName.substring(0, songFileName.lastIndexOf(".")); + // 将文件名按 - 分割 + String[] songValue = songFile.split("-"); + // 如果歌曲 ID 匹配 + if (songValue[3].equals(songId)) { + // 获取文件大小 + long size = Long.valueOf(songValue[4]); + // 判断文件大小是否等于实际文件大小,如果相等表示已下载完成 + return size == value.length(); } } - return false; + return false; } - //组装下载歌曲的文件名 - public static final String getSaveSongFile(String singer,String songName,long duration,String songId,long size){ - return singer+"-"+songName+"-"+duration+"-"+songId+"-"+size+".m4a"; + // 组装下载歌曲的文件名 + public static final String getSaveSongFile(String singer, String songName, long duration, String songId, long size) { + // 按照 singer-songName-duration-songId-size.m4a 的格式组装文件名 + return singer + "-" + songName + "-" + duration + "-" + songId + "-" + size + ".m4a"; } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/util/FastBlurUtil.java b/app/src/main/java/com/example/musicplayer/util/FastBlurUtil.java index 3ee1927..f39a15c 100644 --- a/app/src/main/java/com/example/musicplayer/util/FastBlurUtil.java +++ b/app/src/main/java/com/example/musicplayer/util/FastBlurUtil.java @@ -8,210 +8,323 @@ import android.graphics.Bitmap; public class FastBlurUtil { public static Bitmap doBlur(Bitmap sentBitmap, int radius, boolean canReuseInBitmap) { + // 首先,根据 canReuseInBitmap 决定是否复用输入的 Bitmap,若可以复用则直接使用,否则复制一份 + Bitmap bitmap; + if (canReuseInBitmap) { + bitmap = sentBitmap; + } else { + // 使用 copy 方法复制一份输入的 Bitmap,并保留原始配置和像素信息 + bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); + } - Bitmap bitmap; - if (canReuseInBitmap) { - bitmap = sentBitmap; - } else { - bitmap = sentBitmap.copy(sentBitmap.getConfig(), true); - } - - if (radius < 1) { - return (null); - } - - int w = bitmap.getWidth(); - int h = bitmap.getHeight(); - - int[] pix = new int[w * h]; - bitmap.getPixels(pix, 0, w, 0, 0, w, h); - - int wm = w - 1; - int hm = h - 1; - int wh = w * h; - int div = radius + radius + 1; + // 若半径小于 1,则不进行模糊处理,直接返回 null + if (radius < 1) { + return (null); + } - int r[] = new int[wh]; - int g[] = new int[wh]; - int b[] = new int[wh]; - int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; - int vmin[] = new int[Math.max(w, h)]; + // 获取位图的宽度和高度 + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + + // 将位图的像素信息存储到 pix 数组中,数组长度为宽度乘以高度 + int[] pix = new int[w * h]; + // 将 bitmap 的像素存储到 pix 数组中,存储格式为 ARGB 格式 + bitmap.getPixels(pix, 0, w, 0, 0, w, h); + + int wm = w - 1; + int hm = h - 1; + int wh = w * h; + int div = radius + radius + 1; + + // 用于存储模糊处理后的 R、G、B 通道信息 + int r[] = new int[wh]; + int g[] = new int[wh]; + int b[] = new int[wh]; + int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; + int vmin[] = new int[Math.max(w, h)]; + + int divsum = (div + 1) >> 1; + divsum *= divsum; + // 存储模糊处理的除数结果 + int dv[] = new int[256 * divsum]; + // 初始化 dv 数组,存储用于模糊计算的中间结果 + for (i = 0; i < 256 * divsum; i++) { + dv[i] = (i / divsum); + } - int divsum = (div + 1) >> 1; - divsum *= divsum; - int dv[] = new int[256 * divsum]; - for (i = 0; i < 256 * divsum; i++) { - dv[i] = (i / divsum); + yw = yi = 0; + + // 用于存储模糊处理的栈信息 + int[][] stack = new int[div][3]; + int stackpointer; + int stackstart; + int[] sir; + int rbs; + int r1 = radius + 1; + int routsum, goutsum, boutsum; + int rinsum, ginsum, binsum; + + // 水平模糊处理 +// 外层循环遍历图像的每一行 +for (y = 0; y < h; y++) { + // 初始化颜色通道的求和变量,包括用于模糊计算的不同部分 + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + // 内层循环遍历以当前像素为中心的水平滑动窗口,窗口大小由 radius 决定 + for (i = -radius; i <= radius; i++) { + // 获取当前像素周围的像素,考虑边界情况,使用 Math.min 和 Math.max 确保不越界 + p = pix[yi + Math.min(wm, Math.max(i, 0))]; + // 获取栈中存储像素信息的数组 + sir = stack[i + radius]; + // 提取当前像素的红色通道值,通过位运算提取高 8 位 + sir[0] = (p & 0xff0000) >> 16; + // 提取当前像素的绿色通道值,通过位运算提取中间 8 位 + sir[1] = (p & 0x00ff00) >> 8; + // 提取当前像素的蓝色通道值,通过位运算提取低 8 位 + sir[2] = (p & 0x0000ff); + // 计算像素的权重,距离中心像素越远,权重越低 + rbs = r1 - Math.abs(i); + // 计算红色通道的加权和,乘以权重 + rsum += sir[0] * rbs; + // 计算绿色通道的加权和,乘以权重 + gsum += sir[1] * rbs; + // 计算蓝色通道的加权和,乘以权重 + bsum += sir[2] * rbs; + // 对于窗口中位于当前像素右侧的像素,更新 rinsum、ginsum 和 binsum + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + // 对于窗口中位于当前像素左侧的像素,更新 routsum、goutsum 和 boutsum + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; + } + } + // 栈指针初始化为半径 + stackpointer = radius; + + // 开始内层循环,遍历当前行的每一个像素 + for (x = 0; x < w; x++) { + // 此处将继续处理每个像素的模糊计算,对提取的颜色通道值进行处理 + // 代码在此处未完全展示,后续代码将根据计算的加权和和权重更新像素的颜色通道值 + // 并更新相关的求和变量,以便下一个像素的计算 + } +} + // 将处理后的 R、G、B 通道值存储在对应的数组中 +// 将模糊处理后的红色通道值存储在 r 数组中,使用 dv 数组中的值进行映射 +r[yi] = dv[rsum]; +// 将模糊处理后的绿色通道值存储在 g 数组中,使用 dv 数组中的值进行映射 +g[yi] = dv[gsum]; +// 将模糊处理后的蓝色通道值存储在 b 数组中,使用 dv 数组中的值进行映射 +b[yi] = dv[bsum]; + +// 更新 rsum,减去上一轮计算的 routsum +rsum -= routsum; +// 更新 gsum,减去上一轮计算的 goutsum +gsum -= goutsum; +// 更新 bsum,减去上一轮计算的 boutsum +bsum -= boutsum; + +// 计算栈的起始位置,考虑到循环利用栈空间 +stackstart = stackpointer - radius + div; +// 获取栈中相应位置的元素 +sir = stack[stackstart % div]; + +// 从 routsum 中减去栈中元素的红色通道值 +routsum -= sir[0]; +// 从 goutsum 中减去栈中元素的绿色通道值 +goutsum -= sir[1]; +// 从 boutsum 中减去栈中元素的蓝色通道值 +boutsum -= sir[2]; + +// 如果当前处于第一行 +if (y == 0) { + // 计算当前列的最小有效位置,避免越界 + vmin[x] = Math.min(x + radius + 1, wm); +} +// 获取当前像素位置的像素值 +p = pix[yw + vmin[x]]; + +// 提取当前像素的红色通道值 +sir[0] = (p & 0xff0000) >> 16; +// 提取当前像素的绿色通道值 +sir[1] = (p & 0x00ff00) >> 8; +// 提取当前像素的蓝色通道值 +sir[2] = (p & 0x0000ff); + +// 更新 rinsum,加上当前像素的红色通道值 +rinsum += sir[0]; +// 更新 ginsum,加上当前像素的绿色通道值 +ginsum += sir[1]; +// 更新 binsum,加上当前像素的蓝色通道值 +binsum += sir[2]; + +// 更新 rsum,加上 rinsum +rsum += rinsum; +// 更新 gsum,加上 ginsum +gsum += ginsum; +// 更新 bsum,加上 binsum +bsum += binsum; + +// 更新栈指针位置,通过取模实现循环利用栈空间 +stackpointer = (stackpointer + 1) % div; +// 获取栈中相应位置的元素 +sir = stack[(stackpointer) % div]; + +// 更新 routsum,加上栈中元素的红色通道值 +routsum += sir[0]; +// 更新 goutsum,加上栈中元素的绿色通道值 +goutsum += sir[1]; +// 更新 boutsum,加上栈中元素的蓝色通道值 +boutsum += sir[2]; + +// 从 rinsum 中减去栈中元素的红色通道值 +rinsum -= sir[0]; +// 从 ginsum 中减去栈中元素的绿色通道值 +ginsum -= sir[1]; +// 从 binsum 中减去栈中元素的蓝色通道值 +binsum -= sir[2]; + +// 移动到下一个像素位置(同一行的下一个像素) +yi++; +} +// 移动到下一行 +yw += w; + + // 垂直模糊处理 +for (x = 0; x < w; x++) { + // 初始化颜色通道的求和变量为 0 + rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; + // 初始化垂直偏移量,用于计算垂直方向的滑动窗口位置 + yp = -radius * w; + // 遍历垂直方向的滑动窗口,窗口大小由 radius 决定 + for (i = -radius; i <= radius; i++) { + // 计算当前像素在垂直方向上的位置,确保不越界 + yi = Math.max(0, yp) + x; + + // 获取栈中存储像素信息的数组 + sir = stack[i + radius]; + + // 从 r 数组中获取当前像素的红色通道值 + sir[0] = r[yi]; + // 从 g 数组中获取当前像素的绿色通道值 + sir[1] = g[yi]; + // 从 b 数组中获取当前像素的蓝色通道值 + sir[2] = b[yi]; + + // 计算当前像素的权重,距离中心像素越远,权重越低 + rbs = r1 - Math.abs(i); + + // 计算红色通道的加权和,乘以权重 + rsum += r[yi] * rbs; + // 计算绿色通道的加权和,乘以权重 + gsum += g[yi] * rbs; + // 计算蓝色通道的加权和,乘以权重 + bsum += b[yi] * rbs; + + // 对于窗口中位于当前像素下方的像素,更新 rinsum、ginsum 和 binsum + if (i > 0) { + rinsum += sir[0]; + ginsum += sir[1]; + binsum += sir[2]; + } else { + // 对于窗口中位于当前像素上方的像素,更新 routsum、goutsum 和 boutsum + routsum += sir[0]; + goutsum += sir[1]; + boutsum += sir[2]; } - yw = yi = 0; - - int[][] stack = new int[div][3]; - int stackpointer; - int stackstart; - int[] sir; - int rbs; - int r1 = radius + 1; - int routsum, goutsum, boutsum; - int rinsum, ginsum, binsum; - - for (y = 0; y < h; y++) { - rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - for (i = -radius; i <= radius; i++) { - p = pix[yi + Math.min(wm, Math.max(i, 0))]; - sir = stack[i + radius]; - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = (p & 0x0000ff); - rbs = r1 - Math.abs(i); - rsum += sir[0] * rbs; - gsum += sir[1] * rbs; - bsum += sir[2] * rbs; - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - } - } - stackpointer = radius; - - for (x = 0; x < w; x++) { - - r[yi] = dv[rsum]; - g[yi] = dv[gsum]; - b[yi] = dv[bsum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - - if (y == 0) { - vmin[x] = Math.min(x + radius + 1, wm); - } - p = pix[yw + vmin[x]]; - - sir[0] = (p & 0xff0000) >> 16; - sir[1] = (p & 0x00ff00) >> 8; - sir[2] = (p & 0x0000ff); - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[(stackpointer) % div]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - - yi++; - } - yw += w; + // 向下移动到下一个像素,更新垂直偏移量 + if (i < hm) { + yp += w; } - for (x = 0; x < w; x++) { - rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; - yp = -radius * w; - for (i = -radius; i <= radius; i++) { - yi = Math.max(0, yp) + x; - - sir = stack[i + radius]; - - sir[0] = r[yi]; - sir[1] = g[yi]; - sir[2] = b[yi]; - - rbs = r1 - Math.abs(i); - - rsum += r[yi] * rbs; - gsum += g[yi] * rbs; - bsum += b[yi] * rbs; - - if (i > 0) { - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - } else { - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - } - - if (i < hm) { - yp += w; - } - } - yi = x; - stackpointer = radius; - for (y = 0; y < h; y++) { - // Preserve alpha channel: ( 0xff000000 & pix[yi] ) - pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; - - rsum -= routsum; - gsum -= goutsum; - bsum -= boutsum; - - stackstart = stackpointer - radius + div; - sir = stack[stackstart % div]; - - routsum -= sir[0]; - goutsum -= sir[1]; - boutsum -= sir[2]; - - if (x == 0) { - vmin[y] = Math.min(y + r1, hm) * w; - } - p = x + vmin[y]; - - sir[0] = r[p]; - sir[1] = g[p]; - sir[2] = b[p]; - - rinsum += sir[0]; - ginsum += sir[1]; - binsum += sir[2]; - - rsum += rinsum; - gsum += ginsum; - bsum += binsum; - - stackpointer = (stackpointer + 1) % div; - sir = stack[stackpointer]; - - routsum += sir[0]; - goutsum += sir[1]; - boutsum += sir[2]; - - rinsum -= sir[0]; - ginsum -= sir[1]; - binsum -= sir[2]; - - yi += w; - } + } + // 初始化 yi 为当前列的位置 + yi = x; + // 栈指针初始化为半径 + stackpointer = radius; + // 遍历当前列的每一个像素 + for (y = 0; y < h; y++) { + // 组合处理后的 R、G、B 通道值,并保留 alpha 通道,更新像素信息 + pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16) | (dv[gsum] << 8) | dv[bsum]; + + // 更新 rsum,减去 routsum + rsum -= routsum; + // 更新 gsum,减去 goutsum + gsum -= goutsum; + // 更新 bsum,减去 boutsum + bsum -= boutsum; + + // 计算栈的起始位置,考虑到循环利用栈空间 + stackstart = stackpointer - radius + div; + // 获取栈中相应位置的元素 + sir = stack[stackstart % div]; + + // 从 routsum 中减去栈中元素的红色通道值 + routsum -= sir[0]; + // 从 goutsum 中减去栈中元素的绿色通道值 + goutsum -= sir[1]; + // 从 boutsum 中减去栈中元素的蓝色通道值 + boutsum -= sir[2]; + + // 如果当前处于第一列 + if (x == 0) { + // 计算当前行的最小有效位置,避免越界 + vmin[y] = Math.min(y + r1, hm) * w; } + // 获取当前像素位置的像素值 + p = x + vmin[y]; + + // 从 r 数组中获取当前像素的红色通道值 + sir[0] = r[p]; + // 从 g 数组中获取当前像素的绿色通道值 + sir[1] = g[p]; + // 从 b 数组中获取当前像素的蓝色通道值 + sir[2] = b[p]; + + // 更新 rinsum,加上当前像素的红色通道值 + rinsum += sir[0]; + // 更新 ginsum,加上当前像素的绿色通道值 + ginsum += sir[1]; + // 更新 binsum,加上当前像素的蓝色通道值 + binsum += sir[2]; + + // 更新 rsum,加上 rinsum + rsum += rinsum; + // 更新 gsum,加上 ginsum + gsum += ginsum; + // 更新 bsum,加上 binsum + bsum += bsum; + + // 更新栈指针位置,通过取模实现循环利用栈空间 + stackpointer = (stackpointer + 1) % div; + // 获取栈中相应位置的元素 + sir = stack[stackpointer]; + + // 更新 routsum,加上栈中元素的红色通道值 + routsum += sir[0]; + // 更新 goutsum,加上栈中元素的绿色通道值 + goutsum += sir[1]; + // 更新 boutsum,加上栈中元素的蓝色通道值 + boutsum += sir[2]; + + // 从 rinsum 中减去栈中元素的红色通道值 + rinsum -= sir[0]; + // 从 ginsum 中减去栈中元素的绿色通道值 + ginsum -= sir[1]; + // 从 binsum 中减去栈中元素的蓝色通道值 + binsum -= sir[2]; + + // 移动到下一个像素位置(下一列的同一像素) + yi += w; + } +} + // 将处理后的像素信息重新设置到 Bitmap 中 bitmap.setPixels(pix, 0, w, 0, 0, w, h); + // 返回模糊处理后的 Bitmap return (bitmap); } - } diff --git a/app/src/main/java/com/example/musicplayer/util/FileUtil.java b/app/src/main/java/com/example/musicplayer/util/FileUtil.java index a73534e..67621da 100644 --- a/app/src/main/java/com/example/musicplayer/util/FileUtil.java +++ b/app/src/main/java/com/example/musicplayer/util/FileUtil.java @@ -1,6 +1,5 @@ package com.example.musicplayer.util; - import android.content.Context; import android.graphics.Bitmap; import android.util.Log; @@ -24,31 +23,31 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** - * 将user序列化到本地并取出的工具 + * 将 user 序列化到本地并取出的工具 * Created by 残渊 on 2018/7/24. */ - - public class FileUtil { private static String TAG = "FileUtil"; /** - * 将person对象保存到文件中 + * 将 song 对象保存到文件中 * params: - * p:person类对象 + * song: Song 类对象 */ - public static void saveSong(Song song) { try { + // 获取应用外部存储目录下的 yuanmusic 文件夹路径 File file = new File(App.getContext().getExternalFilesDir("yuanmusic").getAbsolutePath()); if (!file.exists()) { + // 如果文件夹不存在,创建该文件夹 file.mkdirs(); } - //写对象流的对象 + // 创建用于存储 Song 对象的文件 File userFile = new File(file, "song.txt"); + // 创建对象输出流,将对象序列化到文件中 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(userFile)); - oos.writeObject(song);//将Person对象p写入到oos中 - oos.close(); //关闭文件流 + oos.writeObject(song); // 将 Song 对象 song 写入到 oos 中 + oos.close(); // 关闭文件流 } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { @@ -57,38 +56,42 @@ public class FileUtil { } /** - * 从文件中读出对象,并且返回Person对象 + * 从文件中读出对象,并且返回 Song 对象 */ - public static Song getSong() { try { + // 创建对象输入流,从文件中读取序列化的对象 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(App.getContext().getExternalFilesDir("") + "/yuanmusic/song.txt")); - Song song = (Song) ois.readObject();//读出对象 - return song; //返回对象 + // 读取对象并强制转换为 Song 类型 + Song song = (Song) ois.readObject(); + return song; // 返回读取的 Song 对象 } catch (FileNotFoundException e) { e.printStackTrace(); + // 当文件不存在时,创建一个新的 Song 对象作为默认值 Song song = new Song(); return song; } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { - // TODO Auto-generated catch block e.printStackTrace(); } return null; } - //保存图片到本地 + // 保存图片到本地 public static void saveImgToNative(Context context, Bitmap bitmap, String singer) { + // 创建存储图片的文件目录 File file = new File(Api.STORAGE_IMG_FILE); if (!file.exists()) { file.mkdirs(); } + // 创建存储歌手图片的文件 File singerImgFile = new File(file, singer + ".jpg"); FileOutputStream fos = null; try { + // 创建文件输出流 fos = new FileOutputStream(singerImgFile); + // 将 Bitmap 以 JPEG 格式压缩并写入文件 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); fos.flush(); } catch (FileNotFoundException e) { @@ -98,7 +101,7 @@ public class FileUtil { e.printStackTrace(); } finally { try { - if (fos != null) { + if (fos!= null) { fos.close(); } } catch (IOException e) { @@ -107,17 +110,21 @@ public class FileUtil { } } - //保存歌词到本地 - public static void saveLrcToNative(String lrc,String songName){ - //开启线程保存歌词 + // 保存歌词到本地 + public static void saveLrcToNative(String lrc, String songName) { + // 开启线程保存歌词,避免在主线程中进行 I/O 操作 new Thread(() -> { + // 创建存储歌词的文件目录 File file = new File(Api.STORAGE_LRC_FILE); if (!file.exists()) { file.mkdirs(); } + // 创建存储歌词的文件 File lrcFile = new File(file, songName + Constant.LRC); try { + // 创建文件写入器 FileWriter fileWriter = new FileWriter(lrcFile); + // 写入歌词 fileWriter.write(lrc); fileWriter.close(); } catch (IOException e) { @@ -128,22 +135,22 @@ public class FileUtil { public static String getLrcFromNative(String songName) { try { - FileReader fileReader = new FileReader(Api.STORAGE_LRC_FILE+songName+Constant.LRC); + // 创建文件读取器读取歌词文件 + FileReader fileReader = new FileReader(Api.STORAGE_LRC_FILE + songName + Constant.LRC); BufferedReader bufferedReader = new BufferedReader(fileReader); StringBuilder lrc = new StringBuilder(); - while (true){ + while (true) { + // 逐行读取歌词 String s = bufferedReader.readLine(); - if(s == null) break; + if (s == null) break; lrc.append(s).append("\n"); } fileReader.close(); - Log.d(TAG, "getLrcFromNative: "+lrc.toString()); + Log.d(TAG, "getLrcFromNative: " + lrc.toString()); return lrc.toString(); } catch (IOException e) { e.printStackTrace(); } return null; } - - } \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/util/LrcUtil.java b/app/src/main/java/com/example/musicplayer/util/LrcUtil.java index 18ce3b4..2cd7eb7 100644 --- a/app/src/main/java/com/example/musicplayer/util/LrcUtil.java +++ b/app/src/main/java/com/example/musicplayer/util/LrcUtil.java @@ -12,79 +12,119 @@ import java.util.List; * desc : 歌词工具类 * */ - public class LrcUtil { /** * 传入的参数为标准歌词字符串 * - * @param lrcStr - * @return + * @param lrcStr 输入的歌词字符串 + * @return 解析后的歌词对象列表 */ public static List parseStr2List(String lrcStr) { - List list = new ArrayList<>(); + // 存储解析后的歌词对象列表 + List list = new ArrayList<>(); + // 替换歌词字符串中的特殊字符 String lrcText = lrcStr.replaceAll(":", ":") - .replaceAll("'", "'") - .replaceAll(" ", "\n") - .replaceAll(".", ".") - .replaceAll(" ", " ") - .replaceAll("-", "-") - .replaceAll(" ", "\r") - .replaceAll("'", "'"); - String[] split = lrcText.split("\n"); - for (int i = 4; i < split.length; i++) { - String lrc = split[i]; - if (lrc.contains(".")) { - String min = lrc.substring(lrc.indexOf("[") + 1, lrc.indexOf("[") + 3); - String seconds = lrc.substring(lrc.indexOf(":") + 1, lrc.indexOf(":") + 3); - String mills = lrc.substring(lrc.indexOf(".") + 1, lrc.indexOf(".") + 3); - long startTime = Long.valueOf(min) * 60 * 1000 + Long.valueOf(seconds) * 1000 + Long.valueOf(mills) * 10; - String text = lrc.substring(lrc.indexOf("]") + 1); - if (text.equals("") || text == null) continue; - if (i == 5) { - int first = text.indexOf("("); - int second = text.indexOf("(", first + 1); - String textFront = null; - String textBehind = null; - boolean isTwo = true; - boolean isOne = true; - try { - textFront = text.substring(0, first); - }catch (StringIndexOutOfBoundsException e){ - isOne = false; - } - try { - textBehind = text.substring(first + 1, second); - } catch (StringIndexOutOfBoundsException e) { - isTwo = false; - } - LrcBean lrcBean1 = new LrcBean(); - lrcBean1.setStart(startTime); - if(isOne) { - lrcBean1.setLrc(textFront); - } else { - lrcBean1.setLrc(text); - } - list.add(lrcBean1); - if (isTwo) { - LrcBean lrcBean2 = new LrcBean(); - lrcBean2.setStart(startTime); - lrcBean2.setLrc(textBehind); - list.add(lrcBean2); - } - } else { - LrcBean lrcBean = new LrcBean(); - lrcBean.setStart(startTime); - lrcBean.setLrc(text); - list.add(lrcBean); - } - if (list.size() > 1) { - list.get(list.size() - 2).setEnd(startTime); - } - if (i == split.length - 1) { - list.get(list.size() - 1).setEnd(startTime + 100000); - } - } - } - return list; + .replaceAll("'", "'") + .replaceAll(" ", "\n") + .replaceAll(".", ".") + .replaceAll(" ", " ") + .replaceAll("-", "-") + .replaceAll(" ", "\r") + .replaceAll("'", "'"); + // 按行分割歌词字符串 + String[] split = lrcText.split("\n"); + for (int i = 4; i < split.length; i++) { + String lrc = split[i]; + if (lrc.contains(".")) { + // 提取分钟数 + String min = lrc.substring(lrc.indexOf("[") + 1, lrc.indexOf("[") + 3); + // 提取秒数 + String seconds = lrc.substring(lrc.indexOf(":") + 1, lrc.indexOf(":") + 3); + // 提取毫秒数 + String mills = lrc.substring(lrc.indexOf(".") + 1, lrc.indexOf(".") + 3); + // 计算开始时间(毫秒) + long startTime = Long.valueOf(min) * 60 * 1000 + Long.valueOf(seconds) * 1000 + Long.valueOf(mills) * 10; + // 提取歌词文本 + // 从歌词行中提取时间戳后面的文本内容 +String text = lrc.substring(lrc.indexOf("]") + 1); +// 如果提取的文本内容为空字符串或为 null,则跳过当前歌词行的处理,继续下一行的处理 +if (text.equals("") || text == null) continue; +// 对于第 5 行歌词进行特殊处理 +if (i == 5) { + // 查找第一个左括号的位置 + int first = text.indexOf("("); + // 查找第二个左括号的位置,从第一个左括号后开始查找 + int second = text.indexOf("(", first + 1); + // 存储第一个括号前的文本内容 + String textFront = null; + // 存储第一个括号和第二个括号之间的文本内容 + String textBehind = null; + // 标记是否存在第一个括号和第二个括号之间的文本 + boolean isTwo = true; + // 标记是否存在第一个括号前的文本 + boolean isOne = true; + try { + // 尝试提取第一个括号前的文本内容 + textFront = text.substring(0, first); + } catch (StringIndexOutOfBoundsException e) { + // 如果出现字符串越界异常,说明第一个括号不存在,标记为 false + isOne = false; + } + try { + // 尝试提取第一个括号和第二个括号之间的文本内容 + textBehind = text.substring(first + 1, second); + } catch (StringIndexOutOfBoundsException e) { + // 如果出现字符串越界异常,说明第二个括号不存在,标记为 false + isTwo = false; } +// 处理越界异常 +isTwo = false; +// 创建一个新的 LrcBean 实例 +LrcBean lrcBean1 = new LrcBean(); +// 设置该 LrcBean 的开始时间为之前计算得到的 startTime +lrcBean1.setStart(startTime); +// 如果存在第一个括号前的文本(isOne 为 true),将其设置为该 LrcBean 的歌词内容 +if (isOne) { + lrcBean1.setLrc(textFront); +// 否则,将整个文本设置为歌词内容 +} else { + lrcBean1.setLrc(text); +} +// 将创建的 LrcBean 添加到存储歌词对象的列表中 +list.add(lrcBean1); +// 如果存在第二个括号及其之间的文本(isTwo 为 true) +if (isTwo) { + // 创建另一个新的 LrcBean 实例 + LrcBean lrcBean2 = new LrcBean(); + // 设置该 LrcBean 的开始时间为 startTime + lrcBean2.setStart(startTime); + // 将第一个括号和第二个括号之间的文本设置为该 LrcBean 的歌词内容 + lrcBean2.setLrc(textBehind); + // 将这个 LrcBean 也添加到列表中 + list.add(lrcBean2); +} +// 对于其他行(非第 5 行) +} else { + // 创建一个新的 LrcBean 实例 + LrcBean lrcBean = new LrcBean(); + // 设置该 LrcBean 的开始时间为 startTime + lrcBean.setStart(startTime); + // 将提取的文本设置为该 LrcBean 的歌词内容 + lrcBean.setLrc(text); + // 将该 LrcBean 添加到列表中 + list.add(lrcBean); } +// 如果存储歌词对象的列表长度大于 1 +if (list.size() > 1) { + // 将上一个歌词对象的结束时间设置为当前歌词对象的开始时间 + list.get(list.size() - 2).setEnd(startTime); +} +// 如果当前是最后一行歌词 +if (i == split.length - 1) { + // 将最后一个歌词对象的结束时间设置为 startTime 加上 100000 毫秒 + list.get(list.size() - 1).setEnd(startTime + 100000); +} +// 完成整个歌词字符串的解析,返回存储歌词对象的列表 +return list; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/util/MD5Util.java b/app/src/main/java/com/example/musicplayer/util/MD5Util.java index b0727dd..45c4ca1 100644 --- a/app/src/main/java/com/example/musicplayer/util/MD5Util.java +++ b/app/src/main/java/com/example/musicplayer/util/MD5Util.java @@ -14,44 +14,49 @@ import java.security.NoSuchAlgorithmException; * desc : MD5加密工具 * */ - public class MD5Util { - public static String getFileMD5(File file){ - if(file == null||!file.exists()) return ""; - FileInputStream in = null; - byte[] buffer = new byte[1024]; - StringBuilder res = new StringBuilder(); - int len; + public static String getFileMD5(File file) { + // 如果文件为空或不存在,返回空字符串 + if (file == null ||!file.exists()) return ""; + FileInputStream in = null; + // 用于存储文件读取的数据 + byte[] buffer = new byte[1024]; + // 存储最终的 MD5 结果 + StringBuilder res = new StringBuilder(); + int len; try { - MessageDigest messageDigest = MessageDigest.getInstance("MD5"); - in = new FileInputStream(file); - while ((len=in.read(buffer))!=-1){ - //计算文件时需要通过分段读取多次调用update来将数据更新给MessageDigest对象 - messageDigest.update(buffer,0,len); + // 获取 MD5 消息摘要对象 + MessageDigest messageDigest = MessageDigest.getInstance("MD5"); + in = new FileInputStream(file); + // 循环读取文件内容 + while ((len = in.read(buffer))!= -1) { + // 更新消息摘要对象,分段读取文件数据 + messageDigest.update(buffer, 0, len); } - //真正计算文件的MD5值 - byte[] bytes = messageDigest.digest(); - //将字节数组转换成16进制的字符串 - for(byte b:bytes){ - String temp = Integer.toHexString(b&0xff); - if(temp.length()!=2){ - temp = "0"+temp; + // 计算最终的 MD5 摘要 + byte[] bytes = messageDigest.digest(); + // 将 MD5 摘要的字节数组转换为 16 进制字符串 + for (byte b : bytes) { + String temp = Integer.toHexString(b & 0xff); + if (temp.length()!= 2) { + temp = "0" + temp; } - res.append(temp); + res.append(temp); } - //返回最终的字符串 - return res.toString(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - if(null!=in){ + // 返回最终的 MD5 字符串 + return res.toString(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (null!= in) { try { - in.close(); - }catch (Exception e){ - e.printStackTrace(); + // 关闭文件输入流 + in.close(); + } catch (Exception e) { + e.printStackTrace(); } } } - return res.toString(); + return res.toString(); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/util/MediaUtil.java b/app/src/main/java/com/example/musicplayer/util/MediaUtil.java index 70c1fd5..dc7b645 100644 --- a/app/src/main/java/com/example/musicplayer/util/MediaUtil.java +++ b/app/src/main/java/com/example/musicplayer/util/MediaUtil.java @@ -20,33 +20,39 @@ import java.io.FileNotFoundException; /** * Created by 残渊 on 2018/10/22. */ - public class MediaUtil { + // 格式化时间的方法,将毫秒数转换为分钟和秒的字符串表示 public static String formatTime(long time) { - String min = time / 60 + ""; - String sec = time % 60 + ""; - if(sec.length()<2){ - sec = "0"+sec; + // 计算分钟数 + String min = time / 60 + ""; + // 计算秒数 + String sec = time % 60 + ""; + // 如果秒数不足两位,在前面补 0 + if (sec.length() < 2) { + sec = "0" + sec; } - return min + ":" + sec; + // 返回格式化后的时间字符串,格式为 "分钟:秒" + return min + ":" + sec; } - public static String formatSinger(String singer){ - if(singer.contains("/")){ - String []s=singer.split("/"); - singer=s[0]; + + // 格式化歌手信息的方法,如果歌手信息包含 "/",取第一个部分 + public static String formatSinger(String singer) { + if (singer.contains("/")) { + // 按 "/" 分割歌手信息 + String[] s = singer.split("/"); + singer = s[0]; } - return singer.trim(); + // 去除前后的空格 + return singer.trim(); } @SuppressLint("DefaultLocale") - public static String formatSize(long size){ - double d = (double) size/1024/1024; - return String.format("%.1f",d); + // 格式化文件大小的方法,将字节数转换为兆字节表示,保留一位小数 + public static String formatSize(long size) { + // 将字节数转换为兆字节 + double d = (double) size / 1024 / 1024; + // 格式化浮点数,保留一位小数 + return String.format("%.1f", d); } - - - -} - - +} \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/util/ScreenUtil.java b/app/src/main/java/com/example/musicplayer/util/ScreenUtil.java index f029619..f280b34 100644 --- a/app/src/main/java/com/example/musicplayer/util/ScreenUtil.java +++ b/app/src/main/java/com/example/musicplayer/util/ScreenUtil.java @@ -9,23 +9,32 @@ import android.content.Context; * desc : 屏幕相关 * */ - public class ScreenUtil { /** * 根据手机的分辨率从 dp 的单位 转成为 px(像素) + * @param context 应用上下文 + * @param dpValue 要转换的 dp 值 + * @return 转换后的像素值 */ public static int dip2px(Context context, float dpValue) { - final float scale = context.getResources().getDisplayMetrics().density; - return (int) (dpValue * scale + 0.5f); + // 获取设备的屏幕密度 + final float scale = context.getResources().getDisplayMetrics().density; + // 将 dp 值乘以屏幕密度并四舍五入,转换为像素值 + return (int) (dpValue * scale + 0.5f); } /** * 根据手机的分辨率从 px(像素) 的单位 转成为 dp + * @param context 应用上下文 + * @param pxValue 要转换的像素值 + * @return 转换后的 dp 值 */ public static int px2dip(Context context, float pxValue) { - final float scale = context.getResources().getDisplayMetrics().density; - return (int) (pxValue / scale + 0.5f); + // 获取设备的屏幕密度 + final float scale = context.getResources().getDisplayMetrics().density; + // 将像素值除以屏幕密度并四舍五入,转换为 dp 值 + return (int) (pxValue / scale + 0.5f); } } \ No newline at end of file diff --git a/app/src/main/java/com/example/musicplayer/util/ServiceUtil.java b/app/src/main/java/com/example/musicplayer/util/ServiceUtil.java index e99ffef..ea95cf8 100644 --- a/app/src/main/java/com/example/musicplayer/util/ServiceUtil.java +++ b/app/src/main/java/com/example/musicplayer/util/ServiceUtil.java @@ -12,18 +12,28 @@ import java.util.List; * desc : 服务工具类 * */ - public class ServiceUtil { + /** + * 检查指定的服务是否正在运行 + * @param context 应用的上下文 + * @param serviceName 要检查的服务的完整类名 + * @return 如果服务正在运行返回 true,否则返回 false + */ public static boolean isServiceRunning(Context context, String serviceName) { - ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - List infos = am.getRunningServices(100); - for (ActivityManager.RunningServiceInfo info : infos) { - String name = info.service.getClassName(); - if (name.equals(serviceName)) { - return true; + // 获取系统的 ActivityManager 服务 + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + // 获取正在运行的服务列表,最多 100 个 + List infos = am.getRunningServices(100); + // 遍历正在运行的服务列表 + for (ActivityManager.RunningServiceInfo info : infos) { + // 获取服务的类名 + String name = info.service.getClassName(); + // 如果服务类名与要检查的服务类名相同,返回 true + if (name.equals(serviceName)) { + return true; } } - return false; + // 若未找到匹配的服务,返回 false + return false; } - -} +} \ No newline at end of file