基本存储
Android文件的存储有很多种方式,下面会统一进行介绍。
下面我们再来具体介绍相关知识和内容。
内部存储
内部存储,位于data/data/包名/路径下
是否需要用户权限:否
是否能被其他应用访问:否
卸载应用数据是否被删除:是
内部存储控件不需要用户权限,这意味着我们不需要用户去授权。
设备为每个安装的App在data/data目录下创建以应用程序包名对应的文件夹,可以直接读写和操作,且不能被其他应用所访问,保证内部存储文件的安全性和隐私性。
我们可以通过工具查看对应应用的存储文件,它包含如下目录:
/data/data/应用名/cache :存放的是APP的缓存信息
/data/data/应用名/code_cache :在运行时存放应用产生的编译或者优化的代码
/data/data/应用名/files : 存放APP的文件信息
其中,SharedPrefersences和SQLite数据库都是存储在内部空间上的,可以通过Context来获取和操作:
getFilesDir().getAbsolutePath() : /data/user/0/package/files
getCacheDir().getAbsolutePath() :/storage/emulated/0/Android/data/package/cache
getDir(“myFile”, MODE_PRIVATE).getAbsolutePath() : /data/user/0/package/app_myfile
getCodeCacheDir().getAbsolutePath() : /data/user/0/package/code_cache (> Android 5.0)
注意:内部存储空间十分有限,所以在App的开发过程中要尽量避免使用。
外部存储
在Android 4.4(API 19)以前,手机自带的存储卡就是内部存储,而拓展的SD卡则是外部存储。
在Android 4.4以后,将手机机身存储在概念上分为:"内部存储internal" 和 "外部存储external" 。
其中SD卡也是外部存储,为了区分机身存储和SD卡等外部存储方式,在4.4以后的系统中提供下面的API来遍历手机的外部存储路径:
File[] files;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
files = getExternalFilesDirs(Environment.MEDIA_MOUNTED);
for(File file:files){
Log.e("main",file);
}
}
如果手机具备SD卡的话,则会打印两条数据,代表机身存储和SD卡存储的路径:
/storage/emulated/0/Android/data/packname/files/mounted(机身存储路径)
/storage/B3E4-1711/Android/data/packname/files/mounted(SD卡存储路径)
一般对于外部存储可以分为两类,外部公有和外部私有(私有的都会随APP卸载而删除,公有的则不会)。
外部公有
公共目录必须需要用户授权读写的权限,这意味着我们需要在 AndroidManifest.xml 中注册用户权限。
<!-- 外部存储写入数据权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
并且在 Android 6.0 系统之后需要申请用户权限,并获得用户授权,才能读写文件。
可以通过Environment 对象,访问读写公共目录的文件,在读写时应先判断外部存储的状态,是否能够支持读写操作。
Environment.getExternalStorageState()
/** {@link #MEDIA_UNKNOWN}, {@link #MEDIA_REMOVED},
* {@link #MEDIA_UNMOUNTED}, {@link #MEDIA_CHECKING},
* {@link #MEDIA_NOFS}, {@link #MEDIA_MOUNTED},
* {@link #MEDIA_MOUNTED_READ_ONLY}, {@link #MEDIA_SHARED},
* {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}
*/
只有在返回值为 MEDIA_MOUNTED 表示当前是可正常读写的。接下来看看相关的API:
1. Environment.getExternalStorageDirectory() : /storage/emulated/0
2. Environment.getExternalStoragePublicDirectory(String type): /storage/emulated/0/xxx
其中type字段的取值如下:
Environment.getExternalStoragePublicDirectory(DIRECTORY_DOCUMENTS).getAbsolutePath() : /storage/emulated/0/Documents
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath() : /storage/emulated/0/Music
上述的API在Android 10(API 29)基础上已经无法使用,替代方案如下:
File storageDirectory = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
外部私有
私有目录,在 Android4.4 系统以上。不需要注册和用户授权外部私有存储的读写的权限,就可以在应用的外部私有进行读写文件。并且文件不能被其他应用所访问,具有较好的隐私性和安全性。
私有目录地址:/storage/emulated/0/Android/data/应用包名
私有目录相关的API如下所示:
getExternalCacheDir().getAbsolutePath()
// /storage/emulated/0/Android/data/package/cache
getExternalFilesDir("mytest").getAbsolutePath()
// /storage/emulated/0/Android/data/package/files/mytest
getExternalFilesDir(null).getAbsolutePath()
// /storage/emulated/0/Android/data/package/files
Sharedpreferences
SharedPreferences是轻量级的存储类,它是用xml文件存放数据,文件存放在/data/data/
// 存储数据
String fileName = "legend";
SharedPreferences sp = getSharedPreferences(fileName, Context.MODE_PRIVATE);
Editor edit = sp.edit();
edit.putString("name", "zhangsan");
edit.commit();
// 读取数据
String fileName = "legend";
SharedPreferences sp = getSharedPreferences(fileName, Context.MODE_PRIVATE);
String name = sp.getString(fileName, null);
注意:私有目录下的shared_prefs文件夹里面,路径为/data/data/包名/shared_prefs。
模式解析
SharedPreferences有四种操作模式,它们分别是:
模式 | 描述 |
---|---|
MODE_APPEND | 追加方式存储 |
MODE_PRIVATE | 私有方式存储,其他应用无法访问 |
MODE_WORLD_READABLE | 表示当前文件可以被其他应用读取 |
MODE_WOELD_WRITEABLE | 表示当前文件可以被其他应用写入 |
如果想要访问其他应用中的Preference必须满足被访问的应用的Perference创建时指定可读或可写的权限。
其中两个提交的方法:apply和commit的区别如下:
apply没有返回值,而commit反馈boolean类型表明修改是否提交成功。
apply是将修改数据提交到内存,而后异步真正提交到硬件磁盘,而commit是同步提交到硬件磁盘、
在多个并发提交commit时,会等待正在处理的commit保存到磁盘后再操作,效率低。
而apply只是的提交到内容,后面调用apply将会直接覆盖前面的内存数据,效率高。
如果对结果不关心的话,推荐使用apply,关心结果则使用commit。
操作封装
在实际的项目开发中,我们一般会将Sharedpreferences进行封装,以此达到快速使用的目的。
public class SPUtils {
public static final String FILE_NAME = "share_data";
/**保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法*/
public static void put(Context context, String key, Object object) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
if (object instanceof String) {
editor.putString(key, (String) object);
} else if (object instanceof Integer) {
editor.putInt(key, (Integer) object);
} else if (object instanceof Boolean) {
editor.putBoolean(key, (Boolean) object);
} else if (object instanceof Float) {
editor.putFloat(key, (Float) object);
} else if (object instanceof Long) {
editor.putLong(key, (Long) object);
} else {
editor.putString(key, object.toString());
}
SharedPreferencesCompat.apply(editor);
}
/**得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值*/
public static Object get(Context context, String key, Object defaultObject) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
if (defaultObject instanceof String) {
return sp.getString(key, (String) defaultObject);
} else if (defaultObject instanceof Integer) {
return sp.getInt(key, (Integer) defaultObject);
} else if (defaultObject instanceof Boolean) {
return sp.getBoolean(key, (Boolean) defaultObject);
} else if (defaultObject instanceof Float) {
return sp.getFloat(key, (Float) defaultObject);
} else if (defaultObject instanceof Long) {
return sp.getLong(key, (Long) defaultObject);
}
return null;
}
/**移除某个key值已经对应的值*/
public static void remove(Context context, String key) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.remove(key);
SharedPreferencesCompat.apply(editor);
}
/**清除所有数据*/
public static void clear(Context context) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.clear();
SharedPreferencesCompat.apply(editor);
}
/**查询某个key是否已经存在*/
public static boolean contains(Context context, String key) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,Context.MODE_PRIVATE);
return sp.contains(key);
}
/**返回所有的键值对*/
public static Map<String, ?> getAll(Context context) {
SharedPreferences sp = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE);
return sp.getAll();
}
/**创建一个解决SharedPreferencesCompat.apply方法的一个兼容类*/
private static class SharedPreferencesCompat {
private static final Method sApplyMethod = findApplyMethod();
/**反射查找apply的方法*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private static Method findApplyMethod() {
try {
Class clz = SharedPreferences.Editor.class;
return clz.getMethod("apply");
} catch (NoSuchMethodException e) {
}
return null;
}
/**如果找到则使用apply执行,否则使用commit*/
public static void apply(SharedPreferences.Editor editor) {
try {
if (sApplyMethod != null) {
sApplyMethod.invoke(editor);
return;
}
} catch (Exception e) {
}
editor.commit();
}
}
}
资源目录
assets文件夹里面的文件都是保持原始的文件格式,需要用AssetManager以字节流的形式读取文件。
先在Activity里面调用getAssets() 来获取AssetManager引用。
再用AssetManager的open(String fileName, int accessMode) 方法则指定读取的文件以及访问模式就能得到输入流InputStream。
然后就是用已经open file 的inputStream读取文件,读取完成后记得inputStream.close() 。
调用AssetManager.close() 关闭AssetManager。
需要注意的是,来自Resources和Assets 中的文件只可以读取而不能进行写的操作以下为raw文件中读取:
public String getFromRaw(){
try {
InputStreamReader inputReader = new InputStreamReader(getResources().openRawResource(R.raw.test1));
BufferedReader bufReader = new BufferedReader(inputReader);
String line="";
String Result="";
while((line = bufReader.readLine()) != null)
Result += line;
return Result;
} catch (Exception e) {
e.printStackTrace();
}
}
以下为assets文件中读取:
public String getFromAssets(String fileName){
try {
InputStreamReader inputReader = new InputStreamReader(getResources().getAssets().open(fileName) );
BufferedReader bufReader = new BufferedReader(inputReader);
String line="";
String Result="";
while((line = bufReader.readLine()) != null)
Result += line;
return Result;
} catch (Exception e) {
e.printStackTrace();
}
}