【Android-File】Android文件的读写

补充:https://blog.csdn.net/wxz1179503422/article/details/84874171 | Android FileProvider详细解析和踩坑指南

---------------------------------------

参考:https://blog.csdn.net/yoryky/article/details/78675373 | Android中的文件读写操作
参考:https://blog.csdn.net/baidu_17508977/article/details/51007904 | Android常见文件路径介绍
参考:https://blog.csdn.net/mad1989/article/details/37568667 | Android SD卡简单的文件读写操作
参考:https://blog.csdn.net/zadarrien_china/article/details/55226068 | Android 文件的读取和写入

参考:https://blog.csdn.net/weixin_39209728/article/details/79729885 | Linux中drwxr-xr-x.的意思和权限
参考:https://blog.csdn.net/mylf19/article/details/74092011 | Android studio中关于模拟器/data目录不能显示的解决办法

注:本文不严谨。

 

目录

1 权限

2 获取存储路径

2.1 获取应用内部存储区路径方法 - Internal

2.2 获取应用外部存储区路径方法 - External

2.2.1 区域A:使用Context获取package_name相关路径

2.2.2 区域B:使用Environment类获取外部路径

3 读写内外存储空间的文件

3.1 读写App内部存储空间的文件

3.1.1 读文件 - Internal

3.1.2 写文件 - Internal

3.2 读写App外部存储空间的文件

3.2.1 读文件 - External

3.2.2 写文件 - External

4 验证读写功能及权限

4.1 读写Internal文件

4.1.1 写入Internal文件

4.1.2 读出Internal文件

4.2 读写External包名区文件

4.2.1 写入External包名区文件

4.2.2 读出External包名区文件

4.3 读写External Storage自由区的文件

4.3.1 向External Storage自由区写入文件

4.3.2 从External Storage自由区读出文件 



1 权限

<!--文件读写权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

 

2 获取存储路径

android可能有两个地方可以存储文件,相对于应用程序来说,可分为内部外部两个位置,外部又分为私有公共两个区域:

 

存储位置解释文件管理器访问权限应用卸载后外应用访问其他别称
Internal应用安装存储区不可见不需权限文件丢失不可访问 
External_Package外部私有包名区可见不需权限文件丢失不可访问同下统称
External_Storage外部公共自由区可见

READ_EXTERNAL_STORAGE

WRITE_EXTERNAL_STORAGE

文件留存可以访问存储盘/SD卡/
内部存储设备

 

Internal storage: 

总是可用的 
这里的文件默认只能被我们的app所访问。 
当用户卸载app的时候,系统会把internal内该app相关的文件都清除干净。 
Internal是我们在想确保不被用户其他app所访问的最佳存储区域。 

External storage: 

并不总是可用的,因为用户有时会通过USB存储模式挂载外部存储器,当取下挂载的这部分后,就无法对其进行访问了。 
是大家都可以访问的,因此保存在这里的文件可能被其他程序访问 
当用户卸载您的应用时,只有通过 getExternalFilesDir() 将您的应用的文件保存在目录中时,系统才会从此处删除您的应用的文件 
External是在不需要严格的访问权限并且希望这些文件能够被其他app共享或是允许用户通过电脑访问时的最佳存储区域。


2.1 获取应用内部存储区路径方法 - Internal

获取应用内部存储区路径只能通过Context来获取,不需要额外敏感权限

// 位置【data/data/<package_name>/cache】
String pathCache = getCacheDir().getAbsolutePath();

// 位置【data/data/<package_name>/files】
String pathFile = getFilesDir().getAbsolutePath();

其真实目录为:

根目录\data\data\<package_name>\cache                                           // getCacheDir().getAbsolutePath();方法获取到的
根目录\data\data\<package_name>\files                                              // getFilesDir().getAbsolutePath();方法获取到的

根目录\data\data\<package_name>\database                                     // 数据库存储位置
根目录\data\data\<package_name>\shared_prefs                              // SharedPreferences存储目录

打开Android Studio右下角的 Device File Explorer 可以看到手机存储的目录结构

获取到的文件所在的存储路径是App内部存储(不是“外部存储空间”),因此对于用户通过手机的文件管理是看不到该目录的:

 

2.2 获取应用外部存储区路径方法 - External

获取应用外部存储区路径需要声明读取外部存储区的读写权限

<!--文件读写权限-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

 获取应用外部存储区有两种途径:

①使用Context获取package_name相关路径;                                    // 应用卸载后,文件被删除
②使用Environment类获取package_name无关路径。                         // 应用卸载后,文件仍存在

 

2.2.1 区域A:使用Context获取package_name相关路径

获取Cache路径的方法:

// 真实位置【/mnt/sdcard/Android/data/<package_name>/cache】
// 模拟位置【/storage/emulated/0/Android/data/<package_name>/cache】
String pathExternalCache = getExternalCacheDir().getAbsolutePath();

 获取到的目录为:

真实位置:根目录\mnt\sdcard\Android/data\<package_name>\cache
模拟位置:设备/storage/emulated/0/Android/data/<package_name>/cache

获取Files路径的方法:

// 真实位置【/mnt/sdcard/Android/data/<package_name>/files/Movies】
// 模拟位置【/storage/emulated/0/Android/data/<package_name>/files/Movies】
String pathExternalFiles = getExternalFilesDir(Environment.DIRECTORY_MOVIES).getAbsolutePath();

获取到的目录:

真实位置:根目录\mnt\sdcard\Android/data\<package_name>\files\具体目录
模拟位置:设备/storage/emulated/0/Android/data/<package_name>/files/具体目录

 getExternalFilesDir(String type);方法中可以传入Environment的type参数,可用于指定其具体目录。

此方法获取目录路径的文件目录如下,因位于App外部存储空间,是可以在手机自带的 “文件管理” 中看到的:

        

左图是在设备中的真实路径,右图为通过手机“文件管理”看到的目录。此途径应用卸载后存储在该目录下的文件会被清除掉

 

2.2.2 区域B:使用Environment类获取外部路径

方式①:获取公共目录

// 真实位置【/mnt/sdcard/Pictures】
// 模拟位置【/storage/emulated/0/Pictures】
String pathExternalPublic = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath();

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_XXX).getAbsolutePath();
真实位置:根目录\mnt\sdcard\具体公共目录
模拟位置:设备/storage/emulated/0/具体公共目录 

其中,获取公共目录的Environment的Type参数有:

Environment的Type参数对应模拟路径解释说明
DIRECTORY_MOVIES/storage/emulated/0/Movies电影
DIRECTORY_DCIM/storage/emulated/0/DCIM相册
DIRECTORY_ALARMS/storage/emulated/0/Alarms铃音
DIRECTORY_DOCUMENTS/storage/emulated/0/Documents文件
DIRECTORY_DOWNLOADS/storage/emulated/0/Download下载文件
DIRECTORY_MUSIC/storage/emulated/0/Music音乐
DIRECTORY_NOTIFICATIONS/storage/emulated/0/Notifications通知
DIRECTORY_PICTURES/storage/emulated/0/Pictures图片
DIRECTORY_PODCASTS/storage/emulated/0/Podcasts播客
DIRECTORY_RINGTONES/storage/emulated/0/Ringtons铃声

 

方式②:获取自由目录

// 真实位置【/mnt/sdcard】
// 模拟位置【/storage/emulated/0】
String pathExternalRoot = Environment.getExternalStorageDirectory().getAbsolutePath();
// 真实位置【/mnt/sdcard/DCIM】
// 模拟位置【/storage/emulated/0/DCIM】
String pathExternalDcim = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM";
// 真实位置【/mnt/sdcard/DCIM/Camera/test.jpg】
// 模拟位置【/storage/emulated/0/DCIM/Camera/test.jpg】
String pathExternalTest = Environment.getExternalStorageDirectory().getAbsolutePath() + "/DCIM/Camera/test.jpg";

目录:

Environment.getExternalStorageDirectory().getAbsolutePath();
真实位置:根目录\mnt\sdcard
模拟位置:设备/storage/emulated/0/

Environment.getExternalStorageDirectory().getAbsolutePath() + "/xxx/xxx/xxx.txt";
真实位置:根目录\mnt\sdcard\具体目录
模拟位置:设备/storage/emulated/0/具体目录

 看一下目录结构:

        

这也便是我们熟悉的 “文件管理” 界面(右图)展示的目录的真实结构(左图)

这个目录下为即为所谓的相对于App应用的“外部存储空间”。
为什么右图写的是“内部存储设备”呢?这是因为分类标准不同,因现在手机机身存储都集成在手机之内,相对于外部可插拔的SD卡而言,机身存储即为“内部存储设备”。但因本文讨论App的存储位置,因此本文不做特殊说明的话,均是指以相对于App而言的“内”和“外”。

除此之外,Environment类 还有其他的目录结构获取方法,如下:

 

3 读写内外存储空间的文件

3.1 读写App内部存储空间的文件

3.1.1 读文件 - Internal

代码

    /**
     * 读Internal中文件的方法
     *
     * @param filePathName 文件路径及文件名
     * @return 读出的字符串
     * @throws IOException
     */
    public static String readInternal(String filePathName) throws IOException {
        StringBuffer stringBuffer = new StringBuffer();

        // 打开文件输入流
        FileInputStream fileInputStream = new FileInputStream(filePathName);

        byte[] buffer = new byte[1024];
        int len = fileInputStream.read(buffer);
        // 读取文件内容
        while (len > 0) {
            stringBuffer.append(new String(buffer, 0, len));
            // 继续将数据放到buffer中
            len = fileInputStream.read(buffer);
        }
        // 关闭输入流
        fileInputStream.close();
        return stringBuffer.toString();
    }

 

3.1.2 写文件 - Internal

代码

    /**
     * 写Internal中文件的方法
     *
     * @param filePathName 文件路径及文件名
     * @param content      要写进文件中的内容
     * @throws IOException
     */
    public static void writeInternal(String filePathName, String content) throws IOException {

        // 打开文件输出流
        FileOutputStream fileOutputStream = new FileOutputStream(filePathName);

        // 写数据到文件中
        fileOutputStream.write(content.getBytes());

        // 关闭输出流
        fileOutputStream.close();
    }

 

 

3.2 读写App外部存储空间的文件

3.2.1 读文件 - External

代码

    /**
     * 从External文件目录下读取文件
     *
     * @param filePathName 要读取的文件的路径+文件名
     * @return
     * @throws IOException
     */
    public static String readExternal(String filePathName) throws IOException {

        StringBuffer stringBuffer = new StringBuffer();

        // 获取External的可用状态
        String storageState = Environment.getExternalStorageState();

        if (storageState.equals(Environment.MEDIA_MOUNTED)) {
            // 当External的可用状态为可用时

            // 打开文件输入流
            FileInputStream fileInputStream = new FileInputStream(filePathName);

            byte[] buffer = new byte[1024];
            int len = fileInputStream.read(buffer);
            // 读取文件内容
            while (len > 0) {
                stringBuffer.append(new String(buffer, 0, len));

                // 继续把数据存放在buffer中
                len = fileInputStream.read(buffer);
            }

            // 关闭输入流
            fileInputStream.close();
        }
        return stringBuffer.toString();
    }

 

3.2.2 写文件 - External

代码

    /**
     * 向External文件目录下写入文件
     *
     * @param filePathName 要写入的的文件的路径+文件名
     * @param content      要写入的内容
     * @throws IOException
     */
    public static void writeExternal(String filePathName, String content) throws IOException {

        // 获取External的可用状态
        String storageState = Environment.getExternalStorageState();

        if (storageState.equals(Environment.MEDIA_MOUNTED)) {
            // 当External的可用状态为可用时

            // 打开输出流
            FileOutputStream fileOutputStream = new FileOutputStream(filePathName);

            // 写入文件内容
            fileOutputStream.write(content.getBytes());

            // 关闭输出流
            fileOutputStream.close();

        }
    }

 

 

4 验证读写功能及权限

首先选取一个高版本的测试机,众所周知,Android6.0增加了动态权限的申请步骤。本人用的MI2S为Android 5.0就先暂时放一边,换上一个Android8.0的Galaxy S7。本部分改变一下顺序,先写再读。

4.1 读写Internal文件

4.1.1 写入Internal文件

首先找一下 data/data/路径下没有“com.dandelion.abc”的包名

安装该测试应用后出现该包名,但cache目录下为空(下图左)。然后去应用设置中关闭本软件的读写权限,

         

之后执行写入命令,向cache/files目录下分别写入文件,然后查看文件路径(上图右)

String content = "Always believe that something wonderful is about to happen!";
// 获取文件在Internal中文件目录下的路径
String pathInternalCache = this.getCacheDir().getAbsolutePath() + File.separator + "InternalCacheTest.txt";
String pathInternalFiles = this.getFilesDir().getAbsolutePath() + File.separator + "InternalFilesTest.txt";

// 写入操作
try {
    writeInternal(pathInternalCache, content);
    writeInternal(pathInternalFiles, content);
} catch (IOException e) {
    e.printStackTrace();
}

说明写入成功 

4.1.2 读出Internal文件

现在把上一步写入的文件读出

// 获取文件在Internal中文件目录下的路径
String pathInternalCache = this.getCacheDir().getAbsolutePath() + File.separator + "InternalCacheTest.txt";
String pathInternalFiles = this.getFilesDir().getAbsolutePath() + File.separator + "InternalFilesTest.txt";

// 读出操作
String textReadInternalCache = null;
String textReadInternalFiles = null;
try {
    textReadInternalCache = readInternal(pathInternalCache);
    textReadInternalFiles = readInternal(pathInternalFiles);
} catch (IOException e) {
    e.printStackTrace();
}

// 日志打印
log.d(TAG, pathInternalCache);        // 路径,下同
log.d(TAG, textReadInternalCache);    // 内容,下同
log.d(TAG, pathInternalFiles);
log.d(TAG, textReadInternalFiles);

输出结果:

说明读出成功,而此时,该应用并未开启外部存储读写权限,说明读写该目录下的文件不需要外部存储读写权限。

卸载该应用,该目录下的本应用包名及其文件均被删除。

 

4.2 读写External包名区文件

首先应该明确,该区域的存储位置不仅可以从Device File Explorer查看,还可以通过手机的文件管理查看

首先查找一下mnt/sdcard/Android/data/目录下的“com.dandeion.abc”的包名,发现无论从Android Studio的Device File Explorer还是手机的文件管理,均找不到“com.dandeion.abc”的包名,说明当前未向该区域存储文件

然后去应用设置中关闭本软件的外部存储读写权限。

           

 

4.2.1 写入External包名区文件

String content = "Always believe that something wonderful is about to happen!";
// 获取文件在External-Android/<package_name>/cache文件目录下的路径
String pathExternalCache = this.getExternalCacheDir().getAbsolutePath() + File.separator + "ExternalCacheTest.txt";
String pathExternalFiles = this.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath() + File.separator + "ExternalFilesTest.txt";

// 写入操作
try {
    writeExternal(pathExternalCache, content);
    writeExternal(pathExternalFiles, content);
} catch (IOException e) {
    e.printStackTrace();
}

查看目录(下图左:Device File Explorer;下图右:手机文件管理)

          

发现写入成功了,而此时该应用并未获取到外部读写权限。

为了说明这一点,继续做一点更绝的,打开AndroidManifest.xml文件,把该文件中的读写权限的声明一并删除,并在手机上卸载该应用后重新安装。

结果:

Internal应用区:data/data/<包名>/[cache|files] 目录写入成功!
External包名区:mnt/sdcard/Android/data/<包名>/[cache|files] 目录写入成功!

因此可以得出结论: 

Internal应用区External包名区的读写均不需要应用开发者声明和获取外部存储读写权限。该权限是指:
permission.READ_EXTERNAL_STORAGE
permission.WRITE_EXTERNAL_STORAGE

 

4.2.2 读出External包名区文件

读出

// 获取文件在External-Android/<package_name>/[cache|files]文件目录下的路径
String pathExternalCache = this.getExternalCacheDir().getAbsolutePath() + File.separator + "ExternalCacheTest.txt";
String pathExternalFiles = this.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath() + File.separator + "ExternalFilesTest.txt";

// 读出操作
String textReadExternalCache = null;
String textReadExternalFiles = null;
try {
    textReadExternalCache = readExternal(pathExternalCache);
    textReadExternalFiles = readExternal(pathExternalFiles);
} catch (IOException e) {
    e.printStackTrace();
}

// 日志输出
log.d(TAG, pathExternalCache);
log.d(TAG, textReadExternalCache);
log.d(TAG, pathExternalFiles);
log.d(TAG, textReadExternalFiles);

输出

说明读出成功,而此时,该应用并未开启外部存储读写权限,说明读写该目录下的文件不需要外部存储读写权限。

卸载该应用,该目录下的本应用包名及其文件均被删除。

 

4.3 读写External Storage自由区的文件

类似4.2,该区域的存储位置,可从Device File Explorer查看,还可以通过手机的文件管理查看

因实验打算向mnt/sdcard/Android/[ Ducument | Dandelion ] 目录下写入文件,因此先查看一下未写入时的目录情况,以留作比较

然后去应用设置中关闭本软件的外部存储读写权限。

            

 

4.3.1 向External Storage自由区写入文件

写入

String content = "Always believe that something wonderful is about to happen!";

// 获取文件在External自由区(/storage/emulated/0/具体目录)文件目录下的路径
String pathStorageRoot = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "StorageRootTest.txt";
String pathStoragePublic = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath() + File.separator + "StoragePublicTest.txt";
// 需要创建文件夹目录的路径
String dirStorageDandelion = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "Dandelion";
String pathStorageDandelion = dirStorageDandelion + "/StorageDandelionTest.txt";

// 创建文件夹【注意:因本路径本身没有,需要单独创建,不创建就引用会报FileNotFoundException】
boolean dirExist = makeDirectory(dirStorageDandelion);

// 写入操作
try {
    writeExternal(pathStorageRoot, content);
    writeExternal(pathStorageDandelion, content);
    if (dirExist) writeExternal(pathStorageDandelion, content);    // 需要创建目录的路径
} catch (IOException e) {
    e.printStackTrace();
}

执行完毕之后查看目录结构,发现与刚才的目录并无差异。是何原因?

未崩溃是因为所执行的步骤都在try--catch内部,自然不会崩溃报Error,所以找一下Warning吧~查找日志发现:

W/System.err: java.io.FileNotFoundException: /storage/emulated/0/StorageRootTest.txt (Permission denied)

Permission denied

外部存储读写权限的问题。所以,自此开始,就需要:
① 在AndroidManifest.xml文件中增添如下权限声明: 

    <!--文件读写权限-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

② 并在Java代码中,动态申请该敏感权限。动态申请部分此处略去一万字。

由此可知,读写该区域目录下的文件,是需要读写权限的。

添加“声明并动态申请权限”的代码后,继续操作,得出如下文件:

               

 

4.3.2 从External Storage自由区读出文件 

读出操作

// 获取文件在External自由区(/storage/emulated/0/具体目录)文件目录下的路径
String pathStorageRoot = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "StorageRootTest.txt";
String pathStoragePublic = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath() + File.separator + "StoragePublicTest.txt";
String pathStorageDandelion = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "Dandelion/StorageDandelionTest.txt";

// 读出操作
String textReadStorageRoot = null;
String textReadStoragePublic = null;
String textStorageDandelion = null;
try {
    textReadStorageRoot = readExternal(pathStorageRoot);
    textReadStoragePublic = readExternal(pathStoragePublic);
    textStorageDandelion = readExternal(pathStorageDandelion);
} catch (IOException e) {
    e.printStackTrace();
}

// 日志输出
log.d(TAG, pathStorageRoot);
log.d(TAG, textReadStorageRoot);
log.d(TAG, pathStoragePublic);
log.d(TAG, textReadStoragePublic);
log.d(TAG, pathStorageDandelion);
log.d(TAG, textStorageDandelion);

输出

说明读出成功,而此时,该应用必须开启外部存储读写权限,说明读写该目录下的文件必须获取外部存储读写权限。

卸载该应用,该目录下的本应用包名及其文件均未被自动删除,说明该目录下可长期存放某些文件。

  • 12
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Android 11中,由于对文件访问权限进行了限制,使用FileProvider来读写文件已经成为了一种标准的方式。 以下是使用FileProvider读写文件的步骤: 1. 在AndroidManifest.xml文件中定义FileProvider: ``` <provider android:name="androidx.core.content.FileProvider" android:authorities="包名.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> ``` 其中,`android:authorities`是FileProvider的标识符,`android:grantUriPermissions`表示是否授予其他应用访问权限,`android:exported`表示是否允许其他应用调用。 2. 在res/xml目录下创建file_paths.xml文件,定义FileProvider的路径: ``` <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="my_images" path="Pictures" /> </paths> ``` 其中,`<external-files-path>`表示文件的路径,`name`是自定义的名称。 3. 在代码中使用FileProvider读写文件: ``` File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "test.jpg"); Uri uri = FileProvider.getUriForFile(this, "包名.fileprovider", file); try { InputStream inputStream = getContentResolver().openInputStream(uri); // 读取文件内容 OutputStream outputStream = getContentResolver().openOutputStream(uri); // 写入文件内容 } catch (IOException e) { e.printStackTrace(); } ``` 其中,`FileProvider.getUriForFile()`方法将文件转换为Uri,`getContentResolver().openInputStream()`方法和`getContentResolver().openOutputStream()`方法分别用于读取和写入文件内容。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值