谷歌原生DocumentUI文件浏览的原理

来源:互联网 发布:mac ps做图 编辑:程序博客网 时间:2024/05/29 11:27

相信多数想了解谷歌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<String, RootInfo> mRoots = new ArrayMap<>();


具体可以参考:

    private void updateVolumesLocked() {
        mRoots.clear();

        VolumeInfo primaryVolume = null;
        final int userId = UserHandle.myUserId();
        final List<VolumeInfo> volumes = mStorageManager.getVolumes();
        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

原创粉丝点击