前情概要

Android 的文件存储也是非常重要的,比如微信中的聊天时产生的语音,视频,图片,文件等等。在视频 app 中下载的视频,某某音乐 app 所产生的缓存文件等等都是需要保存到安卓手机上磁盘的。

然后,由于过多的文件被保存或者缓存到安卓手机的文件,就会占用非常多的存储空间。这也是一个原因,还有一个原因是因为其 app 中所带的素材实在是非常之多,还有第三方库的导入。有很多 app 都是五六十兆,甚至是上百兆的都有。加上现在的存储空间特别大,app 大小也在变大。

刚开始学,所注意到的也就这些。

内部存储和外部存储

在学习文件存储之前,我还是有去了解 Android 的内部存储和外部存储。

什么是内部存储,我们一直认为,手机内置的存储设备就是内部存储;使用 SD 卡,U盘等这种外部设就是外部存储。在 Android 4.4 之前还确实是如此,不过在 Android 4.4 之后。安卓手机本身的存储空间变得越来越大,从以前的 16G 变成了现在的 512G ,那他们这个存储是不是内部存储呢?

不是的,在 Android 4.4 之后,以上所列出的空间大小是“机身存储”,在概念上区分了“内部存储”和“外部存储”。

如果我们在手机上插了一张 SD 卡,那怎么获取 SD 卡上的存储位置呢?

Android 有给我们一个方法,用于调取全部的外部路径,它就是 context.getExternalFilesDir(),这个方法可以得到所有的外部设备的存储。以及概念上的外部存储。

内部存储

内部存储的保存路径是在 /data/data/applicationId 路径下面,这个路径用户不能通过文件管理器直接访问到,也不能使用 Android 命令行终端来访问。需要获取 root 权限,而且现在的手机使用工具来 root 的成功率实在是太低了,所以我们可以利用安卓模拟器查看里面的内容

因为,安卓模拟器可是自带 root 的。

我们在内部存储的目录中可以看到以下的文件夹,这些文件夹全部都是 app 的文件夹

内部存储的目录

SharedPreferences:/data/data//shared_prefs
数据库目录:/data/data//databases
文件目录:/data/data//files
缓存目录:/data/data//cache

内部存储路径的方法调用

获取内部存储对应的三个路径非常简单,使用以下三个方法。至于第一个 SharedPreferences 可以看看这篇日志《Android 数据存储之 SharedPreferences》

文件目录:getFilesDir()
缓存目录:getCachesDir()
数据库目录:getDatabasePath()

读写内部存储

本次学习的是文件的读写,这里就展示了读取文件、写入文件和删除文件的一些方法。调用方法之后,就可以像 java io 一样操作内部存储的数据。

而且内部存储的读写是不需要在 AndroidManifest.xml 来声明读写权限的。

  • 读取:openFileInput()
    • 参数
      • string:文件名
  • 写入:openFileOutput()
    • 参数
      • string:文件名
      • int:模式(MODE_PRIVATE,MODE_APPEND)
  • 删除:deleteFile()
    • 参数
      • string:文件名

读取和写入,我感觉有点绕,写着写着就混了。

我们可以这样子理解:

老师要求我们背一首诗,要把纸上的诗背到脑子里,肯定需要多读,读的过程相当于输入(Input)到脑子里。脑子相当于是一个“变量”。

然后,老师要开始我们默写这首诗,这个时候就是把脑子里的诗写在一张新纸上,写的过程相当于输出(Output),将变量中的值给打印出来。

例子虽然不怎么好吧,但是我还是能理解的。

调用示例

我们看看使用这三个方法,看看调用的结果如何。这里的 fileName 的值是 test.txt 文件

写入方法

// 存储数据到内部存储
private void saveInternal(String content){
    FileOutputStream fos = null;
    try {
        // 打开文件
        fos = openFileOutput(fileName, MODE_PRIVATE);
        // 将脑子中的数据写入到文件中
        fos.write(content.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            // 关闭文件流
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

写入之后,这个目录下就会生成一个 test.txt 的文件

打开之后,发现里面有我们写入的内容

读取方法

// 从内部存储读取数据
private String readInternal() {
    FileInputStream fis = null;
    StringBuilder sb = new StringBuilder();
    try {
        fis = openFileInput(fileName);
        byte[] bytes = new byte[1024];
        int len;
        while ((len = fis.read(bytes)) > 0) {
            sb.append(new String(bytes, 0, len));
        }
        return sb.toString();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            fis.close();
        } catch (IOException ioException) {
            ioException.printStackTrace();
        }
    }
    return null;
}

点击读取之后,那个文本控件就会把它给显示出来

删除方法

@Override
public void onClick(View v) {
    boolean isDeleted = deleteFile(fileName);
    if(isDeleted){
        Toast.makeText(FileStorageActivity.this, "删除成功", Toast.LENGTH_SHORT).show();
    } else {
        Toast.makeText(FileStorageActivity.this, "删除失败,该文件可能不存在", Toast.LENGTH_SHORT).show();
    }
}

提示删除成功

删除之后,发现那个 test.txt 的文件不见了

外部存储

外部存储分为共有目录和私有目录。外部存储是我们可以见到的目录,一般,读取外部存储目录的所使用的方法是 Environment.getExternalStorageDirectory()

在模拟器获取到的值是:/storage/emulated/0
在我自己的手机上:/storage/0007-CF27/heguo/

为什么会这样呢?那是因为在我自己的手机上,设置把外部存储的默认值指定到我的 SD 卡上面。我们通过调用 getExternalFilesDirs("")方法来获取我的手机中的全部的外部存储路径。

调用的结果如下

/storage/emulated/0/Android/data/top.bestguo.androidlayout/files
/storage/0007-CF27/Android/data/top.bestguo.androidlayout/files

可以发现,在自己的手机上,内置的外部存储的路径也是 /storage/emulated/0/

不过在读取外部存储空间的时候,需要用户来同意读写。否则,程序会报出异常。关于 Android 的权限那一块也会单独来写,毕竟没有学到。这里通过一行代码来让用户是否同意我们读写

ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);

其中,Manifest.permission.WRITE_EXTERNAL_STORAGE 这个就是用于读写外部存储的声明。同时这个声明也需要在 AndroidManifest.xml 文件下来声明

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="top.bestguo.androidlayout">

    <uses-permission android:name="android.permission.INTERNET" />
    <!-- 需要添加的 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
    <!-- 以下是 application 的声明,省略 -->
    
</manifest>

公有目录

通过方法调用:

Environment.getExternalStoragePublicDirectory(String type)

其中 type 有这些,这些就是文件夹名。通过 Environment.DIRXXX 来获取

DIRECTORY_MUSIC				音乐文件夹
DIRECTORY_PODCASTS,			音乐或电台录音或视频中的剪辑
DIRECTORY_RINGTONES,		铃声文件夹
DIRECTORY_ALARMS,			闹钟相关文件夹
DIRECTORY_NOTIFICATIONS,	通知文件夹
DIRECTORY_PICTURES,			图片文件夹
DIRECTORY_MOVIES,			视频保存文件夹
DIRECTORY_DOWNLOADS,		下载文件夹
DIRECTORY_DCIM,				相机照片缓存文件夹
DIRECTORY_DOCUMENTS			文档文件夹

比如,微信发朋友圈,发图片需要获取 DCIM 下的文件,或者 Pictures 下的文件夹。让用户来选择这些图片,最来上传到朋友圈中。

私有目录

私有目录有以下几种:

/storage/emulated/Android/data//cache
/storage/emulated/Android/data//files

不过外部存储的私有目录可以直接使用文件管理器能查看的到,只是用户可能不是很在意。还有一个是因为,它的存储路径有点长,对于大多数的安卓用户来说可能就非常难找了。

外部存储示例

以下代码示例来对外部存储进行读写

注意:在读写外部存储时需要声明读写权限

写入

private void saveOuter(String context) {
    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
    FileOutputStream fos = null;
    try {
        // 获取外部存储的路径
        File dir = new File(Environment.getExternalStorageDirectory(), "bestguo");
        // 创建文件夹
        if(!dir.exists()) {
            Log.d("isCreatedDir", dir.mkdirs() + "");
        }
        // 创建文件
        File file = new File(dir, fileName);
        Log.d("file_path", file.toString());
        if(!file.exists()){
            Log.d("isCreatedDir", "" + file.createNewFile());
        }
        // 创建输入流
        fos = new FileOutputStream(file);
        fos.write(context.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

执行完成之后,外部存储中创建了对应的文件夹和文件

打开之后,对应的内容同样也能读取到

读取

private String readOuter() {
    FileInputStream fos = null;
    // 获取外部存储目录
    String path = Environment.getExternalStorageDirectory().toString();
   	// 创建文件类
    File file = new File(path + File.separator + "bestguo", fileName);
    try {
        StringBuilder sb = new StringBuilder();
        // 创建字节流对象
        fos = new FileInputStream(file);
        int len;
        byte[] bytes = new byte[1024];
        while((len = fos.read(bytes)) > 0) {
            sb.append(fos.read(bytes, 0, len));
        }
        return sb.toString();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            // 关闭
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return null;
}

点击读取,发现读取得到了数据

删除外部文件的话可以直接调用 File 对象下的 delete 方法即可

File file = new File(path + File.separator + "bestguo", fileName);
file.delete();

后记

项目的代码都在 AndroidStudy 的 github 仓库中。

本日志对应的代码:点我进入

参考链接:https://blog.csdn.net/csdn_aiyang/article/details/80665185