存储适配系列文章:
在持久化数据的时候,一般都是选择存入到文件里,本篇将着重分析Android 存储相关的知识,也是为Android 10.0 11存储适配打基础。 通过本篇文章,你将了解到:
1、存储划分 2、内部存储 3、外部存储 4、易混淆点说明
在Android 4.4 之前,由于硬件发展受限,手机自身的存储空间有限,需要通过外置SD卡来扩展存储空间。
如上图,手机自身的存储空间,称之为机身存储,在Android 4.4 之前作为内部存储使用。当然内部存储空间一般是不够用的,所以需要通过插入外置SD卡来扩充存储空间,这当做外部存储。
在Android 4.4 之后(含),手机机身存储扩大了:
如上图,机身存储划分为两部分:
1、内部存储 2、外部存储
当然,依然可以插入SD卡来扩充存储空间,这部分的存储空间称为扩展的外部存储空间。只是现在机身存储都比较大,很少插入SD卡了。 接下来将以Android 4.4 之后的存储划分来分析具体的存储方案。
回想一下平时使用的持久化方案:
1、SharedPreferences---->适用于存储小文件 2、数据库---->存储结构比较复杂的大文件
以上这些文件都是默认放在内部存储里。 “/” 表示根目录,内部存储里给每个应用按照其包名各自划分了目录,假设App的包名为:com.fish.myapplication 那么该文件在内部存储里的目录为: /data/user/0/com.fish.myapplication/
第一个"/“表示根目录,其后每个”/"表示目录分割符。 “0” 表示是第一个用户,后续添加了多用户则生成相应的用户目录: 如上图,新增了两个用户,生成的目录分别是:“11”、“12”。目前来说,很少开启多用户的。 一般来说,adb shell里是没有权限查看/data目录的。若要查看内部存储,通常是通过Android Studio侧边栏Device File Explorer选择对应的目标设备查看。 同样的,如果包名为:com.fish.myapplication,则对应的内部存储目录为: /data/data/com.fish.myapplication/
/data/user/0/com.fish.myapplication/ 会将值转换到/data/data/com.fish.myapplication/ 路径下。 每个App的内部存储空间仅允许自己访问(除非有更高的权限,如root),程序卸载后,该目录也会被删除。
除了SharedPreferences、数据库文件,内部存储还存放了哪些文件呢? 为方便起见,只查看/data/data/目录下的。
刚开始有只有两个空目录。 当进行写入SharedPreferences,创建数据库、写入文件等操作后新增了几个目录:
大致介绍一下以上目录作用:
1、cache–>存放缓存文件 2、code_cache–>存放运行时代码优化等产生的缓存 3、databases–>存放数据库文件 4、files–>存放一般文件 5、shared_prefs–>存放SharedPreferences 文件 6、lib–>存放App依赖的so库 是软链接,指向/data/app/ 某个子目录下
既然知道了各类文件存储的目录,那么如何读写这些文件呢? 我们知道在Java 的世界里,操作文件有两种方式:
字符流和字节流
以字节流为为例,一个简单的读取写入文件Demo:
//写入文件 private void writeFile(String filePath) { if (TextUtils.isEmpty(filePath)) return; try { File file = new File(filePath); FileOutputStream fileOutputStream = new FileOutputStream(file); BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream); String writeContent = "hello world "; bos.write(writeContent.getBytes()); bos.flush(); bos.close(); } catch (Exception e) { } } //从文件读取 private void readFile(String filePath) { if (TextUtils.isEmpty(filePath)) return; try { File file = new File(filePath); FileInputStream fileInputStream = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fileInputStream); byte[] readContent = new byte[1024]; int readLen = 0; while (readLen != -1) { readLen = bis.read(readContent, 0, readContent.length); if (readLen > 0) { String content = new String(readContent); Log.d("test", "read content:" + content.substring(0, readLen)); } } fileInputStream.close(); } catch (Exception e) { } }
可以看出,通过FileInputStream/FileOutputStream构造函数传入File对象即可实现文件读写,而File对象的构造依赖于文件的存放路径,因此重点在于如何获取文件的路径。 分别说明各个目录下文件的读写: 1、读写files目录下文件
#Context.java public abstract File getFilesDir();
使用方式:
private String getFilePath(Context context) { //获取files根目录 File fileDir = context.getFilesDir(); //获取文件 File myFile = new File(fileDir, "myFile"); return myFile.getAbsolutePath(); }
context.getFilesDir()的结果是返回files目录:
/data/user/0/com.fish.myapplication/files/
拿到对应文件的File对象后,构造相应的输入输出流即可实现对该文件的读写。可以看出,过程虽然简单但是有点