前两天处理了那个beam分享的问题单之后,回头来想想,蓝牙进程为什么能读取到文件并分享给另一个手机呢?Android手机本身是LInux系统,每个文件都有相应的读、写、执行权限的,如果权限不符合是无法访问该文件的,而在那个问题单的处理过程当中,我们可以看到,就是使用了Uri标识文件,然后给蓝牙进程赋予读权限,最终蓝牙进程肯定就是通过ContentProvider来实现文件的读取并共享的。问题单虽然处理完了,但是对于ContentProvider还是只了解面貌,底层到底是如何实现的,我们还是不清楚,也就是考虑到这个,所以本节课我们来深究一下ContentProvider的实现原理。
之前也读过老罗关于ContentProvider的研究的博客了,写的非常好,非常细致,大家也可以学习一下,但是还是要说明一下,只是读别人的博客,自己不研究,不跟踪代码,那么学习还是非常肤浅的,基本上就非常模糊。好了,我们这节课呢实现很简单,就是通过访问图库中的图片,来学习一下ContentProvider的实现原理。客户端很简单,就一个方法,其他的调用界面我就不写了,大家可以自己写一下:
public static void queryAllImages(Context context) {
ArrayList
names = new ArrayList
();
ArrayList
descs = new ArrayList
();
ArrayList
datas = new ArrayList
(); Uri uri = Media.EXTERNAL_CONTENT_URI; Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); Log.i(TAG, "query images, " + uri + ", " + uri.toString()); while (cursor.moveToNext()) { String name = cursor.getString(cursor.getColumnIndex(Media.DISPLAY_NAME)); String desc = cursor.getString(cursor.getColumnIndex(Media.DESCRIPTION)); byte[] data = cursor.getBlob(cursor.getColumnIndex(Media.DATA)); names.add(name); descs.add(desc); datas.add(Arrays.toString(data)); mDatas.add(data); } Log.i(TAG, "names = " + names); Log.i(TAG, "datas = " + datas); }
从转换成的字符串结果当中,很清楚的可以看到,它正是我们当前取回来的图片文件在手机上的存储路径。
这个方法当中的目的也非常明确,就是通过Resolver来查询图库中Media.EXTERNAL_CONTENT_URI匹配的所有的图片,然后在返回的结果中把数据读出来,看一下返回来的数据是什么,这样就完成了。那么根据这样的目的,我们也就把这节课分为三个步骤来讲:1、context.getContentResolver().query(uri, null, null, null, null)调用完成,返回一个Cursor对象;2、cursor.moveToNext()到底干了什么?;3、cursor.getBlob(cursor.getColumnIndex(Media.DATA))是如何把数据取回来的。后面两个步骤大家可能有疑问,这两步也要单独拿出来分析?先不着急,大家细心看完就知道了,其实这两步是非常重要的,数据的获取就是通过这两步来完成的。1、context.getContentResolver().query(uri, null, null, null, null)返回Cursor对象
context.getContentResolver()是由ContextImpl实现的,它返回的就是成员变量mContentResolver,而mContentResolver是一个ApplicationContentResolver对象,是在ContextImpl定义的一个内部类,而query方法是由父类ContentResolver来实现的,注意query方法的修饰符为final,即此方法不允许子类自己实现,那么我们就来看一下query方法的实现:
public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder) {
return query(uri, projection, selection, selectionArgs, sortOrder, null);
}
/**
* Query the given URI, returning a {@link Cursor} over the result set
* with optional support for cancellation.
*
* For best performance, the caller should follow these guidelines:
*
-
*
- Provide an explicit projection, to prevent
* reading data from storage that aren't going to be used.
-
*
- Use question mark parameter markers such as 'phone=?' instead of
* explicit values in the {@code selection} parameter, so that queries
* that differ only by those values will be recognized as the same
* for caching purposes.
-
*
*
*
* @param uri The URI, using the content:// scheme, for the content to
* retrieve.
* @param projection A list of which columns to return. Passing null will
* return all columns, which is inefficient.
* @param selection A filter declaring which rows to return, formatted as an
* SQL WHERE clause (excluding the WHERE itself). Passing null will
* return all rows for the given URI.
* @param selectionArgs You may include ?s in selection, which will be
* replaced by the values from selectionArgs, in the order that they
* appear in the selection. The values will be bound as Strings.
* @param sortOrder How to order the rows, formatted as an SQL ORDER BY
* clause (excluding the ORDER BY itself). Passing null will use the
* default sort order, which may be unordered.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* If the operation is canceled, then {@link OperationCanceledException} will be thrown
* when the query is executed.
* @return A Cursor object, which is positioned before the first entry, or null
* @see Cursor
*/
public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
@Nullable String[] projection, @Nullable String selection,
@Nullable String[] selectionArgs, @Nullable String sortOrder,
@Nullable CancellationSignal cancellationSignal) {
Preconditions.checkNotNull(uri, "uri");
IContentProvider unstableProvider = acquireUnstableProvider(uri);
if (unstableProvider == null) {
return null;
}
IContentProvider stableProvider = null;
Cursor qCursor = null;
try {
long startTime = SystemClock.uptimeMillis();
ICancellationSignal remoteCancellationSignal = null;
if (cancellationSignal != null) {
cancellationSignal.throwIfCanceled();
remoteCancellationSignal = unstableProvider.createCancellationSignal();
cancellationSignal.setRemote(remoteCancellationSignal);
}
try {
qCursor = unstableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
} catch (DeadObjectException e) {
// The remote process has died... but we only hold an unstable
// reference though, so we might recover!!! Let's try!!!!
// This is exciting!!1!!1!!!!1
unstableProviderDied(unstableProvider);
stableProvider = acquireProvider(uri);
if (stableProvider == null) {
return null;
}
qCursor = stableProvider.query(mPackageName, uri, projection,
selection, selectionArgs, sortOrder, remoteCancellationSignal);
}
if (qCursor == null) {
return null;
}
// Force query execution. Might fail and throw a runtime exception here.
qCursor.getCount();
long durationMillis = SystemClock.uptimeMillis() - startTime;
maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);
// Wrap the cursor object into CursorWrapperInner object.
final IContentProvider provider = (stableProvider != null) ? stableProvider
: acquireProvider(uri);
final CursorWrapperInner wrapper = new CursorWrapperInner(qCursor, provider);
stableProvider = null;
qCursor = null;
return wrapper;
} catch (RemoteException e) {
// Arbitrary and not worth documenting, as Activity
// Manager will kill this process shortly anyway.
return null;
} finally {
if (qCursor != null) {
qCursor.close();
}
if (cancellationSignal != null) {
cancellationSignal.setRemote(null);
}
if (unstable