Android 5.0文件管理操作公开

网上对于这个功能只有只言片语和混淆的代码,我打算将它公开,借以此文为记录。
首先是研究如何将Android的路径转化为Document所能识别的Uri:(本例为Windows路径)

public static void main(String[] args){
    String uriFileStr;//目标地址:content://com.android.externalstorage.documents/document/BAB8-19F7%3A生物信息学%2F[数据仓库].(Building.The.Data.Warehouse,.4ed),.Inmon,.文字版(ED2000.COM).pdf,这是最后要生成的字符串
    String uriDirectoryStr;//目标地址:content://com.android.externalstorage.documents/tree/BAB8-19F7%3A生物信息学%2FPerl
    String realFilePath="D:\\下载\\生物信息学\\[数据仓库].(Building.The.Data.Warehouse,.4ed),.Inmon,.文字版(ED2000.COM).pdf";
    String realDirectoryPath="D:\\下载\\生物信息学\\Perl";
    uriFileStr=buildDocumentUriUsingRealPath(realFilePath);
    uriDirectoryStr=buildDocumentUriUsingRealPath(realDirectoryPath);
    System.out.println(uriFileStr);
    System.out.println(uriDirectoryStr);
}

private static String buildDocumentUriUsingRealPath(String fileStr){
    File file=new File(fileStr);
    StringBuilder sb=null;
    if(file.isFile()){
        sb=new StringBuilder("content://com.android.externalstorage.documents/document/BAB8-19F7%3A");
        String[] paths=fileStr.split("\\\\");
        if(paths.length>=3){
            for(int i=2;i<paths.length;i++){
                sb.append(paths[i]);
                if(i<paths.length-1){
                    sb.append("%2F");
                }
            }
        }
    }else{
        sb=new StringBuilder("content://com.android.externalstorage.documents/tree/BAB8-19F7%3A");
        String[] paths=fileStr.split("\\\\");
        if(paths.length>=3){
            for(int i=2;i<paths.length;i++){
                sb.append(paths[i]);
                if(i<paths.length-1){
                    sb.append("%2F");
                }
            }
        }
    }
    return sb.toString();
}

运行的结果为:
这里写图片描述

接下来是针对这个文件关键操作的授权和判断权限等工具类

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.UriPermission;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.support.v4.provider.DocumentFile;
import android.util.Log;
import android.widget.Toast;

import com.kk.kkfilemanager.R;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.util.List;

/**
 * Created by Luxuan on 2016/9/5.
 */
//本工具类主要针对Storage Access Framework的Android 5.0 6.0版本
public class FileOperationUtil {

    //测试信息
    private static final String TAG="FileOperationUtil";

    public static int OSApiLevel(){
        return Build.VERSION.SDK_INT;
    }

    public static void performNewDocument(Context mContext,String path, String dest){
        FileInputStream inStream=null;
        OutputStream outStream=null;
        FileChannel inChannel=null;
        FileChannel outChannel=null;
        try{
            inStream=new FileInputStream(new File(path));
            DocumentFile targetDocument= dest.createFile(MimeUtils.guessMimeTypeFromExtension(Util.getExtFromFilename(newFile.getName()))
                ,newFile.getName());//需要对targetDocument先生成一份,然后再读写文件。
            if(targetDocument!=null){
                outStream=mContext.getContentResolver().openOutputStream(targetDocument.getUri());
            }
            if (outStream != null) {
                // Both for SAF and for Kitkat, write to output stream.
                byte[] buffer = new byte[4096]; // MAGIC_NUMBER
                int bytesRead;
                while ((bytesRead = inStream.read(buffer)) != -1) {
                    outStream.write(buffer, 0, bytesRead);
                }
            }
        } catch (Exception e) {

        } finally {
            try {
                inStream.close();
                outStream.close();
                inChannel.close();
                outChannel.close();
            } catch (NullPointerException e) {
                // ignore exception
            } catch(IOException e){

            }
        }
    }

    public static void performDeleteDocument(Context context, String pathUri){
        Uri uri=Uri.parse(pathUri);
        if(uri!=null){
            DocumentsContract.deleteDocument(context.getContentResolver(),uri);
        }
    }

    public static void createDirectory(Context context,String uriString, String directoryName){
        ContentResolver contentResolver=context.getContentResolver();
        Uri docUri=DocumentsContract.buildDocumentUriUsingTree(Uri.parse(uriString),
                DocumentsContract.getTreeDocumentId(Uri.parse(uriString)));
        Uri directoryUri=DocumentsContract
                .createDocument(contentResolver,docUri, DocumentsContract.Document.MIME_TYPE_DIR,directoryName);
        if(directoryUri!=null){
            Log.i(TAG,String.format("Create directory : %s, Document Uri : %s, Create directory Uri : %s",
                    directoryName,docUri,directoryUri));
            Toast.makeText(context,String.format("Create a directory [%s]",directoryName), Toast.LENGTH_SHORT).show();
        }else{
            Log.w(TAG,String.format("Failed to create a directory : %s, Uri %s",directoryName,docUri));
            Toast.makeText(context,String.format("Failed to created a directory [%s]",directoryName), Toast.LENGTH_SHORT).show();
        }
    }

    public static void renameFile(Context context, String uriString, String newName){
        ContentResolver contentResolver=context.getContentResolver();
        Uri documentUri=DocumentsContract
                .renameDocument(contentResolver,Uri.parse(uriString),newName);
        if(documentUri!=null){
            Log.i(TAG,String.format("Document Uri : %s, Renamed Uri : %s",
                    uriString,documentUri));
            Toast.makeText(context,String.format("Renamed [%s]",newName), Toast.LENGTH_SHORT).show();
        }else{
            Log.w(TAG,String.format("Failed to rename an item : %s, Uri %s",uriString,documentUri));
            Toast.makeText(context,String.format("Failed to rename"), Toast.LENGTH_SHORT).show();
        }
    }

    public static boolean hasExternalSDCardPermission(Context context){
        List<UriPermission> permissionList = context.getContentResolver().getPersistedUriPermissions();
        if(isHasReadPermission(permissionList)&&isHasWritePermission(permissionList)){
            return true;
        }
        return false;
    }

    private static boolean isHasReadPermission(List<UriPermission> permissionList){

        for(UriPermission uriPermission : permissionList){
            if(uriPermission.isReadPermission())
                return true;
        }
        return false;
    }

    private static boolean isHasWritePermission(List<UriPermission> permissionList){

        for(UriPermission uriPermission : permissionList){
            if(uriPermission.isWritePermission())
                return true;
        }
        return false;
    }

    public static void showAuthorizeDialog(final Context mContext){
        new AlertDialog.Builder(mContext)
                .setView(R.layout.sdcard_request_authorize)
                .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int which) {
                        Intent authorizedIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
                        ((Activity)mContext).startActivityForResult(authorizedIntent, 42);
                    }
                })
                .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int which) {
                        Toast.makeText(mContext, R.string.authorized_failed, Toast.LENGTH_SHORT).show();
                    }
                })
                .create().show();
    }

}

接下来是删除、复制、新建文件、重命名功能的5.0及以上版本的实现:

public void copy(Context mContext, List<String> origin, String dest,boolean isMove){
    //复制的时候origin,也就是原始复制的文件如果为内置SD卡的路径则不用转化为Uri
    String destUri=buildDocumentUriUsingRealPath(mContext, dest);
    for(String path : origin){
        if(path.contains(externalSdCardPath)){
            String pathUri=buildDocumentUriUsingRealPath(mContext, path);
            FileOperationUtil.performNewDocument(mContext,path,destUri);
            if(isMove){
                FileOperationUtil.performDeleteDocument(mContext,pathUri);
            }
        }else{
            FileOperationUtil.performNewDocument(mContext,path,destUri);
            if(isMove){
                File file=new File(path);
                file.delete();
                ArrayList<String> imageArray = new ArrayList<>();
                imageArray.add("image/jpeg");
                imageArray.add("image/gif");
                imageArray.add("image/png");
                imageArray.add("image/bmp");
                if (imageArray.contains(MimeUtils.guessMimeTypeFromExtension(Util.getExtFromFilename(path)))) {
                    ContentResolver resolver = mContext.getContentResolver();
                    resolver.delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, MediaStore.Images.Media.DATA
                            + "=?", new String[]{path});
                    Intent intent = new Intent(ACTION_FILE_DELETED);
                    mContext.sendBroadcast(intent);//配适三星
                }
            }
        }
    }
}

public void delete(Context context, List<FileInfo> deleteList){
    for(FileInfo fileInfo: deleteList){
        String path=fileInfo.filePath;
        String pathUri=buildDocumentUriUsingRealPath(context,path);
        FileOperationUtil.performDeleteDocument(context,pathUri);
    }

}

public void createNewDirectory(Context context, String path, String newName){
    String newDirectoryUri=buildDocumentUriUsingRealPath(context,path);
    FileOperationUtil.createDirectory(context,newDirectoryUri,newName);
}

public void rename(Context context, String oldFilePath, String newName){
    String renameUri=buildDocumentUriUsingRealPath(context,oldFilePath);
    FileOperationUtil.renameFile(context, renameUri, newName);
}

估计接下来就是测试和发布了。希望一切顺利。
权限不足,需要将获得到的权限给Service进行执行。
还是不行。
目前的问题是获得到的权限无法使用:
FLAG_GRANT_READ_URI_PERMISSION

int FLAG_GRANT_READ_URI_PERMISSION
If set, the recipient of this Intent will be granted permission to perform read operations on the URI in the Intent’s data and any URIs specified in its ClipData. When applying to an Intent’s ClipData, all URIs as well as recursive traversals through data or other ClipData in Intent items will be granted; only the grant flags of the top-level Intent are used.

Constant Value: 1 (0x00000001)

FLAG_GRANT_WRITE_URI_PERMISSION

int FLAG_GRANT_WRITE_URI_PERMISSION
If set, the recipient of this Intent will be granted permission to perform write operations on the URI in the Intent’s data and any URIs specified in its ClipData. When applying to an Intent’s ClipData, all URIs as well as recursive traversals through data or other ClipData in Intent items will be granted; only the grant flags of the top-level Intent are used.

Constant Value: 2 (0x00000002)
不起作用。
原是因为我不能使用自己生成的Uri,必须使用系统内的Uri。
所以将生成DocumentFile的Uri改成一下形式:

//直接返回URI
public static DocumentFile getDocumentFilePath(Context context, String path,String sdCardUri) {
    DocumentFile document = DocumentFile.fromTreeUri(context, Uri.parse(sdCardUri));
    String[] parts = path.split("/");
    for (int i = 3; i < parts.length; i++) {
        document= document.findFile(parts[i]);
    }
    return document;
}

目前复制存在问题。
参考以下项目,成功解决:
https://github.com/zjbpku/filemanager

发布了341 篇原创文章 · 获赞 51 · 访问量 32万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览