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文件存储位置切换的更多相关文章
- 获取tomcat上properties文件的内容——方便文件存储位置的修改,解耦和
在java web开发的时候经常会用到读取读取或存放文件,这个文件的默认路径在哪里呢?写死在程序里面显然是可以的,但这样子不利于位于,假如有一天项目从window移植到linux,或者保存文件的路径变 ...
- HDFS的Java客户端操作代码(查看HDFS下所有的文件存储位置信息)
1.查看HDFS下所有的文件存储位置信息 package Hdfs; import java.net.URI; import org.apache.hadoop.conf.Configuration; ...
- win10上修改docker的镜像文件存储位置
记住:修改的是docker从服务器上拉下来的镜像文件存储位置(本地),是不是镜像源地址(服务器) 首先 win10下的docker有可视化操作界面和命令行操作,下载了docker-ce.exe双击后就 ...
- Android 文件存储浅析
最近做的一个需求和文件存储有关系.由于之前没有系统梳理过,对文件存储方面的知识一直很懵懂.趁着周末有时间,赶紧梳理一波. 这首从网上找到的一张图,很好的概括了外部存储和内部存储. 下面我们再来具体介绍 ...
- android: 文件存储
数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或电脑 关机的情况下,这些数据仍然不会丢失.保存在内存中的数据是处于瞬时状态的,而保存在 存储设备中的数据是处于持久状态的,持久化 ...
- Android文件存储
文件存储是Android中最基本的一种数据存储方式,它不读存储的内容进行任何的格式化处理,所有数据原封不动的保存在文件之中.如果想用文件存储的方式保存一些较为复杂的数据,就需要定义一套自己的格式规范, ...
- 转:Android文件存储路径getFilesDir()与getExternalFilesDir的区别
作为一个开发者,我们经常需要通过缓存一些文件到SD卡中,常见的方式就是,通过: File sdCard = Environment.getExternalStorageDirectory(); 获取S ...
- MySQL-5.7设置InnoDB表数据文件存储位置
1.表空间 Innodb存储引擎可将所有数据存放于ibdata*的共享表空间,也可将每张表存放于独立的.ibd文件的独立表空间. 共享表空间以及独立表空间都是针对数据的存储方式而言的. 共享表空间: ...
- android 文件存储&SharedPreferences
一.文件存储 文件存储主要是I/O流的操作,没什么好说的,需要注意的是保存文件的各个目录. 下面为常用的目录: public static File getInFileDir(Context cont ...
随机推荐
- Hadoop入门进阶课程12--Flume介绍、安装与应用案例
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,博主为石山园,博客地址为 http://www.cnblogs.com/shishanyuan ...
- [python]新手写爬虫v2.5(使用代理的异步爬虫)
开始 开篇:爬代理ip v2.0(未完待续),实现了获取代理ips,并把这些代理持久化(存在本地).同时使用的是tornado的HTTPClient的库爬取内容. 中篇:开篇主要是获取代理ip:中篇打 ...
- 我理解的Android加载器
Android的加载器(loader)是从Android 3.0开始出来的东西.要理解这里需要先理解为什么会出现加载器(也有地方把它说成是装载器)呢? 如果没有加载器... 首先Activity是我们 ...
- [Bootstrap]7天深入Bootstrap(2)整体架构
大多数Bootstrap的使用者都认为Bootstrap只提供了CSS组件 和JavaScript插件,其实CSS组件和JavaScript插件只是Bootstrap框架的表现形式而已,它们都是构建在 ...
- ThroughRain第一次冲刺个人总结
团队名:ThroughRain 项目确定:<餐厅到店点餐系统> 项目背景:本次项目是专门为餐厅开发的一套订餐系统.大家有没有发现在节假日去餐厅吃饭会超级麻烦,人很多, 热门的餐厅基本没有座 ...
- Scrum团队成立
1.团队名称:瘦子不吃肥肉 目标:学习更多,了解更多! 口号:加油 团队照: 2.角色分配: 产品负责人:卓嘉炜 http://www.cnblogs.com/luoliuxi/ Scr ...
- 【Win10】使用 ValidationAttribute 实现数据验证
WPF 中数据验证的方式多种多样,这里就不说了.但是,在 Windows Phone 8.1 Runtime 中,要实现数据验证,只能靠最基础的手动编写条件判断代码来实现.如果用过 ASP.NET M ...
- 计数排序 + 线段树优化 --- Codeforces 558E : A Simple Task
E. A Simple Task Problem's Link: http://codeforces.com/problemset/problem/558/E Mean: 给定一个字符串,有q次操作, ...
- 重构第12天 分解依赖(Break Dependencies)
理解:“分解依赖” 是指对部分不满足我们要求的类和方法进行依赖分解,通过装饰器来达到我们需要的功能. 详解:今天我们所讲的这个重构方法对于单元测试是非常有用的.如果你要在你的代码中加入单元测试但有一部 ...
- easyui数据网格视图(Datagrid View)的简单应用
下面介绍datagrid的数据网格详细视图和数据网格的分组视图 1.先引用的js和css文件 1)包含eauyui必备的四个文件easyui.css,icon.css, jquery-min.js.j ...