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 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 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里添加如下权限:
这个权限是系统应用权限,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 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盘路径的方法。如有哪位大神知道,还望告知。