Android SD卡及U盤插拔狀態監聽及內容讀取
本篇是通過系統方法來對sd卡及U盤插拔監聽及資料獲取,Android盒子端開發,有系統許可權,當然,這個比較簡單,知道具體方法,可以通過反射來實現。
先貼上效果圖:
獲取外接儲存裝置並監聽插拔狀態

獲取檔案內容

前言
先說需求,App在引導過程中,通過外接儲存裝置(U盤或者sd卡)上傳指定的配置檔案,開始我沒打算用系統方法,網上看到 ofollow,noindex">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 這個網站搜尋並檢視相應的原始碼。

Snipaste_2018-09-20_11-43-55.png
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盤插入日誌
U盤撥出,狀態變化:
STATE_EJECTING
——>
STATE_UNMOUNTED
——>
STATE_BAD_REMOVAL
最後呼叫監聽回撥中的 onDiskDestroyed
方法。

U盤拔出日誌
為了避免 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 + "--refreshtype is public"); String bestVolumeDescription = mStorageManager.getBestVolumeDescription(info); File path = info.getPath(); Logutils.d(TAG + "--refreshtype=" + 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); } }