Android4.4之后的外置SD卡文件读写的解决方法
2015-10-22 09:49
567 查看
在Android4.4之后,普通应用就没有外置SD卡的写权限了,对于要操作外置SD的应用来说就是个灾难了。
我最近做的功能是要对视频和图片进行加锁,无法写就无法锁了。怎么解决呢?先百度Google大家都在说这个问题,但没有找到好的解决办法,然后我就去看看其它应用怎么做的。找几个需要控制SD卡的应用,ES文件浏览器。
在写外置SD卡文件时会弹出这样一个界面:
点击选择进入系统的一个文件目录界面:
点击“选择SD"卡,回来就可以对文件进行操作了。
然后我就去反编译看看它是怎么操作的。我反编译了另一个文件管理器的应用,和这个也是差不多,但代码混淆较少,可以看到很多细节。
用ApkTool反编译找到Dialog的提示语,再到代码找到Dialog界面。ApkTool得到的代码比较乱,就用的dex2jar反编译得到的代码,保存用Android Studio打开看。
看到点击Dialog确实后的操作是:
这个Intent就是进入文件目录,再找回来在OnActivityResult的操作:
这开始就是贴的是我改完后的自己代码了,清楚点,都是差不多意思。回来成功选择后,先判断是不是SD卡,不是就继续弹Dialog,是就保存treeUri,就是外置SD的文档Uri。
这个Uri和普通Uri是不一样的,打印出来是这样的:
content://com.android.externalstorage.documents/tree/6635-3265%3A
中间有个tree。
再去找文件操作时是怎么样的,发现外置SD卡都是通过DocumentFile进行文件写的。先根据之前保存的SD卡uri获取根DocumentFile,在一层一层获取子DocumentFile。
有了思路就可以更好的找方法了,再在google搜DocumentFile,SD卡相关的,在stackOverFlow找到了这个:
http://stackoverflow.com/questions/26744842/how-to-use-the-new-sd-card-access-api-presented-for-lollipop
改写了其提供的一些方法,下面的copyFile就是。target可写就直接写,android5通过DucumentFile获取outStream,4.4由contentResolve通过普通Uri获取到输出流。然后写到输出流中。
我最近做的功能是要对视频和图片进行加锁,无法写就无法锁了。怎么解决呢?先百度Google大家都在说这个问题,但没有找到好的解决办法,然后我就去看看其它应用怎么做的。找几个需要控制SD卡的应用,ES文件浏览器。
在写外置SD卡文件时会弹出这样一个界面:
点击选择进入系统的一个文件目录界面:
点击“选择SD"卡,回来就可以对文件进行操作了。
然后我就去反编译看看它是怎么操作的。我反编译了另一个文件管理器的应用,和这个也是差不多,但代码混淆较少,可以看到很多细节。
用ApkTool反编译找到Dialog的提示语,再到代码找到Dialog界面。ApkTool得到的代码比较乱,就用的dex2jar反编译得到的代码,保存用Android Studio打开看。
看到点击Dialog确实后的操作是:
this.val$context.startActivityForResult(new Intent("android.intent.action.OPEN_DOCUMENT_TREE"), this.val$requestCode);
这个Intent就是进入文件目录,再找回来在OnActivityResult的操作:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case SDUtils.REQ_PICK_SDCARD_PATH /*8712*/: if (resultCode == RESULT_OK) { Uri treeUri = data.getData(); if (!":".equals(treeUri.getPath().substring(treeUri.getPath().length() - 1)) || treeUri.getPath().contains("primary")) { SDUtils sdUtils = new SDUtils(); sdUtils.getSdCardUriDialog(SelectVideoActivity.this); } else { Logger.get().e("RESULT_OK"); SDUtils.setSdCardUriPreferences(SelectVideoActivity.this, treeUri.toString()); lockVideo(); } } } super.onActivityResult(requestCode, resultCode, data); }
这开始就是贴的是我改完后的自己代码了,清楚点,都是差不多意思。回来成功选择后,先判断是不是SD卡,不是就继续弹Dialog,是就保存treeUri,就是外置SD的文档Uri。
这个Uri和普通Uri是不一样的,打印出来是这样的:
content://com.android.externalstorage.documents/tree/6635-3265%3A
中间有个tree。
再去找文件操作时是怎么样的,发现外置SD卡都是通过DocumentFile进行文件写的。先根据之前保存的SD卡uri获取根DocumentFile,在一层一层获取子DocumentFile。
public static DocumentFile getDocumentFilePath(Context context, String path, boolean createDirectories) { DocumentFile document = DocumentFile.fromTreeUri(context, Uri.parse(SDUtils.getSdCardUriPreferences(context))); Logger.get().e( document.getName()+":"+SDUtils.getSdCardUriPreferences(context)); String[] parts = path.split(ZIP_FILE_SEPARATOR); for (int i = 3; i < parts.length; i++) { DocumentFile nextDocument = document.findFile(parts[i]); if (nextDocument == null) { if (i < parts.length - 1) { if (createDirectories) { nextDocument = document.createDirectory(parts[i]); } else { return null; } } else { nextDocument = document.createFile("image", parts[i]); } } document = nextDocument; } return document; }path是要获取文件的路径,createDirectories是是否要新建,false用于获取DocumentFile执行删除操作,true用于新建文件。遍历不从0开始是因为path的是绝对路径,前面SD卡的路径不需要,从SD卡的第一层目录开始遍历。
有了思路就可以更好的找方法了,再在google搜DocumentFile,SD卡相关的,在stackOverFlow找到了这个:
http://stackoverflow.com/questions/26744842/how-to-use-the-new-sd-card-access-api-presented-for-lollipop
改写了其提供的一些方法,下面的copyFile就是。target可写就直接写,android5通过DucumentFile获取outStream,4.4由contentResolve通过普通Uri获取到输出流。然后写到输出流中。
public static boolean copyFile(Context context, final File source, final File target) { FileInputStream inStream = null; OutputStream outStream = null; FileChannel inChannel = null; FileChannel outChannel = null; try { inStream = new FileInputStream(source); if (isWritable(target)) { // standard way outStream = new FileOutputStream(target); inChannel = inStream.getChannel(); outChannel = ((FileOutputStream) outStream).getChannel(); inChannel.transferTo(0, inChannel.size(), outChannel); outStream = null; } else if (isAndroid5()) { // Storage Access Framework DocumentFile targetDocument = getDocumentFilePath(context, target.getAbsolutePath(), true); if (targetDocument != null) { outStream = context.getContentResolver().openOutputStream(targetDocument.getUri()); } } else if (isKitkat()) { // Workaround for Kitkat ext SD card Uri uri = getUriFromFile(context, target.getAbsolutePath()); outStream = context.getContentResolver().openOutputStream(uri); } else { return false; } if (outStream != null) { // Both for SAF and for Kitkat, write to output stream. byte[] buffer = new byte[4096]; // MAGIC_NUMBER int bytesRead; while ((bytesRead = inStream.read(buffer)) != -1) { outStream.write(buffer, 0, bytesRead); } } } catch (Exception e) { Logger.get().e("Error when copying file from " + source.getAbsolutePath() + " to " + target.getAbsolutePath(), e); return false; } finally { try { inStream.close(); } catch (Exception e) { // ignore exception } try { outStream.close(); } catch (Exception e) { // ignore exception } try { inChannel.close(); } catch (Exception e) { // ignore exception } try { outChannel.close(); } catch (Exception e) { // ignore exception } } return true; }主要逻辑都写出来了,真正实现还有好多细节需要完善。就不一一列出了。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories