Trong nhiều trường hợp, ứng dụng của bạn sẽ tạo các tệp mà những ứng dụng khác không cần truy cập hoặc không được truy cập. Hệ thống cung cấp những vị trí sau để lưu trữ các tệp dành riêng cho ứng dụng như vậy:
Thư mục bộ nhớ trong: Các thư mục này có cả một vị trí chuyên để lưu trữ các tệp bền vững và một vị trí khác để lưu trữ dữ liệu bộ nhớ đệm. Hệ thống sẽ ngăn các ứng dụng khác truy cập vào những vị trí này, và trên Android 10 (API cấp 29) trở lên, những vị trí này sẽ được mã hoá. Các đặc điểm trên khiến những vị trí này trở thành nơi phù hợp để lưu trữ dữ liệu nhạy cảm mà chỉ ứng dụng của bạn mới có thể truy cập.
Thư mục bộ nhớ ngoài: Các thư mục này có cả một vị trí chuyên để lưu trữ các tệp bền vững và một vị trí khác để lưu trữ dữ liệu bộ nhớ đệm. Mặc dù ứng dụng khác có thể truy cập vào các thư mục này nếu ứng dụng đó có quyền thích hợp, nhưng tệp lưu trữ trong các thư mục này chỉ dành cho ứng dụng của bạn. Nếu bạn định tạo các tệp mà ứng dụng khác có thể truy cập, thì ứng dụng của bạn nên lưu trữ các tệp này trong bộ nhớ dùng chung thuộc bộ nhớ ngoài.
Khi người dùng gỡ cài đặt ứng dụng của bạn, các tệp lưu trong bộ nhớ dành riêng cho ứng dụng sẽ bị xoá. Do đó, bạn không nên dùng bộ nhớ này để lưu bất cứ dữ liệu nào mà người dùng muốn lưu trữ độc lập với ứng dụng của bạn. Ví dụ: nếu ứng dụng của bạn cho phép người dùng chụp ảnh, thì người dùng vẫn muốn truy cập được những bức ảnh đó ngay cả sau khi gỡ cài đặt ứng dụng. Vì vậy, bạn nên sử dụng bộ nhớ dùng chung để lưu các loại tệp đó vào bộ sưu tập nội dung nghe nhìn thích hợp.
Những phần sau đây mô tả cách lưu trữ và truy cập tệp trong các thư mục dành riêng cho ứng dụng.
Truy cập từ bộ nhớ trong
Đối với mỗi ứng dụng, hệ thống sẽ cung cấp các thư mục thuộc bộ nhớ trong, nơi ứng dụng có thể sắp xếp các tệp riêng. Một thư mục được thiết kế để lưu các tệp bền vững của ứng dụng và một thư mục khác chứa các tệp đã lưu vào bộ nhớ đệm của ứng dụng. Ứng dụng của bạn không yêu cầu quyền hệ thống nào để đọc và ghi vào các tệp trong những thư mục này.
Các ứng dụng khác không thể truy cập tệp được lưu vào bộ nhớ trong. Đặc điểm trên khiến bộ nhớ trong trở thành nơi phù hợp để lưu dữ liệu ứng dụng mà ứng dụng khác không được truy cập.
Tuy nhiên, hãy lưu ý rằng các thư mục này thường có xu hướng nhỏ. Trước khi ghi các tệp dành riêng cho ứng dụng vào bộ nhớ trong, ứng dụng của bạn nên truy vấn dung lượng trống trên thiết bị.
Truy cập vào các tệp bền vững
Các tệp thông thường, bền vững của ứng dụng nằm trong một thư mục mà bạn có thể
truy cập thông qua thuộc tính filesDir
của đối tượng ngữ cảnh. Khung này cung cấp một số phương thức nhằm giúp bạn
truy cập và lưu trữ các tệp trong thư mục này.
Truy cập và lưu trữ tệp
Bạn có thể dùng API File
để truy cập và lưu trữ tệp.
Để giúp duy trì hiệu suất của ứng dụng, đừng mở và đóng cùng một tệp nhiều lần.
Đoạn mã sau đây minh hoạ cách sử dụng API File
:
Kotlin
val file = File(context.filesDir, filename)
Java
File file = new File(context.getFilesDir(), filename);
Lưu trữ tệp bằng một luồng
Thay vì sử dụng API File
, bạn có thể gọi
openFileOutput()
để lấy FileOutputStream
ghi
vào một tệp trong thư mục filesDir
.
Đoạn mã sau đây cho biết cách ghi một số văn bản vào tệp:
Kotlin
val filename = "myfile" val fileContents = "Hello world!" context.openFileOutput(filename, Context.MODE_PRIVATE).use { it.write(fileContents.toByteArray()) }
Java
String filename = "myfile"; String fileContents = "Hello world!"; try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) { fos.write(fileContents.toByteArray()); }
Để cho phép ứng dụng khác truy cập vào các tệp lưu trữ trong
thư mục này thuộc bộ nhớ trong, hãy dùng
FileProvider
kèm theo thuộc tính
FLAG_GRANT_READ_URI_PERMISSION
.
Truy cập vào tệp bằng một luồng
Để đọc tệp ở dạng luồng, hãy dùng
openFileInput()
:
Kotlin
context.openFileInput(filename).bufferedReader().useLines { lines -> lines.fold("") { some, text -> "$some\n$text" } }
Java
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(); }
Xem danh sách tệp
Bạn có thể lấy một mảng chứa tên của mọi tệp trong thư mục filesDir
bằng cách gọi
fileList()
, như minh hoạ trong
đoạn mã sau:
Kotlin
var files: Array<String> = context.fileList()
Java
Array<String> files = context.fileList();
Tạo các thư mục lồng nhau
Bạn cũng có thể tạo các thư mục lồng nhau, hoặc mở một thư mục bên trong, bằng cách gọi
getDir()
trong mã
dựa trên Kotlin hoặc bằng cách chuyển thư mục gốc và tên thư mục mới vào hàm khởi tạo File
trong mã dựa trên Java:
Kotlin
context.getDir(dirName, Context.MODE_PRIVATE)
Java
File directory = context.getFilesDir(); File file = new File(directory, filename);
Tạo các tệp bộ nhớ đệm
Nếu chỉ cần lưu trữ tạm thời dữ liệu nhạy cảm, bạn nên dùng thư mục bộ nhớ đệm được chỉ định của ứng dụng thuộc bộ nhớ trong để lưu dữ liệu. Tương tự như với trường hợp tất cả bộ nhớ dành riêng cho ứng dụng, các tệp lưu trữ trong thư mục này sẽ bị xoá khi người dùng gỡ cài đặt ứng dụng của bạn, mặc dù các tệp trong thư mục này có thể bị xoá sớm hơn.
Để tạo một tệp được lưu vào bộ nhớ đệm, hãy gọi
File.createTempFile()
:
Kotlin
File.createTempFile(filename, null, context.cacheDir)
Java
File.createTempFile(filename, null, context.getCacheDir());
Ứng dụng của bạn truy cập vào một tệp trong thư mục này bằng thuộc tính
cacheDir
của
đối tượng ngữ cảnh và API File
:
Kotlin
val cacheFile = File(context.cacheDir, filename)
Java
File cacheFile = new File(context.getCacheDir(), filename);
Xoá các tệp bộ nhớ đệm
Mặc dù đôi khi, Android sẽ tự xoá các tệp bộ nhớ đệm, nhưng bạn không nên dựa vào hệ thống để dọn dẹp các tệp này cho mình. Bạn nên thường xuyên lưu tệp bộ nhớ đệm của ứng dụng vào bộ nhớ trong.
Để xoá một tệp khỏi thư mục bộ nhớ đệm thuộc bộ nhớ trong, hãy dùng một trong các phương thức sau:
Phương thức
delete()
trên đối tượngFile
biểu thị tệp đó:Kotlin
cacheFile.delete()
Java
cacheFile.delete();
Phương thức
deleteFile()
trong ngữ cảnh của ứng dụng, chuyển tên tệp:Kotlin
context.deleteFile(cacheFileName)
Java
context.deleteFile(cacheFileName);
Truy cập từ bộ nhớ ngoài
Nếu bộ nhớ trong không có đủ dung lượng để lưu trữ tệp dành riêng cho ứng dụng, hãy cân nhắc dùng bộ nhớ ngoài. Hệ thống cung cấp các thư mục trong bộ nhớ ngoài nơi ứng dụng có thể sắp xếp những tệp mang lại giá trị cho người dùng chỉ trong ứng dụng của bạn. Một thư mục được thiết kế để lưu các tệp bền vững của ứng dụng và một thư mục khác chứa các tệp được lưu vào bộ nhớ đệm của ứng dụng.
Trên Android 4.4 (API cấp 19) trở lên, ứng dụng của bạn không cần yêu cầu quyền liên quan đến bộ nhớ để truy cập vào các thư mục dành riêng cho ứng dụng trong bộ nhớ ngoài. Các tệp lưu trữ trong những thư mục này sẽ bị xoá khi ứng dụng đó bị gỡ cài đặt.
Trên các thiết bị chạy Android 9 (API cấp 28) trở xuống, ứng dụng của bạn có thể truy cập vào các tệp dành riêng cho ứng dụng thuộc ứng dụng khác, miễn là ứng dụng của bạn có quyền thích hợp liên quan đến bộ nhớ. Để mang lại cho người dùng nhiều quyền kiểm soát hơn đối với tệp của họ và giảm thiểu tình trạng tệp lộn xộn, các ứng dụng nhắm đến Android 10 (API cấp 29) trở lên được cấp quyền truy cập có giới hạn vào bộ nhớ ngoài, hoặc bộ nhớ có giới hạn, theo mặc định. Khi bạn bật bộ nhớ có giới hạn, các ứng dụng không thể truy cập vào thư mục dành riêng cho ứng dụng thuộc ứng dụng khác.
Xác minh rằng bộ nhớ còn trống
Vì bộ nhớ ngoài nằm trên một ổ đĩa thực mà người dùng có thể tháo, hãy xác minh rằng bạn truy cập được ổ đĩa trước khi tìm cách đọc/ghi dữ liệu dành riêng cho ứng dụng từ/vào bộ nhớ ngoài.
Bạn có thể truy vấn trạng thái của ổ đĩa bằng cách gọi
Environment.getExternalStorageState()
.
Nếu trạng thái trả về là
MEDIA_MOUNTED
, thì
bạn có thể đọc và ghi các tệp dành riêng cho ứng dụng trong bộ nhớ ngoài. Nếu trạng thái là
MEDIA_MOUNTED_READ_ONLY
,
bạn chỉ có thể đọc các tệp này.
Ví dụ: các phương thức sau đây hữu ích để xác định phạm vi cung cấp bộ nhớ:
Kotlin
// Checks if a volume containing external storage is available // for read and write. fun isExternalStorageWritable(): Boolean { return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED } // Checks if a volume containing external storage is available to at least read. fun isExternalStorageReadable(): Boolean { return Environment.getExternalStorageState() in setOf(Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED_READ_ONLY) }
Java
// Checks if a volume containing external storage is available // for read and write. private boolean isExternalStorageWritable() { return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); } // Checks if a volume containing external storage is available to at least read. private boolean isExternalStorageReadable() { return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) || Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED_READ_ONLY); }
Trên các thiết bị không có bộ nhớ ngoài có thể tháo rời, hãy dùng lệnh sau để bật ổ đĩa ảo nhằm kiểm thử logic phạm vi cung cấp bộ nhớ ngoài:
adb shell sm set-virtual-disk true
Chọn vị trí bộ nhớ thực
Đôi khi, thiết bị phân bổ một phân vùng của bộ nhớ trong làm bộ nhớ ngoài cũng cung cấp khe cắm thẻ SD. Nghĩa là thiết bị có nhiều ổ đĩa thực có thể chứa bộ nhớ ngoài, do đó, bạn cần chọn ổ đĩa sẽ dùng cho bộ nhớ dành riêng cho ứng dụng.
Để truy cập vào nhiều vị trí, hãy gọi
ContextCompat.getExternalFilesDirs()
.
Như minh hoạ trong đoạn mã, phần tử đầu tiên trong mảng trả về
sẽ được coi là ổ bộ nhớ ngoài chính. Hãy sử dụng ổ đĩa này trừ khi đã đầy
hoặc không dùng được.
Kotlin
val externalStorageVolumes: Array<out File> = ContextCompat.getExternalFilesDirs(applicationContext, null) val primaryExternalStorage = externalStorageVolumes[0]
Java
File[] externalStorageVolumes = ContextCompat.getExternalFilesDirs(getApplicationContext(), null); File primaryExternalStorage = externalStorageVolumes[0];
Truy cập vào các tệp bền vững
Để truy cập vào các tệp dành riêng cho ứng dụng từ bộ nhớ ngoài, hãy gọi
getExternalFilesDir()
.
Để giúp duy trì hiệu suất của ứng dụng, đừng mở và đóng cùng một tệp nhiều lần.
Đoạn mã sau đây minh hoạ cách gọi getExternalFilesDir()
:
Kotlin
val appSpecificExternalDir = File(context.getExternalFilesDir(null), filename)
Java
File appSpecificExternalDir = new File(context.getExternalFilesDir(null), filename);
Tạo các tệp bộ nhớ đệm
Để thêm một tệp dành riêng cho ứng dụng vào bộ nhớ đệm trong bộ nhớ ngoài, hãy
lấy thông tin tham chiếu đến
externalCacheDir
:
Kotlin
val externalCacheFile = File(context.externalCacheDir, filename)
Java
File externalCacheFile = new File(context.getExternalCacheDir(), filename);
Xoá các tệp bộ nhớ đệm
Để xoá một tệp khỏi thư mục bộ nhớ đệm bên ngoài, hãy dùng phương thức
delete()
trên đối tượng File
biểu thị tệp đó:
Kotlin
externalCacheFile.delete()
Java
externalCacheFile.delete();
Nội dung đa phương tiện
Nếu ứng dụng của bạn xử lý các tệp nội dung đa phương tiện mang lại giá trị cho người dùng chỉ trong ứng dụng của bạn, thì bạn nên lưu trữ các tệp đó trong thư mục dành riêng cho ứng dụng trong bộ nhớ ngoài, như minh hoạ trong đoạn mã sau:
Kotlin
fun getAppSpecificAlbumStorageDir(context: Context, albumName: String): File? { // Get the pictures directory that's inside the app-specific directory on // external storage. val file = File(context.getExternalFilesDir( Environment.DIRECTORY_PICTURES), albumName) if (!file?.mkdirs()) { Log.e(LOG_TAG, "Directory not created") } return file }
Java
@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; }
Bạn cần phải sử dụng tên thư mục do các hằng số API cung cấp như
DIRECTORY_PICTURES
.
Những tên thư mục này đảm bảo rằng các tệp được hệ thống xử lý đúng cách.
Nếu không có tên thư mục con
định sẵn nào phù hợp với tệp của bạn, thì bạn có thể
chuyển null
vào getExternalFilesDir()
. Thao tác này sẽ trả về thư mục gốc
dành riêng cho ứng dụng trong bộ nhớ ngoài.
Truy vấn dung lượng trống
Nhiều người dùng không có nhiều không gian lưu trữ trên thiết bị, vì vậy, ứng dụng của bạn nên sử dụng dung lượng một cách hợp lý.
Nếu biết trước lượng dữ liệu bạn sắp lưu trữ, thì bạn có thể nắm được mức dung lượng
mà thiết bị có thể cung cấp cho ứng dụng của mình bằng cách gọi
getAllocatableBytes()
.
Giá trị trả về của getAllocatableBytes()
có thể lớn hơn
lượng dung lượng trống hiện tại trên thiết bị. Nguyên nhân là vì hệ thống đã xác định
được các tệp có thể xoá khỏi thư mục bộ nhớ đệm của ứng dụng khác.
Nếu có đủ dung lượng để lưu dữ liệu của ứng dụng, hãy gọi
allocateBytes()
.
Nếu không, ứng dụng của bạn có thể yêu cầu người dùng xoá một số
tệp khỏi thiết bị hoặc xoá tất cả
các tệp bộ nhớ đệm khỏi thiết bị.
Đoạn mã sau đây cho thấy một ví dụ về cách ứng dụng của bạn có thể truy vấn dung lượng trống trên thiết bị:
Kotlin
// App needs 10 MB within internal storage. const val NUM_BYTES_NEEDED_FOR_MY_APP = 1024 * 1024 * 10L; val storageManager = applicationContext.getSystemService<StorageManager>()!! val appSpecificInternalDirUuid: UUID = storageManager.getUuidForPath(filesDir) val availableBytes: Long = storageManager.getAllocatableBytes(appSpecificInternalDirUuid) if (availableBytes >= NUM_BYTES_NEEDED_FOR_MY_APP) { storageManager.allocateBytes( appSpecificInternalDirUuid, NUM_BYTES_NEEDED_FOR_MY_APP) } else { val storageIntent = Intent().apply { // To request that the user remove all app cache files instead, set // "action" to ACTION_CLEAR_APP_CACHE. action = ACTION_MANAGE_STORAGE } }
Java
// 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 { // To request that the user remove all app cache files instead, set // "action" to ACTION_CLEAR_APP_CACHE. Intent storageIntent = new Intent(); storageIntent.setAction(ACTION_MANAGE_STORAGE); }
Tạo hoạt động quản lý bộ nhớ
Ứng dụng của bạn có thể khai báo và tạo một hoạt động tuỳ chỉnh (khi được chạy) sẽ cho phép
người dùng quản lý dữ liệu mà ứng dụng đó đã lưu trữ trên thiết bị của người dùng. Bạn
khai báo hoạt động "quản lý dung lượng" tuỳ chỉnh này bằng thuộc tính
android:manageSpaceActivity
trong tệp kê khai. Các ứng dụng quản lý tệp có thể gọi
hoạt động này
ngay cả khi ứng dụng của bạn không xuất hoạt động này; tức là khi hoạt động của bạn đặt
android:exported
thành
false
.
Yêu cầu người dùng xoá một số tệp trên thiết bị
Để yêu cầu người dùng xoá các tệp trên thiết bị, hãy gọi một ý định
bao gồm hành động
ACTION_MANAGE_STORAGE
. Ý định này sẽ hiển thị một lời nhắc cho người dùng. Nếu muốn, lời nhắc này có thể
cho biết lượng dung lượng trống trên thiết bị. Để hiển thị
thông tin thân thiện với người dùng này, hãy sử dụng kết quả của phép tính sau:
StorageStatsManager.getFreeBytes() / StorageStatsManager.getTotalBytes()
Yêu cầu người dùng xoá mọi tệp bộ nhớ đệm
Ngoài ra, bạn có thể yêu cầu người dùng xoá tệp bộ nhớ đệm khỏi tất cả
các ứng dụng trên thiết bị. Để làm vậy, hãy gọi một ý định bao gồm thao tác theo ý định
ACTION_CLEAR_APP_CACHE
.
Tài nguyên khác
Để biết thêm thông tin về cách lưu tệp vào bộ nhớ của thiết bị, hãy tham khảo các tài nguyên sau.