源码分析MediaStore是如何管理文件的(二)

我们来重点分析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信息.

你可能感兴趣的:(源码分析MediaStore是如何管理文件的(二))