文章

文件操作

文件操作

文件操作

  • 内部存储
  • 外置SD卡
  • 内置SD卡:/storage/emulated/0,其中又分为私有目录(Android/)和公共目录

image-20211011154001972

7867366-b594bf96be9e3f79

image-20211009202500915

apk

  • res/raw下的和assert下的,这些数据只能读取,不能写入。单个文件大小不能超过1M。
  • res/raw不可以有目录结构,而assets则可以有目录结构。
  • res/raw中的文件会被映射到R.java文件中,访问的时候直接使用资源ID即R.id.filename;assets文件夹下的文件不会被映射到R.java中,访问的时候需要AssetManager类。
  • 读取
1
2
InputStream is = getResources().openRawResource(R.string.app_name);
InputStream is = getResources().getAssets().open("file.txt");

内部存储

  • 内部存储 和外部存储以==是否是应用的安装目录来划分==,内部存储是在应用的安装目录下,外部存储 在应用的安装目录外。

  • 内部存储的文件是应用的私有文件,其他应用不能访问。
  • data是应用安装目录,需要root权限才能查看
  • 应用访问自己的内部存储不需要权限,访问外部存储有可能需要申请权限。
  • 应用卸载后,文件会被移除。
  • 创建模式
    • MODE_APPEND:即向文件尾写入数据
    • MODE_PRIVATE:即仅打开文件可写入数据
    • MODE_WORLD_READABLE:所有程序均可读该文件数据,api17废弃
    • MODE_WORLD_WRITABLE:即所有程序均可写入数据,api17废弃
  • 获取内部存储的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 会新建一个目录/data/user/0/com.example.myfile/app_newFolder
File file1 = getDir("newFolder", MODE_PRIVATE);

// /data/user/0/com.example.myfile/app_
File file2 = getDir("", MODE_PRIVATE);

// /data/user/0/com.example.myfile/files
File file3 = getFilesDir();

// /data/user/0/com.example.myfile/cache
File file4 = getCacheDir();

// /data/user/0/com.example.myfile
String s = getApplicationInfo().dataDir;
  • 访问和存储文件
1
File file = new File(context.getFilesDir(), filename); // 勿多次打开和关闭同一文件,创建新文件时,写入数据才会创建成功
  • 使用信息流存储文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 获取会写入filesDir目录中的文件的FileOutputStream
// Android 7.0以上,必须用Context.MODE_PRIVATE文件模式传递到openFileOutput()
String fileContents = "Hello world!";
// Java7新特性:try括号内的资源会在try语句结束后自动释放
// 前提是这些可关闭的资源必须实现java.lang.AutoCloseable接口
// openFileOutput只能操作filesDir目录
try (FileOutputStream fos = openFileOutput("myfile", Context.MODE_PRIVATE)) {
    fos.write(fileContents.getBytes());
} catch (IOException e) {
    e.printStackTrace();
}

File file = new File(getFilesDir(), "myfile");
Log.d(TAG, String.valueOf(file.exists())); // true
Log.d(TAG, file.getAbsolutePath()); //  /data/user/0/com.example.myfile/files/myfile
  • 使用信息流访问文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 只能使用openFileOutput和openFileInput进行操作
// 并且也只能操作filesDir目录
String fileName = "myfile";
FileInputStream fis = context.openFileInput(fileName);
InputStreamReader inputStreamReader =
        new InputStreamReader(fis, StandardCharsets.UTF_8);
StringBuilder stringBuilder = new StringBuilder();
try (BufferedReader reader = new BufferedReader(inputStreamReader)) {
    String line = reader.readLine();
    while (line != null) {
        stringBuilder.append(line).append('\n');
        line = reader.readLine();
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    String contents = stringBuilder.toString();
    Log.d(TAG, "content:"+contents); // content:Hello world!
}
  • 查看文件列表
1
2
3
4
5
// 获取包含filesDir目录中所有文件名称的数组
String[] files = fileList();
for (String file : files) {
    Log.d(TAG, file);
}
  • 创建嵌套目录
1
2
3
File directory = context.getFilesDir();
// 通过给定的父抽象路径名和子路径名字符串创建一个新的File实例
File file = new File(directory, filename);
  • 创建缓存文件
1
2
3
4
5
/**
 * 参数1:前缀字符串定义的文件名;必须至少有三个字符长,文件名实际会被追加乱码
 * 参数2:后缀字符串定义文件的扩展名;如果为null后缀".tmp" 将被使用
 */
File.createTempFile(filename, null, context.getCacheDir());
  • 访问缓存文件
1
2
// 当设备的内部存储空间不足时,Android 可能会删除这些缓存文件以回收空间。因此,请在读取前检查缓存文件是否存在
File cacheFile = new File(context.getCacheDir(), filename);
  • 移除缓存文件
1
2
3
4
// 对该文件的File对象使用
cacheFile.delete();
// 或者使用上下文的方法,不能包含路径分隔符
context.deleteFile(cacheFileName);

外部存储

image-20211011112957030

  • 对于支持外插SD卡的设备,外部存储包括两部分:内置存储卡和外置SD卡。
  • Android 4.3 以下,只能通过Context.getExternalFilesDir(type) 来获取外部存储在内置存储卡分区的私有目录,无法获取外置SD卡。

  • Android 4.3 开始,可以通过==Context.getExternalFilesDirs(type) 获取一个File数组,包含了内置存储卡分区和外置SD的私有目录地址==。
  • 可以使用兼容库的静态方法 ContextCompate.getExternalFilesDirs() 兼容 4.3。

  • 对外部存储空间访问所需的权限
1
2
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
版本存储位置是否需要读写权限
Android 4.4以前外部存储(公共目录和私有目录)需要
Android 4.4以后外部存储(公共目录)需要
Android 4.4以后外部存储(私有目录)不需要
  • 分区存储

Android 9(API 级别 28)或更低版本的设备上,只要其他应用具有相应的存储权限,任何应用都可以访问外部存储空间中的应用专属文件。

Android 10(API 级别 29)及更高版本为目标平台的应用在默认情况下被赋予了==对外部存储空间的分区访问权限==(即分区存储)。此类应用只能访问外部存储空间上的应用专属目录,以及本应用所创建的特定类型的媒体文件。

  • 验证存储空间可用性
1
2
3
4
5
6
7
8
9
10
// 检查是否可以读写
private boolean isExternalStorageWritable() {
    return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED;
}

// 检查是不是只读
private boolean isExternalStorageReadable() {
     return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED ||
            Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED_READ_ONLY;
}
  • 先在注册清单中申请,再动态申请
1
2
3
4
5
6
7
8
9
10
11
12
// 申请权限
public void requestPermission() {
    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        // 如果没有授权则申请授权,参数3是请求码
        ActivityCompat.requestPermissions(MainActivity.this,
                new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
    }
    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(MainActivity.this,
                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
    }
}
  • 判断授权结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 获取授权结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    // 判断授权结果
    switch (requestCode) {
        case 1:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.d(TAG, "已授权");
            } else {
                Toast.makeText(this, "已拒绝权限", Toast.LENGTH_SHORT).show();
            }
            break;
        default:
    }
}
  • 在没有可移除外部存储空间的设备上,请使用以下命令启用虚拟卷,以测试外部存储空间可用性逻辑
1
adb shell sm set-virtual-disk true
  • 获取外部存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
File file = getExternalFilesDir(null);
Log.d(TAG, "" + file); // /storage/emulated/0/Android/data/com.example.myfile/files

file = getExternalCacheDir();
Log.d(TAG, "" + file); // /storage/emulated/0/Android/data/com.example.myfile/cache

// 内置sd卡绝对路径
Environment.getExternalStorageDirectory().getAbsolutePath();

// 内置sd卡路径:files[0]
// 外置sd卡路径:files[1]
File[] files = getExternalFilesDirs(null);
Log.d(TAG, ""+ files[0]); // /storage/emulated/0/Android/data/com.example.myfile/files
Log.d(TAG, ""+ files[1]); // /storage/0BF1-3C09/Android/data/com.example.myfile/files

files = getExternalCacheDirs();
Log.d(TAG, ""+ files[0]); // /storage/emulated/0/Android/data/com.example.myfile/cache
Log.d(TAG, ""+ files[1]); // /storage/0BF1-3C09/Android/data/com.example.myfile/cache
  • ==内置sd卡路径==:/storage/emulated/0才是最终源头,/mnt/sdcard和/sdcard是指向它的一个软连接而已。
  1. /mnt/sdcard指向/storage/self/primary

image-20211011134008929

  1. /sdcard指向/storage/self/primary

image-20211011134136072

  1. /storage/self/primary指向/storage/emulated/0

image-20211011134240780

  • 选择物理存储位置
1
2
3
4
5
6
// 返回数组中的第一个元素被视为主外部存储卷
// 除非该卷已满或不可用,否则应使用该卷
File[] externalStorageVolumes =
        ContextCompat.getExternalFilesDirs(getApplicationContext(), null);
// /storage/emulated/0/Android/data/com.example.myfile/files
File primaryExternalStorage = externalStorageVolumes[0];
  • 访问持久性文件
1
2
3
// 从外部存储空间访问应用专属文件
// getExternalFilesDir参数默认访问的是files文件夹,也可以指定子文件夹
File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);
  • 创建缓存文件
1
2
// 将应用专属文件添加到外部存储空间中的缓存
File externalCacheFile = new File(context.getExternalCacheDir(), filename);
  • 移除缓存文件
1
2
// 从外部缓存目录中移除文件
externalCacheFile.delete();
  • 媒体内容
1
2
3
4
5
6
7
8
9
10
11
@Nullable
File getAppSpecificAlbumStorageDir(Context context, String albumName) {
    // Get the pictures directory that's inside the app-specific directory on
    // external storage.
    File file = new File(context.getExternalFilesDir(
            Environment.DIRECTORY_PICTURES), albumName);
    if (file == null || !file.mkdirs()) {
        Log.e(LOG_TAG, "Directory not created");
    }
    return file;
}
  • 常用操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
getDatabasePath():>/data/user/0/com.example.myfile/databases/sample.db
getCacheDir():>/data/user/0/com.example.myfile/cache
getFilesDir():>/data/user/0/com.example.myfile/files
getDir("mydir"):>/data/user/0/com.example.myfile/app_webview/Web Data
getPackageCodePath():>/data/app/~~yaBwJJfuCBkU_v4FpprZuA==/com.example.myfile-MU9Z1oQkiOSdUFS7RKD0gw==/base.apk
getPackageResourcePath():/data/app/~~yaBwJJfuCBkU_v4FpprZuA==/com.example.myfile-MU9Z1oQkiOSdUFS7RKD0gw==/base.
getExternalFilesDir():/storage/emulated/0/Android/data/com.example.myfile/files
getExternalFilesDirs():---/storage/emulated/0/Android/data/com.example.myfile/files
getExternalCacheDir():/storage/emulated/0/Android/data/com.example.myfile/cache
getExternalCacheDirs():---/storage/emulated/0/Android/data/com.example.myfile/cache
getObbDir():/storage/emulated/0/Android/obb/com.example.myfile
getObbDirs():---/storage/emulated/0/Android/obb/com.example.myfile
Environment.getExternalStorageState():mounted
Environment.getExternalStorageDirectory():/storage/emulated/0
Environment.getDownloadCacheDirectory():/data/cache
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC):/storage/emulated/0/Music
Environment.getRootDirectory():/system

查询可用空间

  • getAllocatableBytes() 的返回值可能大于设备上的当前可用空间量。这是因为系统已识别出可以从其他应用的缓存目录中移除的文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// App needs 10 MB within internal storage.
private static final long NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L;

StorageManager storageManager =
        getApplicationContext().getSystemService(StorageManager.class);
UUID appSpecificInternalDirUuid = storageManager.getUuidForPath(getFilesDir());
long availableBytes =
        storageManager.getAllocatableBytes(appSpecificInternalDirUuid);
if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) {
    storageManager.allocateBytes(
            appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP);
} else {
    Intent storageIntent = new Intent();
    storageIntent.setAction(ACTION_MANAGE_STORAGE);
    // Display prompt to user, requesting that they choose files to remove.
}
  • 获取存储信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
package com.example.myfile;

import android.app.ActivityManager;
import android.content.Context;
import android.os.Environment;
import android.os.StatFs;
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.Log;

import java.io.File;
import java.lang.reflect.Method;

public class StorageInfo {
    private static final int INTERNAL_STORAGE = 0; // 内置sd卡
    private static final int EXTERNAL_STORAGE = 1; // 外置sd卡
    private static final String TAG = "xxx";

    // RAM
    public static String getRAMInfo(Context context) {
        long totalSize = 0;
        long availableSize = 0;

        ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        // 全局内存的使用信息
        ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
        activityManager.getMemoryInfo(memoryInfo);
        totalSize = memoryInfo.totalMem; // 单位B
        availableSize = memoryInfo.availMem;

        return "内存可用/总共:"
                + Formatter.formatFileSize(context, availableSize)
                + "/" + Formatter.formatFileSize(context, totalSize);
    }

    // 判断SD是否挂载
    public static boolean isSDCardMount() {
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    }

    // 内置sd卡
    public static String getROMInfo(Context context) {
        // getDataDirectory是内置sd卡
        StatFs statFs = new StatFs(Environment.getDataDirectory().getPath()); // /storage/emulated/0
        long totalCounts = statFs.getBlockCountLong(); // 总块数
        long availableCounts = statFs.getAvailableBlocksLong(); // 可用块数
        long size = statFs.getBlockSizeLong(); // 块大小
        long availROMSize = availableCounts * size;
        long totalROMSize = totalCounts * size;

        return "内置sd卡可用/总共:"
                + Formatter.formatFileSize(context, availROMSize) + "/"
                + Formatter.formatFileSize(context, totalROMSize);
    }

    // 外置SD卡
    public static String getSDCardInfo(Context context) {

        if (isSDCardMount()) {
            /**
             * Android 4.4 开始,系统将机身存储划分为内部存储和外部存储,这样在没有扩展SD卡时,
             * 外部存储就是机身存储的一部分,指向/storage/emulated/0。
             * 当有扩展SD卡插入时,系统将获得两个外部存储路径。
             */
            // files[0]是内置sd卡路径,files[1]是外置sd卡路径
            File[] files = context.getExternalFilesDirs(null);
            StatFs statFs = new StatFs(files[1].getAbsolutePath());
            long totalCounts = statFs.getBlockCountLong(); // 总块数
            long availableCounts = statFs.getAvailableBlocksLong(); // 可用块数
            long size = statFs.getBlockSizeLong(); // 块大小
            long availROMSize = availableCounts * size;
            long totalROMSize = totalCounts * size;

            return "外置sd卡可用/总共:"
                    + Formatter.formatFileSize(context, availROMSize) + "/"
                    + Formatter.formatFileSize(context, totalROMSize);
        } else {
            return "无外置SD卡";
        }
    }

    // 获取ROM信息 0:内部ROM,1:外置SD卡
    public static String getStorageInfo(Context context, int type) {
        String path = getStoragePath(context, type);
        // 判断是否有外置SD卡
        if (!isSDCardMount() || TextUtils.isEmpty(path) || path == null) {
            return "无外置SD卡";
        }

        File file = new File(path);
        StatFs statFs = new StatFs(file.getPath());

        long blockCount = statFs.getBlockCountLong(); // 总块数
        long availableBlocks = statFs.getAvailableBlocksLong(); // 可用块数
        long blockSize = statFs.getBlockSizeLong(); // 块大小
        long totalSpace = blockSize * blockCount;
        long availableSpace = availableBlocks * blockSize;

        return "可用/总共:"
                + Formatter.formatFileSize(context, availableSpace) + "/"
                + Formatter.formatFileSize(context, totalSpace);
    }

    // 通过反射获取手机存储路径
    public static String getStoragePath(Context context, int type) {
        StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
        try {
            Method getPathsMethod = sm.getClass().getMethod("getVolumePaths", (Class<?>[]) null);
            String[] path = (String[]) getPathsMethod.invoke(sm, (Object[]) null);
            switch (type) {
                case INTERNAL_STORAGE:
                    return path[type];
                case EXTERNAL_STORAGE:
                    if (path.length > 1) {
                        return path[type];
                    } else {
                        return null;
                    }
                default:
                    break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
1
2
3
4
5
6
Log.d(TAG, "外置sd卡是否加载:" + StorageInfo.isSDCardMount()); // 外置sd卡是否加载:true
Log.d(TAG, StorageInfo.getRAMInfo(this)); // 内存可用/总共:1.22 GB/2.08 GB
Log.d(TAG, StorageInfo.getROMInfo(this)); // 内置sd卡可用/总共:5.93 GB/6.24 GB
Log.d(TAG, StorageInfo.getSDCardInfo(this)); // 外置sd卡可用/总共:535 MB/535 MB
Log.d(TAG, "内置sd卡"+StorageInfo.getStorageInfo(this, 0)); // 内置sd卡可用/总共:5.93 GB/6.24 GB
Log.d(TAG, "外置sd卡"+StorageInfo.getStorageInfo(this, 1)); // 外置sd卡可用/总共:535 MB/535 MB
本文由作者按照 CC BY 4.0 进行授权