本篇是通過(guò)系統(tǒng)方法來(lái)對(duì)sd卡及U盤(pán)插拔監(jiān)聽(tīng)及數(shù)據(jù)獲取,Android盒子端開(kāi)發(fā),有系統(tǒng)權(quán)限,當(dāng)然,這個(gè)比較簡(jiǎn)單,知道具體方法,可以通過(guò)反射來(lái)實(shí)現(xiàn)。
先貼上效果圖:
獲取外置存儲(chǔ)設(shè)備并監(jiān)聽(tīng)插拔狀態(tài)

獲取文件內(nèi)容

前言
先說(shuō)需求,App在引導(dǎo)過(guò)程中,通過(guò)外置存儲(chǔ)設(shè)備(U盤(pán)或者sd卡)上傳指定的配置文件,開(kāi)始我沒(méi)打算用系統(tǒng)方法,網(wǎng)上看到 libaums 這個(gè)庫(kù)文件,嘗試使用了一下,但是最后發(fā)現(xiàn)它并不能友好的支持NTFS格式U盤(pán),能監(jiān)聽(tīng)到,但是好像沒(méi)有辦法獲取到路徑,最后看官方也說(shuō)了不支持NTFS格式,最后索性直接使用系統(tǒng)方法,反正有權(quán)限,真的可以為所欲為。
正文

可以看到系統(tǒng)設(shè)置里面,是能監(jiān)聽(tīng)到ntfs格式u盤(pán)(Evan_zch)的,并且能獲取U盤(pán)里面的文件,這樣就好辦了,挽起袖子直接開(kāi)干。
1、頁(yè)面定位
要查看具體某個(gè)功能的源碼,可以通過(guò)界面定位,這樣能更快的找到我們想要的代碼。
通過(guò)執(zhí)行下面代碼,可以直接定位當(dāng)前展示界面的包名和類名。
linux:
adb shell dumpsys activity | grep "mFocusedActivity"
windows:
adb shell dumpsys activity | findstr "mFocusedActivity"
執(zhí)行結(jié)果:

此時(shí)可以定位到系統(tǒng)設(shè)置存儲(chǔ)界面是StorageSettingsActivity,這個(gè)時(shí)候可以去 Android OS 這個(gè)網(wǎng)站搜索并查看相應(yīng)的源碼。

2、源碼分析
直接查看 StorageSettings 這個(gè)界面源碼,這個(gè)比較簡(jiǎn)單,大致還是能看的清楚,因?yàn)轫?xiàng)目時(shí)間比較緊,沒(méi)有仔細(xì)去研究,只貼一些關(guān)鍵代碼,具體能實(shí)現(xiàn)我的需求,等完成了這個(gè)項(xiàng)目,再好好來(lái)琢磨。
private StorageManager mStorageManager;
// 創(chuàng)建 StorageManager
mStorageManager = context.getSystemService(StorageManager.class);
// 注冊(cè)監(jiān)聽(tīng)
mStorageManager.registerListener(mStorageListener);
// 監(jiān)聽(tīng)回調(diào)
private final StorageEventListener mStorageListener = new StorageEventListener() {
@Override
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
if (isInteresting(vol)) {
refresh();
}
}
@Override
public void onDiskDestroyed(DiskInfo disk) {
refresh();
}
};
private static boolean isInteresting(VolumeInfo vol) {
switch(vol.getType()) {
// 內(nèi)置存儲(chǔ)設(shè)備
case VolumeInfo.TYPE_PRIVATE:
// 外置存儲(chǔ)設(shè)備
case VolumeInfo.TYPE_PUBLIC:
return true;
default:
return false;
}
}
在源碼中,直接在 onCreate 方法中創(chuàng)建 StorageManager 然后通過(guò) registerListener 注冊(cè)存儲(chǔ)設(shè)備的監(jiān)聽(tīng),在監(jiān)聽(tīng)回調(diào)里可以直接判斷存儲(chǔ)設(shè)備的狀態(tài),其中說(shuō)一下 onVolumeStateChanged 回調(diào)中的 oldState 和 newState 參數(shù)。通過(guò)查看源碼,能發(fā)現(xiàn)在 VolumeInfo 類中設(shè)置了存儲(chǔ)設(shè)備的一些基本狀態(tài)。
public static final int STATE_UNMOUNTED = 0;
public static final int STATE_CHECKING = 1;
public static final int STATE_MOUNTED = 2;
public static final int STATE_MOUNTED_READ_ONLY = 3;
public static final int STATE_FORMATTING = 4;
public static final int STATE_EJECTING = 5;
public static final int STATE_UNMOUNTABLE = 6;
public static final int STATE_REMOVED = 7;
public static final int STATE_BAD_REMOVAL = 8;
在回調(diào)中添加打印日志:

通過(guò)后臺(tái)日志,可以發(fā)現(xiàn)在U盤(pán)插入過(guò)程中,其狀態(tài)變化為:
STATE_UNMOUNTED ——> STATE_CHECKING ——> STATE_MOUNTED
U盤(pán)撥出,狀態(tài)變化:
STATE_EJECTING ——> STATE_UNMOUNTED ——> STATE_BAD_REMOVAL最后調(diào)用監(jiān)聽(tīng)回調(diào)中的
onDiskDestroyed 方法。
為了避免 onVolumeStateChanged 的多次回調(diào),我自己寫(xiě)了個(gè)判斷方法 isMounted,我們可以在 onVolumeStateChanged 加入 isMounted判斷方法,只有當(dāng)是外置存儲(chǔ)設(shè)備且掛載成功后才進(jìn)行ui更新。
public boolean isMounted(VolumeInfo vol, int oldState, int newState) {
return (isInteresting(vol) && oldState != newState && newState == VolumeInfo.STATE_MOUNTED);
}
private static boolean isInteresting(VolumeInfo vol) {
switch (vol.getType()) {
// 這里我們只關(guān)心外置存儲(chǔ)設(shè)備,所以直接注釋掉了 TYPE_PRIVATE
// case VolumeInfo.TYPE_PRIVATE:
case VolumeInfo.TYPE_PUBLIC:
return true;
default:
return false;
}
}
在監(jiān)聽(tīng)回調(diào)后,可以通過(guò)StorageManager類的 getVolumes方法,獲取所有的存儲(chǔ)設(shè)備。
public List<VolumeInfo> getStorageDeviceList() {
if (mStorageManager == null) {
throw new RuntimeException("StorageManagerUtils not init");
}
List<VolumeInfo> volumes = mStorageManager.getVolumes();
List<VolumeInfo> publicVolumes = new ArrayList<>();
publicVolumes.clear();
for (VolumeInfo info : volumes) {
int type = info.getType();
// 獲取當(dāng)前存儲(chǔ)設(shè)備的路徑
File path = volumeInfo.getPath();
// 同樣的,只關(guān)心外置存儲(chǔ)設(shè)備。
if (info.getType() == VolumeInfo.TYPE_PUBLIC) {
publicVolumes.add(info);
}else if(info.getType() == VolumeInfo.TYPE_PRIVATE){
// 獲取內(nèi)置存儲(chǔ)設(shè)備
}
}
return publicVolumes;
}
當(dāng)拿到設(shè)備后,通過(guò) VolumeInfo 類的 getPath 方法就可以獲取到U盤(pán)具體路徑

后面就可以通過(guò)這個(gè)路徑直接獲取U盤(pán)的文件了,基本就大功告成,時(shí)間有點(diǎn)急,寫(xiě)得有點(diǎn)粗糙,后面有時(shí)間再整理一下。
最后貼一下工具類的完整代碼:
/**
* @author Evan_zch
* @date 2018/9/17 19:13
* <p>
* 存儲(chǔ)設(shè)備管理類
*/
public class StorageManagerUtils {
private static final String TAG = "StorageManagerUtils";
private final StorageManager mStorageManager;
private static long totalBytes = 0;
private static long usedBytes = 0;
private static final class StorageManagerHolder {
private static final StorageManagerUtils INSTANCE = new StorageManagerUtils();
}
public static StorageManagerUtils getInstance() {
return StorageManagerHolder.INSTANCE;
}
private StorageManagerUtils() {
mStorageManager = DigiTvApplication.getAppContext().getSystemService(StorageManager.class);
}
public List<VolumeInfo> getStorageDeviceList() {
if (mStorageManager == null) {
throw new RuntimeException("StorageManagerUtils not init");
}
List<VolumeInfo> volumes = mStorageManager.getVolumes();
List<VolumeInfo> publicVolumes = new ArrayList<>();
publicVolumes.clear();
for (VolumeInfo info : volumes) {
int type = info.getType();
if (info.getType() == VolumeInfo.TYPE_PUBLIC) {
Logutils.d(TAG + "--refresh type is public");
String bestVolumeDescription = mStorageManager.getBestVolumeDescription(info);
File path = info.getPath();
Logutils.d(TAG + "--refresh type=" + type + ",bestVolumeDescription=" + bestVolumeDescription + ",path=" + path);
publicVolumes.add(info);
}
}
return publicVolumes;
}
public boolean isMounted(VolumeInfo vol, int oldState, int newState) {
return (isInteresting(vol) && oldState != newState && newState == VolumeInfo.STATE_MOUNTED);
}
private static boolean isInteresting(VolumeInfo vol) {
switch (vol.getType()) {
//case VolumeInfo.TYPE_PRIVATE:
case VolumeInfo.TYPE_PUBLIC:
return true;
default:
return false;
}
}
public String getTotalSize(VolumeInfo vol) {
if (vol.isMountedReadable()) {
final File path = vol.getPath();
if (totalBytes <= 0) {
totalBytes = path.getTotalSpace();
}
}
return Formatter.formatFileSize(DigiTvApplication.getAppContext(), totalBytes);
}
public String getUsedSize(VolumeInfo vol) {
if (vol.isMountedReadable()) {
final File path = vol.getPath();
final long freeBytes = path.getFreeSpace();
usedBytes = totalBytes - freeBytes;
}
return Formatter.formatFileSize(DigiTvApplication.getAppContext(), usedBytes);
}
}