在Android 4.4(KitKat)上,谷歌已经限制了对SD卡的访问.
从Android Lollipop(5.0)开始,开发人员可以使用新的API,要求用户确认允许访问特定文件夹,如此Google-Groups帖子所述.
该帖子指导您访问两个网站:
这看起来像是一个内部示例(可能稍后会在API演示中显示),但很难理解发生了什么.
这是新API的官方文档,但它没有详细说明如何使用它.
这是它告诉你的:
如果您确实需要完全访问整个文档子树,请首先启动ACTION_OPEN_DOCUMENT_TREE以允许用户选择目录.然后将生成的getData()传递给fromTreeUri(Context,Uri)以开始使用用户选择的树.
在导航DocumentFile实例树时,您始终可以使用getUri()来获取表示该对象的基础文档的Uri,以便与openInputStream(Uri)等一起使用.
要在运行KITKAT或更早版本的设备上简化代码,可以使用fromFile(File)来模拟DocumentsProvider的行为.
我对新API有几个问题:
从Lollipop开始,应用程序可以访问真正的SD卡(在Kitkat上无法访问之后,并且在之前的版本中尚未获得官方支持),正如我在此处所述.
因为看到支持SD卡的Lollipop设备变得非常罕见,并且因为模拟器实际上没有能力(或者它?)来模拟SD卡支持,所以我花了很长时间来测试它.
无论如何,似乎不是使用普通的File类来访问SD卡(一旦获得了它的许可),你需要使用Uris来使用它,使用DocumentFile.
这限制了对正常路径的访问,因为我找不到将Uris转换为路径的方法,反之亦然(加上它非常烦人).这也意味着我不知道如何检查当前的SD卡是否可访问,因此我不知道何时要求用户允许读取/写入(或向他们).
目前,这就是我获取所有SD卡的路径:
/**
* returns a list of all available sd cards paths, or null if not found.
*
* @param includePrimaryExternalStorage set to true if you wish to also include the path of the primary external storage
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static List<String> getExternalStoragePaths(final Context context,final boolean includePrimaryExternalStorage)
{
final File primaryExternalStorageDirectory=Environment.getExternalStorageDirectory();
final List<String> result=new ArrayList<>();
final File[] externalCacheDirs=ContextCompat.getExternalCacheDirs(context);
if(externalCacheDirs==null||externalCacheDirs.length==0)
return result;
if(externalCacheDirs.length==1)
{
if(externalCacheDirs[0]==null)
return result;
final String storageState=EnvironmentCompat.getStorageState(externalCacheDirs[0]);
if(!Environment.MEDIA_MOUNTED.equals(storageState))
return …
Run Code Online (Sandbox Code Playgroud) 关于如何在SD卡(android 5及以上版本)中编写(和重命名)文件的大量调查结果后,我认为android提供的新SAF需要获得用户写入SD卡文件的许可.
我在这个文件管理器应用程序ES文件资源管理器中看到,最初它采用以下方式读取和写入权限,如图片所示.
选择SD卡后,授予写入权限.
因此我尝试使用SAF的方式相同,但重命名文件失败了.我的代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
rename = (Button) findViewById(R.id.rename);
startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), 42);
}
@Override
public void onActivityResult(int requestCode,int resultCode,Intent resultData) {
if (resultCode != RESULT_OK)
return;
Uri treeUri = resultData.getData();
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
grantUriPermission(getPackageName(), treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
public void renameclick(View v) {
File ff = new File("/storage/sdcard1/try1.jpg");
try {
ff.createNewFile();
} catch (Exception e) {
Log.d("error", "creating");
e.printStackTrace();
}
} …
Run Code Online (Sandbox Code Playgroud) android android-sdcard storage-access-framework android-5.0-lollipop documentfile
好吧,我进行了搜索和搜索,没有人得到我的确切答案,或者我错过了.我让我的用户通过以下方式选择目录:
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, READ_REQUEST_CODE);
Run Code Online (Sandbox Code Playgroud)
在我的活动中,我想捕捉实际路径,这似乎是不可能的.
protected void onActivityResult(int requestCode, int resultCode, Intent intent){
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode == RESULT_OK) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M){
//Marshmallow
} else if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){
//Set directory as default in preferences
Uri treeUri = intent.getData();
//grant write permissions
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
//File myFile = new File(uri.getPath());
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
Run Code Online (Sandbox Code Playgroud)
我选择的文件夹位于:
Device storage/test/
Run Code Online (Sandbox Code Playgroud)
我已经尝试了以下所有方法来获得确切的路径名称,但无济于事.
File myFile = new File (uri.getPath());
//returns: /tree/1AF6-3708:test
treeUri.getPath();
//returns: /tree/1AF6-3708:test/
pickedDir.getName()
//returns: …
Run Code Online (Sandbox Code Playgroud) 我的应用程序使用Java类RandomAccessFile通过SeekableByteChannel接口的实现随机读取/写入SD卡上的文件.现在我需要使用新的Lollipop API为Android 5.0重写它.
我找到了唯一的阅读方式:
InputStream inputStream = getContentResolver().openInputStream(uri);
Run Code Online (Sandbox Code Playgroud)
和写:
ParcelFileDescriptor pfd = getActivity().getContentResolver().openFileDescriptor(uri, "w");
FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
Run Code Online (Sandbox Code Playgroud)
从/到新API中的文件.
我希望能够在某个随机位置设置通道,并将字节读/写到该位置.是否可以在新的SDK 21中执行此操作?新的SDK是否意味着获取频道:
FieInputChannel fieInputChannel = fileInputStream.getChannel();
FieOutputChannel fieOutputChannel = fileOutputStream.getChannel();
Run Code Online (Sandbox Code Playgroud)
或其他一些方法?
filechannel android-sdcard randomaccessfile android-5.0-lollipop documentfile
背景
我有一些应用程序大量使用SD卡进行文件同步.Kitkat上破坏的外部SD卡访问仍然是一个大问题,但我正在尝试使用Lollipop上提供的新API为具有此功能的用户解决此问题.
我成功请求并持有SD卡的权限,我可以列出从授予权限活动返回的根Uri中的文件.
查看有关如何在此处完成此操作的更多信息: 如何使用新的-sd-card-access-api-present-for-lollipop
然后,用户可以选择任何文件夹/子文件夹进行同步,并将文件夹文档Uri作为数据库中的字符串保留.
问题
稍后,可能在重新启动应用程序后,可以启动文件的同步.然后我尝试列出子文件夹中的文件(记住,我已经授予了正确的权限,并且这种工作的持久性也允许我访问所有子文件).
然后,我从存储的字符串创建一个新的DocumentFile实例,并尝试列出文件:
DocumentFile dir = DocumentFile.fromTreeUri(ctx, Uri.parse(storedUri));
dir.listFiles();
Run Code Online (Sandbox Code Playgroud)
问题是listFiles总是返回授予根Uri的子节点,而不是实际Uri的子节点我给DocumentFile.fromTreeUri方法.
我已经检查了DocumentFile的源代码,似乎有一个bug,特别是我没有看到需要进一步修改Uri:
public static DocumentFile fromTreeUri(Context context, Uri treeUri) {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
return new TreeDocumentFile(null, context,
DocumentsContractApi21.prepareTreeUri(treeUri));
} else {
return null;
}
Run Code Online (Sandbox Code Playgroud)
如果我们查看DocumentsContractApi21.prepareTreeUri的来源,我们会看到重建Uri:
public static Uri prepareTreeUri(Uri treeUri) {
return DocumentsContract.buildDocumentUriUsingTree(treeUri,
DocumentsContract.getTreeDocumentId(treeUri));
}
Run Code Online (Sandbox Code Playgroud)
它调用的方法:
public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(treeUri.getAuthority()).appendPath(PATH_TREE)
.appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT)
.appendPath(documentId).build();
}
public static String getTreeDocumentId(Uri documentUri) …
Run Code Online (Sandbox Code Playgroud) 我有DocumentFile
以下两种定义:
DocumentFile documentFile;
Uri documentFileUri;
Run Code Online (Sandbox Code Playgroud)
我可以通过以下方法从SD卡中删除文档文件:
documentFile.delete();
DocumentsContract.deleteDocument(contentResolver, documentFileUri);
但以上方法均不能从中删除相应的条目MediaStore
。
处理该问题的正确方法是什么?如果我使用ContentProvider
删除本地文件,它将File
从数据库(contentResolver.delete(localFileUri, null, null);
)中删除AND行。我希望如果使用,也会发生相同的情况,DocumentsContract
但不会发生...
我想要的是
我想了解一下更新MediaStore
。通常我会打电话给我,contentResolver.delete(documentFileUri, null, null);
但这会失败,但有一个例外,说uri不支持删除...
题
是否有比我的解决方法更有效的方法?
解决方法
目前,我在删除a后使用以下功能立即更新媒体存储DocumentFile
:
public static boolean updateAfterChangeBlocking(String path, int timeToWaitToRecheckMediaScanner)
{
final AtomicBoolean changed = new AtomicBoolean(false);
MediaScannerConnection.scanFile(StorageManager.get().getContext(),
new String[]{path}, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
changed.set(true);
}
});
while (!changed.get()) {
try {
Thread.sleep(timeToWaitToRecheckMediaScanner);
} catch (InterruptedException e) …
Run Code Online (Sandbox Code Playgroud) 我正在尝试使用 DocumentFile 列出 Android 5.1 手机上的外部存储设备中的文件
String rootPathURI = "file:/media/storage/sdcard1/data/example.externalstorage/files/";
File f = new File(URI(rootPathURI));
DocumentFile documentFile = DocumentFile.fromFile(f);
Run Code Online (Sandbox Code Playgroud)
这段代码工作正常,但我想这样做;
String rootPathURI = "file:/media/storage/sdcard1/data/example.externalstorage/files/";
DocumentFile documentFile = DocumentFile.fromTreeUri(getApplicationContext(), Uri.parse(rootPathURI));
Run Code Online (Sandbox Code Playgroud)
但我得到这样的例外:
W/System.err( 5157): java.lang.IllegalArgumentException: Invalid URI:"file:/media/storage/sdcard1/data/example.externalstorage/files/"
Run Code Online (Sandbox Code Playgroud) 使用:ACTION_OPEN_DOCUMENT_TREE + DocumentFile
更多信息: 如何使用针对Android 5.0(Lollipop)推出的新SD卡访问API?
我找不到如何更改文件属性的方法.有没有?
特别是,我需要更改Last Modified属性 - 比如File Class方法:
public boolean setLastModified(long time);
Run Code Online (Sandbox Code Playgroud)
我没有找到任何替补:
https://developer.android.com/reference/android/support/v4/provider/DocumentFile.html
或者在DocumentsContract之类的相关类中,...
新API的文档几乎没用,API函数运行速度极慢,新API的代码重写非常麻烦.我很抱歉这么强硬,但Kitkat"EACCESS(Permission denied)功能"花了我几年的生命而不是解决方案我会得到这个.
似乎setLastModified(...)方法即使使用java.io.File
Class 也不起作用(至少从Android版本4.4开始):
https://code.google.com/p/android/issues/detail?id=18624
例如,如果您有一个归档应用程序,并且您想要真正的上次修改时间,而不是从归档中提取文件的时间 - 抱歉.很多同步工具变得无用......
Android 5.1(模拟器):setLastModified(long time)
方法仍然不起作用.
Android 6.0(模拟器):setLastModified(long time)
方法仍然无法正常工作.
TL:DR; 我解释了如何使用DocumentFile使用创建文件夹和子文件夹以及如何删除使用此类创建的文件。从onActvityResult()和documentFile.getUri.toString()返回的Uri不相同。我的问题是如何在不使用SAF UI的情况下获得有效的Uri来操作文件夹和文件,如果可能的话,不使用hack也不行。
让我分享到目前为止我学到的知识并提出我的问题。如果要获取文件夹的Uri并对其进行处理,则应使用Intent
with ACTION_OPEN_DOCUMENT_TREE
获取一个Uri来访问文件夹并为该uri设置W / R权限。
通过以下方式授予 onActivityResult的持久权限:
final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
Run Code Online (Sandbox Code Playgroud)
如果选择设备的主文件夹:
Uri treeUri = data.getData();
treeUri.toString()
Run Code Online (Sandbox Code Playgroud)
返回:content://com.android.externalstorage.documents/tree/primary:
File mediaStorageDir = new File(Environment.getExternalStorageDirectory(), "");
Run Code Online (Sandbox Code Playgroud)
返回值:存储/模拟/ 0
new File(treeUri.toString()).getAbsolutePath();
Run Code Online (Sandbox Code Playgroud)
返回值:内容:/com.android.externalstorage.documents/tree/primary:
如果使用DocumentFile类获取主文件夹的路径,则会得到
DocumentFile saveDir = null;
saveDir = DocumentFile.fromFile(Environment.getExternalStorageDirectory());
String uriString = saveDir.getUri().toString();
Run Code Online (Sandbox Code Playgroud)
返回值:file:/// storage / emulated / 0
我的第一个问题是如何使用DocumentFile类获取带有内容的Uri。
我正在构建一个摄影应用程序,默认情况下,我想使用DocumentFile类为图像设置初始文件夹。
@TargetApi(19)
protected DocumentFile getSaveDirMainMemory() {
DocumentFile …
Run Code Online (Sandbox Code Playgroud)