本篇是通過系統方法來對sd卡及U盤插拔監聽及數據獲取,Android盒子端開發,有系統權限,當然,這個比較簡單,知道具體方法,可以通過反射來實現。
先貼上效果圖:
獲取外置存儲設備並監聽插拔狀態
獲取文件內容
前言
先說需求,App在引導過程中,通過外置存儲設備(U盤或者sd卡)上傳指定的配置文件,開始我沒打算用系統方法,網上看到 libaums 這個庫文件,嘗試使用了一下,但是最後發現它並不能友好的支持NTFS格式U盤,能監聽到,但是好像沒有辦法獲取到路徑,最後看官方也說了不支持NTFS格式,最後索性直接使用系統方法,反正有權限,真的可以爲所欲爲。
正文
可以看到系統設置裏面,是能監聽到ntfs格式u盤(Evan_zch)的,並且能獲取U盤裏面的文件,這樣就好辦了,挽起袖子直接開幹。
1、頁面定位
要查看具體某個功能的源碼,可以通過界面定位,這樣能更快的找到我們想要的代碼。
通過執行下面代碼,可以直接定位當前展示界面的包名和類名。
linux:
adb shell dumpsys activity | grep "mFocusedActivity"
windows:
adb shell dumpsys activity | findstr "mFocusedActivity"
執行結果:
此時可以定位到系統設置存儲界面是StorageSettingsActivity
,這個時候可以去 Android OS 這個網站搜索並查看相應的源碼。
2、源碼分析
直接查看 StorageSettings
這個界面源碼,這個比較簡單,大致還是能看的清楚,因爲項目時間比較緊,沒有仔細去研究,只貼一些關鍵代碼,具體能實現我的需求,等完成了這個項目,再好好來琢磨。
private StorageManager mStorageManager;
// 創建 StorageManager
mStorageManager = context.getSystemService(StorageManager.class);
// 註冊監聽
mStorageManager.registerListener(mStorageListener);
// 監聽回調
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()) {
// 內置存儲設備
case VolumeInfo.TYPE_PRIVATE:
// 外置存儲設備
case VolumeInfo.TYPE_PUBLIC:
return true;
default:
return false;
}
}
在源碼中,直接在 onCreate
方法中創建 StorageManager
然後通過 registerListener
註冊存儲設備的監聽,在監聽回調裏可以直接判斷存儲設備的狀態,其中說一下 onVolumeStateChanged
回調中的 oldState
和 newState
參數。通過查看源碼,能發現在 VolumeInfo
類中設置了存儲設備的一些基本狀態。
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;
在回調中添加打印日誌:
通過後臺日志,可以發現在U盤插入過程中,其狀態變化爲:
STATE_UNMOUNTED
——> STATE_CHECKING
——> STATE_MOUNTED
U盤撥出,狀態變化:
STATE_EJECTING
——> STATE_UNMOUNTED
——> STATE_BAD_REMOVAL
最後調用監聽回調中的
onDiskDestroyed
方法。爲了避免 onVolumeStateChanged
的多次回調,我自己寫了個判斷方法 isMounted
,我們可以在 onVolumeStateChanged
加入 isMounted
判斷方法,只有當是外置存儲設備且掛載成功後才進行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()) {
// 這裏我們只關心外置存儲設備,所以直接註釋掉了 TYPE_PRIVATE
// case VolumeInfo.TYPE_PRIVATE:
case VolumeInfo.TYPE_PUBLIC:
return true;
default:
return false;
}
}
在監聽回調後,可以通過StorageManager
類的 getVolumes
方法,獲取所有的存儲設備。
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();
// 獲取當前存儲設備的路徑
File path = volumeInfo.getPath();
// 同樣的,只關心外置存儲設備。
if (info.getType() == VolumeInfo.TYPE_PUBLIC) {
publicVolumes.add(info);
}else if(info.getType() == VolumeInfo.TYPE_PRIVATE){
// 獲取內置存儲設備
}
}
return publicVolumes;
}
當拿到設備後,通過 VolumeInfo
類的 getPath
方法就可以獲取到U盤具體路徑
後面就可以通過這個路徑直接獲取U盤的文件了,基本就大功告成,時間有點急,寫得有點粗糙,後面有時間再整理一下。
最後貼一下工具類的完整代碼:
/**
* @author Evan_zch
* @date 2018/9/17 19:13
* <p>
* 存儲設備管理類
*/
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);
}
}