我们来重点分析MediaProvider,首先MediaProvider继承自ContentProvider,一定实现了query方法,我们找到这个query方法,方法很长,我们一部分一部分来看:
uri = safeUncanonicalize(uri);
首先这个safeUncanonicalize方法就不懂,跟踪它,
1267 private Uri safeUncanonicalize(Uri uri) {
1268 Uri newUri = uncanonicalize(uri);
1269 if (newUri != null) {
1270 return newUri;
1271 }
1272 return uri;
1273 }
说明它尝试用uncanonicalize去找到一个新的Uri,如果找不到,那么就返回原来的uri,uncanonicalize字面的意思是不规范的,那么为什么要让它不规范呢?继续跟踪uncanonicalize
1222 if (uri != null && "1".equals(uri.getQueryParameter(CANONICAL))) {
1223 int match = URI_MATCHER.match(uri);
1224 if (match != AUDIO_MEDIA_ID) {
1225 // this type of canonical Uri is not supported
1226 return null;
1227 }
1228 String titleFromUri = uri.getQueryParameter(MediaStore.Audio.Media.TITLE);
1229 if (titleFromUri == null) {
1230 // the required parameter is missing
1231 return null;
1232 }
1233 // clear the query parameters, we don't need them anymore
1234 uri = uri.buildUpon().clearQuery().build();
1235
1236 Cursor c = query(uri, null, null, null, null);
1237 try {
1238 int titleIdx = c.getColumnIndex(MediaStore.Audio.Media.TITLE);
1239 if (c != null && c.getCount() == 1 && c.moveToNext() &&
1240 titleFromUri.equals(getDefaultTitleFromCursor(c))) {
1241 // the result matched perfectly
1242 return uri;
1243 }
1244
1245 IoUtils.closeQuietly(c);
1246 // do a lookup by title
1247 Uri newUri = MediaStore.Audio.Media.getContentUri(uri.getPathSegments().get(0));
1248
1249 c = query(newUri, null, MediaStore.Audio.Media.TITLE + "=?",
1250 new String[] {titleFromUri}, null);
1251 if (c == null) {
1252 return null;
1253 }
1254 if (!c.moveToNext()) {
1255 return null;
1256 }
1257 // get the first matching entry and return a Uri for it
1258 long id = c.getLong(c.getColumnIndex(MediaStore.Audio.Media._ID));
1259 return ContentUris.withAppendedId(newUri, id);
1260 } finally {
1261 IoUtils.closeQuietly(c);
1262 }
1263 }
如果URI是规范的,那么用UriMacher得到一个Int类型的值,首先需要得到一个titleFromUri,它需要从MediaStore.Audio.Media.TITLE得到参数,如果没有这个参数,那么uri就会返回空,这里MediaStore.Audio.Media.TITLE很熟悉,它就是数据表中的某一列,这个方法就是遍历所有的列,返回该列对应的值.之后清空uri中的参数,这里的参数在后面会分析到,是直接在uri后面添加的查询参数,类似于get请求.然后在没有rowId的情况下,查询URI,那么这时候得到的是表中的所有数据,然后判断 titleFromUri.equals(getDefaultTitleFromCursor©,如果找到符合条件的记录,直接返回,那么这里的getDefaultTitleFromCursor是什么意思呢,跟踪它,
2023 private String getDefaultTitle(String title_resource_uri) throws Exception{
2024 try {
2025 return getTitleFromResourceUri(title_resource_uri, false);
2026 } catch (Exception e) {
2027 Log.e(TAG, "Error getting default title for " + title_resource_uri, e);
2028 throw e;
2029 }
2030 }
可以看见返回了getTitleFromResourceUri,这里就是从本身自带的资源文件得到title,这里的逻辑就清楚了,如果记录中的title是本身的资源文件夹,那么完美匹配,直接返回uri即可,如果不匹配,那么返回一个新的uri,这个uri是拼接了记录id的的新的uri.比如原来的uri是content://media/external/files,如果查到的记录id是24,那么就会返回content://media/external/filws/24
int table = URI_MATCHER.match(uri);
跟踪URI_MACHER,它在framework的content包下,这里调用了URI_MACHER这个类下的match方法,match方法返回一个code,这个code对应着不同的标识码.在MediaStore中通过URI_MACHER.addUri的方式将标识码和uri绑定在一起.
接下来再看MediaProvider是如何添加内容的.
1868 public Uri insert(Uri uri, ContentValues initialValues) {
1869 int match = URI_MATCHER.match(uri);
1870
1871 ArrayList<Long> notifyRowIds = new ArrayList<Long>();
1872 Uri newUri = insertInternal(uri, match, initialValues, notifyRowIds);
1873
1874 // do not signal notification for MTP objects.
1875 // we will signal instead after file transfer is successful.
1876 if (newUri != null && match != MTP_OBJECTS) {
1877 // Report a general change to the media provider.
1878 // We only report this to observers that are not looking at
1879 // this specific URI and its descendants, because they will
1880 // still see the following more-specific URI and thus get
1881 // redundant info (and not be able to know if there was just
1882 // the specific URI change or also some general change in the
1883 // parent URI).
1884 getContext().getContentResolver().notifyChange(uri, null, match != MEDIA_SCANNER
1885 ? ContentResolver.NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS : 0);
1886 // Also report the specific URIs that changed.
1887 if (match != MEDIA_SCANNER) {
1888 getContext().getContentResolver().notifyChange(newUri, null, 0);
1889 }
1890 }
1891 return newUri;
1892 }
首先得到了一个match,他是根据uri得到的标识码,然后得到一个notifyRowIds的列表,然后使用了insertInternal得到一个新的Uri,跟踪insertInternal.这里将notifyRowId和intialVlaues传入,intialVlaues实现了parcelable,本质上是一个HashMap,这部分比较长,一部分一部分分析:
2515 final String volumeName = getVolumeName(uri);
首先根据Uri得到VolumeName,这里我们使用的content://media/external/file,标识码是700
2533 String genre = null;
2534 String path = null;
2535 if (initialValues != null) {
2536 genre = initialValues.getAsString(Audio.AudioColumns.GENRE);
2537 initialValues.remove(Audio.AudioColumns.GENRE);
2538 path = initialValues.getAsString(MediaStore.MediaColumns.DATA);
2539 }
如果iniialValues不为空,那么执行如上逻辑,我们跟踪contentValue的getAsString方法
261 public String getAsString(String key) {
262 Object value = mValues.get(key);
263 return value != null ? value.toString() : null;
264 }
那么mValues是什么,继续跟踪,发现是一个HashMap,那么实际上就是根据键来得到值,所以实际上initialValues就是以(表列名,值)形式的键值对.显然path就是从initialValues得到的.
2543 DatabaseHelper helper = getDatabaseForUri(uri);
2544 if (helper == null && match != VOLUMES && match != MTP_CONNECTED) {
2545 throw new UnsupportedOperationException(
2546 "Unknown URI: " + uri);
2547 }
2548
2549 SQLiteDatabase db = ((match == VOLUMES || match == MTP_CONNECTED) ? null
2550 : helper.getWritableDatabase());
如果标识码是VOLUMES或者MTP_CONNECTED,那么db置为null,否则根据uri得到对应的db.我们根据自己的标识码找到对应的case:
2737 case FILES:
2738 rowId = insertFile(helper, uri, initialValues,
2739 FileColumns.MEDIA_TYPE_NONE, true, notifyRowIds);
2740 if (rowId > 0) {
2741 newUri = Files.getContentUri(volumeName, rowId);
2742 }
2743 break;
这里就是调用insertFile方法返回rowId,如果rowId>0,那么返回根据rowId得到的新URI,我们跟踪insertFiles:这里传入的类型是MEDIA_TYPE_NONE,先标记一下
2126 SQLiteDatabase db = helper.getWritableDatabase();
2127 ContentValues values = null;
这里新定义了一个db和valuas,然后找到我们需要的case,然后继续
2235 if (values == null) {
2236 values = new ContentValues(initialValues);
2237 }
2238 // compute bucket_id and bucket_display_name for all files
2239 String path = values.getAsString(MediaStore.MediaColumns.DATA);
2240 if (path != null) {
2241 computeBucketValues(path, values);
2242 }
2243 values.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis() / 1000);
首先这里新创建了一个ContentValues,值就是我们之前传进来的HashMap,我们从里面得到path的值,然后computeBucketValues,这个方法先放着,从注释上看是为每个file计算一个bucket_id,目测是为了查找优化.
2245 long rowId = 0;
2246 Integer i = values.getAsInteger(
2247 MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID);
2248 if (i != null) {
2249 rowId = i.intValue();
2250 values = new ContentValues(values);
2251 values.remove(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID);
2252 }
2253
2254 String title = values.getAsString(MediaStore.MediaColumns.TITLE);
2255 if (title == null && path != null) {
2256 title = MediaFile.getFileTitle(path);
2257 }
2258 values.put(FileColumns.TITLE, title);
首先从得到MEDIA_SCANNER_NEW_OBJECT_ID,如果不为空,那么将其移除.然后从values中得到title,如果title为空,那么为它创建一个title,然后将title的信息放入.
2260 String mimeType = values.getAsString(MediaStore.MediaColumns.MIME_TYPE);
2261 Integer formatObject = values.getAsInteger(FileColumns.FORMAT);
2262 int format = (formatObject == null ? 0 : formatObject.intValue());
2263 if (format == 0) {
2264 if (TextUtils.isEmpty(path)) {
2265 // special case device created playlists
2266 if (mediaType == FileColumns.MEDIA_TYPE_PLAYLIST) {
2267 values.put(FileColumns.FORMAT, MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST);
2268 // create a file path for the benefit of MTP
2269 path = mExternalStoragePaths[0]
2270 + "/Playlists/" + values.getAsString(Audio.Playlists.NAME);
2271 values.put(MediaStore.MediaColumns.DATA, path);
2272 values.put(FileColumns.PARENT, getParent(helper, db, path));
2273 } else {
2274 Log.e(TAG, "path is empty in insertFile()");
2275 }
2276 } else {
2277 format = MediaFile.getFormatCode(path, mimeType);
2278 }
2279 }
然后继续从values中得到mimeType和formatobject,如果没有format,那么就将其置为0,如果format为0,那么判断path是否为null,如果为空继续做一些特殊处理,否则抛出异常.如果path不为空,那么根据path和mime类型,得到format.
那么当MimeType为空的时候会做什么呢?
2291 if (mimeType == null && path != null && format != MtpConstants.FORMAT_ASSOCIATION) {
2292 mimeType = MediaFile.getMimeTypeForFile(path);
2293 }
2294
它会根据path来得到mime类型,我们跟踪getMimeTypeForFile方法,它实际上是调用了MediaFile的getFileType方法
316 public static MediaFileType getFileType(String path) {
317 int lastDot = path.lastIndexOf('.');
318 if (lastDot < 0)
319 return null;
320 return sFileTypeMap.get(path.substring(lastDot + 1).toUpperCase(Locale.ROOT));
321 }
实际上就是截取最后一个点后面的后缀.然后变成大写.
继续看InsertFile中的代码:
335 if (path != null) {
2336 File file = new File(path);
2337 if (file.exists()) {
2338 values.put(FileColumns.DATE_MODIFIED, file.lastModified() / 1000);
2339 if (!values.containsKey(FileColumns.SIZE)) {
2340 values.put(FileColumns.SIZE, file.length());
2341 }
2342 // make sure date taken time is set
2343 if (mediaType == FileColumns.MEDIA_TYPE_IMAGE
2344 || mediaType == FileColumns.MEDIA_TYPE_VIDEO) {
2345 computeTakenTime(values);
2346 }
2347 }
2348 }
当rowId == 0的时候,会执行以上逻辑,这时候相当于数据还没有真正的插入到表中,如果path不为空,那么先区判断path对应的File是否存在,如果存在,那么将其修改时间写上,如果输入的信息包含文件的大小,那么将文件大小也标记上.最后小结一下,使用insert的时候,只要加上URI和contentValues就行了,其中contentValues中必须包含path信息.