MediaProvider使用 SQLite 數(shù)據(jù)庫(kù)存儲(chǔ)圖片、視頻、音頻等多媒體文件的信息,供視頻播放器、音樂(lè)播放器、圖庫(kù)使用。提供了基本的增刪改查等相關(guān)方法。路徑如下:
/packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
??其中包含以下內(nèi)部類(lèi):
????DatabaseHelper——對(duì)于一個(gè)特殊數(shù)據(jù)庫(kù)的包裝類(lèi),用來(lái)管理數(shù)據(jù)的創(chuàng)建和版本更新,繼承SQLiteOpenHelper
????GetTableAndWhereOutParameter——靜態(tài)類(lèi),獲取相關(guān)的參數(shù)
????ScannerClient——靜態(tài)類(lèi),繼承MediaScannerConnectionClient
????ThumbData——方便對(duì)變量的操作
??下面依據(jù)對(duì)數(shù)據(jù)庫(kù)的操作進(jìn)行分析。
1. 創(chuàng)建
首先在MediaProvider的onCreate方法中分別對(duì)內(nèi)部存儲(chǔ)和外部存儲(chǔ)進(jìn)行鏈接數(shù)據(jù)庫(kù):
//綁定內(nèi)部存儲(chǔ)數(shù)據(jù)庫(kù)
attachVolume(INTERNAL_VOLUME);
...
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
//綁定外部存儲(chǔ)數(shù)據(jù)庫(kù)
attachVolume(EXTERNAL_VOLUME);
}
為內(nèi)部存儲(chǔ)和外部存儲(chǔ)進(jìn)行創(chuàng)建數(shù)據(jù)庫(kù),如果此存儲(chǔ)卷已經(jīng)鏈接上了,那么什么也不做,否則就查詢存儲(chǔ)卷的id并且建立對(duì)應(yīng)的數(shù)據(jù)庫(kù)。接下來(lái)分析attachVolume方法:
private Uri attachVolume(String volume) {
... ...
// Update paths to reflect currently mounted volumes
updateStoragePaths();
DatabaseHelper helper = null;
synchronized (mDatabases) {
helper = mDatabases.get(volume);
//判斷是否已經(jīng)attached過(guò)了
if (helper != null) {
if (EXTERNAL_VOLUME.equals(volume)) {
//確保默認(rèn)的文件夾已經(jīng)被創(chuàng)建在掛載的主要存儲(chǔ)設(shè)備上,
//對(duì)每個(gè)存儲(chǔ)卷只做一次這種操作,所以當(dāng)用戶手動(dòng)刪除時(shí)不會(huì)打擾
ensureDefaultFolders(helper, helper.getWritableDatabase());
}
return Uri.parse("content://media/" + volume);
}
Context context = getContext();
if (INTERNAL_VOLUME.equals(volume)) {
//如果是內(nèi)部存儲(chǔ)則直接實(shí)例化DatabaseHelper,傳參,之后調(diào)用DatabaseHelper的方法
helper = new DatabaseHelper(context, INTERNAL_DATABASE_NAME, true,
false, mObjectRemovedCallback);
} else if (EXTERNAL_VOLUME.equals(volume)) {
//如果是外部存儲(chǔ)的操作
final VolumeInfo vol = mStorageManager.getPrimaryPhysicalVolume();
if (vol != null) {
final StorageVolume actualVolume = mStorageManager.getPrimaryVolume();
final int volumeId = actualVolume.getFatVolumeId();
// Must check for failure!
// If the volume is not (yet) mounted, this will create a new
// external-ffffffff.db database instead of the one we expect. Then, if
// android.process.media is later killed and respawned, the real external
// database will be attached, containing stale records, or worse, be empty.
//數(shù)據(jù)庫(kù)都是以類(lèi)似 external-ffffffff.db 的形式命名的,
//后面的 8 個(gè) 16 進(jìn)制字符是該 SD 卡 FAT 分區(qū)的 Volume ID。
//該 ID 是分區(qū)時(shí)決定的,只有重新分區(qū)或者手動(dòng)改變才會(huì)更改,
//可以防止插入不同 SD 卡時(shí)數(shù)據(jù)庫(kù)沖突。
if (volumeId == -1) {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
//已經(jīng)掛載但是sd卡是只讀狀態(tài)
} else {
//還沒(méi)有掛載
}
}
// generate database name based on volume ID
//根據(jù)volume ID設(shè)置數(shù)據(jù)庫(kù)的名稱
String dbName = "external-" + Integer.toHexString(volumeId) + ".db";
//通過(guò)構(gòu)造方法去實(shí)現(xiàn)創(chuàng)建數(shù)據(jù)庫(kù)的過(guò)程
helper = new DatabaseHelper(context, dbName, false,
false, mObjectRemovedCallback);
mVolumeId = volumeId;
} else {
//將之前的數(shù)據(jù)庫(kù)名字進(jìn)行轉(zhuǎn)換
// external database name should be EXTERNAL_DATABASE_NAME
// however earlier releases used the external-XXXXXXXX.db naming
// for devices without removable storage, and in that case we need to convert
// to this new convention
... ...
//根據(jù)之前轉(zhuǎn)換的數(shù)據(jù)庫(kù)名,創(chuàng)建數(shù)據(jù)庫(kù)
helper = new DatabaseHelper(context, dbFile.getName(), false,
false, mObjectRemovedCallback);
}
} else {
throw new IllegalArgumentException("There is no volume named " + volume);
}
//標(biāo)識(shí)已經(jīng)創(chuàng)建過(guò)了數(shù)據(jù)庫(kù)
mDatabases.put(volume, helper);
if (!helper.mInternal) {
// clean up stray album art files: delete every file not in the database
File[] files = new File(mExternalStoragePaths[0],
ALBUM_THUMB_FOLDER).listFiles();
HashSet<String> fileSet = new HashSet();
for (int i = 0; files != null && i < files.length; i++) {
fileSet.add(files[i].getPath());
}
Cursor cursor = query(MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Audio.Albums.ALBUM_ART }, null, null, null);
try {
while (cursor != null && cursor.moveToNext()) {
fileSet.remove(cursor.getString(0));
}
} finally {
IoUtils.closeQuietly(cursor);
}
Iterator<String> iterator = fileSet.iterator();
while (iterator.hasNext()) {
String filename = iterator.next();
if (LOCAL_LOGV) Log.v(TAG, "deleting obsolete album art " + filename);
new File(filename).delete();
}
}
}
if (EXTERNAL_VOLUME.equals(volume)) {
//給外部存儲(chǔ)創(chuàng)建默認(rèn)的文件夾
ensureDefaultFolders(helper, helper.getWritableDatabase());
}
return Uri.parse("content://media/" + volume);
}
下面就是分析創(chuàng)建數(shù)據(jù)庫(kù)的源頭DatabaseHelper:
@Override
public void onCreate(final SQLiteDatabase db) {
//在此方法中對(duì)63版本以下的都會(huì)新建數(shù)據(jù)庫(kù)
updateDatabase(mContext, db, mInternal, 0, getDatabaseVersion(mContext));
}
@Override
public void onUpgrade(final SQLiteDatabase db, final int oldV, final int newV) {
//對(duì)數(shù)據(jù)庫(kù)進(jìn)行更新
mUpgradeAttempted = true;
updateDatabase(mContext, db, mInternal, oldV, newV);
}
現(xiàn)在已經(jīng)找到創(chuàng)建數(shù)據(jù)庫(kù)的方法updateDatabase,現(xiàn)在大致分析一下此方法:
private static void updateDatabase(Context context, SQLiteDatabase db, boolean internal,
int fromVersion, int toVersion) {
// sanity checks
int dbversion = getDatabaseVersion(context);
//對(duì)數(shù)據(jù)庫(kù)的版本進(jìn)行判斷
... ...
long startTime = SystemClock.currentTimeMicro();
//對(duì)傳入的數(shù)據(jù)庫(kù)版本進(jìn)行判斷,如果小于63,或者在84到89,92到94之間的,
//都會(huì)去創(chuàng)建數(shù)據(jù)庫(kù)
if (fromVersion < 63 || (fromVersion >= 84 && fromVersion <= 89) ||
(fromVersion >= 92 && fromVersion <= 94)) {
//下面就是執(zhí)行具體的sqlite CRATE語(yǔ)句,創(chuàng)建對(duì)應(yīng)的表
... ...
}
//下面也是對(duì)版本判斷之后進(jìn)行相應(yīng)操作
... ...
//檢查audio_meta的_data值是否是不同的,如果不同就刪除audio_meta,
//在掃描的時(shí)候從新創(chuàng)建
sanityCheck(db, fromVersion);
long elapsedSeconds = (SystemClock.currentTimeMicro() - startTime) / 1000000;
}
至此,對(duì)于數(shù)據(jù)庫(kù)的創(chuàng)建已經(jīng)分析完畢。
2 更新
@Override
public int update(Uri uri, ContentValues initialValues, String userWhere,
String[] whereArgs) {
//將uri進(jìn)行轉(zhuǎn)換成合適的格式,去除標(biāo)準(zhǔn)化
uri = safeUncanonicalize(uri);
int count;
//對(duì)uri進(jìn)行匹配
int match = URI_MATCHER.match(uri);
//返回查詢的對(duì)應(yīng)uri的數(shù)據(jù)庫(kù)幫助類(lèi)
DatabaseHelper helper = getDatabaseForUri(uri);
//記錄更新的次數(shù)
helper.mNumUpdates++;
//通過(guò)可寫(xiě)的方式獲得數(shù)據(jù)庫(kù)實(shí)例
SQLiteDatabase db = helper.getWritableDatabase();
String genre = null;
if (initialValues != null) {
//獲取流派的信息,然后刪除掉
genre = initialValues.getAsString(Audio.AudioColumns.GENRE);
initialValues.remove(Audio.AudioColumns.GENRE);
}
// special case renaming directories via MTP.
// in this case we must update all paths in the database with
// the directory name as a prefix
... ...
//根據(jù)匹配的uri進(jìn)行相應(yīng)的操作
switch (match) {
case AUDIO_MEDIA_ID:
//更新音樂(lè)人和專輯字段。首先從緩存中判斷是否有值,如果有直接用緩存中的
//數(shù)據(jù),如果沒(méi)有再?gòu)臄?shù)據(jù)庫(kù)中查詢是否有對(duì)應(yīng)的信息,如果有則更新,
//如果沒(méi)有插入這條數(shù)據(jù).接下來(lái)的操作是增加更新次數(shù),并更新流派
... ...
case VIDEO_MEDIA_ID:
//更新視頻,并且發(fā)出生成略縮圖請(qǐng)求
... ...
case AUDIO_PLAYLISTS_ID_MEMBERS_ID:
//更新播放列表數(shù)據(jù)
... ...
}
... ...
}
至此,更新操作已完成。
3 插入
關(guān)于插入,有兩個(gè)方法插入,一個(gè)是大量的插入bulkInsert方法傳入的是ContentValues數(shù)組;一個(gè)是insert,傳入的是單一個(gè)ContentValues。下面分別分析:
@Override
public int bulkInsert(Uri uri, ContentValues values[]) {
//首先對(duì)傳入的Uri進(jìn)行匹配
int match = URI_MATCHER.match(uri);
if (match == VOLUMES) {
//如果是匹配的是存儲(chǔ)卷,則直接調(diào)用父類(lèi)的方法,進(jìn)行循環(huán)插入
return super.bulkInsert(uri, values);
}
//對(duì)DatabaseHelper和SQLiteDatabase的初始化
DatabaseHelper helper = getDatabaseForUri(uri);
if (helper == null) {
throw new UnsupportedOperationException(
"Unknown URI: " + uri);
}
SQLiteDatabase db = helper.getWritableDatabase();
if (db == null) {
throw new IllegalStateException("Couldn't open database for " + uri);
}
if (match == AUDIO_PLAYLISTS_ID || match == AUDIO_PLAYLISTS_ID_MEMBERS) {
//插入播放列表的數(shù)據(jù),在playlistBulkInsert中是開(kāi)啟的事務(wù)進(jìn)行插入
return playlistBulkInsert(db, uri, values);
} else if (match == MTP_OBJECT_REFERENCES) {
//將MTP對(duì)象的ID轉(zhuǎn)換成音頻的ID,最終也是調(diào)用到playlistBulkInsert
int handle = Integer.parseInt(uri.getPathSegments().get(2));
return setObjectReferences(helper, db, handle, values);
}
//如果不滿足上述的條件,則開(kāi)啟事務(wù)進(jìn)行插入其他的數(shù)據(jù)
db.beginTransaction();
ArrayList<Long> notifyRowIds = new ArrayList<Long>();
int numInserted = 0;
try {
int len = values.length;
for (int i = 0; i < len; i++) {
if (values[i] != null) {
//循環(huán)調(diào)用insertInternal去插入相關(guān)的數(shù)據(jù)
insertInternal(uri, match, values[i], notifyRowIds);
}
}
numInserted = len;
db.setTransactionSuccessful();
} finally {
//結(jié)束事務(wù)
db.endTransaction();
}
// Notify MTP (outside of successful transaction)
if (uri != null) {
if (uri.toString().startsWith("content://media/external/")) {
notifyMtp(notifyRowIds);
}
}
//通知更新
getContext().getContentResolver().notifyChange(uri, null);
return numInserted;
}
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
int match = URI_MATCHER.match(uri);
ArrayList<Long> notifyRowIds = new ArrayList<Long>();
//只是調(diào)用insertInternal進(jìn)行插入
Uri newUri = insertInternal(uri, match, initialValues, notifyRowIds);
if (uri != null) {
if (uri.toString().startsWith("content://media/external/")) {
notifyMtp(notifyRowIds);
}
}
// do not signal notification for MTP objects.
// we will signal instead after file transfer is successful.
if (newUri != null && match != MTP_OBJECTS) {
getContext().getContentResolver().notifyChange(uri, null);
}
return newUri;
}
insertInternal方法比較簡(jiǎn)單,但是類(lèi)別較多,暫時(shí)不做分析。
4 刪除
@Override
public int delete(Uri uri, String userWhere, String[] whereArgs) {
uri = safeUncanonicalize(uri);
int count;
int match = URI_MATCHER.match(uri);
// handle MEDIA_SCANNER before calling getDatabaseForUri()
//因?yàn)槿绻ヅ涞膗ri是掃描過(guò)程中的,此時(shí)uri直接通過(guò)getDatabaseForUri獲取不到
//數(shù)據(jù)庫(kù),需要對(duì)uri進(jìn)行重新拼裝
if (match == MEDIA_SCANNER) {
if (mMediaScannerVolume == null) {
return 0;
}
DatabaseHelper database = getDatabaseForUri(
Uri.parse("content://media/" + mMediaScannerVolume + "/audio"));
if (database == null) {
Log.w(TAG, "no database for scanned volume " + mMediaScannerVolume);
} else {
database.mScanStopTime = SystemClock.currentTimeMicro();
String msg = dump(database, false);
//刪除掉在數(shù)據(jù)庫(kù)的log表記錄
logToDb(database.getWritableDatabase(), msg);
}
mMediaScannerVolume = null;
//因?yàn)橹簧婕暗?行,所以返回值是1
return 1;
}
if (match == VOLUMES_ID) {
//對(duì)外部存儲(chǔ)設(shè)備進(jìn)行關(guān)閉數(shù)據(jù)庫(kù)的操作
detachVolume(uri);
count = 1;
} else if (match == MTP_CONNECTED) {
synchronized (mMtpServiceConnection) {
if (mMtpService != null) {
// MTP has disconnected, so release our connection to MtpService
getContext().unbindService(mMtpServiceConnection);
count = 1;
// mMtpServiceConnection.onServiceDisconnected might not get called,
// so set mMtpService = null here
mMtpService = null;
} else {
count = 0;
}
}
} else {
final String volumeName = getVolumeName(uri);
//初始化DatabaseHelper和SQLiteDatabase
... ...
synchronized (sGetTableAndWhereParam) {
//拼裝字段
getTableAndWhere(uri, match, userWhere, sGetTableAndWhereParam);
if (sGetTableAndWhereParam.table.equals("files")) {
String deleteparam =
uri.getQueryParameter(MediaStore.PARAM_DELETE_DATA);
if (deleteparam == null || ! deleteparam.equals("false")) {
database.mNumQueries++;
Cursor c = db.query(sGetTableAndWhereParam.table,
sMediaTypeDataId,
sGetTableAndWhereParam.where, whereArgs, null, null, null);
String [] idvalue = new String[] { "" };
String [] playlistvalues = new String[] { "", "" };
try {
while (c.moveToNext()) {
final int mediaType = c.getInt(0);
final String data = c.getString(1);
final long id = c.getLong(2);
if (mediaType == FileColumns.MEDIA_TYPE_IMAGE) {
//判斷是圖片類(lèi)型,直接刪除源文件
deleteIfAllowed(uri, data);
MediaDocumentsProvider.onMediaStoreDelete(
getContext(),volumeName,
FileColumns.MEDIA_TYPE_IMAGE, id);
idvalue[0] = String.valueOf(id);
database.mNumQueries++;
//查詢略縮圖文件并刪除
Cursor cc = db.query("thumbnails", sDataOnlyColumn,
"image_id=?", idvalue, null, null, null);
try {
while (cc.moveToNext()) {
deleteIfAllowed(uri, cc.getString(0));
}
database.mNumDeletes++;
//刪除數(shù)據(jù)庫(kù)中的信息
db.delete("thumbnails", "image_id=?", idvalue);
} finally {
IoUtils.closeQuietly(cc);
}
} else if (mediaType == FileColumns.MEDIA_TYPE_VIDEO) {
//如果是視頻文件,直接刪除源文件
deleteIfAllowed(uri, data);
MediaDocumentsProvider.onMediaStoreDelete(
getContext(),volumeName,
FileColumns.MEDIA_TYPE_VIDEO, id);
} else if (mediaType == FileColumns.MEDIA_TYPE_AUDIO) {
//如果是音頻文件并且判斷是否是外部存儲(chǔ)
if (!database.mInternal) {
MediaDocumentsProvider.onMediaStoreDelete(
getContext(),volumeName,
FileColumns.MEDIA_TYPE_AUDIO, id);
idvalue[0] = String.valueOf(id);
database.mNumDeletes += 2; // also count the one below
//刪除流派信息
db.delete("audio_genres_map","audio_id=?",idvalue);
// for each playlist that the item appears in, move
// all the items behind it forward by one
Cursor cc = db.query("audio_playlists_map",
sPlaylistIdPlayOrder,
"audio_id=?", idvalue, null, null, null);
try {
while (cc.moveToNext()) {
playlistvalues[0] = "" + cc.getLong(0);
playlistvalues[1] = "" + cc.getInt(1);
database.mNumUpdates++;
//刪除對(duì)應(yīng)播放列表信息
db.execSQL("UPDATE audio_playlists_map" +
" SET play_order=play_order-1" +
" WHERE playlist_id=? AND play_order>?",
playlistvalues);
}
db.delete("audio_playlists_map", "audio_id=?", idvalue);
} finally {
IoUtils.closeQuietly(cc);
}
}
} else if (mediaType == FileColumns.MEDIA_TYPE_PLAYLIST) {
// TODO, maybe: remove the audio_playlists_cleanup trigger and
// implement functionality here (clean up the playlist map)
}
}
} finally {
IoUtils.closeQuietly(c);
}
}
}
//對(duì)其他的匹配類(lèi)型進(jìn)行刪除
switch (match) {
//刪除MTP,流派信息,視頻文件的略縮圖
... ...
}
// Since there are multiple Uris that can refer to the same files
// and deletes can affect other objects in storage (like subdirectories
// or playlists) we will notify a change on the entire volume to make
// sure no listeners miss the notification.
Uri notifyUri = Uri.parse("content://" + MediaStore.AUTHORITY + "/" + volumeName);
getContext().getContentResolver().notifyChange(notifyUri, null);
}
}
return count;
}
5 查詢
public Cursor query(Uri uri, String[] projectionIn, String selection,
String[] selectionArgs, String sort) {
uri = safeUncanonicalize(uri);
int table = URI_MATCHER.match(uri);
List<String> prependArgs = new ArrayList<String>();
// handle MEDIA_SCANNER before calling getDatabaseForUri()
if (table == MEDIA_SCANNER) {
if (mMediaScannerVolume == null) {
return null;
} else {
// create a cursor to return volume currently being scanned by the media scanner
MatrixCursor c = new MatrixCursor(
new String[] {MediaStore.MEDIA_SCANNER_VOLUME});
c.addRow(new String[] {mMediaScannerVolume});
//直接返回的是有關(guān)存儲(chǔ)卷的cursor
return c;
}
}
// Used temporarily (until we have unique media IDs) to get an identifier
// for the current sd card, so that the music app doesn't have to use the
// non-public getFatVolumeId method
if (table == FS_ID) {
MatrixCursor c = new MatrixCursor(new String[] {"fsid"});
c.addRow(new Integer[] {mVolumeId});
return c;
}
if (table == VERSION) {
MatrixCursor c = new MatrixCursor(new String[] {"version"});
c.addRow(new Integer[] {getDatabaseVersion(getContext())});
return c;
}
//初始化DatabaseHelper和SQLiteDatabase
String groupBy = null;
DatabaseHelper helper = getDatabaseForUri(uri);
if (helper == null) {
return null;
}
helper.mNumQueries++;
SQLiteDatabase db = null;
try {
db = helper.getReadableDatabase();
} catch (Exception e) {
e.printStackTrace();
return null;
}
if (db == null) return null;
// SQLiteQueryBuilder類(lèi)是組成查詢語(yǔ)句的幫助類(lèi)
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
//獲取uri里面的查詢字符
String limit = uri.getQueryParameter("limit");
String filter = uri.getQueryParameter("filter");
String [] keywords = null;
if (filter != null) {
filter = Uri.decode(filter).trim();
if (!TextUtils.isEmpty(filter)) {
//對(duì)字符進(jìn)行篩選
String [] searchWords = filter.split(" ");
keywords = new String[searchWords.length];
for (int i = 0; i < searchWords.length; i++) {
String key = MediaStore.Audio.keyFor(searchWords[i]);
key = key.replace("\\", "\\\\");
key = key.replace("%", "\\%");
key = key.replace("_", "\\_");
keywords[i] = key;
}
}
}
if (uri.getQueryParameter("distinct") != null) {
qb.setDistinct(true);
}
boolean hasThumbnailId = false;
//對(duì)匹配的其他類(lèi)型進(jìn)行設(shè)置查詢語(yǔ)句的操作
switch (table) {
case IMAGES_MEDIA:
//設(shè)置查詢的表是images
qb.setTables("images");
if (uri.getQueryParameter("distinct") != null)
//設(shè)置為唯一的
qb.setDistinct(true);
break;
//其他類(lèi)型相類(lèi)似
... ...
}
//根據(jù)拼裝的搜索條件,進(jìn)行查詢
Cursor c = qb.query(db, projectionIn, selection,
combine(prependArgs, selectionArgs), groupBy, null, sort, limit);
if (c != null) {
String nonotify = uri.getQueryParameter("nonotify");
if (nonotify == null || !nonotify.equals("1")) {
//通知更新數(shù)據(jù)庫(kù)
c.setNotificationUri(getContext().getContentResolver(), uri);
}
}
return c;
}
至此,關(guān)于MediaProvider的增刪改查,創(chuàng)建數(shù)據(jù)庫(kù)等操作的分析已經(jīng)完成。