前言

由于项目需要使用到涉及到Android文件的保存及读取,那就学呗。

原先的计划是使用文本进行保存,数据格式使用Json

后面发现了SharedPreferences存储,一步到位,我喜欢。

我为啥看不到android中data的文件_SharedPre

内存&外存 1

开始之前,我们先来补充一下安卓中内部存储外部存储的概念。
当然标题所说的内存并不是手机上的运行内存…而是指手机的内部存储

内部存储位于系统中很特殊的一个位置,如果你想将文件存储于内部存储中,那么文件默认只能被你的应用访问到,且一个应用所创建的所有文件都在和应用包名相同的目录下。也就是说应用创建于内部存储的文件,与这个应用是关联起来的。当一个应用卸载之后,内部存储中的这些文件也被删除。

至于外部存储,在4.4(API19)之前,将手机自身带的存储卡划分为内部存储,而扩展的SD卡就是外部存储。但是从Android4.4开始,Google机身存储(内置存储)在概念上分成了内部存储 (internal)外部存储 (external) 两部分。

我为啥看不到android中data的文件_内部存储_02


那么怎么区分机身存储外部存储SD卡外部存储呢?对,SD卡也是外部存储,那怎么区分呢,在Android 4.4以后的系统中,API提供了这样一个方法来遍历手机的外部存储路径:

File[] files;
        //API > 19(Android 4.4)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            files = getExternalFilesDirs(Environment.MEDIA_MOUNTED);
            for(File file:files){
                Log.e("main", String.valueOf(file));
            }
        }

荣耀小7遍历结果:

我为啥看不到android中data的文件_SharedPre_03


可以看到它们都位于/storage下:

  • /storage/emulated/0是机身存储
  • /storage/BE16-1E17是外部存储

关于文件路径,可能需要先了解一下linux文件挂载,这里不展开。

由于内部存储相对于外部存储更外宝贵,因而文件存储的常规操作是:先检测有无外部存储(SD卡),无外部存储时再使用机身存储。

SharedPreferences

不同于文件存储,SharedPreferences使用键值对的形式保存,即在保存时需要提供一个字符串作为键值,在读取数据时就可以通过键值把对应的数据读取出来。官方文档提醒您:该类不支持跨进程使用

我为啥看不到android中data的文件_android_04

存储格式:XML

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
	<!--类型 name = "键值" value = "数据"/-->
    <int name="25" value="625" />
</map>

保存

想要使用SharedPreferences进行存储,首先需要获取到SharedPreferences对象

上面的例子,使用的是Context类中的getSharedPreferences()方法。

SharedPreferences getSharedPreferences (String name,int mode)

其他参数如下:

  • name:文件名,如果指定的文件不在,则会创建一个。
  • mode:模式,目前只有 MODE_PRIVATE 一种可选项,当然使用0的效果也是一样的。
  • MODE_PRIVATE:只有当前应用程序才能对这个SharedPreferences文件进行读写。
  • MODE_WORLD_READABLEAndroid 4.2+ 弃用
  • MODE_WORLD_WRITEABLEAndroid 4.2+ 弃用
  • MODE_MULTI_PROCESSAndroid 6.0+ 弃用

除此之外,我们还可以通过以下方法获取SharedPreferences对象
Activity类的getPreferences()方法。

SharedPreferences getPreferences(@Context.PreferencesMode int mode)

该方法与getSharedPreferences()类似,但它只有一个参数mode,文件名则是当前Activity的类名。

取得SharedPreferences对象后,我们可以通过edit()方法获取其Edit对象,通过Edit对象可以修改内存中的SharedPreferences的数据,再原子地提交到硬盘SharedPreferences。(没有理解错的话)

例如我们要添加文本数据,则使用putString();需要添加整型数据,则使用putint()…就不一一列举了。

此外,Edit类还为我们提供了clear()方法以清空数据。

修改完以后就是提交,目前提交有两种方法:

  • apply():立即将其更改提交到内存,对机身存储进行异步提交,且不会收到任何故障警告。
  • commit():同步地写到持久性存储SharedPreferences中,因此我们应避免在主线程中直接使用它

下面给出一个简单的文件保存示例:

sharedPreferences = getSharedPreferences("SharedPreferences", MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();

                editor.putString("test",editText.getText().toString());
                editor.apply();
                //editor.commit();

保存路径(需root用户才能看到)

那么保存的文件存储再哪里呢?

目前使用模拟器寻得的路径为:/data/data/pagename(包名)/shared_prefs

我为啥看不到android中data的文件_android_05


而在未获取root权限的手机上是无法直接查看到该文件的。

而目前接触的真机基本都是未获取root权限的;手机上看不到文件,心里虚得慌怎么办?

放心,我们可以在读取文件的地方debug一下,直接查看获取到的SharedPreferences对象

我为啥看不到android中data的文件_内部存储_06


可以发现,该demo荣耀小7上运行时,文件是存储在机身存储上的。

读取

可能要让你失望了,读取的方法比保存更为简单。

首先同样是取得SharedPreferences对象,这点和上面是一样的。

随后我们就可以通过get()方法取得我们所需要的数据(只要它在SharedPreferences中)。

每一个Editorput()方法,在SharedPreferences中都能找到一个get()方法将数据取出。

下面直接看例子:

String value = sharedPreferences.getString("test","Null");

                textView.setText(value);

demo效果

我为啥看不到android中data的文件_文件保存_07

这是在学习之前,自己问自己的问题,这里给出答案

Q:保存为同名SharedPreferences文件,会造成文件覆盖吗?
若文件已存在则不会覆盖,而是追加内容。
若追加同键值内容则会造成覆盖,否则添加新的键值对。

局限性 2:

SharedPreferences虽好,但不可滥用

  • value内容不宜过大
  • 第一次从SharedPreferences中获取值的时候,有可能阻塞主线程,使界面卡顿、掉帧。
  • 解析SharedPreferences的时候会产生大量的临时对象,导致频繁GC,引起界面卡顿。
  • 这些keyvalue会永远存在于内存之中,不会被释放。
  • 存储JSON特殊符号很多的value
  • JSON或者HTML格式存放在SharedPreferences里面的时候,需要转义,这样会带来很多 &这种特殊符号,SharedPreferences在解析碰到这个特殊符号的时候会进行特殊的处理,引发额外的字符串拼接以及函数调用开销。
  • 多次edit()多次apply()
  • 每次edit()都会创建一个Editor对象,额外占用内存;当然多创建几个对象也影响不了多少;但是,多次apply()也会卡界面你造吗?
  • 至于跨进程,这个SharedPreferences类开篇就澄清了, 不支持跨线程
    而容易让人引起误解的MODE_MULTI_PROCESS模式也于Android6.0弃用了。

我为啥看不到android中data的文件_android_08

结语

本文只是简单的介绍了如何使用SharedPreferences进行文件的读写。在此需要注意的是,当设备内部空间不足时,Android可能会删除缓存文件回收空间,但是我们不应该依赖系统为我们清理这些文件,而应始终自行维护缓存文件,使其占用空间保持在合理的范围内(例如1M)。因此我的操作是,每次打开文件写入前,先用clear()清空。