一:觀察Device File Explorer
- System目錄下的app目錄
- 同級System目錄下的Priv-app目錄
- Priv-app目錄下存在MediaProvider目錄,裏面有個app,功能是掃描手機中的媒體類文件
二:找到儲存的對應數據庫位置
- data目錄和system同級。6.0以後就看不見了,應該是沒權限,之後動態獲取就好了
- 導出數據庫就可以看到字段結構了
三.加載音樂數據
- 需要拿到數據庫內容就要用四大組件裏的內容解析者ContentResolver,新建對象,通過該對象連接服務器,查詢語言
val resolver= context?.contentResolver
//cursor是查詢的返回類型 前車之鑑 放到主線程卡死 加到子線程中 完成再更新UI適配
val cursor=resolver?.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.SIZE,
MediaStore.Audio.Media.DISPLAY_NAME,//title可能會有編碼問題
MediaStore.Audio.Media.ARTIST),
null,null,null)//查詢感興趣的字段 數組封裝 後三個參數排序什麼的
//打印所有數據
CursorUtil.logCursor(cursor)*/
- 寫個遍歷cursor的方法
object CursorUtil {
fun logCursor(cursor: Cursor?){
cursor?.let {
//將遊標復位
//count是cursor含有的條目個數
//columnCount是每個條目含有的字段
it.moveToPosition(-1)
while (it.moveToNext()){
for (index in 0 until it.columnCount)//每個字段對應的位置[)
{
println("key=${it.getColumnName(index)} --value=${it.getString(index)}")
}
}
}
}
}
- 運行結果,展示一條記錄
四.異步查詢
1. 異步查詢可以開啓新線程,完成後再更新界面,避免堵塞進程,前車之鑑就是這個,剛打開應用一片白,而且還加載專輯圖片,堵的不要不要,運氣不好直接就閃退了。
2. 開啓線程
方法一:使用Thread開啓子線程,並使用Handler回調給主線程,進行線程間通信(相對麻煩,還要解決線程問題)
//開啓一個單獨的線程查詢音樂數據
Thread(object :Runnable{
override fun run() {
val cursor=resolver?.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.SIZE,
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.Media.ARTIST),
null,null,null)
//這裏通過handler的方法將查詢到的cursor傳入主線程
val msg =Message.obtain()//消息池中拿消息
msg.obj=cursor
handler.sendMessage(msg)
}
}).start()
需要一個handler對象實現方法,即使用匿名內部類
/* val handler = @SuppressLint("HandlerLeak") object : Handler(){//os包
//new Handler要複寫其方法,所以用匿名內部類
override fun handleMessage(msg: Message) {//接收消息
msg.let {
val cursor=msg.obj as Cursor
CursorUtil.logCursor(cursor)
}
}
}*/
方法二:安卓提供的繼承AsyncTask類(專門用於處理異步任務)
- initData外創建AudioTask類繼承AsyncTask
//因爲查詢使用的是ContentProvider(內容提供者對應resolver)
//音樂查詢異步任務
class AudioTask:AsyncTask<ContentResolver,Void,Cursor>(){//三個泛型 參數 進度 返回的Result類型
//1.後臺開啓新線程執行任務 相當於new一個新handler
override fun doInBackground(vararg params: ContentResolver?): Cursor ?{//第一個參數是content 傳過來的返回結果是Cursor 我們這邊就一個參數拿出來就好了
val cursor=params[0]?.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.SIZE,
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.Media.ARTIST),
null,null,null)
return cursor
}
//2.後臺任務結果回調到主線程
override fun onPostExecute(result: Cursor?) {
super.onPostExecute(result)
//打印Cursor
CursorUtil.logCursor(result)
}
}
- initData補充,將resolver傳過去
val resolver= context?.contentResolver
AudioTask().execute(resolver)//第二種異步傳遞的參數params可變參數後面取也取第一個就好了()只一個參數
方法三:安卓提供的繼承AsyncQueryHandler類(專門用於處理數據庫的操作)註釋的運用
val resolver= context?.contentResolver
val handler= @SuppressLint("HandlerLeak") object:AsyncQueryHandler(resolver){//token是用於區分的 調用查詢可以加入token 同flag
override fun onQueryComplete(token: Int, cookie: Any?, cursor: Cursor?) {
//查詢完成的回調 只有回調是在主線程中的
//打印數據
CursorUtil.logCursor(cursor)
/* when(token){條目或者非常多 內嵌語句非常多 就可以考慮用cookie傳遞adapter startquery中第二個參數,現在沒適配adapter先寫null
0->adapter1
1->adapter2
}*/
}
}
//開始查詢 如果與n個startquery要加識別token
handler.startQuery(0,null,MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Audio.Media.DATA,
MediaStore.Audio.Media.SIZE,
MediaStore.Audio.Media.DISPLAY_NAME,
MediaStore.Audio.Media.ARTIST),
null,null,null)
start方法對應on方法獲取結果