Android OTG 读写U盘文件

时间:2022-07-26 18:00:10

最近项目需要做一个关于OTG的功能,其实也就使用到U盘的文件读取,一般这种需求是不需要的,因为大部分的系统都会自动挂载U盘,但是个别系统还是需要自己去实现,查了很多资料都没有找到一个完整的能用的例子,要么就是年代偏远,好不容易运行起来跑不了,或者没效果,这个过程是很烦的,google了一下后,发现了一个开源的文件管理器,但是该管理器在使用的时候却出现了OTG无法读取的问题,详细的话可以自己去看看 https://github.com/1hakr/AnExplorer,我是去看源码它的USB实现过程中从中提取了一部分东西,再结合看的其他博客,总算能实现读写的效果,
阅读之前,可先看一下这几篇文章:

http://blog.csdn.net/hetangbian/article/details/50800807 
http://blog.csdn.net/lincyang/article/details/50739342 
http://download.csdn.net/detail/glouds/8060217
https://developer.android.com/guide/topics/connectivity/usb/host.html

然后,FAT32格式的U盘测试没问题,NTFS格式U盘测试时没通过,如果手机只有一个OTG线的接口,没办法看到调试信息,可以看看这篇文章
http://blog.csdn.net/tianruxishui/article/details/38338087
好吧,其实看了也只是有个大概的了解,要想实现其实还是差了点,特别是获取U盘目录什么的简直不知道该怎么下手,好了,废话不多说,看一下个人实现,如果你有更好的实现,欢迎留言讨论。
首先,肯定需要获取读取OTG和文件权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.hardware.usb.host" android:required="false" />
//因为部分手机上是没有otg的所以需要加入特性。
<uses-feature android:name="android.hardware.usb.host" android:required="true" />  

有了权限后,就可以开始编码了,

1,注册广播,监听otg插拔事件。

如果想要实时去管理U盘的文件插入,肯定是需要做广播监听的,也方便自己的数据处理。

//监听otg插入 拔出
    IntentFilter usbDeviceStateFilter = new IntentFilter();
    usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);

    usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);

  //注册监听自定义广播
        IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
        registerReceiver(mUsbReceiver, filter);

    //otg插入 播出广播
    getActivity().registerReceiver(mUsbReceiver, usbDeviceStateFilter);//这里我用的碎片
//mUsbReceiver只是一个普通的广播,根据action,去分别处理对应的事件。
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            switch (action) {
                case ACTION_USB_PERMISSION://接受到自定义广播
                    synchronized (this) {
                        UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                        if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {  //允许权限申请
                            if (usbDevice != null) {
                                //Do something
                            }
                        } else {
                            TShow("用户未授权,读取失败");
                        }
                    }
                    break;
                case UsbManager.ACTION_USB_DEVICE_ATTACHED://接收到存储设备插入广播
                    UsbDevice device_add = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                    if (device_add != null) {                   
                        TShow("接收到存储设备插入广播");           
                    }
                    break;
                case UsbManager.ACTION_USB_DEVICE_DETACHED://接收到存储设备拔出广播
                    UsbDevice device_remove = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                    if (device_remove != null) {
                        TShow("接收到存储设备拔出广播");
//拔出或者碎片 Activity销毁时 释放引用
//device.close();
                    }
                    break;
            }
        }
    };

Tshow()是我个人的Toast消息封装,上述中的广播每一个都可以获取到usb设备的实体信息类,可以根据个人逻辑处理。

2,枚举设备

//申请
    UsbManager usbManager = (UsbManager) getContext().getSystemService(Context.USB_SERVICE);
//枚举设备
    UsbMassStorageDevice[] storageDevices = UsbMassStorageDevice.getMassStorageDevices(getContext());//获取存储设备
    PendingIntent pendingIntent = PendingIntent.getBroadcast(getActivity(), 0, new Intent(ACTION_USB_PERMISSION), 0);
    for (UsbMassStorageDevice device : storageDevices) {//可能有几个 一般只有一个 因为大部分手机只有1个otg插口
        if (usbManager.hasPermission(device.getUsbDevice())) {//有就直接读取设备是否有权限
            read(device);
        } else {//没有就去发起意图申请
            usbManager.requestPermission(device.getUsbDevice(), pendingIntent); //该代码执行后,系统弹出一个对话框,
        }
    }

申请意图执行后,会进入上面的广播中,然后,在广播需要处理的地方去执行读取操作。详见3

3,读取U盘信息
这里需要说明一下,UsbMassStorageDevice这个类是用的别人封装好的工具库,里面包含了一些获取大容量存储设备的操作和其他封装。可以在Gradle中加入依赖

compile 'com.github.mjdev:libaums:+'

该库GitHub地址:https://github.com/magnusja/libaums#using-buffered-streams-for-more-efficency

ACTION_USB_PERMISSION是定义的一个权限字段

private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";

read(device);的详细代码:
private void read(UsbMassStorageDevice massDevice) {
        // before interacting with a device you need to call init()!
        try {
            massDevice.init();//初始化
//          Only uses the first partition on the device
            Partition partition = massDevice.getPartitions().get(0);
            FileSystem currentFs = partition.getFileSystem();
//fileSystem.getVolumeLabel()可以获取到设备的标识
//通过FileSystem可以获取当前U盘的一些存储信息,包括剩余空间大小,容量等等
            Log.d(TAG, "Capacity: " + currentFs.getCapacity());
            Log.d(TAG, "Occupied Space: " + currentFs.getOccupiedSpace());
            Log.d(TAG, "Free Space: " + currentFs.getFreeSpace());
            Log.d(TAG, "Chunk size: " + currentFs.getChunkSize());
            UsbFile root = currentFs.getRootDirectory();
        } catch (Exception e) {
            e.printStackTrace();
            TShow("读取失败");
        }
    }
到这里就已经可以获取到U盘根目录的实体类了 UsbFile 接下来如何使用呢?
UsbFile的函数:(部分)
listFiles(),该函数可以获取到某个目录下所有的文件
boolean isDirectory();判断是否是目录
void setName(String newName) throws IOException;给目录或文件设置名称
String getName(); 获取该文件或目录的名称
UsbFile getParent();该函数可以获取父类对象,比如根目录是root,它有个子目录music,那么通过music的UsbFile对象
可以获取到root的文件和目录列表,也就解决了获取上一级目录文件的问题。
long getLength();如果是个文件,可以获取到文件大小
void read(long offset, ByteBuffer destination) throws IOException;文件读取操作,可以不用管,有兴趣也可以去看看
具体实现
void write(long offset, ByteBuffer source) throws IOException;文件写入操作,同上
UsbFile createDirectory(String name) throws IOException;如果是个目录,那么会在该目录下创建一个新的目录
UsbFile createFile(String name) throws IOException;如果该目录存在,会在该目录下生成一个新的文件
void delete() throws IOException;文件和目录删除操作
boolean isRoot();当前目录是否是根目录
常用的函数就这几个,基本上包括了U盘文件的增删改查,然后如果系统未自动装载,就是说没有将U盘放在一个具体的能直接访问的目录下,如果想要去读取文件,可以调用上面的函数,如果说还需要一个File的对象,那么可以通过流的形式进行保存再获取。因为File对象指向了一个具体的uri地址,也就是说,必须要有能访问的具体路径,比如内置存储和sd卡的目录,
具体步骤:先创建一个具体的文件,然后以io形式进行文件写入,相当于复制,io完成后即可获取File对象
大概如下:
new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            FileOutputStream os = new FileOutputStream(tempFile);
                            InputStream is = new UsbFileInputStream(usbFile);
                            int bytesRead = 0;
                            byte[] buffer = new byte[1024 * 8];//作者的推荐写法是currentFs.getChunkSize()为buffer长度
                            while ((bytesRead = is.read(buffer)) != -1) {
                                os.write(buffer, 0, bytesRead);
                            }
                            os.flush();
                            os.close();
                            is.close();
                            //Do something
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }).start();

好了,基本上整个流程就是这样的,demo代码和apk会在下面给出,如果还有疑问可以留言,很少写博客,写的不好的地方还望见谅。

https://pan.baidu.com/s/1qYM3lbm