从跨用户文件拷贝说起DocumentUI记录

前言

需求是,从分用户中,获取主用户中下载目录(sd卡Download目录)下的文件/目录,并复制到分用户中的下载目录(sd卡Download目录),开始的思路是,通过File类读取主用户的文件/目录,再写入分用户的下载目录。但是,再使用的时候发现几个问题:

  • File不能跨用户读取。
  • 就算你有准确的文件sd路径,也无法跨进程读取到文件的流。

那么,既然从File无法入手,我们就从Uri入手了。解决方案:

  • 先获取要复制文件/目录的uri。
  • 再通过getContentResolver().openInputStream(uri)拷贝复制;(更正一下,是可以通过file还读取到的。)

PS:跨用户操作需要系统权限。

具体示例:

假设,我们要复制主用户中的图片到分用户中。

    void cpoyMainUserToCurrentUser(){

        ContentResolver mContentResolver = getContentResolver();

        //通过MediaStore去查询图片
        Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        Uri mainUri = maybeAddUserId(uri, 0);//将uri转为主用户下uri.  0为主用户id

        //开始先查询主用户中的图片
        Cursor mCursor = mContentResolver.query(mainUri, null,
                MediaStore.Images.Media.MIME_TYPE + "=? or " + MediaStore.Images.Media.MIME_TYPE + "=?",
                new String[] { "image/jpeg", "image/png" }, MediaStore.Images.Media.DEFAULT_SORT_ORDER);

        Map<Uri, String> imageInfoMap = new HashMap<>();//存入图片的uri和名称
        while (mCursor.moveToNext()) {
            try {
                //图片路径
                String path = mCursor.getString(mCursor
                        .getColumnIndex(MediaStore.Images.Media.DATA));
                //图片名称
                String display_name = mCursor.getString(mCursor
                        .getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));
                //图片id
                int _id = mCursor.getInt(mCursor
                        .getColumnIndex(MediaStore.MediaColumns._ID));

                /*******
                 * 得到图片的Uri,这个就是我们复制的关键
                 ******/
                Uri imageUri = Uri.withAppendedPath(mainUri, "" + _id);

                /*****
                 * 接下来去从主用户复制到当前用户
                 *****/
                FileOutputStream fos = null;
                try {
                    //将图片复制到当前用户下的DICM图片目录下,防止命名重复,这里简单处理了一下
                    fos = new FileOutputStream(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getPath()+"/"+System.currentTimeMillis()+"_"+display_name);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }

                InputStream is =  null ;
                try {
                    ByteArrayOutputStream baos =  new  ByteArrayOutputStream();
                    is = getContentResolver().openInputStream(imageUri);
                    byte [] buffer =  new   byte [ 1024 ];
                    int  len = is.read(buffer);
                    while  (len >=  0 ){
                        baos.write(buffer,  0 , len);
                        len = is.read(buffer);
                    }
                    baos.writeTo(fos);
                    Log.e("cpoyMainUserToCurrentUser", "复制完成了");
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    if  (is !=  null ) {
                        is.close();
                    }
                    if(fos !=null ){
                        fos.close();
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    //通过反射来使用maybeAddUserId方法
    private Uri maybeAddUserId(Uri uri, int userId){
        Uri ret = uri;
        try {
            Class c = ContentProvider.class;
            Method method = null;
            method = c.getMethod("maybeAddUserId", new Class[]{ Uri.class, int.class});
            method.setAccessible(true);
            Object obj = method.invoke(null, uri, userId);
            ret = (Uri) obj;
        }catch (Exception e) {
            e.printStackTrace();
        }
        return ret;
    }

上面我们可以通过MediaStore可以获取图片,那如果我们想要操作某某些目录或分类怎么获取uri呢?比如下载目录?比如视频,或者某个目录,这块就要去研究一下DocumentsUI了。
在这里插入图片描述

导入Download目录下文件/目录

基于源码内置的下载app中能很好的显示各个目录和文件,所以下面简单分析一下DocumentsUI源码。不求熟悉整个DocumentsUI,但至少知道Download目录的uri和子uri的使用方式。这样,才能继续备份Download目录下文件这个需求。

源码是8.0的
我们先从xml入手,先看AndroidManifest.xml
在这里插入图片描述
LauncherActivity是主activity,顺便说一下DocumentsUI在桌面显示的icon名称是下载,但是app的名称是文件。我们看下LauncherActivity.java。

###LauncherActivity.java###

 @Override
60    protected void onCreate(Bundle savedInstanceState) {
61        super.onCreate(savedInstanceState);
62
63        launch();
64
65        finish();
66    }


   private void launch() {
69        ActivityManager activities = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
		  
		  ...........
		  
83        startTask();
84    }


98    private void startTask() {
99        Intent intent = createLaunchIntent(this); //常见要启动的intent
		  
		  ........................
		  
107        startActivity(intent);
108    }


        /**********创建要启动的activity*****************/
132    public static final Intent createLaunchIntent(Activity activity) {
133        Intent intent = new Intent(activity, FilesActivity.class); //启动FilesActivity activity
134        intent.setData(buildLaunchUri());
135
136        // Relay any config overrides bits present in the original intent.
137        Intent original = activity.getIntent();
138        if (original != null) {
139            copyExtras(original, intent);
140            if (original.hasExtra(Intent.EXTRA_TITLE)) {
141                intent.putExtra(
142                        Intent.EXTRA_TITLE,
143                        original.getStringExtra(Intent.EXTRA_TITLE));
144            }
145        }
146        return intent;
147    }

从上面可以看到,最终启动了FilesActivity,继承BaseActivity。我们继续从xml开始分析。

###FilesActivity.java###
 
	  public FilesActivity() {
	        super(R.layout.files_activity, TAG);
	    }
    
###values/layouts.xml###
	<resources>
	    <item name="documents_activity" type="layout">@layout/drawer_layout</item>
	    <item name="files_activity" type="layout">@layout/drawer_layout</item>
	</resources>

可见FilesActivity使用的是R.layout.files_activity 即 drawer_layout.xml。

###BaseActivity.java###

   public void onCreate(Bundle icicle) {
        // Record the time when onCreate is invoked for metric.
        mStartTime = new Date().getTime();

        super.onCreate(icicle);

        final Intent intent = getIntent();

        addListenerForLaunchCompletion();

        setContentView(mLayoutId);
      
       ..................
       mDrawer = DrawerController.create(this, mInjector.config); // Drawerlayout的控制类
      ..............
      
      mProviders = DocumentsApplication.getProvidersCache(this); //ProvidersCache
      ..............
  }
  
###ProvidersCache.java 获取###

       @Override
        protected Void doInBackground(Void... params) {
            final long start = SystemClock.elapsedRealtime();

            mTaskRoots.put(mRecentsRoot.authority, mRecentsRoot);

            final PackageManager pm = mContext.getPackageManager();
         
            // Pick up provider with action string
            final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); 
            final List<ResolveInfo> providers = pm.queryIntentContentProviders(intent, 0);
            for (ResolveInfo info : providers) {
                handleDocumentsProvider(info.providerInfo);
            }
         ....................
         
        }

ProvidersCache中通过UpdateTask中异步通过DocumentsContract.PROVIDER_INTERFACE来获取根目录uri。如上图中图片,视频,音频,最近,下载等等对应的uri,后面再通过这些uri,获取到其子目录/文件,一层套一层。

这边举一个点击左侧滑栏 下载 的逻辑分析。

###BaseActivity.java###

284    @Override
285    public void onRootPicked(RootInfo root) {
286        // Clicking on the current root removes search
287        mSearchManager.cancelSearch();
          
          ....................
          
307        // Recents is always in memory, so we just load it directly.
308        // Otherwise we delegate loading data from disk to a task
309        // to ensure a responsive ui.
310        if (mProviders.isRecentsRoot(root)) {
311            refreshCurrentRootAndDirectory(AnimationView.ANIM_NONE);
312        } else {
313            mInjector.actions.getRootDocument(
314                    root,
315                    TimeoutTask.DEFAULT_TIMEOUT,
316                    doc -> mInjector.actions.openRootDocument(doc));
317        }
318    }

if是进入右侧最近选项,else是其他选项(图片,下载等等)的。不过最终都会调用到refreshCurrentRootAndDirectory中

###BaseActivity.java###

    @Override
    public final void refreshCurrentRootAndDirectory(int anim) {
        // The following call will crash if it's called before onCreateOptionMenu() is called in
        // which we install menu item to search view manager, and there is a search query we need to
        // restore. This happens when we're still initializing our UI so we shouldn't cancel the
        // search which will be restored later in onCreateOptionMenu(). Try finding a way to guard
        // refreshCurrentRootAndDirectory() from being called while we're restoring the state of UI
        // from the saved state passed in onCreate().
        mSearchManager.cancelSearch();

        refreshDirectory(anim);

        final RootsFragment roots = RootsFragment.get(getFragmentManager()); //RootsFragment展示的内容部分
        if (roots != null) {
            roots.onCurrentRootChanged();
        }
    
    ...................
    
    }

RootsFragment就是我们要展示内容的主区域,我们进去看一下。

    ###RootsFragment.java###

    .......
    private ListView mList;
    private RootsAdapter mAdapter;
    private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
    .....    

mCallbacks = new LoaderCallbacks<Collection<RootInfo>>() {
            @Override
            public Loader<Collection<RootInfo>> onCreateLoader(int id, Bundle args) {
                return new RootsLoader(activity, providers, state);
            }

            @Override
            public void onLoadFinished(
                    Loader<Collection<RootInfo>> loader, Collection<RootInfo> result) {
                if (!isAdded()) {
                    return;
                }

                Intent handlerAppIntent = getArguments().getParcelable(EXTRA_INCLUDE_APPS);

                List<Item> sortedItems = sortLoadResult(result, handlerAppIntent);  //sortLoadResult ,将根目录排序
                mAdapter = new RootsAdapter(activity, sortedItems, mDragListener);
                mList.setAdapter(mAdapter);

                onCurrentRootChanged();
            }

            @Override
            public void onLoaderReset(Loader<Collection<RootInfo>> loader) {
                mAdapter = null;
                mList.setAdapter(null);
            }
        };
        
        
  * @param handlerAppIntent When not null, apps capable of handling the original intent will
     *            be included in list of roots (in special section at bottom).
     */
    private List<Item> sortLoadResult(
            Collection<RootInfo> roots, @Nullable Intent handlerAppIntent) {
        final List<Item> result = new ArrayList<>();

        final List<RootItem> libraries = new ArrayList<>();
        final List<RootItem> others = new ArrayList<>();

        for (final RootInfo root : roots) {
            final RootItem item = new RootItem(root, mActionHandler);  //mActionHandler 

            Activity activity = getActivity();
            if (root.isHome() && !Shared.shouldShowDocumentsRoot(activity)) {
                continue;
            } else if (root.isLibrary()) {
                libraries.add(item);
            } else {
                others.add(item);
            }
        }
        
       ................
    }

上面中root记录根目录信息,mActionHandler继承AbstractActionHandler的,AbstractActionHandler提供了一些文档操作,包括根据父uri获取到子uri和其他信息。

  @Override
        public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
            Context context = mActivity;

            if (mState.stack.isRecents()) {

                if (DEBUG) Log.d(TAG, "Creating new loader recents.");
                return new RecentsLoader(context, mProviders, mState, mInjector.features);

            } else {
                Log.e("ddddddddddddd---000", mSearchMgr.isSearching()+","+mState.stack.getRoot().toString());
                Uri contentsUri = mSearchMgr.isSearching()
                        ? DocumentsContract.buildSearchDocumentsUri(
                            mState.stack.getRoot().authority,
                            mState.stack.getRoot().rootId,
                            mSearchMgr.getCurrentSearch())
                        : DocumentsContract.buildChildDocumentsUri(
                                mState.stack.peek().authority,
                                mState.stack.peek().documentId);

                if (mInjector.config.managedModeEnabled(mState.stack)) {
                    contentsUri = DocumentsContract.setManageMode(contentsUri);
                }

                if (DEBUG) Log.d(TAG,
                        "Creating new directory loader for: "
                                + DocumentInfo.debugString(mState.stack.peek()));

                return new DirectoryLoader(
                        mInjector.features,
                        context,
                        mState.stack.getRoot(),
                        mState.stack.peek(),
                        contentsUri,
                        mState.sortModel,
                        mDirectoryReloadLock,
                        mSearchMgr.isSearching());
            }
        }

else中通过authority和documentId得到保存了uri、cursor等信息的对象DirectoryLoader, 通过获取到对应的uri,完成后再调用Model保存,我们进入DirectoryLoader看看这个loader。

###DirectoryLoader.java###

 @Override
88    public final DirectoryResult loadInBackground() {

		   ........................
96        final ContentResolver resolver = getContext().getContentResolver();
97        final String authority = mUri.getAuthority();
98
99        final DirectoryResult result = new DirectoryResult();
100        result.doc = mDoc; 
102        ContentProviderClient client = null;
103        Cursor cursor;
104        try {
105            client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
106            if (mDoc.isInArchive()) {
107                ArchivesProvider.acquireArchive(client, mUri);
108            }
109            result.client = client;
110
111            Resources resources = getContext().getResources();
112            if (mFeatures.isContentPagingEnabled()) {
113                Bundle queryArgs = new Bundle();
114                mModel.addQuerySortArgs(queryArgs);
115
116                // TODO: At some point we don't want forced flags to override real paging...
117                // and that point is when we have real paging.
118                DebugFlags.addForcedPagingArgs(queryArgs);
119
120                cursor = client.query(mUri, null, queryArgs, mSignal);
121            } else {
122                cursor = client.query(
123                        mUri, null, null, null, mModel.getDocumentSortQuery(), mSignal);
124            }
125
126            if (cursor == null) {
127                throw new RemoteException("Provider returned null");
128            }
129
130            cursor.registerContentObserver(mObserver);
131
132            cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1);
133
134            if (mSearchMode && !mFeatures.isFoldersInSearchResultsEnabled()) {
135                // There is no findDocumentPath API. Enable filtering on folders in search mode.
136                cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES);
137            }
138
139            // TODO: When API tweaks have landed, use ContentResolver.EXTRA_HONORED_ARGS
140            // instead of checking directly for ContentResolver.QUERY_ARG_SORT_COLUMNS (won't work)
141            if (mFeatures.isContentPagingEnabled()
142                        && cursor.getExtras().containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
143                if (VERBOSE) Log.d(TAG, "Skipping sort of pre-sorted cursor. Booya!");
144            } else {
145                cursor = mModel.sortCursor(cursor);
146            }
147            result.cursor = cursor;
148        } catch (Exception e) {
149            Log.w(TAG, "Failed to query", e);
150            result.exception = e;
151        } finally {
152            synchronized (this) {
153                mSignal = null;
154            }wenj
155            // TODO: Remove this call.
156            ContentProviderClient.releaseQuietly(client);
157        }
158
159        return result;
160    }
161

获取到目录的cursor,并保存到DirectoryResult中,下面我们看看view,即ListDocumentHolder

###ListDocumentHolder.java###

    @Override
    public void bind(Cursor cursor, String modelId) {
        assert (cursor != null);

        mModelId = modelId;

        mDoc.updateFromCursor(cursor, getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY));

        mIconHelper.stopLoading(mIconThumb);

        .................
     }

###DocumentInfo###

    public void updateFromCursor(Cursor cursor, String authority) {
        this.authority = authority;
        this.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
        this.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
        this.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
        this.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
        this.flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
        this.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
        this.size = getCursorLong(cursor, Document.COLUMN_SIZE);
        this.icon = getCursorInt(cursor, Document.COLUMN_ICON);
        this.deriveFields();
        ///M: DRM refactory, get drm info from cursor
        this.isDrm = getCursorInt(cursor, MediaStore.MediaColumns.IS_DRM) > 0;
        this.drmMethod = getCursorInt(cursor, MtkMediaStore.MediaColumns.DRM_METHOD);
        this.data = getCursorString(cursor, MediaStore.MediaColumns.DATA);
    }

在bind中通过cursor获取目录下各个目录/文件的名称,大小,类型等等信息。

好了,这个时候,我们大体知道了DocumentsUI中文件的存储形式,是一个树状结构。下面我们来实现一下我们刚开始的需求,备份下载中的文件。

 void cpoyMainDownloadToCurrentUser(){

        DownloadRoot downloadRoot =  getCurrentDownloadRoot(getApplicationContext());//获取当期下载download的信息
        String parentDocumentId = downloadRoot.documentId;
        //获取download目录下的文件和文件夹
        List<DownloadFileEntry> fileEntries = getDownloadFiles();//获取住用户下载download目录信息
        //备份这些文件和整个文件夹
        for (DownloadFileEntry entry : fileEntries){

            try {
                Uri parentDocumentUri = DocumentsContract.buildDocumentUri(entry.authority, parentDocumentId);
                Uri documentUri = DocumentsContract.buildDocumentUri(entry.authority, entry.documentId);
                Log.e("uri", "documentUri=" + documentUri + ", parentDocumentUri=" + parentDocumentUri);

                String downloadPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath();
                if (entry.type==1){//文件夹
                    //判断本地有相同命名的文件夹
                    int fileIndex = 0;
                    File file = new File(downloadPath+"/"+entry.name);
                    while (file.isDirectory()){
                        fileIndex++;
                        file = new File(downloadPath+"/"+fileIndex+"_"+entry.name);
                    }
                    file.mkdir();

                    int sucess = copyDir(entry, file.getAbsolutePath());
                }else {
                    //判断本地有相同命名的文件
                    int fileIndex = 0;
                    File file = new File(downloadPath+"/"+entry.name);
                    while (file.exists()){
                        fileIndex++;
                        file = new File(downloadPath+"/"+fileIndex+"_"+entry.name);

                    }
                    int sucess = copyFile(entry, file.getAbsolutePath());
                }
                //Build.VERSION.SDK_INT >= 24中DocumentsContract也有封装了一些方法,如重命名,移动,删除等
public class DownloadRoot {
    public String authority;
    public String rootId;
    public int flags;
    public int icon;
    public String title;
    public String summary;
    public String documentId;
    public long availableBytes;
    public String mimeType;

    @Override
    public String toString() {
        return authority+","+rootId+","+documentId+","+title+","+mimeType+","+summary+","+availableBytes+","+flags+","+icon;
    }


    public static DownloadRoot fromDownloadRootCursor(String authority, Cursor cursor){
        final DownloadRoot root = new DownloadRoot();
        root.authority = authority;
        root.rootId = getCursorString(cursor, DocumentsContract.Root.COLUMN_ROOT_ID);
        root.flags = getCursorInt(cursor, DocumentsContract.Root.COLUMN_FLAGS);
        root.icon = getCursorInt(cursor, DocumentsContract.Root.COLUMN_ICON);
        root.title = getCursorString(cursor, DocumentsContract.Root.COLUMN_TITLE);
        root.summary = getCursorString(cursor, DocumentsContract.Root.COLUMN_SUMMARY);
        root.documentId = getCursorString(cursor, DocumentsContract.Root.COLUMN_DOCUMENT_ID);
        root.availableBytes = getCursorLong(cursor, DocumentsContract.Root.COLUMN_AVAILABLE_BYTES);
        root.mimeType = getCursorString(cursor, DocumentsContract.Root.COLUMN_MIME_TYPES);
//        root.deriveFields();
        return root;
    }

    public Uri getUri() {
        return DocumentsContract.buildRootUri(authority, rootId);
    }


    public static String getCursorString(Cursor cursor, String columnName) {
        if (cursor == null)
            return null;
        final int index = cursor.getColumnIndex(columnName);
        return ((index != -1) && !cursor.isClosed()) ? cursor.getString(index) : null;
    }

    /**
     * Missing or null values are returned as -1.
     */
    public static long getCursorLong(Cursor cursor, String columnName) {
        final int index = cursor.getColumnIndex(columnName);
        String value = null;
        if (index == -1) return -1;
        /// M: seldom NPE, no side-effect of using default size
        try {
            value = cursor.getString(index);
        } catch (NullPointerException e) {
            e.printStackTrace();
            return -1;
        }
        if (value == null) return -1;
        try {
            return Long.parseLong(value);
        } catch (NumberFormatException e) {
            return -1;public class DownloadRoot {
    public String authority;
    public String rootId;
    public int flags;
    public int icon;
    public String title;
    public String summary;
    public String documentId;
    public long availableBytes;
    public String mimeType;

    @Override
    public String toString() {
        return authority+","+rootId+","+documentId+","+title+","+mimeType+","+summary+","+availableBytes+","+flags+","+icon;
    }


    public static DownloadRoot fromDownloadRootCursor(String authority, Cursor cursor){
        final DownloadRoot root = new DownloadRoot();
        root.authority = authority;
        root.rootId = getCursorString(cursor, DocumentsContract.Root.COLUMN_ROOT_ID);
        root.flags = getCursorInt(cursor, DocumentsContract.Root.COLUMN_FLAGS);
        root.icon = getCursorInt(cursor, DocumentsContract.Root.COLUMN_ICON);
        root.title = getCursorString(cursor, DocumentsContract.Root.COLUMN_TITLE);
        root.summary = getCursorString(cursor, DocumentsContract.Root.COLUMN_SUMMARY);
        root.documentId = getCursorString(cursor, DocumentsContract.Root.COLUMN_DOCUMENT_ID);
        root.availableBytes = getCursorLong(cursor, DocumentsContract.Root.COLUMN_AVAILABLE_BYTES);
        root.mimeType = getCursorString(cursor, DocumentsContract.Root.COLUMN_MIME_TYPES);
//        root.deriveFields();
        return root;
    }

    public Uri getUri() {
        return DocumentsContract.buildRootUri(authority, rootId);
    }


    public static String getCursorString(Cursor cursor, String columnName) {
        if (cursor == null)
            return null;
        final int index = cursor.getColumnIndex(columnName);
        return ((index != -1) && !cursor.isClosed()) ? cursor.getString(index) : null;
    }

    /**
     * Missing or null values are returned as -1.
     */
    public static long getCursorLong(Cursor cursor, String columnName) {
        final int index = cursor.getColumnIndex(columnName);
        String value = null;
        if (index == -1) return -1;
        /// M: seldom NPE, no side-effect of using default size
        try {
            value = cursor.getString(index);
        } catch (NullPointerException e) {
            e.printStackTrace();
            return -1;
        }
        if (value == null) return -1;
        try {
            return Long.parseLong(value);
        } catch (NumberFormatException e) {
            return -1;
        }
    }

    /**
     * Missing or null values are returned as 0.
     */
    public static int getCursorInt(Cursor cursor, String columnName) {
        final int index = cursor.getColumnIndex(columnName);
        return ((index != -1) && !cursor.isClosed()) ? cursor.getInt(index) : 0;
    }

    public boolean isDirectory() {
        return DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
    }

}public class DownloadRoot {
    public String authority;
    public String rootId;
    public int flags;
    public int icon;
    public String title;
    public String summary;
    public String documentId;
    public long availableBytes;
    public String mimeType;

    @Override
    public String toString() {
        return authority+","+rootId+","+documentId+","+title+","+mimeType+","+summary+","+availableBytes+","+flags+","+icon;
    }


    public static DownloadRoot fromDownloadRootCursor(String authority, Cursor cursor){
        final DownloadRoot root = new DownloadRoot();
        root.authority = authority;
        root.rootId = getCursorString(cursor, DocumentsContract.Root.COLUMN_ROOT_ID);
        root.flags = getCursorInt(cursor, DocumentsContract.Root.COLUMN_FLAGS);
        root.icon = getCursorInt(cursor, DocumentsContract.Root.COLUMN_ICON);
        root.title = getCursorString(cursor, DocumentsContract.Root.COLUMN_TITLE);
        root.summary = getCursorString(cursor, DocumentsContract.Root.COLUMN_SUMMARY);
        root.documentId = getCursorString(cursor, DocumentsContract.Root.COLUMN_DOCUMENT_ID);
        root.availableBytes = getCursorLong(cursor, DocumentsContract.Root.COLUMN_AVAILABLE_BYTES);
        root.mimeType = getCursorString(cursor, DocumentsContract.Root.COLUMN_MIME_TYPES);
//        root.deriveFields();
        return root;
    }

    public Uri getUri() {
        return DocumentsContract.buildRootUri(authority, rootId);
    }


    public static String getCursorString(Cursor cursor, String columnName) {
        if (cursor == null)
            return null;
        final int index = cursor.getColumnIndex(columnName);
        return ((index != -1) && !cursor.isClosed()) ? cursor.getString(index) : null;
    }

    /**
     * Missing or null values are returned as -1.
     */
    public static long getCursorLong(Cursor cursor, String columnName) {
        final int index = cursor.getColumnIndex(columnName);
        String value = null;
        if (index == -1) return -1;
        /// M: seldom NPE, no side-effect of using default size
        try {
            value = cursor.getString(index);
        } catch (NullPointerException e) {
            e.printStackTrace();
            return -1;
        }
        if (value == null) return -1;
        try {
            return Long.parseLong(value);
        } catch (NumberFormatException e) {
            return -1;
        }
    }

    /**
     * Missing or null values are returned as 0.
     */
    public static int getCursorInt(Cursor cursor, String columnName) {
        final int index = cursor.getColumnIndex(columnName);
        return ((index != -1) && !cursor.isClosed()) ? cursor.getInt(index) : 0;
    }

    public boolean isDirectory() {
        return DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
    }

}


        }
    }

    /**
     * Missing or null values are returned as 0.
     */
    public static int getCursorInt(Cursor cursor, String columnName) {
        final int index = cursor.getColumnIndex(columnName);
        return ((index != -1) && !cursor.isClosed()) ? cursor.getInt(index) : 0;
    }

    public boolean isDirectory() {
        return DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
    }

}

            }catch (Exception e) {
                e.printStackTrace();
            }
        }

    }



    List<DownloadFileEntry> queryDocuments(String authority, String parentDocumentId){

        List<DownloadFileEntry> retsul = new ArrayList<>();

        Uri mUri = DocumentsContract.buildChildDocumentsUri(authority, parentDocumentId);
        mUri = maybeAddUserId(mUri, 0);
        ContentResolver resolver = getContentResolver();
        ContentProviderClient client = resolver.acquireUnstableContentProviderClient (mUri.getAuthority());
        Cursor c = null;
        try {
            c = client.query(mUri, null, null, null, null);
            if (c!=null){
                Log.e("====parentDocumentId", parentDocumentId+","+mUri+","+c.getCount());
                while (c.moveToNext()){
                    DownloadFileEntry itemInfo = getEntryFromCursor(c, authority);
                    Uri parentDocumentUri = DocumentsContract.buildDocumentUri(authority, parentDocumentId);
                    Uri documentUri = DocumentsContract.buildDocumentUri(authority, itemInfo.documentId);
                    Log.e("queryDocuments","documentUri="+documentUri+", parentDocumentUri="+parentDocumentUri);
                    retsul.add(itemInfo);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (c!=null) {
                c.close();
            }
        }
        return retsul;
    }

    private List<DownloadFileEntry> getDownloadFiles() {
        List<DownloadFileEntry> fileEntries = new ArrayList<>();
        Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE);
        final List<ResolveInfo> providers = getPackageManager().queryIntentContentProviders(intent, 0);
        for (ResolveInfo info : providers) {
            String authority = info.providerInfo.authority;
            Uri rootsUri = DocumentsContract.buildRootsUri(authority);
            if ("com.android.providers.downloads.documents".equals(authority)){
                //只获取download的目录霞的目录。
                ContentResolver resolver = getContentResolver();
                ContentProviderClient client = null;
                Cursor cursor = null;
                try {
                    client = resolver.acquireUnstableContentProviderClient (authority);
                    cursor = client.query(rootsUri, null, null, null, null);
                    if (cursor!=null && cursor.moveToFirst()){
                        DownloadRoot root = DownloadRoot.fromDownloadRootCursor(authority, cursor);
                        Log.e("getDownloadFiles", root.toString()+","+root.getUri()+",");
                        fileEntries.addAll(queryDocuments(root.authority, root.documentId));
                    }

                } catch (Exception e) {

                } finally {
                    if (cursor!=null){
                        cursor.close();
                    }
                }

            }


        }



        return fileEntries;
    }


    //0:成功 ,1:失败
    public int copyDir(DownloadFileEntry fromEntry, String toFileDir){
        int result = 0;
        //创建目录
        File toList=new File(toFileDir);
        if(!toList.exists()){
            toList.mkdirs();
        }

        //要复制的文件目录Entry
        List<DownloadFileEntry> fromList = queryDocuments(fromEntry.authority, fromEntry.documentId);

        //判断文件是否存在
        if(fromList==null){
            return result;
        }

        //遍历要复制的全部文件
        for(int i=0;i<fromList.size();i++){
            DownloadFileEntry itemEntry = fromList.get(i);
            if (itemEntry.type==1){
                //文件夹
                result = result | copyDir(itemEntry, toFileDir+"/ "+itemEntry.name+"/" );
            }else {
                //文件
                result = result | copyFile(itemEntry, toFileDir+"/ "+itemEntry.name);
            }

        }
        return result;
    }

    /**
     * 拷贝文件
     */
    public int copyFile(DownloadFileEntry entry, String toFile){
        try {
            ContentResolver resolver = getContentResolver();
            String display_name = entry.name;
            FileOutputStream fos = new FileOutputStream(toFile);

            InputStream is =  null ;
            ByteArrayOutputStream baos =  new  ByteArrayOutputStream();
            Uri partURI = DocumentsContract.buildDocumentUri(entry.authority, entry.documentId);
            partURI = maybeAddUserId(partURI, 0);
            is = resolver.openInputStream(partURI);
            String dddd = is.toString();
            Log.e("=====", entry.name+"----"+ dddd);
            byte [] buffer =  new   byte [ 1024 ];
            int  len = is.read(buffer);
            while  (len >=  0 )
            {
                baos.write(buffer,  0 , len);
                len = is.read(buffer);
            }
            baos.writeTo(fos);
            Log.e("imprt img sucess", ""+display_name);

            if  (is !=  null ) {
                try  {
                    is.close();
                }  catch  (IOException e) {

                }
            }
            if(fos !=null ){
                try  {
                    fos.close();
                }  catch  (IOException e) {
                }
            }
        }catch (Exception e){
            e.printStackTrace();
            return 1;
        }

        return 0;
    }


    public DownloadFileEntry getEntryFromCursor(Cursor cursor, String authority) {

        DownloadFileEntry info = new DownloadFileEntry();
        info.authority = authority;
        info.documentId = getCursorString(cursor, DocumentsContract.Document.COLUMN_DOCUMENT_ID);
        info.mimeType = getCursorString(cursor, DocumentsContract.Document.COLUMN_MIME_TYPE);
        info.name = getCursorString(cursor, DocumentsContract.Document.COLUMN_DISPLAY_NAME);
        info.lastModify = getCursorLong(cursor, DocumentsContract.Document.COLUMN_LAST_MODIFIED);
        info.bytesize = getCursorLong(cursor, DocumentsContract.Document.COLUMN_SIZE);
        info.icon = getCursorInt(cursor, DocumentsContract.Document.COLUMN_ICON);
        if (DocumentsContract.Document.MIME_TYPE_DIR.equals(info.mimeType)){
            info.icon = R.mipmap.ic_launcher;
            info.type = 1;
        }else if (info.mimeType.startsWith("application/vnd.android.package-archive")){
            info.icon = R.mipmap.ic_launcher;
        }else if (info.mimeType.startsWith("image/")){
            info.icon = R.mipmap.ic_launcher;
        }else if (info.mimeType.startsWith("video/")){
            info.icon = R.mipmap.ic_launcher;
        }else if (info.mimeType.startsWith("audio/")){
            info.icon = R.mipmap.ic_launcher;
        }else {
            info.icon = R.mipmap.ic_launcher;
        }


        return info;
    }

    /**
     * Missing or null values are returned as 0.
     */
    public static int getCursorInt(Cursor cursor, String columnName) {
        final int index = cursor.getColumnIndex(columnName);
        return ((index != -1) && !cursor.isClosed()) ? cursor.getInt(index) : 0;
    }
    public static String getCursorString(Cursor cursor, String columnName) {
        if (cursor == null)
            return null;
        final int index = cursor.getColumnIndex(columnName);
        return ((index != -1) && !cursor.isClosed()) ? cursor.getString(index) : null;
    }
    /**
     * Missing or null values are returned as -1.
     */
    public static long getCursorLong(Cursor cursor, String columnName) {
        final int index = cursor.getColumnIndex(columnName);
        String value = null;
        if (index == -1) return -1;
        /// M: seldom NPE, no side-effect of using default size
        try {
            value = cursor.getString(index);
        } catch (NullPointerException e) {
            e.printStackTrace();
            return -1;
        }
        if (value == null) return -1;
        try {
            return Long.parseLong(value);
        } catch (NumberFormatException e) {
            return -1;
        }
    }

    public class DownloadFileEntry implements Serializable {
        public int icon;//图标
        public String name;//名字
        public String path;//路径
        public long lastModify;//修改时间,时间戳
        public String time;//显示年月日
        public long bytesize;//文件大小
        public String size;//文件大小,显示,kb, m, g
        public int type;//类型, 0:文件,1:文件夹

        public boolean isSelected = false;

        public String documentId;
        public String parentDocument;
        public String authority;
        public String mimeType;

        @Override
        public String toString() {
            return parentDocument+","+documentId+","+authority+","+mimeType+","+name+","+type+","+path+","+size+","+time+","+icon;
        }
    }

public class DownloadRoot {
    public String authority;
    public String rootId;
    public int flags;
    public int icon;
    public String title;
    public String summary;
    public String documentId;
    public long availableBytes;
    public String mimeType;

    @Override
    public String toString() {
        return authority+","+rootId+","+documentId+","+title+","+mimeType+","+summary+","+availableBytes+","+flags+","+icon;
    }


    public static DownloadRoot fromDownloadRootCursor(String authority, Cursor cursor){
        final DownloadRoot root = new DownloadRoot();
        root.authority = authority;
        root.rootId = getCursorString(cursor, DocumentsContract.Root.COLUMN_ROOT_ID);
        root.flags = getCursorInt(cursor, DocumentsContract.Root.COLUMN_FLAGS);
        root.icon = getCursorInt(cursor, DocumentsContract.Root.COLUMN_ICON);
        root.title = getCursorString(cursor, DocumentsContract.Root.COLUMN_TITLE);
        root.summary = getCursorString(cursor, DocumentsContract.Root.COLUMN_SUMMARY);
        root.documentId = getCursorString(cursor, DocumentsContract.Root.COLUMN_DOCUMENT_ID);
        root.availableBytes = getCursorLong(cursor, DocumentsContract.Root.COLUMN_AVAILABLE_BYTES);
        root.mimeType = getCursorString(cursor, DocumentsContract.Root.COLUMN_MIME_TYPES);
//        root.deriveFields();
        return root;
    }

    public Uri getUri() {
        return DocumentsContract.buildRootUri(authority, rootId);
    }


    public static String getCursorString(Cursor cursor, String columnName) {
        if (cursor == null)
            return null;
        final int index = cursor.getColumnIndex(columnName);
        return ((index != -1) && !cursor.isClosed()) ? cursor.getString(index) : null;
    }

    /**
     * Missing or null values are returned as -1.
     */
    public static long getCursorLong(Cursor cursor, String columnName) {
        final int index = cursor.getColumnIndex(columnName);
        String value = null;
        if (index == -1) return -1;
        /// M: seldom NPE, no side-effect of using default size
        try {
            value = cursor.getString(index);
        } catch (NullPointerException e) {
            e.printStackTrace();
            return -1;
        }
        if (value == null) return -1;
        try {
            return Long.parseLong(value);
        } catch (NumberFormatException e) {
            return -1;
        }
    }

    /**
     * Missing or null values are returned as 0.
     */
    public static int getCursorInt(Cursor cursor, String columnName) {
        final int index = cursor.getColumnIndex(columnName);
        return ((index != -1) && !cursor.isClosed()) ? cursor.getInt(index) : 0;
    }

    public boolean isDirectory() {
        return DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
    }

}

更正一下:
上面说的,就算你有准确的文件sd路径,也无法跨进程读取到文件的流,是不对的,是可以通过file还读取到的。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值