相信多数想了解谷歌DocumentUI设计思想的码农都会遇到障碍,文件浏览究竟是怎么实现的,进入DocumentUI的UI层,不难发现,我们是通过查询数据库获取cursor,但是查询的哪个数据库,怎么能够查询数据库就可以把文件层级一级级浏览呢?带着这些疑问,本地通过SourceInsight工具查找了所有的ContentProvider,谷歌原生有一个抽象类DocumentsProvider。
public abstract class DocumentsProvider extends ContentProvider
呢么,究竟是哪一个类实现了该Provider并实现了DocumentUI的数据提供功能呢?
带着这个问题,本地查找了所有的实现类,发现有一个类ExternalStorageProvider:
public class ExternalStorageProvider extends DocumentsProvider
正是该类实现这个接口。熟悉APN流程的码农应该都知道,数据库的创建.db是在TelephonyProvider.java中创建的,那么,是不是我们的DocumentUI的查询数据也是来自ExternalStorageProvider创建数据库,进行增、删、查找获取的呢?但是,查找该文件并没有找到与数据库相关的文件,是不是很好奇?
细心的人也会发现该类中有一个静态成员:
public static final String AUTHORITY = "com.android.externalstorage.documents";
如果经常调试DocumentUI,或者使用该应用的功能,比如分享时,是不是遇到过该熟悉的Uri内容呢,本地启动DocumentUI.
C:\Users\xx>adb logcat | find "ExternalStorage"
09-14 14:34:48.829 520 1574 I ActivityManager: Start proc 7456:com.android.externalstorage/u0a10 for content provider com.android.externalstorage/.ExternalSt
orageProvider
根据log也可以看到ExternalStorageProvider 确认启动了。
在DocumentUI执行分享文件功能,查看下面log:
C:\Users\xx>adb logcat | find "Activity"
// 弹出选择分享功能界面
09-14 14:38:45.591 520 672 I ActivityManager: Displayed android/com.android.internal.app.ChooserActivity: +135ms
// 选择蓝牙,文件的mimeType 是通过 getTypeForFile(File file)接口获得,分享文件名ylog_debug,Uri如下:
// content://com.android.externalstorage.documents/document/CAB0-17E4%3Aylog%2Fap%2Fcurrent%2F00-0913_183933%2Fylog_debug
09-14 14:38:46.812 520 1596 I ActivityManager: START u0 {act=android.intent.action.SEND cat=[android.intent.category.DEFAULT] typ=application/octet-stream fl
g=0xb080001 cmp=com.android.bluetooth/.opp.BluetoothOppLauncherActivity clip={application/octet-stream U:content://com.android.externalstorage.documents/documen
t/CAB0-17E4%3Aylog%2Fap%2Fcurrent%2F00-0913_183933%2Fylog_debug} (has extras)} from uid 10013, pid -1
因此,DocumentUi实际数据提供者 是 ExternalStorageProvider毫无疑问,下面我们就可以对该文件进行简单分析。
public Cursor queryChildDocuments(
String parentDocumentId, String[] projection, String sortOrder)
throws FileNotFoundException ;
当浏览文件夹时,点击进入该文件夹,需要通过 parent.listFiles()遍历列出所有文件,调用的是该接口。
private File getFileForDocId(String docId) throws FileNotFoundException;
通过入参DocumentId获取对应文件,该字符串格式: docId = CAB0-17E4:ylog/ap/current/00-0913_183933,存储路径名:具体文件路径
DirectoryCursor extends MatrixCursor 类:
构造一个文件夹目录Cursor对象,可以动态添加一行文件数据,这样该对象可以包含所有文件夹下面的文件,
该类构造中(创建)监听文件,FileObserver机制 startObserving(File file, Uri notifyUri)。
重点是当调用queryChildDocuments()时,所有的文件信息是怎么得到?
该类中通过遍历得到每一个File对象,然后通过对File对象进行解析,存储数据到MatrixCursor result,
这里就是所有数据获取的关键所在。
private void includeFile(MatrixCursor result, String docId, File file)
throws FileNotFoundException {
if (docId == null) {
docId = getDocIdForFile(file);
} else {
file = getFileForDocId(docId);
}
int flags = 0;
if (file.canWrite()) {
if (file.isDirectory()) {
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
flags |= Document.FLAG_SUPPORTS_DELETE;
flags |= Document.FLAG_SUPPORTS_RENAME;
flags |= Document.FLAG_SUPPORTS_MOVE;
} else {
flags |= Document.FLAG_SUPPORTS_WRITE;
flags |= Document.FLAG_SUPPORTS_DELETE;
flags |= Document.FLAG_SUPPORTS_RENAME;
flags |= Document.FLAG_SUPPORTS_MOVE;
}
}
// 获取文件的mimetype,并非查询数据库,也是调用相关接口实现
final String mimeType = getTypeForFile(file);
if (mArchiveHelper.isSupportedArchiveType(mimeType)) {
flags |= Document.FLAG_ARCHIVE;
}
// 获取文件名
final String displayName = file.getName();
/*
* Modify for documentui_DRM
* original code
if (mimeType.startsWith("image/")) {
*@{
*/
if (mimeType.startsWith("image/") || mimeType.equals("application/vnd.oma.drm.content")) {
/*@}*/
flags |= Document.FLAG_SUPPORTS_THUMBNAIL;
}
// 创建新行,存储文件信息
final RowBuilder row = result.newRow();
row.add(Document.COLUMN_DOCUMENT_ID, docId);
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
// 存入文件大小 file.length()
row.add(Document.COLUMN_SIZE, file.length());
row.add(Document.COLUMN_MIME_TYPE, mimeType);
row.add(Document.COLUMN_FLAGS, flags);
row.add(DocumentArchiveHelper.COLUMN_LOCAL_FILE_PATH, file.getPath());
// Only publish dates reasonably after epoch
long lastModified = file.lastModified();
if (lastModified > 31536000000L) {
row.add(Document.COLUMN_LAST_MODIFIED, lastModified);
}
}
以上就是DocumentUI浏览时,显示文件信息的数据来源。
当然还有如下接口:
public void updateVolumes() {
synchronized (mRootsLock) {
updateVolumesLocked();
}
}
更新存储获取所有存储卡数据,保存到mRoots对象。
private ArrayMap
具体可以参考:
private void updateVolumesLocked() {
mRoots.clear();
VolumeInfo primaryVolume = null;
final int userId = UserHandle.myUserId();
final List
for (VolumeInfo volume : volumes) {
...............
mRoots.put(rootId, root);
}
}
是不是很熟悉,数据来源于StorageManager接口。
该类提供给外部获取所有存储根目录的接口:
@Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
synchronized (mRootsLock) {
for (RootInfo root : mRoots.values()) {
final RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, root.rootId);
row.add(Root.COLUMN_FLAGS, root.flags);
row.add(Root.COLUMN_TITLE, root.title);
row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
row.add(Root.COLUMN_AVAILABLE_BYTES,
root.reportAvailableBytes ? root.path.getFreeSpace() : -1);
}
}
return result;
}
是不是觉得很清晰了,原来是从之前更新好的mRoots对象中去拿到数据啊,Cursor不过就是数据的封装保存外壳,伪装的是不是太牛逼了~~
到此,我们基本清楚DocumentUI的浏览文件的查询corsor所有的数据来源了,是不是很嗨皮~~
困扰已久的疑问,终于搞定了~~
当然,开发第三方文件管理器,谷歌原生DocumentUI的设计里,有很多值得我们借鉴之处~~
------------------------------------------------ 继续更新 android8.1 相关类-------------------------------------------
frameworks/base/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
frameworks/base/core/java/com/android/internal/content/FileSystemProvider.java
frameworks/base/core/java/android/provider/DocumentsProvider.java
public abstract class DocumentsProvider extends ContentProvider;
ExternalStorageProvider 继承FileSystemProvider
FileSystemProvider继承DocumentsProvider