当前位置: 首页 > news >正文

Android开发之音乐播放器添加排行需求

Music统计功能需求
1.记录歌曲名称与次数(歌曲播放结束算一次),根据播放次数制作一个排行列表;(开始说要记录歌手,后面debug发现这个字段没有,暂时不记录)
2.记录播放歌曲的时长,时间累加;(经沟通,需要细分成每一个月的播放时长,另外再加一个首次使用的记录)
前几天需要实现这功能,昨天实现了,今天验证Ok,来谈谈我的做法,先上图(暂时版):
在这里插入图片描述
上面是一个简单的例子,具体UI后期需要给图再做调整,目前结构上面是一个通用的带返回键的titlebar,下面recyclerview加文本做数据展示,方便测试看数据,当然我都是用sqlite expert去查看数据:
表1
表2,目前只记录六月份和七月份的数据

设备的数据库
这里需要注意的是当我们从设备导出数据库的时候需要把.db和.db-shm和.db-wal都要导出,不然可能没有表和数据.
1.首先创建音乐播放数据库:

package com.hiby.music.musicinfofetchermaster.db;import static com.hiby.music.musicinfofetchermaster.db.MusicRecordDao.COLUMN_PLAY_COUNT;
import static com.hiby.music.musicinfofetchermaster.db.MusicRecordDao.KEY_DB_TAB;import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;import androidx.annotation.NonNull;
import androidx.annotation.Nullable;public class MusicRecordOpenHelper extends SQLiteOpenHelper {private volatile static MusicRecordOpenHelper instances = null;public static final String DB_NAME = "music_record.db";public static final int DB_VERSION = 1;public static MusicRecordOpenHelper getInstances(Context context) {if (instances == null) {synchronized (MusicRecordOpenHelper.class) {if (instances == null) {instances = new MusicRecordOpenHelper(context.getApplicationContext());}}}return instances;}public MusicRecordOpenHelper(Context context) {super(context, DB_NAME, null, DB_VERSION);}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL(MusicRecordDao.CREATE_TABLE_SONGS);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {db.execSQL("DROP TABLE IF EXISTS " + KEY_DB_TAB);onCreate(db);}public void insertOrUpdateSong(String name, String author) {SQLiteDatabase db = instances.getWritableDatabase();// 查询数据库中是否已经存在相同的歌曲String selection = MusicRecordDao.COLUMN_NAME + " = ? AND " + MusicRecordDao.COLUMN_AUTHOR + " = ?";String[] selectionArgs = {name, author};Cursor cursor = db.query(KEY_DB_TAB,new String[]{COLUMN_PLAY_COUNT},selection,selectionArgs,null,null,null);if (cursor.moveToFirst()) {// 歌曲已存在,更新播放次数int playCount = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_PLAY_COUNT));playCount++; // 增加播放次数ContentValues values = new ContentValues();values.put(COLUMN_PLAY_COUNT, playCount);db.update(KEY_DB_TAB, values, selection, selectionArgs);} else {// 歌曲不存在,插入新记录ContentValues values = new ContentValues();values.put(MusicRecordDao.COLUMN_NAME, name);values.put(MusicRecordDao.COLUMN_AUTHOR, author);values.put(COLUMN_PLAY_COUNT, 1); // 初始播放次数为1,因为这是第一次插入db.insert(KEY_DB_TAB, null, values);}cursor.close();db.close();}// 插入新歌曲public void insertSong(String name, String author) {SQLiteDatabase db = instances.getWritableDatabase();ContentValues values = new ContentValues();values.put(MusicRecordDao.COLUMN_NAME, name);values.put(MusicRecordDao.COLUMN_AUTHOR, author);values.put(COLUMN_PLAY_COUNT, 0); // 初始播放次数为0db.insert(KEY_DB_TAB, null, values);db.close();}// 更新播放次数public void incrementPlayCount(String name, String author) {SQLiteDatabase db = instances.getWritableDatabase();String selection = MusicRecordDao.COLUMN_NAME + " = ? AND " + MusicRecordDao.COLUMN_AUTHOR + " = ?";String[] selectionArgs = {name, author};Cursor cursor = db.query(MusicRecordDao.KEY_DB_TAB, new String[]{COLUMN_PLAY_COUNT},selection, selectionArgs, null, null, null);if (cursor != null && cursor.moveToFirst()) {int playCount = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_PLAY_COUNT));playCount++;ContentValues values = new ContentValues();values.put(COLUMN_PLAY_COUNT, playCount);db.update(MusicRecordDao.KEY_DB_TAB, values, selection, selectionArgs);}if (cursor != null) {cursor.close();}db.close();}// 获取按播放次数排序的音乐记录public Cursor getMusicSortedByPlayCount() {SQLiteDatabase db = this.getReadableDatabase();return db.query(KEY_DB_TAB, null, null, null, null, null, COLUMN_PLAY_COUNT + " DESC");}
}

这里我直接把对数据进行插入的逻辑和查询写在里面了,下面是建表,放在Dao里:


public class MusicRecordDao {public static final String KEY_DB_TAB = "music_record";public static final String COLUMN_ID = "id";public static final String COLUMN_NAME = "name";public static final String COLUMN_AUTHOR = "author";public static final String COLUMN_PLAY_COUNT = "play_count";public static final String CREATE_TABLE_SONGS = "CREATE TABLE " + KEY_DB_TAB + " ("+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "+ COLUMN_NAME + " TEXT NOT NULL, "+ COLUMN_AUTHOR + " TEXT NOT NULL, "+ COLUMN_PLAY_COUNT + " INTEGER DEFAULT 0)";public static final String CLEAN_MUSIC_TAB = "DELETE FROM " + KEY_DB_TAB;}

2.根据音乐播放生命周期的onAudioComplete中,进行数据库的保存:

                @Overridepublic void onAudioComplete(IPlayer player, AudioInfo audio) {saveMusicRecord(audio);LogPlus.d("###onAudioComplete###");}...private static void saveMusicRecord(AudioInfo audio) {ThreadPoolExecutor.execute(() -> {SmartPlayerApplication.getMusicRecordOpenHelper().getWritableDatabase();String displayName = audio.displayName();SmartPlayerApplication.getMusicRecordOpenHelper().insertOrUpdateSong(displayName, "");});}

上面就实现了音乐播放文件名和播放次数的更新插入
3.在具体页面进行查询:

    private List getDataFromDb() {List<MusicRankModel> list = new ArrayList<>();MusicRecordOpenHelper musicRecordOpenHelper = SmartPlayerApplication.getMusicRecordOpenHelper();musicRecordOpenHelper.getReadableDatabase();Cursor cursor = musicRecordOpenHelper.getMusicSortedByPlayCount();if (cursor != null && cursor.moveToFirst()) {do {@SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex(MusicRecordDao.COLUMN_NAME));@SuppressLint("Range") int playCount = cursor.getInt(cursor.getColumnIndex(MusicRecordDao.COLUMN_PLAY_COUNT));list.add(new MusicRankModel(name, playCount));} while (cursor.moveToNext());cursor.close();}if (list.size() > 100) {list = list.subList(0, 100);}return list;}

上面是经典的数据库写法,限制100个数量,
4.下面是UI层的展示

    private void initData() {recyclerView = findViewById(R.id.rv_rank_list);recyclerView.setLayoutManager(new LinearLayoutManager(this));//从数据库获取musicList = getDataFromDb();//musicList = generateMusicList();adapter = new MusicRankAdapter(musicList);recyclerView.setAdapter(adapter);}

Adapter层做了TYPE_HEADER和TYPE_ITEM的处理:


public class MusicRankAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {private List<MusicRankModel> musicList;private static final int VIEW_TYPE_HEADER = 0;private static final int VIEW_TYPE_ITEM = 1;public static class MusicViewHolder extends RecyclerView.ViewHolder {public TextView musicName;public TextView playCount;public MusicViewHolder(@NonNull View itemView) {super(itemView);musicName = itemView.findViewById(R.id.musicName);playCount = itemView.findViewById(R.id.playCount);}}public static class HeaderViewHolder extends RecyclerView.ViewHolder {public HeaderViewHolder(@NonNull View itemView) {super(itemView);}}public MusicRankAdapter(List<MusicRankModel> musicList) {this.musicList = musicList;}@NonNull@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {if (viewType == VIEW_TYPE_HEADER) {View headerView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_music_header, parent, false);return new HeaderViewHolder(headerView);} else {View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_music, parent, false);return new MusicViewHolder(itemView);}}@Overridepublic void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {if (holder instanceof MusicViewHolder) {MusicRankModel currentMusic = musicList.get(position - 1); // 减1因为第一个位置是头部视图((MusicViewHolder) holder).musicName.setText(currentMusic.getName());((MusicViewHolder) holder).playCount.setText(String.valueOf(currentMusic.getPlayCount()));}}@Overridepublic int getItemViewType(int position) {return position == 0 ? VIEW_TYPE_HEADER : VIEW_TYPE_ITEM;}@Overridepublic int getItemCount() {return musicList.size() + 1; // 加1表示包括头部视图}
}

具体item两个的layout:
item_music_header:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:padding="8dp"><TextViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="2"android:text="歌曲名"android:gravity="center"android:textSize="16sp"android:textStyle="bold" /><TextViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="播放次数"android:gravity="center"android:textSize="16sp"android:textStyle="bold" />
</LinearLayout>

item_music:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:padding="8dp"><TextViewandroid:id="@+id/musicName"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="2"android:gravity="center"android:text="Music Name"android:textSize="16sp" /><TextViewandroid:id="@+id/playCount"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Play Count"android:layout_weight="1"android:textSize="16sp"android:gravity="center"android:paddingStart="16dp"/>
</LinearLayout>

这样就实现了上面列表的效果。下面是时间戳记录和按月记录播放时长的思路放在下一篇去讲.

相关文章:

Android开发之音乐播放器添加排行需求

Music统计功能需求 1.记录歌曲名称与次数(歌曲播放结束算一次)&#xff0c;根据播放次数制作一个排行列表;&#xff08;开始说要记录歌手&#xff0c;后面debug发现这个字段没有&#xff0c;暂时不记录&#xff09; 2.记录播放歌曲的时长&#xff0c;时间累加&#xff1b;&…...

latex 方括号编号

最近在做简历&#xff0c;需要列出发表的论文。 论文编号一般是采用[1]这种样式&#xff0c;但是找了几个简历模板里头没有直接包含这种编号样式。 我只好求助网络。 在CSDN上找了一圈&#xff0c;这篇博客给了一个思路&#xff1a;在\begin{enumerate}后面添加对应的样式即…...

Vue CLI 4与项目构建实战指南

title: Vue CLI 4与项目构建实战指南 date: 2024/6/9 updated: 2024/6/9 excerpt: 这篇文章介绍了如何使用Vue CLI优化项目构建配置&#xff0c;提高开发效率&#xff0c;涉及配置管理、项目部署策略、插件系统定制以及Webpack和TypeScript的深度集成技巧。 categories: 前端…...

深入解析Web通信 HTTP、HTTPS 和 WebSocket

在现代Web开发中,了解和掌握HTTP、HTTPS以及WebSocket协议是非常重要的。这些协议是实现Web应用程序之间通信的基石。本文将详细介绍这三种协议,包括它们的基本概念、工作原理、优缺点以及适用场景。通过深入解析它们的特点和应用,帮助读者更好地理解和使用这些协议。 一、…...

FISCO BCOS x GitLink,为国产开源技术生态注入新活力

作为中国领先的区块链底层平台之一&#xff0c;FISCO BCOS 自成立以来始终致力于推动国产开源区块链技术的应用和普及。近期&#xff0c;FISCO BCOS 将开源代码托管到CCF官方代码托管平台 GitLink &#xff08;确实开源&#xff09;&#xff0c;为国产开源技术生态注入新活力。…...

Linux crontabs定时执行任务

文章目录 前言一、安装二、服务1. 启动crond服务2. 关闭crond服务3. 重启crond服务4. 设置crond开机启动5. 禁用crond开机启动6. 查看crond是否开机启动7. 重新载入配置8. 查看crond运行状态 三、使用1. 查看当前用户的crontab2. 编辑用户的crontab3. 删除用户的crontab的内容 …...

QNX简述

文章目录 前言1. QNX简介1.1 什么是QNX1.2 QNX的应用场景1.3 QNX的优点1.4 QNX的发展史1.5 QNX的商业模式 2. QNX的技术特点3. QNX和其它操作系统的比较3.1 QNX VS LINUX3.2 QNX VS FreeRTOS3.3 QNX VS 鸿蒙操作系统 4. 我的疑问4.1 微内核看起来又稳定又容易调试&#xff0c;为…...

[Llama3] ReAct Prompt 测试实验

ReAct 是一种 LLM 提示和结果处理方法&#xff0c;结合了推理、行动计划和知识源整合&#xff0c;使 LLM 超越其语言模型&#xff0c;并在预测中使用来自现实世界的信息。 ReAct 是推理和行动的结合。 介绍 ReAct 的论文表明它比思维链提示更好。与后者不同的是&#xff0c;Re…...

nodejs 某音douyin网页端搜索接口及x_bogus、a_bogus(包含完整源码)(2024-06-13)

前言 x_bogus或a_bogus算法大概是对数据、ua、时间戳、浏览器的几个指纹进行计算&#xff0c;拿到一个110位大数组&#xff0c;然后转字符&#xff0c;在头部再添加十二位随机字符&#xff0c;再进行魔改的base64加密。 问&#xff1a;抖音的x_bogus、a_bogus值有什么用&#x…...

继承深度剖析

前言 从继承开始就开始C进阶了&#xff0c; 这一块需要好好学习&#xff0c;这块知识很重要&#xff0c; 坑有点多&#xff0c;所以是面试笔试的常客。 基本概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c; 它允许程序员在保持原有…...

使用 Vue 和 Ant Design 实现抽屉效果的模块折叠功能

功能描述&#xff1a; 有两个模块&#xff0c;点击上面模块的收起按钮时&#xff0c;上面的模块可以折叠&#xff0c;下面的模块随之扩展 代码实现&#xff1a; 我们在 Vue 组件中定义两个模块的布局和状态管理&#xff1a; const scrollTableY ref(560); // 表格初始高度…...

Springboot整合SpringCache+redis简化缓存开发

使用步骤&#xff1a; 1.引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId> </dependency><dependency><groupId>org.springframework.boot</groupI…...

关于EOF标识符

EOF的概念 EOF是C语言中表示文件结束的标志符号&#xff0c;通常被定义为-1&#xff0c;它用于指示已到达文件的末尾或输入流的末尾。 EOF的使用 在输入操作中&#xff0c;EOF常常用于判断是否到达了文件末尾或输入流末尾&#xff0c;以便终止读取操作。例如&#xff0c;在使…...

家用洗地机排行榜前十名:2024十大王牌机型精准种草

最近很多人都在问我洗地机相关的问题&#xff0c;不愧是改善家庭生活品质的“三神器”之一。洗地机依靠其清洁力和清洁效率吸引了越来越多的平时需要做家务人群的兴趣&#xff0c;为了解答大家关于洗地机的各种疑问&#xff0c;我把市面上目前非常火爆的洗地机型号和参数都进行…...

【Chrome插件】如何在Chrome插件开发中处理复杂数据结构的存储

最近俺在接触 Chrome 插件开发&#xff0c;需要把一个数据存放到浏览器的存储中。这个数据结构有点复杂&#xff0c;它包含一个 Map 和一个数组。我使用 chrome.storage.local API来存储这个数据&#xff0c;然后在另一个地方获取数据。保存数据的代码并没有报错&#xff0c;但…...

MySQL 保姆级教程(二):使用 MySQL 检索数据

使用 MySQL 3.2 选择数据库 使用数据库: 输入: USE 数据库名;输出: Database changed分析: 不返回任何结果&#xff0c;显示某种形式的通知 ​ 例如: 使用 crashcourse 数据库 use crashcourse; 3.3 了解数据库和表 列出所有的数据库: 输入: SHOW DATABASES;输出: --------…...

Sui Bridge在测试网上线并推出10万SUI激励计划

是一种为Sui设计的原生桥接协议&#xff0c;专门用于在Sui与其他网络之间桥接资产和数据。今天&#xff0c;Sui Bridge宣布在测试网上线。作为一种原生协议&#xff0c;Sui Bridge能够在Ethereum和Sui之间轻松且安全地转移ETH、wBTC、USDC和USDT&#xff0c;使其成为Sui基础设施…...

Spring系统学习 - Bean的作用域

bean作用域介绍 Spring框架提供了不同的作用域来管理Bean的生命周期和可见性&#xff0c;这对于控制不同类型的组件和处理并发请求尤其重要。 singleton&#xff08;默认&#xff09;&#xff1a; 每个Spring IoC容器只有一个bean实例。当容器创建bean后&#xff0c;它会被缓存…...

贪吃蛇双人模式设计(2)

敲上瘾-CSDN博客控制台程序设置_c语言控制程序窗口大小-CSDN博客贪吃蛇小游戏_贪吃蛇小游戏csdn-CSDN博客 一、功能实现&#xff1a; 玩家1使用↓ → ← ↑按键来操作蛇的方向&#xff0c;使用右Shift键加速&#xff0c;右Ctrl键减速玩家2使用W A S D按键来操作蛇的方向&am…...

mysql什么时候不需要建立索引

WHERE 条件&#xff0c;GROUP BY&#xff0c;ORDER BY 里用不到的字段&#xff0c;索引的价值是快速定位&#xff0c;如果起不到定位的字段通常是不需要创建索引的&#xff0c;因为索引是会占用物理空间的。字段中存在大量重复数据&#xff0c;不需要创建索引&#xff0c;比如性…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

深入剖析AI大模型:大模型时代的 Prompt 工程全解析

今天聊的内容&#xff0c;我认为是AI开发里面非常重要的内容。它在AI开发里无处不在&#xff0c;当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗"&#xff0c;或者让翻译模型 "将这段合同翻译成商务日语" 时&#xff0c;输入的这句话就是 Prompt。…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力

引言&#xff1a; 在人工智能快速发展的浪潮中&#xff0c;快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型&#xff08;LLM&#xff09;。该模型代表着该领域的重大突破&#xff0c;通过独特方式融合思考与非思考…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

ElasticSearch搜索引擎之倒排索引及其底层算法

文章目录 一、搜索引擎1、什么是搜索引擎?2、搜索引擎的分类3、常用的搜索引擎4、搜索引擎的特点二、倒排索引1、简介2、为什么倒排索引不用B+树1.创建时间长,文件大。2.其次,树深,IO次数可怕。3.索引可能会失效。4.精准度差。三. 倒排索引四、算法1、Term Index的算法2、 …...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

优选算法第十二讲:队列 + 宽搜 优先级队列

优选算法第十二讲&#xff1a;队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...

人机融合智能 | “人智交互”跨学科新领域

本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...