Android获取外接U盘USB设备外接sd卡路径及文件的正确姿势

  • Post author:
  • Post category:其他


android应用开发中和U盘打交道的场景其实比较少,这次项目因为是做的汽车的中控系统里的一个视频播放应用,需求是可以播放插入的U盘里的视频。因为之前没有接触过这方面的东西,算是记录一下吧。主要分两步,第一:监听U盘的插拔;第二:获取U盘路径以及扫描该路径下所有文件。

1.监听U盘的插拔:

这里是通过系统广播来实现监听的,网上也有很多这方面的资料,但是很多说的方法是无效的,以下方式在5.0以上系统都验证过了是可以的。手里没有5.0以下的机子所以没验证5.0以下的系统,有需要的同学可以自己验证下。

public class ExternalStorageMonitor {
    public static final String TAG = "ExternalStorageMonitor";
    public static final String EXTERNAL_STORAGE_DEFAULT_PATH = "/mnt/media_rw";
    private static ExternalStorageMonitor mInstance;
    private List<ExternalStorageDeviceListener> deviceListeners;
    private Context mContext;

    public static ExternalStorageMonitor getInstance(Context context){
        if (mInstance == null){
            synchronized (ExternalStorageMonitor.class){
                if (mInstance == null){
                    mInstance = new ExternalStorageMonitor(context);
                }
            }
        }
        return mInstance;
    }

    private ExternalStorageMonitor(Context context) {
        mContext = context.getApplicationContext();
        deviceListeners = new ArrayList<>();
    }

    public boolean init(ExternalStorageDeviceListener listener) {
        addDeviceListener(listener);
        startMonitor();
        // 在系统启动前是否已经有设备插入
        detectDevice();
        return true;
    }

    public void addDeviceListener(ExternalStorageDeviceListener listener) {
        for (ExternalStorageDeviceListener deviceListener : deviceListeners) {
            if (listener == deviceListener) {
                return;
            }
        }
        deviceListeners.add(listener);
    }

    public void removeDeviceListener(ExternalStorageDeviceListener listener) {
        deviceListeners.remove(listener);
    }

    /**
     * 开始监听USB设备的插拔
     *
     */
    private void startMonitor() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);

        USBBroadCastReceiver usbBroadCastReceiver = new USBBroadCastReceiver();
        mContext.registerReceiver(usbBroadCastReceiver, filter);
    }

    /**
     * 检测是否有USB设备的插入
     */
    private void detectDevice() {
        UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
        if (usbManager != null) {
            HashMap<String, UsbDevice> deviceHashMap = usbManager.getDeviceList();
            if (deviceHashMap.isEmpty()) {
                return;
            }
            for (UsbDevice device : deviceHashMap.values()) {
                Log.d(TAG, "device name: " + device.getDeviceName() + "\nvendor id:" + device.getVendorId());
                dispatchAttachedListener();
            }
        }
    }

    private void dispatchAttachedListener() {
        for (ExternalStorageDeviceListener deviceListener : deviceListeners) {
            deviceListener.onAttached();
        }
    }

    private void dispatchDetachedListener() {
        for (ExternalStorageDeviceListener deviceListener : deviceListeners) {
            deviceListener.onDetached();
        }
    }

    public interface ExternalStorageDeviceListener {
        void onAttached();

        void onDetached();
    }

    private class USBBroadCastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, "onReceive: ");
            switch (intent.getAction()) {
                case UsbManager.ACTION_USB_DEVICE_ATTACHED:
                    Log.d(TAG, "U盘插入");
                    dispatchAttachedListener();
                    break;
                case UsbManager.ACTION_USB_DEVICE_DETACHED:
                    Log.d(TAG, "U盘拔出");
                    dispatchDetachedListener();
                    break;
                default:
                    break;
            }
        }
    }
}

2.获取U盘路径并扫描文件:

关于获取U盘路径花费了比较多的时间,中间遇到了不少问题,好在最后还是成功了。

首先是权限问题,U盘的挂载点和外接sd卡的路径是不同的,所以就算是申请了”android.permission.WRITE_EXTERNAL_STORAGE”和

“android.permission.READ_EXTERNAL_STORAGE”的权限,那也仅仅是能获取到外接sd卡路径的权限有了,还是没有权限获取U盘的路径。这就是为什么用adb shell命令查看到了U盘路径,但是代码里写死路径去扫描,却扫描不到任何文件的原因,这里需要在manifest里添加如下权限:

<uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE"
    tools:ignore="ProtectedPermissions" />

这个权限是系统应用权限,U盘的挂载路径是在/mnt/路径下的,而不是像外接sd卡一样大多是在/storage/目录下。

然后就是获取U盘路径了,因为我们这个车中控系统本身就是项目组其他组的同事定制做的,所以可以保证U盘的挂载路径唯一,我们应用层可以写死这个路径直接去扫描文件就可以了。但是也有方法动态获取的,我这边自己研究了下总结了两个方法:

/**
    * 根据label获取外部存储路径(此方法适用于android7.0以上系统)
    * @param context
    * @param label 内部存储:Internal shared storage    SD卡:SD card    USB:USB drive(USB storage)
    */
   @RequiresApi(api = Build.VERSION_CODES.N)
   public static String getExternalPath(Context context, String label) {
      String path = "";
      StorageManager mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
      //获取所有挂载的设备(内部sd卡、外部sd卡、挂载的U盘)
      List<StorageVolume> volumes = mStorageManager.getStorageVolumes();//此方法是android 7.0以上的
      try {
         Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
         //通过反射调用系统hide的方法
         Method getPath = storageVolumeClazz.getMethod("getPath");
         Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
//       Method getUserLabel = storageVolumeClazz.getMethod("getUserLabel");//userLabel和description是一样的
         for (int i = 0; i < volumes.size(); i++) {
            StorageVolume storageVolume = volumes.get(i);//获取每个挂载的StorageVolume
            // 通过反射调用getPath、isRemovable、userLabel
            String storagePath = (String) getPath.invoke(storageVolume); //获取路径
            boolean isRemovableResult = (boolean) isRemovable.invoke(storageVolume);//是否可移除
            String description = storageVolume.getDescription(context);//此方法是android 7.0以上的
            if (label.equals(description)){
               path = storagePath;
               break;
            }
            LogUtils.d(TAG+"getExternalPath--", " i=" + i + " ,storagePath=" + storagePath +  " ,description=" + description);
         }
      } catch (Exception e) {
         LogUtils.d(TAG+"getExternalPath--", " e:" + e);
      }
      return path;
   }
public static void getAllExternalPath(Context context) {
      StorageManager mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
      try {
         Class<?> volumeInfoClazz = Class.forName("android.os.storage.VolumeInfo");
         //这里需要说明一下,还有一个getVolumeList方法获取到的是StorageVolume数组(但是此数组不包含USB外接设备)
         Method getVolumeList = mStorageManager.getClass().getMethod("getVolumes");
         //获取所有挂载的设备(内部sd卡、外部sd卡、挂载的U盘)
         List result = (List) getVolumeList.invoke(mStorageManager);
//       final int size = Array.getLength(result);
         //通过反射调用系统hide的方法
         Method getPath = volumeInfoClazz.getMethod("getPath");
         Method getDescription = volumeInfoClazz.getMethod("getDescription");
         for (int i = 0; i < result.size(); i++) {
//          Object storageVolume = Array.get(result, i);
            Object storageVolume = result.get(i);
            // 通过反射调用getPath
            File filePath = (File) getPath.invoke(storageVolume); //获取路径
            String storagePath = filePath.getAbsolutePath();
            String description = (String) getDescription.invoke(storageVolume);
            LogUtils.d(TAG, " i=" + i + " ,storagePath=" + storagePath +  " ,description=" + description);
         }
      } catch (Exception e) {
         LogUtils.d(TAG, " e:" + e);
      }
   }

说明一下,第一个方法适用于Android7.0以上系统,可以很好的得到内置sd,外置sd卡,和usb外接设备的路径。

第二个方法这边只是提供了一个思路,可以根据具体不同情况分析得到的description来区分是sd卡的路径还是usb设备的路径,或者根据得到的storagePath路径来排除掉sd卡的,剩下的就是usb设备的路径了,因为不同厂商都会对android系统做不同程度的各种定制,导致外接sd卡的路径实在太多了,据不完全统计,外接sd卡常见的路径有如下:

/storage/emulated/0/

/storage/extSdCard

/mnt/external_sd/

/mnt/sdcard2/

/mnt/sdcard/external_sd/

/mnt/sdcard-ext/

/mnt/sdcard/

/storage/sdcard0/

/mnt/extSdCard/

/mnt/extsd/

/mnt/emmc/

/mnt/extern_sd/

/mnt/ext_sd/

/mnt/ext_card/

/mnt/_ExternalSD/

/sdcard2/

/sdcard/

/sdcard/sd/

/sdcard/external_sd/

/mnt/sd/

/mnt/

/storage/

/mnt/sdcard/sd/

/mnt/exsdcard/

/mnt/sdcard/extStorages/SdCard/

/ext_card/

/storage/extSdCard

真的是贼多,想要用第二种方法获取usb设备(即U盘)路径 的,基本可以从获取到的storagePath里排除掉包含”sd” “card” “storage”的路径,剩下的基本就是U盘路径了,但还不绝对是。。

我也很无奈,目前位置没找到好的能获取7.0以下系统的U盘路径的方法。如有哪位大神知道,还望告知。



版权声明:本文为woshiliuzemin原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。