Media Data之多媒體數(shù)據(jù)庫(kù)(二)MediaProvider

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)完成。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,104評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,884評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,647評(píng)論 6 408
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,130評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,366評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,737評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,939評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,174評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,586評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,827評(píng)論 1 283
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,608評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,914評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容