Android 读取外接储存设备的数据(如挂载的U盘,SD卡等)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/da_caoyuan/article/details/81408497

本篇文章,将围绕以下几点来讲解:

1:OTG是什么?
2:Android手机和一些Android系统的TV盒子对OTG的支持情况?
3:如何得知外接储存设备的插入和拔出的广播事件?
4:得到插入广播后,而又如何去读取外部设备的数据?

一: OTG是什么?

OTG是On-The-Go的缩写,是近年发展起来的技术,2001年12月18日由USB Implementers Forum公布,主要应用于各种不同的设备或移动设备间的联接,进行数据交换。

它提出的背景是移动消费类电子产品的迅猛增加,而之前USB协议的主从协议标准让这些电子产品在离开PC电脑时的数据传输变得艰难,OTG技术正是为了解决这一问题的标准。

二: Android手机和一些Android系统的TV盒子对OTG的支持情况?

Android4.0或以上系统的智能手机芯片都是支持USB-OTG的。但是一些android系统的TV盒子可能会不支持。不管是TV盒子还是android手机如果不支持OTG的话,可能有以下几点原因:

  1. 硬件上缺少5V升压器,外接设备没有电压供应。
  2. 硬件设备制造商为了省电考虑,从系统上屏蔽了USB-OTG功能。

解决系统屏蔽OTG问题,网上找到一个处理方案,好不好用未亲测:

ROOT后打开RE管理器,编辑system/etc/vold.fstab文件,
在vold.fstab的末尾添加如下代码# usb otg diskdev_mount usbotg /mnt/usbotg auto /devices/platform/mt_usb /devices/platform/musbfsh_hdrc 最后,修改保存,重启手机

三: 如何得知外接储存设备的插入和拔出的广播事件?

我们可以通过动态或静态的方式注册相应的广播,在广播中我们就能收到U盘的插入和拔出操作。

方式一:

我们动态注册这四个广播:

       //主要是这两个,测试发现,这两个广播的接受比较快,而且很准确
        UsbManager.ACTION_USB_DEVICE_ATTACHED;//动作USB设备已连接 广播
        UsbManager.ACTION_USB_DEVICE_DETACHED;//动作USB设备已分离 广播

        UsbManager.ACTION_USB_ACCESSORY_ATTACHED;//行动USB配件附件 广播
        UsbManager.ACTION_USB_ACCESSORY_DETACHED;//动作USB附件已分离 广播

方式二:

//主要是这两个,不过测试发现,这两个广播的接受有些缓慢,偶尔还会收不到
Intent.ACTION_MEDIA_MOUNTED //动作媒体安装 广播
Intent.ACTION_MEDIA_REMOVED //行动媒体被删除 广播

Intent.ACTION_MEDIA_UNMOUNTED//行动媒体未分配 广播
Intent.ACTION_MEDIA_EJECT//行动媒体EJECT 广播

此时你可能要问了,怎么还有两种方式呢,他们有什么区别呢?其实

通过方式一,我们能准确的得知外接储存设备(例如:U盘)的插入和拔出的操作。

通过方式二,虽然广播的接受有些缓慢,偶尔有时还会收不到,不过在广播的接受中,我们可以从返回的 Intent 中,获取不少有用的信息。例如,U盘的挂载路径。得到U盘的路径,我们就能操作U盘中的文件啦。下面的第四点,会做详情的讲解。不过需要说明一下:要想读取外部设备的数据 ,你的手机或者TV盒子等等,本身要支持USB-OTG功能哦!

四: 得到插入广播后,而又如何去读取外部设备的数据?

强调一下:要想读取外部设备的数据 ,你的手机或者TV盒子等等,本身要支持USB-OTG功能!不然的话,你是无法读取数据的。

我们首先讲解 第三点中 通过方式一,得知插拔广播后,怎样去读取外部设备数据。

首先,我们项目中用到了一个开源框架,开源地址是:
https://github.com/magnusja/libaums
他是干什么的呢?看看开源作者对他的介绍:

A library to access USB mass storage devices (pen drives, external HDDs, card readers) using the Android USB Host API. Currently it supports the SCSI command set and the FAT32 file system.

大概意思是:

使用Android USB主机API访问USB大容量存储设备(笔驱动器、外部HDDs、读卡器)的库。目前它支持SCSI命令集和FAT32文件系统。

远程依赖为: implementation 'com.github.mjdev:libaums:0.5.5'

除此之外我们还需要一个依赖库,来帮助实现我们广播的通讯,那就是EventBus3.0
implementation 'org.greenrobot:eventbus:3.0.0'


下面我就开始以贴出代码为主了,代码中也会做出关键性的注释!

布局文件:R.layout.activity_method_one

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="10dp"
        android:text="Android盒子外接U盘文件读写测试DEMO"
        />

    <EditText
        android:id="@+id/u_disk_edt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:hint="输入要保存到U盘中的文字内容"/>


    <Button
        android:id="@+id/u_disk_write"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:gravity="center"
        android:text="往U盘中写入数据"/>

    <Button
        android:id="@+id/u_disk_read"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:gravity="center"
        android:text="从U盘中读取数据"/>

    <TextView
        android:id="@+id/u_disk_show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginLeft="10dp"
        />
</LinearLayout>

java类 MethodOneActivity.java:

import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.example.usbreadwriterdaemon.receiver.UsbStateChangeReceiver;
import com.example.usbreadwriterdaemon.receiver.UsbStatusChangeEvent;
import com.example.usbreadwriterdaemon.utils.FileUtil2;
import com.example.usbreadwriterdaemon.utils.ToastUtil;
import com.github.mjdev.libaums.UsbMassStorageDevice;
import com.github.mjdev.libaums.fs.FileSystem;
import com.github.mjdev.libaums.fs.UsbFile;
import com.github.mjdev.libaums.fs.UsbFileInputStream;
import com.github.mjdev.libaums.partition.Partition;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MethodOneActivity extends AppCompatActivity {
    private static final String TAG = "MethodOneActivity";
    @BindView(R.id.u_disk_edt)
    EditText mUDiskEdt;
    @BindView(R.id.u_disk_write)
    Button mUDiskWrite;
    @BindView(R.id.u_disk_read)
    Button mUDiskRead;
    @BindView(R.id.u_disk_show)
    TextView mUDiskShow;

    private UsbMassStorageDevice[] storageDevices;
    private UsbFile cFolder;

    //自定义U盘读写权限
    public static final String ACTION_USB_PERMISSION = "com.example.usbreadwriterdaemon.USB_PERMISSION";
    private final static String U_DISK_FILE_NAME = "u_disk.txt";

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 100:
                    ToastUtil.showToast("保存成功");
                    break;
                case 101:
                    String txt = msg.obj.toString();
                    if (!TextUtils.isEmpty(txt))
                        mUDiskShow.setText("读取到的数据是:" + txt);
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_method_one);
        ButterKnife.bind(this);

        EventBus.getDefault().register(this);//EventBus 注册
        registerUDiskReceiver();//usb插拔广播 注册
    }


    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onNetworkChangeEvent(UsbStatusChangeEvent event) {
        if (event.isConnected) {
            //接收到U盘插入广播,尝试读取U盘设备数据
            redUDiskDevsList();
        } else if (event.isGetPermission) {
            UsbDevice usbDevice = event.usbDevice;

            //用户已授权,可以进行读取操作
            Log.i(TAG, "onNetworkChangeEvent: ");
            ToastUtil.showToast("onReceive: 权限已获取");
            readDevice(getUsbMass(usbDevice));
        } else {

        }
    }


    @OnClick({R.id.u_disk_write, R.id.u_disk_read})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.u_disk_write:
                final String content = mUDiskEdt.getText().toString().trim();
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        saveText2UDisk(content);
                    }
                });


                break;
            case R.id.u_disk_read:
                readFromUDisk();

                break;
        }
    }

    /**
     * @description 保存数据到U盘,目前是保存到根目录的
     * @author ldm
     * @time 2017/9/1 17:17
     */
    private void saveText2UDisk(String content) {
        //项目中也把文件保存在了SD卡,其实可以直接把文本读取到U盘指定文件
        File file = FileUtil2.getSaveFile(getPackageName() + File.separator + FileUtil2.DEFAULT_BIN_DIR, U_DISK_FILE_NAME);
        try {
            FileWriter fw = new FileWriter(file);
            fw.write(content);
            fw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (null != cFolder) {
            FileUtil2.saveSDFile2OTG(file, cFolder);
            mHandler.sendEmptyMessage(100);
        }
    }

    StringBuffer stringBuffer = new StringBuffer();

    private void readFromUDisk() {
        UsbFile[] usbFiles = new UsbFile[0];
        try {
            usbFiles = cFolder.listFiles();
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (null != usbFiles && usbFiles.length > 0) {

            for (UsbFile usbFile : usbFiles) {
                stringBuffer.append(", " + usbFile.getName());
                if (usbFile.getName().equals(U_DISK_FILE_NAME)) {
                    readTxtFromUDisk(usbFile);
                }
            }
            //mUDiskShow.setText("文件名:" + stringBuffer.toString());
        }
    }


    /**
     * @description U盘设备读取
     * @author ldm
     * @time 2017/9/1 17:20
     */
    private void redUDiskDevsList() {
        //设备管理器
        UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
        //获取U盘存储设备
        storageDevices = UsbMassStorageDevice.getMassStorageDevices(this);

        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
        //一般手机只有1个OTG插口
        for (UsbMassStorageDevice device : storageDevices) {
            //读取设备是否有权限,
            if (usbManager.hasPermission(device.getUsbDevice())) {
                ToastUtil.showToast("有权限");
                readDevice(device);
            } else {
                ToastUtil.showToast("没有权限,进行申请");
                //没有权限,进行申请,此时系统会有个弹框,询问你是否同意,当然我们应该同意啦!
                usbManager.requestPermission(device.getUsbDevice(), pendingIntent);
            }
        }
        if (storageDevices.length == 0) {
            ToastUtil.showToast("请插入可用的U盘");
        }
    }


    private UsbMassStorageDevice getUsbMass(UsbDevice usbDevice) {
        for (UsbMassStorageDevice device : storageDevices) {
            if (usbDevice.equals(device.getUsbDevice())) {
                return device;
            }
        }
        return null;
    }

    private void readDevice(UsbMassStorageDevice device) {
        try {
            device.init();//初始化
            //设备分区
            Partition partition = device.getPartitions().get(0);

            //文件系统
            FileSystem currentFs = partition.getFileSystem();
            currentFs.getVolumeLabel();//可以获取到设备的标识

            //通过FileSystem可以获取当前U盘的一些存储信息,包括剩余空间大小,容量等等
            Log.e("Capacity: ", currentFs.getCapacity() + "");
            Log.e("Occupied Space: ", currentFs.getOccupiedSpace() + "");
            Log.e("Free Space: ", currentFs.getFreeSpace() + "");
            Log.e("Chunk size: ", currentFs.getChunkSize() + "");

            ToastUtil.showToast("可用空间:" + currentFs.getFreeSpace());


            cFolder = currentFs.getRootDirectory();//设置当前文件对象为根目录


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void readTxtFromUDisk(UsbFile usbFile) {
        Log.i(TAG, "readTxtFromUDisk: ");
        UsbFile descFile = usbFile;
        //读取文件内容
        InputStream is = new UsbFileInputStream(descFile);
        //读取秘钥中的数据进行匹配
        StringBuilder sb = new StringBuilder();
        BufferedReader bufferedReader = null;
        try {
            bufferedReader = new BufferedReader(new InputStreamReader(is));
            String read;
            while ((read = bufferedReader.readLine()) != null) {
                sb.append(read);
            }
            Message msg = mHandler.obtainMessage();
            msg.what = 101;
            msg.obj = sb;
            mHandler.sendMessage(msg);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

      /**
     * usb插拔广播 注册
     */
    private void registerUDiskReceiver() {
        IntentFilter usbDeviceStateFilter = new IntentFilter();
        usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
        usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
        usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
        usbDeviceStateFilter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
        usbDeviceStateFilter.addAction("android.hardware.usb.action.USB_STATE");

        usbDeviceStateFilter.addAction(ACTION_USB_PERMISSION); //自定义广播

        registerReceiver(new UsbStateChangeReceiver(), usbDeviceStateFilter);

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().unregister(this);
        }


    }
}

usb插拔广播接受 UsbStateChangeReceiver.java

**
 * Created by yuanpk on 2018/8/2  14:22
 * <p>
 * Description:usb插拔广播接受
 */
public class UsbStateChangeReceiver extends BroadcastReceiver {
    private static final String TAG = "UsbStateChangeReceiver";

    private boolean isConnected;

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action.equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
            isConnected = true;
            ToastUtil.showToast("onReceive: USB设备已连接");

            UsbDevice device_add = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            if (device_add != null) {
                EventBus.getDefault().post(new UsbStatusChangeEvent(isConnected));
            } else {
                ToastUtil.showToast("onReceive: device is null");
            }


        } else if (action.equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
            //Log.i(TAG, "onReceive: USB设备已分离");
            isConnected = false;
            ToastUtil.showToast("onReceive: USB设备已拔出");

            EventBus.getDefault().post(new UsbStatusChangeEvent(isConnected));
        } else if (action.equals(MethodOneActivity.ACTION_USB_PERMISSION)) {

            UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            //允许权限申请
            if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                if (usbDevice != null) {
                    Log.i(TAG, "onReceive: 权限已获取");
                    EventBus.getDefault().post(new UsbStatusChangeEvent(true, usbDevice));
                } else {
                    ToastUtil.showToast("没有插入U盘");
                }
            } else {
                ToastUtil.showToast("未获取到U盘权限");
            }
        } else {
            //Log.i(TAG, "onReceive: action=" + action);
            ToastUtil.showToast("action= " + action);
        }


    }
}


UsbStatusChangeEvent.java

import android.hardware.usb.UsbDevice;

/**
 * Created by yuanpk on 2018/8/1  9:43
 * Description:TODO
 */
public class UsbStatusChangeEvent {
    public boolean isConnected = false;
    public boolean isGetPermission = false;
    public UsbDevice usbDevice;

    public String filePath = "";


    public UsbStatusChangeEvent(boolean isConnected) {
        this.isConnected = isConnected;
    }

    public UsbStatusChangeEvent(String filePath) {
        this.filePath = filePath;
    }

    public UsbStatusChangeEvent(boolean isGetPermission, UsbDevice usbDevice) {

        this.isGetPermission = isGetPermission;
        this.usbDevice = usbDevice;
    }


}

FileUtil2.java 工具类

import android.os.Environment;

import com.github.mjdev.libaums.fs.UsbFile;
import com.github.mjdev.libaums.fs.UsbFileOutputStream;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import static android.os.Environment.getExternalStorageDirectory;

/**
 * 文件操作工具类
 *
 * @author ldm
 * @description:
 * @date 2016-4-28 下午3:17:10
 */
public final class FileUtil2 {
    public static final String DEFAULT_BIN_DIR = "usb";

    /**
     * 检测SD卡是否存在
     */
    public static boolean checkSDcard() {
        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    }

    /**
     * 从指定文件夹获取文件
     *
     * @return 如果文件不存在则创建, 如果如果无法创建文件或文件名为空则返回null
     */
    public static File getSaveFile(String folderPath, String fileNmae) {
        File file = new File(getSavePath(folderPath) + File.separator + fileNmae);
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return file;
    }

    /**
     * 获取SD卡下指定文件夹的绝对路径
     *
     * @return 返回SD卡下的指定文件夹的绝对路径
     */
    public static String getSavePath(String folderName) {
        return getSaveFolder(folderName).getAbsolutePath();
    }

    /**
     * 获取文件夹对象
     *
     * @return 返回SD卡下的指定文件夹对象,若文件夹不存在则创建
     */
    public static File getSaveFolder(String folderName) {
        File file = new File(getExternalStorageDirectory()
                .getAbsoluteFile()
                + File.separator
                + folderName
                + File.separator);
        file.mkdirs();
        return file;
    }

    /**
     * 关闭流
     */
    public static void closeIO(Closeable... closeables) {
        if (null == closeables || closeables.length <= 0) {
            return;
        }
        for (Closeable cb : closeables) {
            try {
                if (null == cb) {
                    continue;
                }
                cb.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static void redFileStream(OutputStream os, InputStream is) throws IOException {
        int bytesRead = 0;
        byte[] buffer = new byte[1024 * 8];
        while ((bytesRead = is.read(buffer)) != -1) {
            os.write(buffer, 0, bytesRead);
        }
        os.flush();
        os.close();
        is.close();
    }

    /**
     * @description 把本地文件写入到U盘中
     * @author ldm
     * @time 2017/8/22 10:22
     */
    public static void saveSDFile2OTG(final File f, final UsbFile usbFile) {
        UsbFile uFile = null;
        FileInputStream fis = null;
        try {//开始写入
            fis = new FileInputStream(f);//读取选择的文件的
            if (usbFile.isDirectory()) {//如果选择是个文件夹
                UsbFile[] usbFiles = usbFile.listFiles();
                if (usbFiles != null && usbFiles.length > 0) {
                    for (UsbFile file : usbFiles) {
                        if (file.getName().equals(f.getName())) {
                            file.delete();
                        }
                    }
                }
                uFile = usbFile.createFile(f.getName());
                UsbFileOutputStream uos = new UsbFileOutputStream(uFile);
                try {
                    redFileStream(uos, fis);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (final Exception e) {
            e.printStackTrace();
        }
    }
}

到此我们的方式一读取方式就结束啦!虽然代码有点多,但是逻辑还是比较清楚的,首先是我们的布局文件,然后是我们布局文件对应的类,再然后呢是我们的广播接受类,最后是我们的一个工具类。

接着讲解 第三点中 通过方式二,得知插拔广播后,怎样去读取外部设备数据

这里,就不再像方式一,说的那么详细啦,不过也不必担心,最后我会把源码放到github上,供大家参考的。

首先,我们可以通过动态或静态方法,注册广播。这里是采用的静态注册方式,如下:

 <receiver android:name=".receiver.USBReceiver">
            <intent-filter>

                <action android:name="android.intent.action.MEDIA_REMOVED"/>
                <action android:name="android.intent.action.MEDIA_MOUNTED"/>

                <data android:scheme="file"/>
            </intent-filter>
        </receiver>

广播的意义我们已经在第三点中,说明了哦。

广播接受类, USBReceiver.java

/**
 * Created by yuanpk on 2018/8/3  11:43
 * <p>
 * Description:TODO
 */
public class USBReceiver extends BroadcastReceiver {
    private static final String TAG = USBReceiver.class.getSimpleName();

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {//ACTION_MEDIA_REMOVED
            String mountPath = intent.getData().getPath();
            Log.d(TAG, "mountPath = " + mountPath);
            if (!TextUtils.isEmpty(mountPath)) {
                //读取到U盘路径再做其他业务逻辑
                //ToastUtil.showToast("路径=" + mountPath);
                EventBus.getDefault().post(new UsbStatusChangeEvent(mountPath));


            }
        } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED) || action.equals(Intent.ACTION_MEDIA_EJECT)) {

            Toast.makeText(context, "No services information detected !", Toast.LENGTH_SHORT).show();


        } else if (action.equals("android.intent.action.BOOT_COMPLETED")) {
            //如果是开机完成,则需要调用另外的方法获取U盘的路径
        }
    }
}

我们通过EventBus,把得到的文件路径,发送的指定Activity中,然后在指定Activity中注册EventBus广播,接受就可以啦:

  @Subscribe(threadMode = ThreadMode.MAIN)
    public void onNetworkChangeEvent(UsbStatusChangeEvent event) {
        strFilePath = event.filePath;
        Toast.makeText(this, "u盘路径:" + strFilePath, Toast.LENGTH_SHORT).show();

       //这是我测试是U盘中,创建的文件,并且拷贝了一个.mkv格式视频文件。
        File file = new File(strFilePath + "/wode369tv", "local.mkv");
        Toast.makeText(this, "getPath=" + file.getPath(), Toast.LENGTH_SHORT).show();
       //例如:拿到视频文件之后,我们就可以通过播放视频的控件,去播放我们U盘中的视频啦!

    }

到此,我们的文章,就介绍完毕啦!文章也是写了好几天,着实不易,如果对你有所帮助,点个赞吧!十分谢谢!

文章源码


参考博客:

Android设备与外接U盘实现数据读取操作

Android开发——遍历读写U盘、SD卡等外部存储

猜你喜欢

转载自blog.csdn.net/da_caoyuan/article/details/81408497