文章目录
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 intoopenFileOutput()
, aSecurityException
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 withopenRawResource()
, passing in the filename prefixed withR.raw
as the resource ID. This method returns anInputStream
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 aFile
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();
}
}