Android获取usb上的U盘存储路径

在公司已经交付的几个项目中,因为板子硬件和系统的一些差别,导致经常出现获取U盘路径失败的问题,导致文件读写失败,之前这块代码已经经过几个人的修改,早就想拔掉这根刺了,趁最近手头项目不多解决了这个问题。

公司交付的项目使用了A83和rk3288两类板子,安卓系统版本都是低于6.0,参考了多篇博客并逐一尝试,并结合实际使用过程中遇到的问题修改为适合使用的代码,封装成一个java文件,无需依赖第三方库,仅仅一个java文件即可获取U盘路径。

需要注意的地方:Android是默认支持FAT32格式的U盘,其他格式的U盘如果拔插无反应和读写失败,可以把U盘格式化成FAT32格式。

我在使用过程中,A83的板子可以读写FAT32格式的U盘,读写不了NTFS格式的U盘,但是rk3288板子可以读写FAT32和NTFS这两种格式的U盘。其他格式没试过,但是把U盘格式化成FAT32是一定可以读写的。

自己写的获取U盘路径的工具类UsbFlashUtil,将这个类做成单例模式,在app一开始启动的时候就注册监听U盘拔插广播,用于获取U盘路径和监听拔插状态:

IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_SHARED);//如果SDCard未安装,并通过USB大容量存储共享返回
filter.addAction(Intent.ACTION_MEDIA_MOUNTED);//表明sd对象是存在并具有读/写权限
filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);//SDCard已卸掉,如果SDCard是存在但没有被安装
filter.addAction(Intent.ACTION_MEDIA_CHECKING);  //表明对象正在磁盘检查
filter.addAction(Intent.ACTION_MEDIA_EJECT);  //物理的拔出 SDCARD
filter.addAction(Intent.ACTION_MEDIA_REMOVED);  //完全拔出
filter.addDataScheme("file"); // 必须要有此行,否则无法收到广播
this.application.getApplicationContext().registerReceiver(receiverU, filter);

U盘拔插的广播接收者:

/**
* 获取U盘根路径
*/
public String getUsbPath() {return UsbFlashUtil.this.usbPath;}

/**
* 广播接收者
*/
private BroadcastReceiver receiverU = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
                //U盘插入
                String path = intent.getData().getPath();
                path = getCorrectPath(path);//获取正确的,完整的路径
                UsbFlashUtil.this.usbPath = path;
                Util.logE("------>U盘路径:"+UsbFlashUtil.this.usbPath);
                if (diskListenerList.size() > 0){
                    for (int i = 0; i < diskListenerList.size(); i++) {
                        if (null != diskListenerList.get(i))diskListenerList.get(i).onConnect();
                    }
                }
            } else if (Intent.ACTION_MEDIA_UNMOUNTED.equals(action) || Intent.ACTION_MEDIA_EJECT.equals(action)) {
                //U盘拔出
                UsbFlashUtil.this.usbPath = "";
                if (diskListenerList.size() > 0){
                    for (int i = 0; i < diskListenerList.size(); i++) {
                        if (null != diskListenerList.get(i))diskListenerList.get(i).onDisconnect();
                    }
                }
            }
        }
};

/**
* U盘连接状态回调
*/
public interface IUDiskListener{
    void onConnect();
    void onDisconnect();
}
private List<IUDiskListener> diskListenerList = new ArrayList<>();
public void setUDiskListener(IUDiskListener uDiskListener){
    diskListenerList.add(uDiskListener);
}
public void removeUDiskListener(IUDiskListener uDiskListener){
    diskListenerList.remove(uDiskListener);
}

这样子就可以获取到U盘的路径了。

食用方式也很简单,在app一开始启动的时候就注册监听广播,根据getUsbPath()这来判断是否插入了U盘(不是根据监听回调来判断,该回调作用是提供给一些界面需要知道U盘拔插动作),getUsbPath()得到的结果就是U盘的路径,getUsbPath()为空则U盘没插入或者获取失败。

实际使用中按上面贴的代码是可以使用了,项目有些机子就可以正常使用,获取到的正确U盘路径:“/mnt/usb_storage/udisk0”或者是“/mnt/usb_storage/USB_DISK1/udisk0”

但是,总是有些个别机子有些差异,还有U盘也有一些不一样。有些机子只获取到“mnt/usb_storage/USB_DISK1”这一段,并不是正确的完整的U盘路径,新格式化为写入数据是可以直接追加/udisk0的,但是有些U盘命名了名字的U盘并不能直接在末尾追加/udisk0,比如U盘命名了“xxx”,正确的路径应该是“/mnt/usb_storage/USB_DISK1/xxx”。如果你注意到logcat日志打印里,系统底层Void这个打印的是正确且完整的路径(是的,我也尝试过获取logcat日志,最后没找到正确的方法)。所以做了获取完整路径的判断:

private String getCorrectPath(String path) {
    if (!TextUtils.isEmpty(path)){
        int lastSeparator = path.lastIndexOf(File.separator);
        String endStr = path.substring(lastSeparator + 1, path.length());
        if (!TextUtils.isEmpty(endStr) && (endStr.contains("USB_DISK") || endStr.contains("usb_disk"))){//不区分大小写
            File file = new File(path);
            if (file.exists() && file.listFiles().length == 1 && file.listFiles()[0].isDirectory()){
                path = file.listFiles()[0].getAbsolutePath();
            }
        }
    }
    return path;
}

这样就完整了,可以获取拔插的U盘路径了。

实际使用过程中呢,有一点体验不好的地方,使用在在打开app之前就已经插入了U盘,需要app启动之后拔插一下U盘才能获取到U盘路径。在U盘拔插广播注册之后,去获取U盘路径,两种方式:1.通过mount命令获取,2.通过StorageManager这个对象获取。方式1可以获取内外所有存储路径,过滤一下可以获取到U盘正确的完整的路径,不需要再矫正追加,但是机子需要root;方式1可以获取到内外存储,范围比方式1小,需要过滤和判断挂载,路径不一定是完整的,需要矫正追加,但不需要机子是root过的。两种方式找合适自己的食用,两种方式其实也可以获取其他存储的路径,修改一下过滤条件就好。为了解决这个问题,试了好多的代码才总结好的。

    
    /**
     * 注册监听U盘拔插广播
     */
    public void registerBroadcast(Application application){
        this.application = application;
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_MEDIA_SHARED);//如果SDCard未安装,并通过USB大容量存储共享返回
        filter.addAction(Intent.ACTION_MEDIA_MOUNTED);//表明sd对象是存在并具有读/写权限
        filter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);//SDCard已卸掉,如果SDCard是存在但没有被安装
        filter.addAction(Intent.ACTION_MEDIA_CHECKING);  //表明对象正在磁盘检查
        filter.addAction(Intent.ACTION_MEDIA_EJECT);  //物理的拔出 SDCARD
        filter.addAction(Intent.ACTION_MEDIA_REMOVED);  //完全拔出
        filter.addDataScheme("file"); // 必须要有此行,否则无法收到广播
        this.application.getApplicationContext().registerReceiver(receiverU, filter);

        //在本app未启动前已经插着U盘的情况下,获取U盘路径
        List<String> list = getPathListByStorageManager();//根据StorageManager获取U盘路径
//        List<String> list = getPathByMount();//根据mount命令获取U盘路径
        if (list.size() > 0)UsbFlashUtil.this.usbPath = list.get(0);
    }

    /**
     * 根据StorageManager获取Usb插入的U盘路径
     * 可以获取内部存储、sd卡以及所有usb路径
     * 获取到的路径可能是不完整的,需要判断追加
     */
    private List<String> getPathListByStorageManager() {
        List<String> pathList = new ArrayList<>();
        try {
            StorageManager storageManager = (StorageManager)this.application.getSystemService(Context.STORAGE_SERVICE);
            Method method_volumeList = StorageManager.class.getMethod("getVolumeList");
            method_volumeList.setAccessible(true);
            Object[] volumeList = (Object[]) method_volumeList.invoke(storageManager);
            if (volumeList != null) {
                for (int i = 0; i < volumeList.length; i++) {
                    try {
                        String path = (String) volumeList[i].getClass().getMethod("getPath").invoke(volumeList[i]);
                        boolean isRemovable = (boolean) volumeList[i].getClass().getMethod("isRemovable").invoke(volumeList[i]);
                        String state = (String) volumeList[i].getClass().getMethod("getState").invoke(volumeList[i]);
//                        Util.logE("isRemovable:"+isRemovable+" / state:"+state+" / path:"+path);
                        if (isRemovable && "mounted".equalsIgnoreCase(state) && path.contains("usb_storage")){
                            pathList.add(getCorrectPath(path));//将正确的路径添加到集合中
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return pathList;
    }

    /**
     * 使用mount命令获取usb插入的U盘路径
     * 可以获取内部存储、外部存储、tf卡、otg、系统分区等路径,获取到的U盘路径是完整的正确的
     * 限制条件是机子必须得解开root
     */
    public static List<String> getPathByMount() {
        List<String> usbMemoryList = new ArrayList<>();
        try {
            Runtime runtime = Runtime.getRuntime();
            // 运行mount命令,获取命令的输出,得到系统中挂载的所有目录
            Process process = runtime.exec("mount");
            InputStream is = process.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            String line;
            BufferedReader br = new BufferedReader(isr);
            while ((line = br.readLine()) != null) {
                // 将常见的linux分区过滤掉
                if (line.contains("proc") || line.contains("tmpfs") || line.contains("media") || line.contains("asec") || line.contains("secure") || line.contains("system") || line.contains("cache")
                        || line.contains("sys") || line.contains("data") || line.contains("shell") || line.contains("root") || line.contains("acct") || line.contains("misc") || line.contains("obb")) {
                    continue;
                }
//                Util.logE("==========>line:"+line);
                if (!TextUtils.isEmpty(line) && line.contains("usb_storage")){//根据情况过来需要的字段
                    String items[] = line.split(" ");
                    if (null != items && items.length > 1) {
                        String path = items[1];
//                        Util.logE("------->path:"+path);
                        // 添加一些判断,确保是sd卡,如果是otg等挂载方式,可以具体分析并添加判断条件
                        if (path != null && !usbMemoryList.contains(path) && path.contains("sd"))usbMemoryList.add(items[1]);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return usbMemoryList;
    }

食用方式:在app开始加载第一个界面的时候就注册,调用registerBroadcast()方法,获取U盘路径和判断U盘是否插入调用getUsbPath()即可。需要接收U盘拔插动作调用setUDiskListener()监听,界面销毁的时候调用removeUDiskListener()方法取消监听。

完整代码连接:点击这里

补充:U盘拔插的时候,特别是U盘插入的时候,要过1秒多钟才接收到广播,如果想接收U盘插入广播快一些,可以在广播接收者判断插入那里加一个Intent.ACTION_MEDIA_CHECKING.equals(action)如下,会接收到两次广播,先接收到Intent.ACTION_MEDIA_CHECKING.equals(action)的广播,但是U盘路径矫正追加的时候失败,但是第二个广播Intent.ACTION_MEDIA_MOUNTED.equals(action)晚80毫秒左右,所以最终还是可以获取到正确的U盘路径

if (Intent.ACTION_MEDIA_MOUNTED.equals(action) || Intent.ACTION_MEDIA_CHECKING.equals(action)) {
    //U盘插入            
} else if (Intent.ACTION_MEDIA_UNMOUNTED.equals(action) || Intent.ACTION_MEDIA_EJECT.equals(action)) {
    //U盘拔出              
}

 

 

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值