Android的App可以读写的位置为:
一、内置data目录下对应app名称的目录;
二、扩展SD卡(包括虚拟的内置SD卡和外置SD卡);

一、先说说内置data目录下文件的读写。

内置data目录即内部存储,指的是应用内部独有的存储,这部分存储的文件、数据,只能被应用自身访问到,其他应用都没有权限访问。

一般情况下,/data开头的路径都是内部存储。而一般应用所能够访问到的就是下面几个路径,称为应用内部私有存储。

应用内部私有存储:
/data/user/0/<包名>
/data/user/0/<包名>/files #存放文件数据
/data/user/0/<包名>/databases #存放Sqlite的数据库文件
/data/user/0/<包名>/shared_prefs #存放SharedPreference的数据
/data/user/0/<包名>/cache #存放缓存文件

一旦App被卸载,系统将会移除内部存储中相关应用的数据。

方式1:内置API读写

这个位置的读写有提供一套单独的API来读写,无需申明特殊权限。

代码中有个openFileInput的方法,这个方法是Android内置的,需放在Activity中才能执行。

如下:


    //读取内置data目录下文件
    public String readDataFile(String fileName) {
        String res = "";
        try {
            FileInputStream fin = openFileInput(fileName);
            int length = fin.available();
            byte[] buffer = new byte[length];
            fin.read(buffer);
            res = new String(buffer);
            fin.close();

        } catch (Exception e) {
            e.printStackTrace();
            Log.e("Exception", "readDataFile Error!" + e.getMessage());
        }
        return res;
    }

    //写入内置data目录下文件
    private void writeDataFile(String fileName, String content) {
        try {
            FileOutputStream fut = openFileOutput(fileName, Context.MODE_PRIVATE | Context.MODE_APPEND);
            byte[] bytes = content.getBytes();
            fut.write(bytes);
            fut.close();
        } catch (Exception e) {
            e.printStackTrace();
            Log.e("Exception", "writeDataFile Error!" + e.getMessage());
        }
    }

测试代码:

String fileName="test.txt";
writeDataFile(fileName, "Hello Android");
String txt = readDataFile(fileName);
Log.e("txt", txt);

方式2:获取对应的data路径后,通过普通的方法读写data中的文件。

手动获取拼接data目录下文件路径,然后用通用的文件读写方式进行读写。

 通用读写文件的辅助类,FileHelper.java

package com.rc114.scanner;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.URLDecoder;

public class FileHelper {
    public static String combinePath(String path1, String path2) {
        File file1 = new File(path1);
        File file2 = new File(file1, path2);
        return file2.getPath();
    }

    /**
     * 读取文件
     *
     * @param filepath 文件路径
     */
    public static String readFile(String filepath) {
        String encoding = "UTF-8";
        return readFile(filepath, encoding);
    }

    /**
     * 读取文件,指定编码
     *
     * @param filepath 文件路径
     */
    public static String readFile(String filepath, String encoding) {
        try {
            filepath = URLDecoder.decode(filepath, "utf-8");
            File file = new File(filepath);
            Long filelength = file.length();
            byte[] filecontent = new byte[filelength.intValue()];
            FileInputStream in = new FileInputStream(file);
            in.read(filecontent);
            in.close();
            return new String(filecontent, encoding);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 将字符串写入文件
     *
     * @param filepath
     * @param text
     * @param isAppend
     */
    public static void writeFile(String filepath, String text, boolean isAppend) {

        try {
            filepath = URLDecoder.decode(filepath, "utf-8");
            File file = new File(filepath);
            File parentFile = file.getParentFile();
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
            if (!file.exists()) {
                file.createNewFile();
            }

            FileOutputStream f = new FileOutputStream(filepath, isAppend);
            f.write(text.getBytes());
            f.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void writeFile(String filepath, String text, String encodeing) {

        try {
            filepath = URLDecoder.decode(filepath, "utf-8");
            File file = new File(filepath);
            File parentFile = file.getParentFile();
            if (!parentFile.exists()) {
                parentFile.mkdirs();
            }
            if (!file.exists()) {
                file.createNewFile();
            }

            // FileOutputStream f = new FileOutputStream(filepath, "GBK");
            // f.write(text.getBytes());
            // f.close();

            OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(filepath), encodeing);
            writer.append(text);
            writer.close();

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

测试代码:

//获取data目录下对应包名根目录
String dataDir = Environment.getDataDirectory().getPath() + "/data/" + packageName;

//拼接待读写文件路径
String filepath = FileHelper.combinePath(dataDir, "test.txt");

//写入文件
FileHelper.writeFile(filepath, "Hello", "utf-8");

//读取文件
String txt = FileHelper.readFile(filepath);

Log.e("txt", txt);

二、扩展SD卡文件读写

扩展SD卡文件即外部存储,指的是是公共的存储,这部分存储理论上是全局可见的,所有的应用都可以访问这部分数据,一般情况下,路径都是以/storage开头的,比如说/storage/emulated/0就是属于外部存储,这个路径的实际的挂载点是/data/media。又比如外置sdcard的路径为/storage/13FC-0F0B。 相比较内部存储一定会存在,外部存储可能是sdcard或者通过otg挂载的U盘形式,所以可能出现没有挂载的情况,所以所有的外部存储要在使用前通过下面的方式判断是否有被挂载。

    /*检查外部存储是否可写*/
    public boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;
    }

    /*检查外部存储是否可读*/
    public boolean isExternalStorageReadable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state) ||
                Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
            return true;
        }
        return false;
    }

访问外部存储需在AndroidManifest.xml文件中申明权限:

<!-- 存储权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" tools:ignore="ProtectedPermissions" />

安卓6.0以后,谷歌要求危险权限必须动态获取,所以还要使用requestPermissions在运行时获取权限。

  private static String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };
    private static int REQUEST_PERMISSION_CODE = 100;

    //获取权限
    private void getPermission() {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_PERMISSION_CODE);
            } else {

                boolean writable = isExternalStorageWritable();
                boolean readable = isExternalStorageReadable();
                Log.e("外部存储读写", "写:" + writable + "-读:" + readable);

                File root = Environment.getExternalStorageDirectory();//取得外部存储根路径
                File[] files = root.listFiles();
                if (files != null) {
                    for (int i = 0; i < files.length; i++) {
                        if (files[i].isDirectory()) {
                            Log.e("文件夹", files[i].toString());
                        }
                    }
                }

            }
        }

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_PERMISSION_CODE) {
            for (int i = 0; i < permissions.length; i++) {
                Log.i("MainActivity", "申请的权限为:" + permissions[i] + ",申请结果:" + grantResults[i]);
            }
        }
    }

 在Activity的onCreate事件中执行getPermission()方法,动态获取存储权限。

完成了上面的操作后,在代码:

File root = Environment.getExternalStorageDirectory();//取得外部存储根路径
File[] files = root.listFiles();

中,root.listFiles()可能会返回null,还是无法读取或写入文件。

查了下资料,Android 10 或更高版本,还需要做一个配置:在AndroidManifest.xml中,还需增加:android:requestLegacyExternalStorage="true"

 利用上面的FileHelper.java类,测试写入一个文件:

File root = Environment.getExternalStorageDirectory();
FileHelper.writeFile(FileHelper.combinePath(root.getPath(), "text.txt"), "Hello", false);

测试遍历根目录:

File root = Environment.getExternalStorageDirectory();
File[] files = root.listFiles();
if (files != null) {
	for (int i = 0; i < files.length; i++) {
		if (files[i].isDirectory()) {
			Log.e("文件夹", files[i].toString());
		}
		if (files[i].isFile()) {
			Log.e("文件", files[i].toString());
		}
	}
}

输出结果:

至此,完成了外部存储的读写功能。 

2023-9-20 14:07:56 补充

Android 13(Api Level 33)将READ_EXTERNAL_STORAGE进一步细分成为了:

读取图片的:READ_MEDIA_IMAGE、

读取视频的:READ_MEDIA_VIDEO、

读取音频的:READ_MEDIA_AUDIO

所以如果要适配Android 13,还需做以下处理:

在AndroidManifest.xml中增加申明:

<!--  一般来说,允许用户自定义头像的app都需要这个权限  -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"
    android:minSdkVersion = "33"/>
<!--  如果你想开发音乐播放器之类需要获取音频的app,加上这个权限  -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"
    android:minSdkVersion = "33"/>
<!--  如果你想开发视频编辑器之类需要获取视频的app,加上这个权限  -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"
    android:minSdkVersion = "33"/>
<!--  向前兼容  -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
    android:maxSdkVersion="32" />

动态申请权限做相应的修改:

public static final String READ_MEDIA_AUDIO = "android.permission.READ_MEDIA_AUDIO";
public static final String READ_MEDIA_IMAGES = "android.permission.READ_MEDIA_IMAGES";
public static final String READ_MEDIA_VIDEO = "android.permission.READ_MEDIA_VIDEO";


if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
   ActivityCompat.requestPermissions( activity,new String[]{
           READ_MEDIA_IMAGES,
           READ_MEDIA_AUDIO,
           READ_MEDIA_VIDEO
   },MEDIA_READ_REQUEST_CODE);
}else{
   ActivityCompat.requestPermissions( activity,new String[]{
           READ_EXTERNAL_STORAGE
   },MEDIA_READ_REQUEST_CODE);
}
 

参考资料:

​​​​​​1.Android中的内部存储和外部存储_build hero的博客-CSDN博客_android 外部存储

2.android - Environment.getExternalStorageDirectory() deprecation alternatives - Stack Overflow

3.Android 13媒体文件访问权限适配_闲暇部落的博客-CSDN博客

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐