前言
項目中經(jīng)常要選擇本地照片或者視頻的需求附帽,如果去掃描整個SD卡就太耗時間莲蜘,其實Android系統(tǒng)在啟動時就已經(jīng)把整個設(shè)備中的多媒體文件信息(文件名璃饱,類型脊凰,大小等)都存到了數(shù)據(jù)庫咽袜,然后提供了ContentPrivider這個API來管理這個數(shù)據(jù)庫雏掠,我們可以利用ContentPrivider來獲取所有的照片和視頻下愈。
ContentPrivider初識
先看下管理的的數(shù)據(jù)庫在哪
data/data/目錄下:有很多這種文件夾(日歷矫钓,聯(lián)系人毡琉,下載管理铁瞒,多媒體等)
我們需要的照片和視頻就在media下面,進去看看
進去找到database然后打開external.db桅滋,就可以看到多張表(音頻慧耍,文件,Log丐谋,圖像芍碧,視頻等)
照片相冊
那么獲取照片直接通過 ContentProvider讀取Images這個數(shù)據(jù)庫就OK了,這里開啟工作線程讀取所有.jpeg和.png的圖片号俐,附上代碼段:
/**
* 讀取手機中所有圖片信息
*/
private void getAllPhotoInfo() {
new Thread(new Runnable() {
@Override
public void run() {
List<MediaBean> mediaBeen = new ArrayList<>();
HashMap<String,List<MediaBean>> allPhotosTemp = new HashMap<>();//所有照片
Uri mImageUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String[] projImage = { MediaStore.Images.Media._ID
, MediaStore.Images.Media.DATA
,MediaStore.Images.Media.SIZE
,MediaStore.Images.Media.DISPLAY_NAME};
Cursor mCursor = getContentResolver().query(mImageUri,
projImage,
MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?",
new String[]{"image/jpeg", "image/png"},
MediaStore.Images.Media.DATE_MODIFIED+" desc");
if(mCursor!=null){
while (mCursor.moveToNext()) {
// 獲取圖片的路徑
String path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DATA));
int size = mCursor.getInt(mCursor.getColumnIndex(MediaStore.Images.Media.SIZE))/1024;
String displayName = mCursor.getString(mCursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));
//用于展示相冊初始化界面
mediaBeen.add(new MediaBean(MediaBean.Type.Image,path,size,displayName));
// 獲取該圖片的父路徑名
String dirPath = new File(path).getParentFile().getAbsolutePath();
//存儲對應關(guān)系
if (allPhotosTemp.containsKey(dirPath)) {
List<MediaBean> data = allPhotosTemp.get(dirPath);
data.add(new MediaBean(MediaBean.Type.Image,path,size,displayName));
continue;
} else {
List<MediaBean> data = new ArrayList<>();
data.add(new MediaBean(MediaBean.Type.Image,path,size,displayName));
allPhotosTemp.put(dirPath,data);
}
}
mCursor.close();
}
//更新界面
runOnUiThread(new Runnable() {
@Override
public void run() {
//...
}
});
}
}).start();
}
有四點需要注意:
- MediaBean是文件實體類泌豆,代碼就不貼了
- 照片集合不是放在List<MediaBean>這樣存儲的,而是HashMap<String,List<MediaBean>>吏饿,這樣把圖片已文件夾(也就是父目錄)分類踪危,更節(jié)省內(nèi)存蔬浙,其次支持相冊展示不同文件夾的照片
- 貌似沒辦法獲取當前設(shè)備的拍照默認路徑,有的設(shè)備是/DCIM贞远,有的是/100andro還有/camera畴博,那相冊就默認展示最近所有照片吧。然后給用戶列出一個文件夾列表讓他選蓝仲,這時可以把這幾個文件夾放到最前面展示绎晃,算是小優(yōu)化吧。
- 系統(tǒng)會時刻檢測數(shù)據(jù)變化杂曲,有新的照片這個數(shù)據(jù)庫會自動更新庶艾,不需干預。
看下相冊效果
視頻相冊
獲取視頻文件和上面基本一樣擎勘,不過改下查詢條件就行了咱揍,實際中有個問題:視頻封面的獲取。
首先視頻封面縮略圖在這個videothumbnails數(shù)據(jù)庫棚饵,照片縮略圖在thumbnails煤裙,對應到本地SD卡就是在sdcard/DCIM/.thumbnails/文件夾(有的設(shè)備可能不同)
PS:這個文件夾是隱藏的,so你知道你的手機為何存儲空間越來越小了吧噪漾,拍的照片縮略圖全在這兒硼砰。。欣硼。非常非常多
- 實際中發(fā)現(xiàn)讀取不到新錄制的視頻封面题翰,需要手動調(diào)用一個方法,來生成這個封面然后才能在videothumbnails讀取到:
參考:http://stackoverflow.com/questions/27903264/how-to-get-the-video-thumbnail-path-and-not-the-bitmap
//videoId是這個視頻文件在數(shù)據(jù)庫的ID
MediaStore.Video.Thumbnails.getThumbnail(getContentResolver(), videoId, MediaStore.Video.Thumbnails.MICRO_KIND, null);
- 并且這里封面和視頻不在一個數(shù)據(jù)庫诈胜,需要在兩個cursor來讀取
我這里獲取整個SD的mp4格式視頻豹障,代碼段如下:
/**
* 獲取手機中所有視頻的信息
*/
private void getAllVideoInfos(){
new Thread(new Runnable() {
@Override
public void run() {
HashMap<String,List<MediaBean>> allPhotosTemp = new HashMap<>();//所有照片
Uri mImageUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
String[] proj = { MediaStore.Video.Thumbnails._ID
, MediaStore.Video.Thumbnails.DATA
,MediaStore.Video.Media.DURATION
,MediaStore.Video.Media.SIZE
,MediaStore.Video.Media.DISPLAY_NAME
,MediaStore.Video.Media.DATE_MODIFIED};
Cursor mCursor = getContentResolver().query(mImageUri,
proj,
MediaStore.Video.Media.MIME_TYPE + "=?",
new String[]{"video/mp4"},
MediaStore.Video.Media.DATE_MODIFIED+" desc");
if(mCursor!=null){
while (mCursor.moveToNext()) {
// 獲取視頻的路徑
int videoId = mCursor.getInt(mCursor.getColumnIndex(MediaStore.Video.Media._ID));
String path = mCursor.getString(mCursor.getColumnIndex(MediaStore.Video.Media.DATA));
int duration = mCursor.getInt(mCursor.getColumnIndex(MediaStore.Video.Media.DURATION));
long size = mCursor.getLong(mCursor.getColumnIndex(MediaStore.Video.Media.SIZE))/1024; //單位kb
if(size<0){
//某些設(shè)備獲取size<0,直接計算
Log.e("dml","this video size < 0 " + path);
size = new File(path).length()/1024;
}
String displayName = mCursor.getString(mCursor.getColumnIndex(MediaStore.Video.Media.DISPLAY_NAME));
long modifyTime = mCursor.getLong(mCursor.getColumnIndex(MediaStore.Video.Media.DATE_MODIFIED));//暫未用到
//提前生成縮略圖焦匈,再獲妊:http://stackoverflow.com/questions/27903264/how-to-get-the-video-thumbnail-path-and-not-the-bitmap
MediaStore.Video.Thumbnails.getThumbnail(getContentResolver(), videoId, MediaStore.Video.Thumbnails.MICRO_KIND, null);
String[] projection = { MediaStore.Video.Thumbnails._ID, MediaStore.Video.Thumbnails.DATA};
Cursor cursor = getContentResolver().query(MediaStore.Video.Thumbnails.EXTERNAL_CONTENT_URI
, projection
, MediaStore.Video.Thumbnails.VIDEO_ID + "=?"
, new String[]{videoId+""}
, null);
String thumbPath = "";
while (cursor.moveToNext()){
thumbPath = cursor.getString(cursor.getColumnIndex(MediaStore.Video.Thumbnails.DATA));
}
cursor.close();
// 獲取該視頻的父路徑名
String dirPath = new File(path).getParentFile().getAbsolutePath();
//存儲對應關(guān)系
if (allPhotosTemp.containsKey(dirPath)) {
List<MediaBean> data = allPhotosTemp.get(dirPath);
data.add(new MediaBean(MediaBean.Type.Video,path,thumbPath,duration,size,displayName));
continue;
} else {
List<MediaBean> data = new ArrayList<>();
data.add(new MediaBean(MediaBean.Type.Video,path,thumbPath,duration,size,displayName));
allPhotosTemp.put(dirPath,data);
}
}
mCursor.close();
}
//更新界面
runOnUiThread(new Runnable() {
@Override
public void run() {
//...
}
});
}
}).start();
}
后記
其實Android已經(jīng)提供叫做CursorLoader的API做這個事情,不需要手動new 工作線程缓熟,使用起來很簡單有需要可以對上面代碼改造累魔。