Android SD卡及U盤(pán)插拔狀態(tài)監(jiān)聽(tīng)及內(nèi)容讀取

本篇是通過(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)的源碼。

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

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)中的 oldStatenewState 參數(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)插入日志

U盤(pán)撥出,狀態(tài)變化:
STATE_EJECTING ——> STATE_UNMOUNTED ——> STATE_BAD_REMOVAL
最后調(diào)用監(jiān)聽(tīng)回調(diào)中的 onDiskDestroyed 方法。
U盤(pán)拔出日志

為了避免 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);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、通過(guò)CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫(kù)組件 SD...
    陽(yáng)明AI閱讀 16,228評(píng)論 3 119
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,319評(píng)論 25 708
  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料? 從這篇文章中你...
    hw1212閱讀 14,096評(píng)論 2 59
  • 目的很簡(jiǎn)單,就是利用APT生成一個(gè)HelloWorld的類。 第一步 在工程項(xiàng)目中新增一個(gè)Java Library...
    壹?jí)m子閱讀 922評(píng)論 0 0
  • 不管天有多黑,夜有多晚,總有人在外徘徊。 深夜十二點(diǎn),整座城市都漸漸沉入睡眠。而郊外,一座不大的寺廟,卻依舊點(diǎn)著燈...
    無(wú)宇子閱讀 358評(píng)論 0 0

友情鏈接更多精彩內(nèi)容