前言
最近需要實(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