Android SD卡及U盤插拔狀態監聽及內容讀取

本篇是經過系統方法來對sd卡及U盤插拔監聽及數據獲取,Android盒子端開發,有系統權限,固然,這個比較簡單,知道具體方法,能夠經過反射來實現。java

先貼上效果圖:linux

獲取外置存儲設備並監聽插拔狀態

獲取文件內容

前言

先說需求,App在引導過程當中,經過外置存儲設備(U盤或者sd卡)上傳指定的配置文件,開始我沒打算用系統方法,網上看到 libaums 這個庫文件,嘗試使用了一下,可是最後發現它並不能友好的支持NTFS格式U盤,能監聽到,可是好像沒有辦法獲取到路徑,最後看官方也說了不支持NTFS格式,最後索性直接使用系統方法,反正有權限,真的能夠隨心所欲android

正文


能夠看到系統設置裏面,是能監聽到ntfs格式u盤(Evan_zch)的,而且能獲取U盤裏面的文件,這樣就好辦了,挽起袖子直接開幹。

一、頁面定位

要查看具體某個功能的源碼,能夠經過界面定位,這樣能更快的找到咱們想要的代碼。
經過執行下面代碼,能夠直接定位當前展現界面的包名和類名。git

linux:

adb shell dumpsys activity | grep "mFocusedActivity"

windows:

adb shell dumpsys activity | findstr "mFocusedActivity"
複製代碼

執行結果:
github

此時能夠定位到系統設置存儲界面是StorageSettingsActivity,這個時候能夠去 Android OS 這個網站搜索並查看相應的源碼。web

Snipaste_2018-09-20_11-43-55.png
Snipaste_2018-09-20_11-43-55.png

二、源碼分析

直接查看 StorageSettings 這個界面源碼,這個比較簡單,大體仍是能看的清楚,由於項目時間比較緊,沒有仔細去研究,只貼一些關鍵代碼,具體能實現個人需求,等完成了這個項目,再好好來琢磨。shell

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 回調中的 oldStatenewState 參數。經過查看源碼,能發如今 VolumeInfo 類中設置了存儲設備的一些基本狀態。windows

    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;
複製代碼

在回調中添加打印日誌:
app


經過後臺日志,能夠發如今U盤插入過程當中,其狀態變化爲:
STATE_UNMOUNTED ——> STATE_CHECKING ——> STATE_MOUNTED
U盤插入日誌
U盤插入日誌

U盤撥出,狀態變化:
STATE_EJECTING ——> STATE_UNMOUNTED ——> STATE_BAD_REMOVAL
最後調用監聽回調中的 onDiskDestroyed 方法。
U盤拔出日誌
U盤拔出日誌

爲了不 onVolumeStateChanged 的屢次回調,我本身寫了個判斷方法 isMounted,咱們能夠在 onVolumeStateChanged 加入 isMounted判斷方法,只有當是外置存儲設備且掛載成功後才進行ui更新。ide

 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);
    }
}
複製代碼
相關文章
相關標籤/搜索