android文件存储位置切换

 最近有个需求,助手的google卫星地图和OpenCycleMap下载的离线地图数据,要能够在内置存储和外置存储空间之间切换,因为离线瓦片数据非常大,很多户外用户希望将这些文件存储在外置TF卡上,不占用内置存储空间,所以把最近研究的整理了下,分享给大家。

  需要考虑和遇到的问题(主要是不同手机、不同系统的兼容性):

  1.这样获取手机所有挂载的存储器?

   Android是没有提供显式的接口的,首先肯定是要阅读系统设置应用“存储”部分的源码,看存储那里是通过什么方式获取的。最后找到StorageManager和StorageVolume这2个重要的类,然后通过反射获取StorageVolume[]列表。

  2.用什么标示一个存储器的唯一性?

   存储路径?不行(有些手机不插TF卡,内置存储路径是/storage/sdcard0,插上TF卡后,内置存储路径变成/storage/sdcard1,TF卡变成/storage/sdcard0)。

   存储卡名称?不行(可能会切换系统语言,导致名称匹配失败,名称的resId也不行,较低的系统版本StorageVolume没有mDescriptionId这一属性)。

   经过测试,发现使用mStorageId可以标示存储器的唯一性,存储器数量改变,每个存储器的id不会改变。

  3.如何获得存储器的名称?

   经测试,不同的手机主要有3种获取存储器名换的方法:getDescription()、getDescription(Context context)、先获得getDescriptionId()再通过resId获取名称。

  4.任务文件下载一半时,切换文件保存存储器,怎么处理?

   有2种方案:

   4.1 切换时,如果新的存储空间足够所有文件转移,先停止所有下载任务,将所有下载完和下载中的文件拷贝到新的存储空间,然后再更新下载数据库下载任务的存储路径,再恢复下载任务;

   4.2 切换时,先拷贝所有下载完成的文件到新的存储空间,下载任务继续下载,下载完成再移动到新的存储空间。

  5.在4.4系统上,第三方应用无法读取外置存储卡的问题。(参考“External Storage”)

   google为了在程序卸载时,能够完全彻底的将程序所有数据清理干净,应用将不能向2级存储区域写入文件。

   “The WRITE_EXTERNAL_STORAGE permission must only grant write access to the primary external storage on a device. Apps must not be allowed to write to secondary external storage devices, except in their package-specific directories as allowed by synthesized permissions. Restricting writes in this way ensures the system can clean up files when applications are uninstalled.”

   要能够在4.4系统上TF卡写入文件,必须先root,具体方法可以google。

   所以4.4系统上,切换会导致文件转移和下载失败,用户如果要切换到TF卡,至少需要提醒用户,并最好给出4.4上root解决方法。

  以下是获取存储器的部分代码:

public static class MyStorageVolume{
        public int mStorageId;
        public String mPath;
        public String mDescription;
        public boolean mPrimary;
        public boolean mRemovable;
        public boolean mEmulated;
        public int mMtpReserveSpace;
        public boolean mAllowMassStorage;
        public long mMaxFileSize;  //最大文件大小。(0表示无限制)
        public String mState;      //返回null

        public MyStorageVolume(Context context, Object reflectItem){
            try {
                Method fmStorageId = reflectItem.getClass().getDeclaredMethod("getStorageId");
                fmStorageId.setAccessible(true);
                mStorageId = (Integer) fmStorageId.invoke(reflectItem);
            } catch (Exception e) {
            }

            try {
                Method fmPath = reflectItem.getClass().getDeclaredMethod("getPath");
                fmPath.setAccessible(true);
                mPath = (String) fmPath.invoke(reflectItem);
            } catch (Exception e) {
            }

            try {
                Method fmDescriptionId = reflectItem.getClass().getDeclaredMethod("getDescription");
                fmDescriptionId.setAccessible(true);
                mDescription = (String) fmDescriptionId.invoke(reflectItem);
            } catch (Exception e) {
            }
            if(mDescription == null || TextUtils.isEmpty(mDescription)){
                try {
                    Method fmDescriptionId = reflectItem.getClass().getDeclaredMethod("getDescription");
                    fmDescriptionId.setAccessible(true);
                    mDescription = (String) fmDescriptionId.invoke(reflectItem, context);
                } catch (Exception e) {
                }
            }
            if(mDescription == null || TextUtils.isEmpty(mDescription)){
                try {
                    Method fmDescriptionId = reflectItem.getClass().getDeclaredMethod("getDescriptionId");
                    fmDescriptionId.setAccessible(true);
                    int mDescriptionId = (Integer) fmDescriptionId.invoke(reflectItem);
                    if(mDescriptionId != 0){
                        mDescription = context.getResources().getString(mDescriptionId);
                    }
                } catch (Exception e) {
                }
            }

            try {
                Method fmPrimary = reflectItem.getClass().getDeclaredMethod("isPrimary");
                fmPrimary.setAccessible(true);
                mPrimary = (Boolean) fmPrimary.invoke(reflectItem);
            } catch (Exception e) {
            }

            try {
                Method fisRemovable = reflectItem.getClass().getDeclaredMethod("isRemovable");
                fisRemovable.setAccessible(true);
                mRemovable = (Boolean) fisRemovable.invoke(reflectItem);
            } catch (Exception e) {
            }

            try {
                Method fisEmulated = reflectItem.getClass().getDeclaredMethod("isEmulated");
                fisEmulated.setAccessible(true);
                mEmulated = (Boolean) fisEmulated.invoke(reflectItem);
            } catch (Exception e) {
            }

            try {
                Method fmMtpReserveSpace = reflectItem.getClass().getDeclaredMethod("getMtpReserveSpace");
                fmMtpReserveSpace.setAccessible(true);
                mMtpReserveSpace = (Integer) fmMtpReserveSpace.invoke(reflectItem);
            } catch (Exception e) {
            }

            try {
                Method fAllowMassStorage = reflectItem.getClass().getDeclaredMethod("allowMassStorage");
                fAllowMassStorage.setAccessible(true);
                mAllowMassStorage = (Boolean) fAllowMassStorage.invoke(reflectItem);
            } catch (Exception e) {
            }

            try {
                Method fMaxFileSize = reflectItem.getClass().getDeclaredMethod("getMaxFileSize");
                fMaxFileSize.setAccessible(true);
                mMaxFileSize = (Long) fMaxFileSize.invoke(reflectItem);
            } catch (Exception e) {
            }

            try {
                Method fState = reflectItem.getClass().getDeclaredMethod("getState");
                fState.setAccessible(true);
                mState = (String) fState.invoke(reflectItem);
            } catch (Exception e) {
            }
        }

        /**
         * 获取Volume挂载状态, 例如Environment.MEDIA_MOUNTED
         */
        public String getVolumeState(Context context){
            return StorageVolumeUtil.getVolumeState(context, mPath);
        }

        public boolean isMounted(Context context){
            return getVolumeState(context).equals(Environment.MEDIA_MOUNTED);
        }

        public String getDescription(){
            return mDescription;
        }

        /**
         * 获取存储设备的唯一标识
         */
        public String getUniqueFlag(){
            return "" + mStorageId;
        }

        /*public boolean isUsbStorage(){
            return mDescriptionId == android.R.string.storage_usb;
        }*/

        /**
         * 获取目录可用空间大小
         */
        public long getAvailableSize(){
            return StorageVolumeUtil.getAvailableSize(mPath);
        }

        /**
         * 获取目录总存储空间
         */
        public long getTotalSize(){
            return StorageVolumeUtil.getTotalSize(mPath);
        }

        @Override
        public String toString() {
            return "MyStorageVolume{" +
                    "\nmStorageId=" + mStorageId +
                    "\n, mPath='" + mPath + '\'' +
                    "\n, mDescription=" + mDescription +
                    "\n, mPrimary=" + mPrimary +
                    "\n, mRemovable=" + mRemovable +
                    "\n, mEmulated=" + mEmulated +
                    "\n, mMtpReserveSpace=" + mMtpReserveSpace +
                    "\n, mAllowMassStorage=" + mAllowMassStorage +
                    "\n, mMaxFileSize=" + mMaxFileSize +
                    "\n, mState='" + mState + '\'' +
                    '}' + "\n";
        }
    }

存储信息MyStorageVolume

public static List<MyStorageVolume> getVolumeList(Context context){
        List<MyStorageVolume> svList = new ArrayList<MyStorageVolume>(3);
        StorageManager mStorageManager = (StorageManager)context
                .getSystemService(Activity.STORAGE_SERVICE);
        try {
            Method mMethodGetPaths = mStorageManager.getClass().getMethod("getVolumeList");
            Object[] list = (Object[]) mMethodGetPaths.invoke(mStorageManager);
            for(Object item : list){
                svList.add(new MyStorageVolume(context, item));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return svList;
    }

获取所有存储器

github上的测试例子:

https://github.com/John-Chen/BlogSamples/tree/master/StorageTest

  

如果还有什么地方没有考虑到的,欢迎讨论。  

其他精彩文章文章

android学习笔记(41)android选项菜单和子菜单(SubMenu )

android学习笔记(40)Notification的功能与用法

android学习笔记(42)android使用监听器来监听菜单事件

android学习笔记(43)android创建单选菜单和复选菜单

 jQuery教程(12)-ajax操作之基于请求加载数据

 jQuery教程(13)-ajax操作之追加 HTML

更多关于android开发文章


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。