从 Cursor 覆盖或更新 Android 播放列表数据库

Overwrite or update Android playlist database from Cursor

我正在编写一个小应用程序,它可以检索 Android 创建的播放列表中的所有项目,并能够按特定顺序对列表进行排序。我想要做的是将这种类型实际保存回数据库,或更新播放列表文件。作为 Android 开发的新手,我不确定正确的方法是什么。

这是我的代码:

import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Spinner;

import java.util.List;

public class PlaylistSongActivity extends AppCompatActivity {
    final String SONG_ID_KEY  = MediaStore.Audio.AudioColumns._ID;
    final String SONG_ARTIST_KEY  = MediaStore.Audio.AudioColumns.ARTIST;
    final String SONG_ALBUM_KEY = MediaStore.Audio.AudioColumns.ALBUM;
    final String SONG_TITLE_KEY  = MediaStore.Audio.AudioColumns.TITLE;
    final String SONG_TRACK_NO_KEY = MediaStore.Audio.AudioColumns.TRACK;
    final String CASE_INSENTIVE = " COLLATE NOCASE ";
    final String[] PLAYLIST_QUERY_COLUMNS = {SONG_ID_KEY, SONG_ARTIST_KEY, SONG_TITLE_KEY, SONG_ALBUM_KEY, SONG_TRACK_NO_KEY};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_playlist);
        // Spinner values
        final Spinner playlistSettingsDropdown = (Spinner) findViewById(R.id.playlistSettingsDropdown);
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.playlist_settings_array, android.R.layout.simple_spinner_item);
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
        playlistSettingsDropdown.setAdapter(adapter);
        playlistSettingsDropdown.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
                sortGrid(pos);
            }

            public void setId(int pos) {
                playlistSettingsDropdown.setSelection(pos);
            }

            public void onNothingSelected(AdapterView<?> parent) {
                // Another interface callback
            }
        });

        // Song grid
        ListView playlistSongsView = getSongGrid();
        final Cursor playlistSongs = createPlaylistCursor(null);
        PlaylistSongCursor playlistSongsAdapter = new PlaylistSongCursor(playlistSongsView.getContext(), playlistSongs);
        playlistSongsView.setAdapter((playlistSongsAdapter));
    }

    public void sortGrid(int sortType) {
        ListView songGrid = getSongGrid();
        PlaylistSongCursor songGridAdapter = (PlaylistSongCursor) songGrid.getAdapter();
        String sort = SONG_ARTIST_KEY + CASE_INSENTIVE + " ASC," +
                SONG_ALBUM_KEY + CASE_INSENTIVE + " ASC," +
                SONG_TRACK_NO_KEY + " ASC";
        switch (sortType) {
            case 1:
                sort = SONG_ARTIST_KEY + CASE_INSENTIVE + " DESC," +
                        SONG_ALBUM_KEY + CASE_INSENTIVE + " DESC," +
                        SONG_TRACK_NO_KEY + " DESC";
        }
        songGridAdapter.changeCursor(createPlaylistCursor(sort));
    }

    public ListView getSongGrid() {
        return (ListView) findViewById((R.id.playlistItems));
    }

    public Uri getPlaylistUri() {
        int playlistId = getIntent().getExtras().getInt("playlistId");
        return MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
    }

    public Cursor createPlaylistCursor(String sort) {
        return getContentResolver().query(getPlaylistUri(), null, null, null, sort);
    }
}

所以我从我传递的意图中获取列表的 ID,从 MediaStore 包中获取它的 URI,然后使用 URI 从使用 ContentProvider 的查询中获取游标...这所有作品,播放列表歌曲显示得很好,我的排序也很好(尽管,我欢迎对我正在做的事情提出任何批评)。问题是我如何使用光标保存回数据库,以便它持续到其他音乐应用程序? (BlackPlayer 具有按艺术家排序 - 专辑选项的此功能)

我想了几个方法,但我不太确定如何执行它们:

有没有人有任何见解?

更新

我开始沿着使用 bulkInsert 的路线前进,但它似乎没有将 ContentValues[] 插入我的数据库...这肯定会删除我的行,而我的 retVal 包含要插入的项目数,它只是不插入...

    myButton.setOnClickListener(new AdapterView.OnClickListener() {
        public void onClick(View view) {
            ListView songGrid = getSongGrid();
            PlaylistSongCursor songGridAdapter = (PlaylistSongCursor) songGrid.getAdapter();
            Cursor myCursor = songGridAdapter.getCursor();
            ContentValues[] retVal = new ContentValues[myCursor.getCount()];
            ContentValues map;
            int i = 0;
            if(myCursor.moveToFirst()) {
                do {
                    map = new ContentValues();
                    DatabaseUtils.cursorRowToContentValues(myCursor, map);
                    retVal[i++] = map;
                } while(myCursor.moveToNext());
            }
            // Empties db
            getContentResolver().delete(getPlaylistUri(), null, null);
            // Logs 2 (as that's how many items are in my list)
            Log.d("BLA", String.valueOf(retVal.length));
            final int retInt = getContentResolver().bulkInsert(getPlaylistUri(), retVal);
            // Logs 0
            Log.d("DONE", String.valueOf(retInt));
        }
    });

查看 ContentProvider 的官方文档。基本上 ContentProvider 隐藏了数据库,因此您可以使用 URI 与其通信(如果需要,还可以使用 ContentValues),但不能直接与数据库通信。

//Create empty values
ContentValues values= new ContentValues();

//put all you want to update
values.put(MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER, sort);

//call update method on ContentResolver
int rows = getContentResolver().update(
    getPlaylistUri(), //uri to modify
    values, //new values for 
    MediaStore.Audio.Playlists.Members.PLAYLIST_ID + "=?", //selection clause 
    new String[] {String.valueOf(playlistId)} //selection args
);

如果您不指定排序参数,我假设 query 应该根据此列对您的播放列表进行排序

return getContentResolver().query(getPlaylistUri(), 
                      PLAYLIST_QUERY_COLUMNS, null, null, null);

根据 ContentResolver 文档:

String: How to order the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself). Passing null will use the default sort order, which may be unordered.

还有另一种选择——创建你自己的数据库,它将存储带有 order by 子句的播放列表的 ID,你可以自己维护(即你的项目中关于播放列表或排序的逻辑更复杂等)。

编辑

您确定 myCursor(映射到 retVal)包含插入所需的所有数据吗?您应该确保 所有必需的列都保留在您要根据 URI 插入的每一行的内容值中。您检索到 (MediaStore.Audio.Playlists.Members) 个 URI,其中包含您的光标所需的列。但是您 select 编辑的专栏来自继承 MediaStore.Audio.AudioColumnsquery 没问题,因为 ContentProvider 可能只是加入一些音频 table 来为您获取这些音频。但是现在您想将数据(根据 URI)插入 MediaStore.Audio.Playlists.Members,但您没有指定它的任何列,因为您得到的只是来自链接 table 的数据(AudioColumns).

  • query - select 任何你想要的select 来自提供的列
  • update - 根据 selection
  • 放置要更新的行的值
  • insert - 为 table
  • 提供所有必需的值

最后一件事,不幸的是你不能强迫第 3 方供应商尊重 DEFAULT_SORT_ORDER。您可以尝试清除缓存,但我怀疑它是否有用。

qbeck, I was able to figure this one out. I was missing some key things... in order to save back to the playlist's Uri, I had to make sure it used the same columns its schema has... looking at the MediaStore.Audio.Playlists.Members.* attributes, I could sort of glean what the table's schema's actual fields were... Android Studio made these keys in bold, but looking back on it now, I think that's what this page 告诉我,我得到了难以置信的帮助。

我使用此信息确定我需要更新 PLAY_ORDER value, so I first started down the route of ContentProvider.update, but for each row, this took quite some time for large lists, and I reached some sort of Cursor undefined error. I then tried deleting all rows in the DB and doing a ContentProvider.bulkInsert,这要快得多并且完成了我需要它做的事情...现在我的列表按此排序顺序显示在其他音乐应用。

getSaveButton().setOnClickListener(new AdapterView.OnClickListener() {
    public void onClick(View view) {
        Cursor myCursor = createPlaylistCursor(sort);
        int i = 0;
        if(myCursor.moveToFirst()) {
            Uri playListUri = getPlaylistUri();
            ContentValues[] values = new ContentValues[myCursor.getCount()];
            ContentResolver contentResolver = getContentResolver();
            do {
                ContentValues map = new ContentValues();
                map.put(PLAY_ORDER_KEY, Long.valueOf(i + 1));
                map.put(AUDIO_ID_KEY, myCursor.getInt(myCursor.getColumnIndexOrThrow(AUDIO_ID_KEY)));
                values[i++] = map;
            } while(myCursor.moveToNext());
            contentResolver.delete(playListUri, null, null);
            contentResolver.bulkInsert(playListUri, values);
            Log.d("BLA", "done");
        }
    }
});