Android 系统读取外置存储设备及其插拔监听

Android 设备存储一般分成内置存储(自身ROM)和外置存储,外置存储设备大致就两种,即 SD 卡和 U 盘,本篇将介绍如何获取外置存储设备的路径、读取文件列表和监听其插拔状态。

一、文件读写基本知识

Android 中文件读写的方式一般有如下三种

Android 定义了以下与存储相关的权限

Android 13 后增加的细分媒体权限

<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

一般持有MANAGE_EXTERNAL_STORAGE权限的即可访问绝大多数文件路径,例外情况Android/dataAndroid/obb访问私有应用文件目录,只有使用SAF方式访问,需要用户授权目录。

另外OTG存储,无法通过MediaStore API方式查询其媒体文件,但是可以查询内置存储和SD卡存储。

二、存储卷读取

storageManager.getVolumes()StorageManager.getVolumeList() 都是用于获取设备上所有存储卷的方法,但它们之间存在一些关键区别:

1. 返回值:

  • storageManager.getVolumes() 返回一个 List<VolumeInfo> 对象,其中包含有关每个存储卷的信息,例如卷名、路径、类型等。
  • StorageManager.getVolumeList() 返回一个 StorageVolume[] 数组,其中包含每个存储卷的引用。

2. 可访问性:

  • storageManager.getVolumes() 可以访问所有类型的存储卷,包括内部存储、外部存储和 USB 存储。
  • StorageManager.getVolumeList() 只能访问可公开访问的存储卷,例如外部存储。(测试USB 存储无法读取

3. 权限:

  • storageManager.getVolumes() 需要 READ_EXTERNAL_STORAGE 权限才能访问外部存储和 USB 存储。
  • StorageManager.getVolumeList() 不需要任何权限。

4. 版本:

  • storageManager.getVolumes() 是 Android 5.0 (API 21) 中引入的新方法。
  • StorageManager.getVolumeList() 是旧方法,在 Android 4.0 (API 14) 中引入。

表格总结了 storageManager.getVolumes()StorageManager.getVolumeList() 之间的区别:

特性storageManager.getVolumes()StorageManager.getVolumeList()
返回值List<VolumeInfo>StorageVolume[]
可访问性所有类型可公开访问
权限READ_EXTERNAL_STORAGE
版本Android 5.0 (API 21)Android 4.0 (API 14)
  • 如果您需要访问所有类型的存储卷,并可以使用 READ_EXTERNAL_STORAGE 权限,请使用 storageManager.getVolumes() 方法。
  • 如果您只需要访问可公开访问的存储卷,或者不想使用任何权限,请使用 StorageManager.getVolumeList() 方法。

三、 VolumeInfo和StorageVolume属性及其含义

VolumeInfoStorageVolume 是 Android 系统中用于表示存储卷的两个类。它们之间存在一些相似之处,但也有一些关键区别。

VolumeInfo 类包含有关存储卷的更多信息,例如卷的状态和 UUID,而 StorageVolume 类只包含基本信息,例如卷名、路径、类型等。

以下是 VolumeInfoStorageVolume 类的属性及其含义:

VolumeInfo 类属性:

  • _id: 卷 ID
  • type: 卷类型,例如内部存储、外部存储、USB 存储等
  • state: 卷状态,例如挂载、卸载、不可访问等
  • mountPoint: 卷挂载路径
  • fsUuid: 卷文件系统 UUID
  • partGuid: 卷分区 GUID
  • flags: 卷标志,例如只读、可移动等
  • ownerUid: 卷所有者用户 ID
  • primaryDir: 卷主目录
  • volumeId: 卷 ID

StorageVolume 类属性:

  • volumeId: 卷 ID
  • fsType: 卷文件系统类型
  • state: 卷状态,例如挂载、卸载、不可访问等
  • path: 卷挂载路径
  • isPrimary: 是否为主要存储卷
  • isEmulated: 是否为模拟存储卷
  • maxFileSize: 卷上最大文件大小

以下是一些属性的含义:

  • type:

    指示存储卷的类型。可能的类型包括:

    • TYPE_INTERNAL: 内部存储
    • TYPE_EXTERNAL: 外部存储
    • TYPE_PUBLIC: 公共存储
    • TYPE_USB: USB 存储
    • TYPE_SDCARD: SD 卡
  • state:

    指示存储卷的状态。可能的 state 包括:

    • MEDIA_MOUNTED: 存储卷已挂载
    • MEDIA_UNMOUNTED: 存储卷已卸载
    • MEDIA_BAD_REMOVAL: 存储卷被不正确移除
    • MEDIA_NOFS: 存储卷没有文件系统
    • MEDIA_CHECKING: 存储卷正在检查
  • mountPoint: 指示存储卷的挂载路径。

  • fsUuid: 指示存储卷的文件系统 UUID。

  • partGuid: 指示存储卷的分区 GUID。

  • flags:

    指示存储卷的标志。可能的标志包括:

    • FLAG_READ_ONLY: 存储卷只读
    • FLAG_REMOVABLE: 存储卷可移动
  • ownerUid: 指示存储卷的所有者用户 ID。

  • primaryDir: 指示存储卷的主目录。

  • volumeId: 指示存储卷的 ID。

  • fsType: 指示存储卷的文件系统类型。

  • isPrimary: 指示存储卷是否为主要存储卷。

  • isEmulated: 指示存储卷是否为模拟存储卷。

  • maxFileSize: 指示存储卷上最大文件大小。

四、输出存储卷代码

输出打印存储卷代码段:

    public void getStorageVolume(Context context) {
        StorageManager mStorageManagerr = ((StorageManager) context.getSystemService(Context.STORAGE_SERVICE));
        // 使用 storageManager.getVolumes() 获取所有存储卷
        List<VolumeInfo> volumeInfos = mStorageManager.getVolumes();
        for (VolumeInfo volumeInfo : volumeInfos) {
            android.util.Log.e("maxx", "getVolumes volumeInfo:" + volumeInfo.toString());
        }

        // 使用 StorageManager.getVolumeList() 获取所有可公开访问的存储卷
        StorageVolume[] storageVolumes = mStorageManager.getVolumeList();
        ArrayList<StorageVolume> mountVolumeList = new ArrayList<>();
        for (StorageVolume storageVolume : storageVolumes) {
            android.util.Log.e("maxx", "getVolumeList storageVolume:" + storageVolume.dump());
        }
    }

输出打印存储卷信息:

M144CA9  03-12 17:50:36.871 22035 22035 E maxx    : getVolumes volumeInfo:VolumeInfo{public:8,49}:
M144CA9  03-12 17:50:36.871 22035 22035 E maxx    :     type=PUBLIC diskId=disk:8,48 partGuid= mountFlags=0 mountUserId=0 
M144CA9  03-12 17:50:36.871 22035 22035 E maxx    :     state=MOUNTED 
M144CA9  03-12 17:50:36.871 22035 22035 E maxx    :     fsType=vfat fsUuid=BEA6-BBCE fsLabel= 
M144CA9  03-12 17:50:36.871 22035 22035 E maxx    :     path=/mnt/media_rw/BEA6-BBCE internalPath=/mnt/media_rw/BEA6-BBCE 
M144CA9  03-12 17:50:36.871 22035 22035 E maxx    :     linkName=usbdisk 
M144CAA  03-12 17:50:36.871 22035 22035 E maxx    : getVolumes volumeInfo:VolumeInfo{private}:
M144CAA  03-12 17:50:36.871 22035 22035 E maxx    :     type=PRIVATE diskId=null partGuid=null mountFlags=0 mountUserId=-10000 
M144CAA  03-12 17:50:36.871 22035 22035 E maxx    :     state=MOUNTED 
M144CAA  03-12 17:50:36.871 22035 22035 E maxx    :     fsType=null fsUuid=null fsLabel=null 
M144CAA  03-12 17:50:36.871 22035 22035 E maxx    :     path=/data internalPath=null linkName=unknown 
M144CAB  03-12 17:50:36.871 22035 22035 E maxx    : getVolumes volumeInfo:VolumeInfo{public:179,1}:
M144CAB  03-12 17:50:36.871 22035 22035 E maxx    :     type=PUBLIC diskId=disk:179,0 partGuid= mountFlags=VISIBLE mountUserId=0 
M144CAB  03-12 17:50:36.871 22035 22035 E maxx    :     state=MOUNTED 
M144CAB  03-12 17:50:36.871 22035 22035 E maxx    :     fsType=vfat fsUuid=4819-161B fsLabel= 
M144CAB  03-12 17:50:36.871 22035 22035 E maxx    :     path=/storage/4819-161B internalPath=/mnt/media_rw/4819-161B 
M144CAB  03-12 17:50:36.871 22035 22035 E maxx    :     linkName=sdcard0 
M144CAC  03-12 17:50:36.871 22035 22035 E maxx    : getVolumes volumeInfo:=VolumeInfo{emulated;0}:
M144CAC  03-12 17:50:36.871 22035 22035 E maxx    :     type=EMULATED diskId=null partGuid= mountFlags=PRIMARY|VISIBLE 
M144CAC  03-12 17:50:36.871 22035 22035 E maxx    :     mountUserId=0 state=MOUNTED 
M144CAC  03-12 17:50:36.871 22035 22035 E maxx    :     fsType=null fsUuid=null fsLabel=null 
M144CAC  03-12 17:50:36.871 22035 22035 E maxx    :     path=/storage/emulated internalPath=/data/media linkName= 
M144CAD  03-12 17:50:36.871 22035 22035 E maxx    : getVolumeList storageVolume:StorageVolume:
M144CAD  03-12 17:50:36.871 22035 22035 E maxx    :     mId=emulated;0 mPath=/storage/emulated/0 mInternalPath=/storage/emulated/0 
M144CAD  03-12 17:50:36.871 22035 22035 E maxx    :     mDescription=内部共享存储空间 mPrimary=true mRemovable=false mEmulated=true 
M144CAD  03-12 17:50:36.871 22035 22035 E maxx    :     mAllowMassStorage=false mMaxFileSize=0 mOwner=UserHandle{0} mFsUuid=null 
M144CAD  03-12 17:50:36.871 22035 22035 E maxx    :     mState=mounted 
M144CCE  03-12 17:50:36.931 22035 22035 E maxx    : getVolumeList storageVolume=StorageVolume:
M144CCE  03-12 17:50:36.931 22035 22035 E maxx    :     mId=public:179,1 mPath=/storage/4819-161B 
M144CCE  03-12 17:50:36.931 22035 22035 E maxx    :     mInternalPath=/mnt/media_rw/4819-161B mDescription=SanDisk SD 卡 
M144CCE  03-12 17:50:36.931 22035 22035 E maxx    :     mPrimary=false mRemovable=true mEmulated=false mAllowMassStorage=false 
M144CCE  03-12 17:50:36.931 22035 22035 E maxx    :     mMaxFileSize=4294967295 mOwner=UserHandle{0} mFsUuid=4819-161B 
M144CCE  03-12 17:50:36.931 22035 22035 E maxx    :     mState=mounted 

如何判断是SD卡还是OTG还是内部存储,根据输出的Log可以分析,内部存储的 type=EMULATED 并且 diskId=null,而SD卡和OTG类型都是 type=PUBLIC,并且 diskId 不为空。

所以通过volumeInfo的话,一般volumeInfo.getType() == VolumeInfo.TYPE_PRIVATE或者volumeInfo.getType() == VolumeInfo.TYPE_EMULATED就是内部存储,我遇到有些设备这种判断还是不准确。volumeInfovol.getDisk() != null && vol.getDisk().isUsb()就是OTG,volumeInfo.getDisk() != null && sv.getDisk().isSd()就是SD卡。

大部分情况下,如果要获取所有存储卷,我们都是使用mStorageManager.getVolumes(),但是通过打印的Log,可以发现storageVolume会多一个属性mDescription用于描述设备的内容,一般会包含设备的品牌名,如何从VolumeInfo中获取描述设备属性呢?

String mDescription = mStorageManager.getBestVolumeDescription(volumeInfo);

我们还可以通过VolumeInfo获取存储设备的根目录,通过直接文件路径遍历文件目录

public static String ROOT_PATH = "/storage/emulated/0";

String path = (volume.getType() == VolumeInfo.TYPE_PRIVATE && VolumeInfo.ID_PRIVATE_INTERNAL.equals(volume.getId())
                || volume.getType() == VolumeInfo.TYPE_EMULATED)
                ? ROOT_PATH
                : volume.getPath().toString();

五、监听插拔状态

5.1 监听SD卡插拔

// 监听广播
private final BroadcastReceiver mSDReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        switch (action) {
            case Intent.ACTION_MEDIA_CHECKING://SD卡正在检查
            case Intent.ACTION_MEDIA_MOUNTED://SD卡挂载成功
                android.util.Log.e("maxx","mSDReceiver mounted");
                break;
            case Intent.ACTION_MEDIA_EJECT://SD卡拔出
            case Intent.ACTION_MEDIA_UNMOUNTED://SD卡卸载成功
                android.util.Log.e("maxx","mSDReceiver eject");
                break;
            default:
                break;
        }
    }
};

// 注册监听
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_MEDIA_CHECKING);
intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
registerReceiver(mSDReceiver, intentFilter);

// 取消监听
unregisterReceiver(mScannerReceiver);

5.2 监听OTG插拔

网上很多说使用UsbManager.ACTION_USB_DEVICE_ATTACHEDUsbManager.ACTION_USB_DEVICE_DETACHED这二个广播可以监听到USB设备插拔,但是我使用OTG的设备插拔无法接受到此广播。

后面使用VolumeInfo.ACTION_VOLUME_STATE_CHANGED 才可以监听到,而且可以查看设备有没有准备好,有没有mounted或者unmounted。
VolumeInfo

public static final String ACTION_VOLUME_STATE_CHANGED =
        "android.os.storage.action.VOLUME_STATE_CHANGED";
public static final String EXTRA_VOLUME_ID =
        "android.os.storage.extra.VOLUME_ID";
public static final String EXTRA_VOLUME_STATE =
        "android.os.storage.extra.VOLUME_STATE";
// 监听广播
public final BroadcastReceiver mDiskReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        if(intent.getAction().equals(VolumeInfo.ACTION_VOLUME_STATE_CHANGED) ){
            int id = intent.getIntExtra(VolumeInfo.EXTRA_VOLUME_ID, 0);
            int state =intent.getIntExtra(VolumeInfo.EXTRA_VOLUME_STATE, VolumeInfo.STATE_UNMOUNTABLE);
            android.util.Log.e("maxx","mDiskReceiver id:"+id+" state="+state);
        }
    }
};

//注册监听
registerReceiver(mDiskReceiver, new IntentFilter(VolumeInfo.ACTION_VOLUME_STATE_CHANGED));

//取消监听
unregisterReceiver(mDiskReceiver);

相关参考

[1] USB 存储媒介

[2] 使用 MediaStore 查询 USB OTG 中的文件失败

  • 28
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android中可以通过注册U盘插拔监听广播来实现对U盘插拔事件的监听。具体的实现步骤如下: 1. 在AndroidManifest.xml文件中添加如下权限: ``` <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> ``` 2. 在AndroidManifest.xml文件中添加如下广播接收器: ``` <receiver android:name=".UsbReceiver"> <intent-filter> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" /> </intent-filter> </receiver> ``` 3. 在UsbReceiver.java文件中实现广播接收器: ``` public class UsbReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) { // U盘插入 Toast.makeText(context, "U盘已插入", Toast.LENGTH_SHORT).show(); } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) { // U盘拔出 Toast.makeText(context, "U盘已拔出", Toast.LENGTH_SHORT).show(); } } } ``` 注意:在Android 6.0及以上版本中,需要动态获取读取外部存储的权限。可以使用如下代码实现: ``` if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1); } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值