Android 設(shè)備與 U 盤之間的交互

前言

最近需要實(shí)現(xiàn)一個(gè) TV 或一體機(jī)從 U 盤讀取數(shù)據(jù)顯示的功能闯捎,該功能主要解決的問題是:

  • 獲取 U 盤根目錄
  • 解決拔出 U 盤進(jìn)程被殺死的問題

獲取 U 盤根目錄

獲取 U 盤根目錄需要分兩種情況:

1. 應(yīng)用程序已經(jīng)在運(yùn)行蚀乔,這個(gè)時(shí)候插入 U 盤。

這種情況我是通過監(jiān)聽媒體掛載的廣播來實(shí)現(xiàn)的,具體代碼如下:
注冊廣播:

        <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>

監(jiān)聽 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://擴(kuò)展介質(zhì)被插入僚祷,而且已經(jīng)被掛載尺锚。
                if (intent.getData() != null) {
                    String path = intent.getData().getPath();
                    String usbRealRootDirectory = FileUtils.getUSBRealRootDirectory(path);
                }
                break;
        }
    }
}

經(jīng)測試,intent.getData().getPath(); 在一體機(jī)上獲取的并不是 U 盤最終的根目錄讶凉,所以通過 getUSBRealRootDirectory() 方法再一次提取最終的根目錄染乌,該方法具體如下:

    /**
     * 獲取 U 盤真正根目錄
     *
     * @param usbTempRootDirectory U 盤臨時(shí)根目錄
     * @return U 盤真正根目錄
     */
    public static String getUSBRealRootDirectory(String usbTempRootDirectory) {
        String realUSBRootDirectory = "";
        File dir = new File(usbTempRootDirectory);
        File[] files = dir.listFiles();

        /**
         * 注意:
         * 經(jīng)測試,
         * TV 直接是 usbTempRootDirectory 作為 U 盤的根目錄懂讯,例如:/storage/577F-85CA
         * 一體機(jī)會(huì)在 U 盤的根目錄(usbTempRootDirectory=/mnt/usb_storage/USB_DISK4)下再創(chuàng)建多個(gè)包含 "udisk" 的目錄荷憋,然后其中一個(gè)作為 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. 應(yīng)用程序還未運(yùn)行眉撵,U 盤就已經(jīng)插入了媒殉。

這種情況就無法通過監(jiān)聽廣播拿到 U 盤根目錄了寇钉,經(jīng)查詢也沒找到特定 API 可以獲取到矾睦,所以這里只能用反射的方法藕施。具體如下:
通過反射方法獲取 U 盤臨時(shí)根目錄

    /**
     * 獲取 U 盤臨時(shí)根目錄(一體機(jī)會(huì)在臨時(shí)目錄下再創(chuàng)建多個(gè)包含 "udisk" 的目錄摩幔,所以臨時(shí)目錄并不是 U 盤真正的根目錄)
     *
     * @param context Context
     * @return U 盤臨時(shí)根目錄集合
     */
    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 一般是內(nèi)部存儲(chǔ)铐尚,例如 /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;
    }

同樣宣增,在一體機(jī)上獲取的并不是 U 盤最終的根目錄,所以還是通過 getUSBRealRootDirectory() 方法再一次提取最終的根目錄矛缨,具體如下:

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

解決拔出 U 盤進(jìn)程被殺死的問題

因?yàn)樾枰獜?U 盤獲取視頻地址進(jìn)行播放帖旨,當(dāng)正在播放的時(shí)候拔出 U 盤就會(huì)出現(xiàn)進(jìn)程被殺死的情況,報(bào)錯(cuò)日志如下:

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

這是因?yàn)榘纬?U 盤的時(shí)候灵妨,視頻資源被視頻播放器占用所導(dǎo)致的碉就。可是我明明是做了拔出處理的闷串,即在收到 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://擴(kuò)展介質(zhì)存在,但是還沒有被掛載烹吵。(擴(kuò)展介質(zhì)已被拔出)
                //這里釋放所有占用的資源
                break;
        }
    }
}

后來 debug 發(fā)現(xiàn)碉熄,其實(shí)在還未收到 U 盤被拔出的廣播,進(jìn)程就被殺死了肋拔。锈津。。

既然不能在監(jiān)聽到 U 盤拔出的時(shí)候釋放播放資源凉蜂,那就只能換一種方法了琼梆。最后想到的方法是將播放視頻的 activity 單獨(dú)放到一個(gè)進(jìn)程,這樣即使該進(jìn)程被殺死窿吩,也不會(huì)影響到整個(gè)應(yīng)用奔潰茎杂。

雖然通過上面的方法解決了整個(gè)應(yīng)用奔潰的問題,但是還是覺得不完美纫雁,總覺得 Android 不可能只提供了 U 盤拔出后的廣播煌往,而沒有提供 U 盤將要被拔出的廣播呀!經(jīng)過一番查找轧邪,嗯刽脖,真香!確實(shí)有這個(gè)廣播-android.intent.action.MEDIA_EJECT忌愚,該廣播表示用戶想要移除擴(kuò)展介質(zhì)曲管,即擴(kuò)展介質(zhì)將要被拔出。收到這個(gè)廣播釋放占用的資源即可硕糊,例如視頻播放器釋放視頻資源院水,文本讀寫需要關(guān)閉流等等。
完整的廣播監(jiān)聽如下:

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://擴(kuò)展介質(zhì)被插入癌幕,而且已經(jīng)被掛載衙耕。
                if (intent.getData() != null) {
                    String path = intent.getData().getPath();
                    String usbRealRootDirectory = FileUtils.getUSBRealRootDirectory(path);
                }
                break;
            case Intent.ACTION_MEDIA_EJECT://用戶想要移除擴(kuò)展介質(zhì)(擴(kuò)展介質(zhì)將要被拔出)
                //這里釋放所有占用的資源
                break;
            case Intent.ACTION_MEDIA_UNMOUNTED://擴(kuò)展介質(zhì)存在,但是還沒有被掛載勺远。(擴(kuò)展介質(zhì)已被拔出)
                //這里做一些拔出 U 盤后的其他操作
                break;
        }
    }
}

以上就是 Android 設(shè)備與 U 盤之間的交互知識(shí),關(guān)于獲取 U 盤根目錄时鸵,如果你有更好的方法歡迎交流~

相關(guān)源碼:AndroidUSB

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末胶逢,一起剝皮案震驚了整個(gè)濱河市厅瞎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌初坠,老刑警劉巖和簸,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碟刺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)半沽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來者填,“玉大人,你說我怎么就攤上這事占哟。” “怎么了榨乎?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長刺覆。 經(jīng)常有香客問我,道長谦屑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任氢橙,我火速辦了婚禮恬偷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘袍患。我一直安慰自己坦康,他們只是感情好诡延,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著肆良,像睡著了一般逸绎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棺牧,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天朗儒,我揣著相機(jī)與錄音颊乘,去河邊找鬼醉锄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛榆鼠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播妆够,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼神妹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起鸵荠,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛹找,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體庸疾,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乍楚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年届慈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片金顿。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖揍拆,靈堂內(nèi)的尸體忽然破棺而出渠概,到底是詐尸還是另有隱情礁凡,我是刑警寧澤高氮,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布顷牌,位于F島的核電站剪芍,受9級(jí)特大地震影響窟蓝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜运挫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望峡继。 院中可真熱鬧匈挖,春花似錦碾牌、人聲如沸舶吗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肴捉。三九已至,卻和暖如春齿穗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缤灵。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帖鸦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓作儿,卻偏偏與公主長得像馋劈,于是被迫代替她去往敵國和親晾嘶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 面試題總結(jié) 通用 安卓學(xué)習(xí)途徑, 尋找資料學(xué)習(xí)的博客網(wǎng)站 AndroidStudio使用, 插件使用 安卓和蘋果的...
    JingBeibei閱讀 1,677評(píng)論 2 21
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 6,424評(píng)論 0 17
  • 2.1 Activity 2.1.1 Activity的生命周期全面分析 典型情況下的生命周期:在用戶參與的情況下...
    AndroidMaster閱讀 3,044評(píng)論 0 8
  • 一机断、Python簡介和環(huán)境搭建以及pip的安裝 4課時(shí)實(shí)驗(yàn)課主要內(nèi)容 【Python簡介】: Python 是一個(gè)...
    _小老虎_閱讀 5,746評(píng)論 0 10
  • 一绣夺、簡歷準(zhǔn)備 1、個(gè)人技能 (1)自定義控件陶耍、UI設(shè)計(jì)、常用動(dòng)畫特效 自定義控件 ①為什么要自定義控件烈钞? Andr...
    lucas777閱讀 5,208評(píng)論 2 54