Android实现操作U盘,解决写入不完整的问题

因为项目需要,APP要把数据导出到U盘中,下文将介绍下Android5.0以上怎么操作U盘。重点就是写入文件之后,必须调用内核文件同步函数,否则可能存在写入不完全的问题.这里不详叙DocumentFile相关的操作,网上已经有很多了.

获取外部存储

 private List<StorageVolume> getVolume() {
        StorageManager manager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
        if (manager != null) {
             // 过滤掉不符合需求的设备,USB设备肯定是可移动的,这里注意存储卡也会出来 最好是用对话框来提示用户要操作那个存储设备
            return ListUtils.filiter(Utils.getVolume(manager, mActivity), it -> Utils.compatStorageRemoveable(it));
        }
        return null;
    }

DocumentFile授权

private void requestUri(String uuid) {
        //记住授权的ID,授权之前建议用界面引导用户如何授权,授权界面是Android自带的,程序不可控!
        currentUUID = uuid;
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
        startActivityForResult(intent, DOCUMENT_TREE_REQUEST);
    }

授权之后保存权限


    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            if (requestCode == DOCUMENT_TREE_REQUEST) {
                Uri treeUri;
                //获取授权之后的Uri
                treeUri = data.getData();
                if (treeUri != null) {
                    String value = treeUri.toString();
                    String uuid = value.substring(value.lastIndexOf("/") + 1).replace("%3A", "");
                    //判断用户选的设备是否是我们想要的设备!
                    if (uuid.equals(currentUUID)) {
                    	//将URL存起来 后续访问时可以直接用
                        PreferencesUtils.put(ACTION_OPEN_DOCUMENT_TREE_URL + uuid, value);
                        final int takeFlags = data.getFlags()
                                & (Intent.FLAG_GRANT_READ_URI_PERMISSION
                                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                                //固化授权信息,下次该设备插入次APP就无需授权
                        getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
                    } else {
                        appendMessage(new Message("您选择的不是移动存储设备的根目录或不是该移动存储设备!", CallBack.ERROR));
                    }
                }
            } 
        }
    }

读取文件

DocumentFile file = Utils.findPath(DocumentFile.fromTreeUri(mActivity, Uri.parse(uri)), "xj_data/" + fileName);
//拿到流了就不需要我多说什么了吧
InputStream inputStream = getContext().getContentResolver().openInputStream(file.getUri());

写入文件

//与读取文件差不多,先通过DocumentFile的相关方法找到文件,然后打开输出流,把流写进去就行
 OutputStream outputStream =context.getContentResolver().openOutputStream(dataFile.getUri());
 ...
 //流写完了必须调用内核方法强制sync到设备上,不然拔快了,文件还没有同步到外部存储设备上
  ((ParcelFileDescriptor.AutoCloseOutputStream) outputStream).getFD().sync();

操作工具类代码

package com.cnksi.upan.utils;

import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import android.support.v4.provider.DocumentFile;
import android.text.TextUtils;

import com.cnksi.common.utils.ExceptionLog;
import com.cnksi.core.utils.FileUtils;
import com.cnksi.upan.bean.Message;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class Utils {

 public static DocumentFile findPath(DocumentFile root, String path) {
        String[] split = path.split(File.separator);
        if (!root.exists()) {
            return null;
        } else {
            for (String s : split) {
                if (TextUtils.isEmpty(s)) {
                    continue;
                }
                DocumentFile file = root.findFile(s);
                if (file == null || !file.exists()) {
                    return null;
                } else {
                    root = file;
                }
            }
        }
        return root;
    }
	/**
	* 获取存储设备的UUID
	*/
    public static String compatStorageUUID(StorageVolume volume) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return volume.getUuid();
        } else {
            try {
                Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
                Method uuid = storageVolumeClazz.getDeclaredMethod("getUuid");
                uuid.setAccessible(true);
                return (String) uuid.invoke(volume);
            } catch (Exception e) {
            }
        }
        return null;
    }
	/**
	* 获取存储设备是否可移动
	*/
    public static boolean compatStorageRemoveable(StorageVolume volume) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return volume.isRemovable();
        } else {
            try {
                Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
                Method uuid = storageVolumeClazz.getDeclaredMethod("isRemovable");
                uuid.setAccessible(true);
                return (boolean) uuid.invoke(volume);
            } catch (Exception e) {
            }
        }
        return false;
    }
    /**
	* 获取存储设备的描述
	*/
    public static String compatStorageDesc(StorageVolume volume, Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return volume.getDescription(activity);
        } else {
            try {
                Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
                Method desc = storageVolumeClazz.getMethod("getDescription", Context.class);
                desc.setAccessible(true);
                return (String) desc.invoke(volume, activity);
            } catch (Exception ignored) {
            }
        }
        return null;
    }
   /**
	* 获取所有存储设备
	*/
    public static List<StorageVolume> getVolume(StorageManager manager, Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return manager.getStorageVolumes();
        } else {
            try {
                Method getVolume = StorageManager.class.getDeclaredMethod("getVolumeList", int.class, int.class);
                StorageVolume[] invoke = (StorageVolume[]) getVolume.invoke(manager, getUserId(context), 0);
                return Arrays.asList(invoke);
            } catch (Exception ignored) {

            }
            return null;
        }
    }

    private static int getUserId(Context context) {
        try {
            Method userId = Context.class.getDeclaredMethod("getUserId");
            return (int) userId.invoke(context);
        } catch (Exception ignored) {
        }
        return android.os.Process.myUid() / 100000;
    }

}

你可能感兴趣的:(Android,U盘写入)