SharedPreference与文件存储

Android常用数据存储方式有SharedPreferences存储数据(虽然还是属于内部存储)、文件存储(内部,外部)、SQLite数据库存储、ContentProvider存储数据、网络存储数据等几种。本篇博客主要是介绍Shared Preference的原理与使用,区分内部与外部文件存储,以及它们的使用方式。那就从清除缓存与清除数据到底清除了什么这个问题开始吧!

内部存储 InternalStorage

在Android开发中,内存 Memory、内部存储 InternalStorage、外部存储 ExternalStorage这三者有啥区别呢? 在我们打开手机设置 -> 应用管理,随便选择一个软件,然后会看到一个是清除缓存的按钮,一个清除数据的按钮,那么当点击清除缓存的时候清除的是哪里的数据?当点击清除数据的时候又是清除的哪里的数据呢? 打开Device File Explorer会看到如下目录结构:

其实在使用SharedPreferenced的时候,将数据持久化存储于本地,其实就是存在这个文件中的xml文件里,App里边的数据库文件就存储于databases文件夹中,还有我们的普通数据存储在files中,缓存文件存储在cache文件夹中,存储在这里的文件我们都称之为内部存储。

下面来说说使用内部存储的代表——SharedPreferences,SharedPreferences也是在开发中使用的比较多的一种方案,用于存放一些类似登录的配置等信息。

SharedPreferences

1、用于存放一些类似登录的配置信息 2、本质上是一个xml文件,是通过类似键值对的方式存放信息 3、位于程序私有目录中,即data/data/[packageName]/shared_prefs

SharedPreferences的操作模式 1、MODE_APPEND:追加方式存储 2、MODE_PRIVATE:私有方式存储,其他应用无法访问 3、MODE_WORLD_READABLE:可被其他应用读取 4、MODE_WORLD_WRITEABLE:可被其他应用写入

SharedPreferences使用方式:

 1// 取数据
 2@Override
 3protected void onCreate(Bundle savedInstanceState) {
 4    super.onCreate(savedInstanceState);
 5    setContentView(R.layout.activity_main);
 6    etUserName = findViewById(R.id.et_username);
 7    etPassword = findViewById(R.id.et_password);
 8    SharedPreferences login_info = getSharedPreferences("login_info", MODE_PRIVATE);
 9	// 第一个参数为Key,第二个是默认值
10    etUserName.setText(login_info.getString("user_name", ""));
11    etPassword.setText(login_info.getString("password", ""));
12}
13
14
15// 存储据
16public void userLogin(View view) {
17    String userName = etUserName.getText().toString();
18    String password = etPassword.getText().toString();
19    if(TextUtils.isEmpty(userName) || TextUtils.isEmpty(password)){
20        Toast.makeText(MainActivity.this, "输入不完整", Toast.LENGTH_SHORT).show();
21    }
22
23    // 存储输入的信息
24    // 1、拿到SharedPreference对象
25    SharedPreferences loginInfoSP = getSharedPreferences("login_info", MODE_PRIVATE);
26    // 2、获取Editor对象
27    SharedPreferences.Editor editor = loginInfoSP.edit();
28    // 3、通过Editor存储数据
29    editor.putString("user_name", userName);
30    editor.putString("password", password);
31    // 3、调用提交方法
32    boolean commit = editor.commit();
33    Log.i(TAG, "userLogin: commitRet = " + commit);
34
35    // 校验登录结果
36    if(!("admin".equals(userName) && "123456".equals(password))){
37        Toast.makeText(MainActivity.this, "用户名或密码错误", Toast.LENGTH_SHORT).show();
38    }else {
39        Toast.makeText(MainActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
40        // TODO ...
41    }
42}

所以其实不难发现,SharedPreference存取数据非常简单,存数据只需要四步,取数据只要两步。

通过查看设备上的文件也可以发现,SharedPreference本质上就是一个xml文件而已,是通过类似键值对的方式存放信息。

获得内部存储目录

Context.getFileDir(),获取/data/data/包名/files Context.getCacheDir(),获取/data/data/包名/cache,下面通过代码演示一下:

 1public void saveToFileDir() {
 2    File filesDir = MainActivity.this.getFilesDir();
 3    File myFileTxt = new File(filesDir, "myFile.txt");
 4    try (BufferedWriter writer = new BufferedWriter(new FileWriter(myFileTxt))){
 5        writer.write("这是内部存储文件目录的文件内容!");
 6    } catch (IOException e) {
 7        e.printStackTrace();
 8    }
 9}
10
11public void saveToCacheDir() {
12    File cacheDir = MainActivity.this.getCacheDir();
13    File myCacheTxt = new File(cacheDir, "myCache.txt");
14    try (BufferedWriter writer = new BufferedWriter(new FileWriter(myCacheTxt))){
15        writer.write("这是内部存储缓存文件内容!");
16    } catch (IOException e) {
17        e.printStackTrace();
18    }
19}

外部存储ExternalStorage

ExternalStorage是我们平时操作最多的,外部存储一般就是我们上面看到的storage或者mnt文件夹,不同厂家有可能不一样,请见下图:

一般来说,在storage文件夹中有一个sdcard文件夹,这个文件夹中的文件又分为两类,一类是公有目录,还有一类是私有目录,其中的公有目录有九大类,比如DCIM、DOWNLOAD等这种系统为我们创建的文件夹,私有目录就是Android这个文件夹,这个文件夹打开之后里边有一个data文件夹,打开这个data文件夹,里面有许多应用包名组成的文件夹,那些就是对应的应用程序的私有外部存储区域(Android/data/应用包名)。比如浏览器的私有外部存储空间:

下面是一个存储与读取的例子:

 1<?xml version="1.0" encoding="utf-8"?>
 2<LinearLayout
 3    xmlns:android="http://schemas.android.com/apk/res/android"
 4    xmlns:tools="http://schemas.android.com/tools"
 5    android:layout_width="match_parent"
 6    android:layout_height="match_parent"
 7    android:padding="20dp"
 8    android:orientation="vertical"
 9    tools:context=".MainActivity">
10
11    <EditText
12        android:id="@+id/et_content"
13        android:layout_width="match_parent"
14        android:layout_height="wrap_content"
15        android:minLines="10" />
16
17    <Button
18        android:id="@+id/btn_save"
19        android:onClick="save"
20        android:layout_width="wrap_content"
21        android:layout_height="wrap_content"
22        android:text="保存" />
23
24    <Button
25        android:onClick="read"
26        android:id="@+id/btn_read"
27        android:layout_width="wrap_content"
28        android:layout_height="wrap_content"
29        android:text="读取" />
30
31    <TextView
32        android:id="@+id/tv_show"
33        tools:text="Content Text!"
34        android:layout_width="match_parent"
35        android:layout_height="wrap_content"/>
36</LinearLayout>

MainActivity.java

 1public class MainActivity extends AppCompatActivity {
 2    private static final String TAG = "MainActivity";
 3    private EditText etContent;
 4    private TextView tvShow;
 5
 6    // 申请权限请求码
 7    private static final int REQUEST_EXTERNAL_STORAGE = 1001;
 8
 9    // 检查权限,这种写法主要是针对比较新的Android6.0及以后的版本
10    public static void verifyStoragePermissions(Activity activity) {
11        int writePermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
12        int readPermission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE);
13
14        if (writePermission != PackageManager.PERMISSION_GRANTED
15                || readPermission != PackageManager.PERMISSION_GRANTED) {
16            // 如果没有权限需要动态地去申请权限
17            ActivityCompat.requestPermissions(
18                    activity,
19                    // 权限数组
20                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE},
21                    // 权限请求码
22                    REQUEST_EXTERNAL_STORAGE
23            );
24        }
25    }
26    // 如果在申请权限的过程中需要做一些对应的处理,则在此方法中处理
27    @Override
28    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
29        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
30        if(REQUEST_EXTERNAL_STORAGE == requestCode){
31            // TODO ...
32        }
33    }
34
35    @Override
36    protected void onCreate(Bundle savedInstanceState) {
37        super.onCreate(savedInstanceState);
38        setContentView(R.layout.activity_main);
39        // 获得外部存储的目录
40        File externalStorageDirectory = Environment.getExternalStorageDirectory();
41        Log.i(TAG, "onCreate: externalStorageDirectory = " + externalStorageDirectory.getAbsolutePath());
42        etContent = findViewById(R.id.et_content);
43        tvShow = findViewById(R.id.tv_show);
44        verifyStoragePermissions(this);
45    }
46
47    public void save(View view) {
48        String content = etContent.getText().toString();
49        if(TextUtils.isEmpty(content)) {
50            Toast.makeText(MainActivity.this, "输入为空", Toast.LENGTH_SHORT).show();
51            return;
52        }
53
54        // 判断外部存储的状态
55        if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
56            // 外部存储已挂载
57            String absolutePath = Environment.getExternalStorageDirectory().getAbsolutePath();
58            String descPath = absolutePath + "/input_content.txt";
59            File descFile = new File(descPath);
60            Log.i(TAG, "save: descFile = " + descFile.getAbsolutePath());
61            try {
62                BufferedWriter writer = new BufferedWriter(new FileWriter(descFile, true));
63                writer.write(content);
64                writer.close();
65                Toast.makeText(MainActivity.this, "写入成功", Toast.LENGTH_SHORT).show();
66            } catch (IOException e) {
67                e.printStackTrace();
68            }
69        }else{
70            Toast.makeText(MainActivity.this, "存储设备未挂载", Toast.LENGTH_SHORT).show();
71        }
72    }
73
74    public void read(View view) {
75        String absolutePath = Environment.getExternalStorageDirectory().getAbsolutePath();
76        String descPath = absolutePath + "/input_content.txt";
77        try {
78            BufferedReader bufferedReader = new BufferedReader(new FileReader(new File(descPath)));
79            String line;
80            StringBuilder builder = new StringBuilder();
81            while((line = bufferedReader.readLine()) != null){
82                builder.append(line).append("\n");
83            }
84            tvShow.setText(builder.toString());
85        } catch (IOException e) {
86            e.printStackTrace();
87        }
88    }
89}

manifest文件中别忘记声明权限:

1<!-- 在SDCard中创建与删除文件的权限 -->
2<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
3<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

获得外部存储目录

通过Environment.getExternalStorageDirectory(),我们很容易获得外部存储的根目录,通过拼接,很容易拿到DOWNLOAD、DCIM、MUSIC、MOVIES、LOST.DIR等公有目录,如何拿到外部存储的私有目录呢?

Context.getExternalFilesDir(String type),通常用于需要长时间保存的数据,获取到SDCard/Android/data/包名/files/目录。

Context.getExternalCacheDir(),通常用于需要临时保存的数据,获取到SDCard/Android/data/包名/cache/目录。下面演示一下向私有空间和缓存空间分别存入一个文件:

 1// 存储一个字符串到私有外部存储空间 Downloads
 2public void saveToPrivate(View view) {
 3    File externalPrivateDir = MainActivity.this.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
 4    File myDownloadTxt = new File(externalPrivateDir, "myDownload.txt");
 5    try {
 6        BufferedWriter writer = new BufferedWriter(new FileWriter(myDownloadTxt));
 7        writer.write("这是下载目录的文件内容!");
 8        writer.close();
 9    } catch (IOException e) {
10        e.printStackTrace();
11    }
12
13}
14
15// 存储一个字符串到私有外部存储空间 cache
16public void saveToCache(View view) {
17    File externalPrivateDir = MainActivity.this.getExternalCacheDir();
18    File myCacheTxt = new File(externalPrivateDir, "myCache.txt");
19    try {
20        BufferedWriter writer = new BufferedWriter(new FileWriter(myCacheTxt));
21        writer.write("这是缓存文件内容!");
22        writer.close();
23    } catch (IOException e) {
24        e.printStackTrace();
25    }
26}

所以总结一下就是:要获取外部存储就公用空间就用Environment,获取外部存储私有空间就用Context对象。

清除缓存与清除数据

清除缓存

反射调用接口:PackageManager.deleteApplicationCacheFiles

它会清除以下项目:

1、清除data/data/应用包名/cache/下的所有文件

2、清除data/data/应用包名/code_cache/下的所有文件

3、清除mnt/sdcard/Android/data/应用包名/下的cache文件夹

清除数据

反射调用接口:ActivityManager.clearApplicationUserData

它会清除以下项目:

1、清除data/data/应用包名/下的所有文件和文件夹

2、清除mnt/sdcard/Android/data/应用包名

3、清除mnt/sdcard/Android/media/应用包名

4、清除应用包名对应的App所有运行时权限的授权

总结一下内部存储与外部存储