
本文档旨在指导开发者如何在Android Scoped Storage环境下,使用Storage Access Framework (SAF) 读取外部存储特定文件夹中的文件。我们将提供详细的代码示例和步骤,帮助你理解SAF的工作原理,并安全高效地访问所需的文件。
在Android 10 (API level 29) 及更高版本中,引入了Scoped Storage,旨在提高用户隐私和数据安全。Scoped Storage限制了应用直接访问外部存储的能力,应用只能访问自己的应用特定目录以及用户通过系统文件选择器授予访问权限的目录。为了在Scoped Storage环境下访问外部存储,我们需要使用Storage Access Framework (SAF)。
SAF允许用户选择一个目录,并授权应用访问该目录及其子目录。用户授权后,应用可以读取和写入该目录中的文件,而无需请求READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 权限。
以下步骤展示了如何使用SAF读取特定文件夹中的文件:
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.storage.StorageManager;
import android.util.Log;
public class Check {
Context context;
private String TAG="SOMNATH";
public Check(Context context) {
this.context = context;
}
public boolean ch(){
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Intent intent = sm.getPrimaryStorageVolume().createOpenDocumentTreeIntent();
//String startDir = "Android";
//String startDir = "Download"; // Not choosable on an Android 11 device
//String startDir = "DCIM";
//String startDir = "DCIM/Camera"; // replace "/", "%2F"
//String startDir = "DCIM%2FCamera";
String startDir = "Documents";
Uri uri = intent.getParcelableExtra("android.provider.extra.INITIAL_URI");
String scheme = uri.toString();
Log.d(TAG, "INITIAL_URI scheme: " + scheme);
scheme = scheme.replace("/root/", "/document/");
scheme += "%3A" + startDir;
uri = Uri.parse(scheme);
intent.putExtra("android.provider.extra.INITIAL_URI", uri);
Log.d(TAG, "uri: " + uri.toString());
((Activity) context).startActivityForResult(intent, 12123);
return true;
}
else{
return false;
}
}
}package com.axanor.saf_sample
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.provider.DocumentsContract
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
class StorageAccess {
val activity:Activity;
val LOGTAG = "SOMNATH"
val REQUEST_CODE = 12123
constructor(activity: Activity) {
this.activity = activity
}
public fun openDocumentTree() {
val check = Check(activity)
val uriString = SpUtil.getString(SpUtil.FOLDER_URI, "")
when {
uriString == "" -> {
Log.w(LOGTAG, "uri not stored")
if (!check.ch()){
askPermission()
}
}
arePermissionsGranted(uriString) -> {
makeDoc(Uri.parse(uriString))
}
else -> {
Log.w(LOGTAG, "uri permission not stored")
if (!check.ch()){
askPermission()
}
}
}
}
// this will present the user with folder browser to select a folder for our data
public fun askPermission() {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
activity.startActivityForResult(intent, REQUEST_CODE)
}
public fun makeDoc(dirUri: Uri) {
val dir = DocumentFile.fromTreeUri(activity, dirUri)
if (dir == null || !dir.exists()) {
//the folder was probably deleted
Log.e(LOGTAG, "no Dir")
//according to Commonsware blog, the number of persisted uri permissions is limited
//so we should release those we cannot use anymore
//https://commonsware.com/blog/2020/06/13/count-your-saf-uri-permission-grants.html
releasePermissions(dirUri)
//ask user to choose another folder
Toast.makeText(activity,"Folder deleted, please choose another!", Toast.LENGTH_SHORT).show()
openDocumentTree()
} else {
val file = dir.createFile("*/txt", "test.txt")
if (file != null && file.canWrite()) {
Log.d(LOGTAG, "file.uri = ${file.uri.toString()}")
alterDocument(file.uri)
} else {
Log.d(LOGTAG, "no file or cannot write")
//consider showing some more appropriate error message
Toast.makeText(activity,"Write error!", Toast.LENGTH_SHORT).show()
}
}
}
public fun releasePermissions(uri: Uri) {
val flags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
activity.contentResolver.releasePersistableUriPermission(uri,flags)
//we should remove this uri from our shared prefs, so we can start over again next time
SpUtil.storeString(SpUtil.FOLDER_URI, "")
}
//Just a test function to write something into a file, from https://developer.android.com
//Please note, that all file IO MUST be done on a background thread. It is not so in this
//sample - for the sake of brevity.
public fun alterDocument(uri: Uri) {
try {
activity.contentResolver.openFileDescriptor(uri, "w")?.use { parcelFileDescriptor ->
FileOutputStream(parcelFileDescriptor.fileDescriptor).use {
it.write(
("String written at ${System.currentTimeMillis()}\n")
.toByteArray()
)
Toast.makeText(activity,"File Write OK!", Toast.LENGTH_SHORT).show()
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "application/pdf"
// Optionally, specify a URI for the file that should appear in the
// system file picker when it loads.
// Optionally, specify a URI for the file that should appear in the
// system file picker when it loads.
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri)
activity.startActivityForResult(intent, 2)
}
}
} catch (e: FileNotFoundException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
}
public fun arePermissionsGranted(uriString: String): Boolean {
// list of all persisted permissions for our app
val list = activity.contentResolver.persistedUriPermissions
for (i in list.indices) {
val persistedUriString = list[i].uri.toString()
//Log.d(LOGTAG, "comparing $persistedUriString and $uriString")
if (persistedUriString == uriString && list[i].isWritePermission && list[i].isReadPermission) {
//Log.d(LOGTAG, "permission ok")
return true
}
}
return false
}
}StorageAccess access = new StorageAccess(this); access.openDocumentTree();
public fun makeDoc(dirUri: Uri) {
val dir = DocumentFile.fromTreeUri(activity, dirUri)
if (dir == null || !dir.exists()) {
//the folder was probably deleted
Log.e(LOGTAG, "no Dir")
//according to Commonsware blog, the number of persisted uri permissions is limited
//so we should release those we cannot use anymore
//https://commonsware.com/blog/2020/06/13/count-your-saf-uri-permission-grants.html
releasePermissions(dirUri)
//ask user to choose another folder
Toast.makeText(activity,"Folder deleted, please choose another!", Toast.LENGTH_SHORT).show()
openDocumentTree()
} else {
val file = dir.createFile("*/txt", "test.txt")
if (file != null && file.canWrite()) {
Log.d(LOGTAG, "file.uri = ${file.uri.toString()}")
alterDocument(file.uri)
} else {
Log.d(LOGTAG, "no file or cannot write")
//consider showing some more appropriate error message
Toast.makeText(activity,"Write error!", Toast.LENGTH_SHORT).show()
}
}
} public fun arePermissionsGranted(uriString: String): Boolean {
// list of all persisted permissions for our app
val list = activity.contentResolver.persistedUriPermissions
for (i in list.indices) {
val persistedUriString = list[i].uri.toString()
//Log.d(LOGTAG, "comparing $persistedUriString and $uriString")
if (persistedUriString == uriString && list[i].isWritePermission && list[i].isReadPermission) {
//Log.d(LOGTAG, "permission ok")
return true
}
}
return false
}通过使用Storage Access Framework (SAF),你可以安全高效地在Android Scoped Storage环境下访问外部存储的特定文件夹。记住,尊重用户隐私是至关重要的,始终以用户为中心设计你的应用。本教程提供了一个基本的框架,你可以根据自己的需求进行扩展和定制。
以上就是Android Scoped Storage: 如何读取外部存储特定文件夹的文件的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号