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

 这里需要注意的是当我们从设备导出数据库的时候需要把.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);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(MusicRecordDao.CREATE_TABLE_SONGS);
    }
    @Override
    public 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); // 初始播放次数为0
        db.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中,进行数据库的保存:
                @Override
                public 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
    @Override
    public 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);
        }
    }
    @Override
    public 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()));
        }
    }
    @Override
    public int getItemViewType(int position) {
        return position == 0 ? VIEW_TYPE_HEADER : VIEW_TYPE_ITEM;
    }
    @Override
    public 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">
    <TextView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        android:text="歌曲名"
        android:gravity="center"
        android:textSize="16sp"
        android:textStyle="bold" />
    <TextView
        android: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">
    <TextView
        android: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" />
    <TextView
        android: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>
 
这样就实现了上面列表的效果。下面是时间戳记录和按月记录播放时长的思路放在下一篇去讲.


















