Android 设备与 U 盘之间的交互

前言

最近需要实现一个 TV 或一体机从 U 盘读取数据显示的功能,该功能主要解决的问题是:

  • 获取 U 盘根目录
  • 解决拔出 U 盘进程被杀死的问题

获取 U 盘根目录

获取 U 盘根目录需要分两种情况:

1. 应用程序已经在运行,这个时候插入 U 盘。

这种情况我是通过监听媒体挂载的广播来实现的,具体代码如下:
注册广播:

        <receiver
            android:name=".USBBroadcastReceiver">
            <intent-filter>
                <action android:name="android.intent.action.MEDIA_MOUNTED"/>
                <action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
                <action android:name="android.intent.action.MEDIA_EJECT"/>

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

监听 U 盘插入广播并获取 U 盘根目录:

public class USBBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent == null || intent.getAction() == null) {
            return;
        }
        switch (intent.getAction()) {
            case Intent.ACTION_MEDIA_MOUNTED://扩展介质被插入,而且已经被挂载。
                if (intent.getData() != null) {
                    String path = intent.getData().getPath();
                    String usbRealRootDirectory = FileUtils.getUSBRealRootDirectory(path);
                }
                break;
        }
    }
}

经测试,intent.getData().getPath(); 在一体机上获取的并不是 U 盘最终的根目录,所以通过 getUSBRealRootDirectory() 方法再一次提取最终的根目录,该方法具体如下:

    /**
     * 获取 U 盘真正根目录
     *
     * @param usbTempRootDirectory U 盘临时根目录
     * @return U 盘真正根目录
     */
    public static String getUSBRealRootDirectory(String usbTempRootDirectory) {
        String realUSBRootDirectory = "";
        File dir = new File(usbTempRootDirectory);
        File[] files = dir.listFiles();

        /**
         * 注意:
         * 经测试,
         * TV 直接是 usbTempRootDirectory 作为 U 盘的根目录,例如:/storage/577F-85CA
         * 一体机会在 U 盘的根目录(usbTempRootDirectory=/mnt/usb_storage/USB_DISK4)下再创建多个包含 "udisk" 的目录,然后其中一个作为 U 盘的根目录,例如:/mnt/usb_storage/USB_DISK4/udisk0
         */
        if (files != null) {
            for (File file : files) {
                //如果根目录下还有包含 "udisk" 的目录,则该包含 "udisk" 的目录才是 U 盘真正的根目录
                if (file.isDirectory() && file.list().length > 0 && file.getAbsolutePath().contains("udisk")) {
                    realUSBRootDirectory = file.getAbsolutePath();
                    break;
                } else { // 如果根目录下没有包含 "udisk" 的目录,说明 dir 就是根目录
                    realUSBRootDirectory = dir.getAbsolutePath();
                }
            }
        }
        return realUSBRootDirectory;
    }

2. 应用程序还未运行,U 盘就已经插入了。

这种情况就无法通过监听广播拿到 U 盘根目录了,经查询也没找到特定 API 可以获取到,所以这里只能用反射的方法。具体如下:
通过反射方法获取 U 盘临时根目录

    /**
     * 获取 U 盘临时根目录(一体机会在临时目录下再创建多个包含 "udisk" 的目录,所以临时目录并不是 U 盘真正的根目录)
     *
     * @param context Context
     * @return U 盘临时根目录集合
     */
    public static List<String> getUSBTempRootDirectory(Context context) {
        List<String> usbTempRootDirectory = new ArrayList<>();
        try {
            StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
            Class<StorageManager> storageManagerClass = StorageManager.class;
            String[] paths = (String[]) storageManagerClass.getMethod("getVolumePaths").invoke(storageManager);
            for (String path : paths) {
                Object volumeState = storageManagerClass.getMethod("getVolumeState", String.class).invoke(storageManager, path);
                //路劲包含 internal 一般是内部存储,例如 /mnt/internal_sd,需要排除
                if (!path.contains("emulated") && !path.contains("internal") && Environment.MEDIA_MOUNTED.equals(volumeState)) {
                    usbTempRootDirectory.add(path);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return usbTempRootDirectory;
    }

同样,在一体机上获取的并不是 U 盘最终的根目录,所以还是通过 getUSBRealRootDirectory() 方法再一次提取最终的根目录,具体如下:

        List<String> usbTempRootDirectory = FileUtils.getUSBTempRootDirectory(this);
        for (int i = 0; i < usbTempRootDirectory.size(); i++) {
            String usbRealRootDirectory = FileUtils.getUSBRealRootDirectory(usbTempRootDirectory.get(i));
        }

解决拔出 U 盘进程被杀死的问题

因为需要从 U 盘获取视频地址进行播放,当正在播放的时候拔出 U 盘就会出现进程被杀死的情况,报错日志如下:

ProcessKiller: Process com.xxx.xxx (2088) has open file /mnt/usb_storage/USB_DISK4/udisk0/xxx.mp4
ProcessKiller: Sending SIGHUP to process 2088
Vold: Failed to unmount /mnt/usb_storage/USB_DISK4/udisk0 (Device or resource busy, retries 1, action 2)
ActivityManagerService: Process com.xxx.xxx (pid 2088) has died

这是因为拔出 U 盘的时候,视频资源被视频播放器占用所导致的。可是我明明是做了拔出处理的,即在收到 U 盘被拔出的广播后释放视频资源,如下:

public class USBBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent == null || intent.getAction() == null) {
            return;
        }
        switch (intent.getAction()) {
            case Intent.ACTION_MEDIA_UNMOUNTED://扩展介质存在,但是还没有被挂载。(扩展介质已被拔出)
                //这里释放所有占用的资源
                break;
        }
    }
}

后来 debug 发现,其实在还未收到 U 盘被拔出的广播,进程就被杀死了。。。

既然不能在监听到 U 盘拔出的时候释放播放资源,那就只能换一种方法了。最后想到的方法是将播放视频的 activity 单独放到一个进程,这样即使该进程被杀死,也不会影响到整个应用奔溃。

虽然通过上面的方法解决了整个应用奔溃的问题,但是还是觉得不完美,总觉得 Android 不可能只提供了 U 盘拔出后的广播,而没有提供 U 盘将要被拔出的广播呀!经过一番查找,嗯,真香!确实有这个广播-android.intent.action.MEDIA_EJECT,该广播表示用户想要移除扩展介质,即扩展介质将要被拔出。收到这个广播释放占用的资源即可,例如视频播放器释放视频资源,文本读写需要关闭流等等。
完整的广播监听如下:

public class USBBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent == null || intent.getAction() == null) {
            return;
        }
        switch (intent.getAction()) {
            case Intent.ACTION_MEDIA_MOUNTED://扩展介质被插入,而且已经被挂载。
                if (intent.getData() != null) {
                    String path = intent.getData().getPath();
                    String usbRealRootDirectory = FileUtils.getUSBRealRootDirectory(path);
                }
                break;
            case Intent.ACTION_MEDIA_EJECT://用户想要移除扩展介质(扩展介质将要被拔出)
                //这里释放所有占用的资源
                break;
            case Intent.ACTION_MEDIA_UNMOUNTED://扩展介质存在,但是还没有被挂载。(扩展介质已被拔出)
                //这里做一些拔出 U 盘后的其他操作
                break;
        }
    }
}

以上就是 Android 设备与 U 盘之间的交互知识,关于获取 U 盘根目录,如果你有更好的方法欢迎交流~

相关源码:AndroidUSB

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容

  • 面试题总结 通用 安卓学习途径, 寻找资料学习的博客网站 AndroidStudio使用, 插件使用 安卓和苹果的...
    JingBeibei阅读 1,601评论 2 21
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,225评论 0 17
  • 2.1 Activity 2.1.1 Activity的生命周期全面分析 典型情况下的生命周期:在用户参与的情况下...
    AndroidMaster阅读 2,977评论 0 8
  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,622评论 0 10
  • 一、简历准备 1、个人技能 (1)自定义控件、UI设计、常用动画特效 自定义控件 ①为什么要自定义控件? Andr...
    lucas777阅读 5,130评论 2 54