Android - Access app-specific files(不完整)

Access app-specific files

In many cases, your app creates files that other apps don’t need to access, or shouldn’t access. The system provides the following locations for storing such app-specific files:

  • Internal storage directories: These directories include both a dedicated location for storing persistent files, and another location for storing cache data. The system prevents other apps from accessing these locations, and on Android 10 (API level 29) and higher, these locations are encrypted. These characteristics make these locations a good place to store sensitive data that only your app itself can access.
  • External storage directories: These directories include both a dedicated location for storing persistent files, and another location for storing cache data. Although it’s possible for another app to access these directories if that app has the proper permissions, the files stored in these directories are meant for use only by your app. If you specifically intend to create files that other apps should be able to access, your app should store these files in the shared storage part of external storage instead.

When the user uninstalls your app, the files saved in app-specific storage are removed. Because of this behavior, you shouldn’t use this storage to save anything that the user expects to persist independently of your app. For example, if your app allows users to capture photos, the user would expect that they can access those photos even after they uninstall your app. So you should instead use shared storage to save those types of files to the appropriate media collection.

Note: To further protect app-specific files, use the Security library that’s part of Android Jetpack to encrypt these files at rest. The encryption key is specific to your app.

The following sections describe how to store and access files within app-specific directories.

Access from internal storage

For each app, the system provides directories within internal storage where an app can organize its files. One directory is designed for your app’s persistent files, and another contains your app’s cached files. Your app doesn’t require any system permissions to read and write to files in these directories.

Other apps cannot access files stored within internal storage. This makes internal storage a good place for app data that other apps shouldn’t access.

Keep in mind, however, that these directories tend to be small. Before writing app-specific files to internal storage, your app should query the free space on the device.

Access persistent files

Your app’s ordinary, persistent files reside in a directory that you can access using the filesDir property of a context object. The framework provides several methods to help you access and store files in this directory.

Access and store files

You can use the File API to access and store files.

To help maintain your app’s performance, don’t open and close the same file multiple times.

The following code snippet demonstrates how to use the File API:

File file = new File(context.getFilesDir(), filename);
Store a file using a stream

As an alternative to using the File API, you can call [openFileOutput()](https://developer.android.google.cn/reference/android/content/Context#openFileOutput(java.lang.String, int)) to get a FileOutputStream that writes to a file within the filesDir directory.

The following code snippet shows how to write some text to a file:

String filename = "myfile";
String fileContents = "Hello world!";
try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {
    fos.write(fileContents.toByteArray());
}

Caution: On devices that run Android 7.0 (API level 24) or higher, unless you pass the Context.MODE_PRIVATE file mode into openFileOutput(), a SecurityException occurs.

To allow other apps to access files stored in this directory within internal storage, use a FileProvider with the FLAG_GRANT_READ_URI_PERMISSION attribute.

Access a file using a stream

To read a file as a stream, use openFileInput():

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) {
    // Error occurred when opening raw file for reading.
} finally {
    String contents = stringBuilder.toString();
}

Note: If you need to access a file as a stream at install time, save the file in your project’s /res/raw directory. You can open these files with openRawResource(), passing in the filename prefixed with R.raw as the resource ID. This method returns an InputStream that you can use to read the file. You cannot write to the original file.

View list of files

You can get an array containing the names of all files within the filesDir directory by calling fileList(), as shown in the following code snippet:

Array<String> files = context.fileList();
Create nested directories

You can also create nested directories, or open an inner directory, by calling getDir() in Kotlin-based code or by passing the root directory and a new directory name into a File constructor in Java-based code:

File directory = context.getFilesDir();
File file = new File(directory, filename);

Note: filesDir is always an ancestor directory of this new directory.

Create cache files

If you need to store sensitive data only temporarily, you should use the app’s designated cache directory within internal storage to save the data. As is the case for all app-specific storage, the files stored in this directory are removed when the user uninstalls your app, although the files in this directory might be removed sooner.

Note: This cache directory is designed to store a small amount of your app’s sensitive data. To determine how much cache space is currently available for your app, call getCacheQuotaBytes().

To create a cached file, call [File.createTempFile()](https://developer.android.google.cn/reference/java/io/File#createTempFile(java.lang.String, java.lang.String)):

File.createTempFile(filename, null, context.getCacheDir());

Your app accesses a file in this directory using the cacheDir property of a context object and the File API:

File cacheFile = new File(context.getCacheDir(), filename);

Caution: When the device is low on internal storage space, Android may delete these cache files to recover space. So check for the existence of your cache files before reading them.

Remove cache files

Even though Android sometimes deletes cache files on its own, you shouldn’t rely on the system to clean up these files for you. You should always maintain your app’s cache files within internal storage.

To remove a file from the cache directory within internal storage, use one of the following methods:

  • The delete() method on a File object that represents the file:

    cacheFile.delete();
    
  • The deleteFile() method of the app’s context, passing in the name of the file:

    context.deleteFile(cacheFileName);
    

准备

IDE:

Android Studio 4.1.1
Build #AI-201.8743.12.41.6953283, built on November 5, 2020
Runtime version: 1.8.0_242-release-1644-b01 amd64
VM: OpenJDK 64-Bit Server VM by JetBrains s.r.o
Windows 10 10.0

Android Virtual Devices:

Name: Pixel_2_API_28
CPU/ABI: Google Play Intel Atom (x86)
Path: C:\Users\86188\.android\avd\Pixel_2_API_28.avd
Target: google_apis_playstore [Google Play] (API level 28)
Skin: pixel_2
SD Card: 512M
fastboot.chosenSnapshotFile: 
runtime.network.speed: full
hw.accelerometer: yes
hw.device.name: pixel_2
hw.lcd.width: 1080
hw.initialOrientation: Portrait
image.androidVersion.api: 28
tag.id: google_apis_playstore
hw.mainKeys: no
hw.camera.front: emulated
avd.ini.displayname: Pixel 2 API 28
hw.gpu.mode: auto
hw.ramSize: 1536
PlayStore.enabled: true
fastboot.forceColdBoot: no
hw.cpu.ncore: 4
hw.keyboard: yes
hw.sensors.proximity: yes
hw.dPad: no
hw.lcd.height: 1920
vm.heapSize: 256
skin.dynamic: yes
hw.device.manufacturer: Google
hw.gps: yes
hw.audioInput: yes
image.sysdir.1: system-images\android-28\google_apis_playstore\x86\
showDeviceFrame: yes
hw.camera.back: virtualscene
AvdId: Pixel_2_API_28
hw.lcd.density: 420
hw.arc: false
hw.device.hash2: MD5:55acbc835978f326788ed66a5cd4c9a7
fastboot.forceChosenSnapshotBoot: no
fastboot.forceFastBoot: yes
hw.trackBall: no
hw.battery: yes
hw.sdCard: yes
tag.display: Google Play
runtime.network.latency: none
disk.dataPartition.size: 6442450944
hw.sensors.orientation: yes
avd.ini.encoding: UTF-8
hw.gpu.enabled: yes

注意:以下示例仅在安卓虚拟设备上运行测试,并没有在真实的设备上运行测试。

访问内部存储

应用特定的文件存储在 data/data/app_name/ 目录中,可以使用 Device File Explorer 查看。

菜单:View > Tool Windows > Device File Explorer

在这里插入图片描述

效果:

在这里插入图片描述

编辑 src\main\res\layout\activity_main.xml 布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/editText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:ems="10"
        android:gravity="start|top"
        android:inputType="textMultiLine"
        android:lines="4"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/buttonStoreToFile"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:text="Store To File"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editText" />

    <Button
        android:id="@+id/buttonRetrieveFromFile"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:text="Retrieve From File"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/buttonStoreToFile" />

    <Button
        android:id="@+id/buttonDeleteFile"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:text="Delete File"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/buttonRetrieveFromFile" />
</androidx.constraintlayout.widget.ConstraintLayout>

编辑 MainActivity

package com.mk;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private static final String FILE_NAME = "app-specific.txt";

    private File file;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        file = new File(getFilesDir(), FILE_NAME);

        ((Button) findViewById(R.id.buttonStoreToFile)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String content = ((EditText) findViewById(R.id.editText)).getText().toString();

                // 写字符串到文件
                try {
                    writeStringToFile(file, content);
                } catch (IOException e) {
                    e.printStackTrace();
                }

                // 列出所有文件(方式一)
                for (File file : getFilesDir().listFiles()) {
                    Log.d(TAG, "onCreate: " + file.getAbsolutePath());
                }
            }
        });

        ((Button) findViewById(R.id.buttonRetrieveFromFile)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 读取文件的内容
                try {
                    String content = readStringFromFile(file);

                    Toast.makeText(MainActivity.this, content, Toast.LENGTH_LONG).show();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

        ((Button) findViewById(R.id.buttonDeleteFile)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (file.delete())
                    Toast.makeText(MainActivity.this, "删除成功", Toast.LENGTH_LONG).show();
                else
                    Toast.makeText(MainActivity.this, "删除失败,文件可能不存在!", Toast.LENGTH_LONG).show();

                // 列出所有文件(方式二)
                for (String file : fileList()) {
                    Log.d(TAG, "onCreate: " + file);
                }
            }
        });
    }

    private void writeStringToFile(File file, String content) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream(file);
        fileOutputStream.write(content.getBytes());
        fileOutputStream.flush();
        fileOutputStream.close();
    }

    private String readStringFromFile(File file) throws IOException {
        FileInputStream fileInputStream = new FileInputStream(file);
        final InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
        final BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

        StringBuilder content = new StringBuilder();

        char[] buffer = new char[1024];
        for (int length = bufferedReader.read(buffer); length != -1; length = bufferedReader.read(buffer)) {
            content.append(buffer, 0, length);
        }

        return content.toString();
    }
}

参考

Save to app-specific storage

View on-device files with Device File Explorer

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值