安卓4.4中应用无法读取修改sd卡的问题——程序员解决方案

Google去年11月正式发布了Android 4.4,代号为KitKat(奇巧,雀巢的一款巧克力品牌),该系统带来了诸多新的特性

但需要注意的是,该系统可能会让你之前一直正常使用的SD卡变为无用的“摆设”,因为根据新版本的API改进,应用程序将不能再往SD卡中写入文件。

来看Android开发者网站的“外部存储技术信息”文档中的描述:

引用

WRITE_EXTERNAL_STORAGE只为设备上的主要外部存储授予写权限,应用程序无法将数据写入二级外部存储设备,除非指定了应用程序允许访问的特定的目录。

这目前只影响双存储设备,如果你的设备有内部存储空间,即通常所说的机身存储(这就是指主要外部存储),那么你的SD卡就是一个二级外部存储设备。

在Android 4.4中,如果你同时使用了机身存储和SD卡,那么应用程序将无法在SD卡中创建、修改、删除数据。比如,你无法使用文件管理器通过无线网络从电脑往SD卡中复制文件了。但是应用程序仍然可以往主存储的任意目录中写入数据,不受任何限制。

Google表示,这样做的目的是,通过这种方式进行限制,系统可以在应用程序被卸载后清除遗留文件。

目前三星已经通过OTA向部分手机发送了Android 4.4的更新,已经有Note3用户抱怨FX文件管理器现在不能往SD卡中复制内容了。

解决办法

获得系统的ROOT权限是一个解决方法。

很显然,这是针对用户的解决办法,但是并不是所有的用户都愿意进行ROOT,那么需要SD卡写入权限的开发者该如何做呢?

XDA论坛已经有大神给出了解决方案——在应用中嵌入一段代码,这段代码作用是在Android
4.4+设备上,如果其他方式写入失败,则将数据写入二级存储设备。

详细方案:http://forum.xda-developers.com/showthread.php?p=50008987

Java代码

  1. /*
  2. * Copyright (C) 2014 NextApp, Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
  10. * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
  11. * governing permissions and limitations under the License.
  12. */
  13. package nextapp.mediafile;
  14. import java.io.File;
  15. import java.io.IOException;
  16. import java.io.OutputStream;
  17. import android.content.ContentResolver;
  18. import android.content.ContentValues;
  19. import android.net.Uri;
  20. import android.provider.MediaStore;
  21. /**
  22. * Wrapper for manipulating files via the Android Media Content Provider. As of Android 4.4 KitKat, applications can no longer write
  23. * to the "secondary storage" of a device. Write operations using the java.io.File API will thus fail. This class restores access to
  24. * those write operations by way of the Media Content Provider.
  25. *
  26. * Note that this class relies on the internal operational characteristics of the media content provider API, and as such is not
  27. * guaranteed to be future-proof. Then again, we did all think the java.io.File API was going to be future-proof for media card
  28. * access, so all bets are off.
  29. *
  30. * If you‘re forced to use this class, it‘s because Google/AOSP made a very poor API decision in Android 4.4 KitKat.
  31. * Read more at https://plus.google.com/+TodLiebeck/posts/gjnmuaDM8sn
  32. *
  33. * Your application must declare the permission "android.permission.WRITE_EXTERNAL_STORAGE".
  34. */
  35. public class MediaFile {
  36. private final File file;
  37. private final ContentResolver contentResolver;
  38. private final Uri filesUri;
  39. private final Uri imagesUri;
  40. public MediaFile(ContentResolver contentResolver, File file) {
  41. this.file = file;
  42. this.contentResolver = contentResolver;
  43. filesUri = MediaStore.Files.getContentUri("external");
  44. imagesUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
  45. }
  46. /**
  47. * Deletes the file. Returns true if the file has been successfully deleted or otherwise does not exist. This operation is not
  48. * recursive.
  49. */
  50. public boolean delete()
  51. throws IOException {
  52. if (!file.exists()) {
  53. return true;
  54. }
  55. boolean directory = file.isDirectory();
  56. if (directory) {
  57. // Verify directory does not contain any files/directories within it.
  58. String[] files = file.list();
  59. if (files != null && files.length > 0) {
  60. return false;
  61. }
  62. }
  63. String where = MediaStore.MediaColumns.DATA + "=?";
  64. String[] selectionArgs = new String[] { file.getAbsolutePath() };
  65. // Delete the entry from the media database. This will actually delete media files (images, audio, and video).
  66. contentResolver.delete(filesUri, where, selectionArgs);
  67. if (file.exists()) {
  68. // If the file is not a media file, create a new entry suggesting that this location is an image, even
  69. // though it is not.
  70. ContentValues values = new ContentValues();
  71. values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
  72. contentResolver.insert(imagesUri, values);
  73. // Delete the created entry, such that content provider will delete the file.
  74. contentResolver.delete(filesUri, where, selectionArgs);
  75. }
  76. return !file.exists();
  77. }
  78. public File getFile() {
  79. return file;
  80. }
  81. /**
  82. * Creates a new directory. Returns true if the directory was successfully created or exists.
  83. */
  84. public boolean mkdir()
  85. throws IOException {
  86. if (file.exists()) {
  87. return file.isDirectory();
  88. }
  89. ContentValues values;
  90. Uri uri;
  91. // Create a media database entry for the directory. This step will not actually cause the directory to be created.
  92. values = new ContentValues();
  93. values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
  94. contentResolver.insert(filesUri, values);
  95. // Create an entry for a temporary image file within the created directory.
  96. // This step actually causes the creation of the directory.
  97. values = new ContentValues();
  98. values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath() + "/temp.jpg");
  99. uri = contentResolver.insert(imagesUri, values);
  100. // Delete the temporary entry.
  101. contentResolver.delete(uri, null, null);
  102. return file.exists();
  103. }
  104. /**
  105. * Returns an OutputStream to write to the file. The file will be truncated immediately.
  106. */
  107. public OutputStream write()
  108. throws IOException {
  109. if (file.exists() && file.isDirectory()) {
  110. throw new IOException("File exists and is a directory.");
  111. }
  112. // Delete any existing entry from the media database.
  113. // This may also delete the file (for media types), but that is irrelevant as it will be truncated momentarily in any case.
  114. String where = MediaStore.MediaColumns.DATA + "=?";
  115. String[] selectionArgs = new String[] { file.getAbsolutePath() };
  116. contentResolver.delete(filesUri, where, selectionArgs);
  117. ContentValues values = new ContentValues();
  118. values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
  119. Uri uri = contentResolver.insert(filesUri, values);
  120. if (uri == null) {
  121. // Should not occur.
  122. throw new IOException("Internal error.");
  123. }
  124. return contentResolver.openOutputStream(uri);
  125. }
  126. }
时间: 2024-10-13 19:56:43

安卓4.4中应用无法读取修改sd卡的问题——程序员解决方案的相关文章

【android】在eclipse中查看genymotion模拟器的sd卡目录

如果用google自带模拟器或者真机调试时,sd卡目录是在/mnt/sdcard.这个相信大家都知道. 可是今天用genymotion调试时,发现根本打不开/mnt/sdcard这个目录,当时也没注意看其他信息,以为是adb.eclipse出了什么问题,重启它们后依然打不开,经过一番苦苦挣扎后,发现了一个重要的线索! 哦,原来和我完捉迷藏呢,它指向了另一个目录,接着找,发现: 晕,这孩子挺顽皮的,还是指向了其他目录: 终于找到了,真正的SD卡目录是在/mnt/shell/emulated/0/里

Android 读取本地(SD卡)图片

private Bitmap getDiskBitmap(String pathString) { Bitmap bitmap = null; try { File file = new File(pathString); if(file.exists()) { bitmap = BitmapFactory.decodeFile(pathString); } } catch (Exception e) { // TODO: handle exception } return bitmap; }

【android】在eclipse中查看genymotion模拟器的sd卡文件夹

假设用google自带模拟器或者真机调试时,sd卡文件夹是在/mnt/sdcard.这个相信大家都知道. 但是今天用genymotion调试时.发现根本打不开/mnt/sdcard这个文件夹,当时也没注意看其它信息.以为是adb.eclipse出了什么问题,重新启动它们后依旧打不开,经过一番苦苦挣扎后,发现了一个重要的线索! 哦,原来和我完捉迷藏呢,它指向了还有一个文件夹,接着找,发现: 晕,这孩子挺顽皮的,还是指向了其它文件夹: watermark/2/text/aHR0cDovL2Jsb2c

将android程序中的数据库导出到SD卡

private void copyDBToSDcrad() { String DATABASE_NAME = "数据库文件名称"; String oldPath = "data/data/com.packagename/databases/" + DATABASE_NAME; String newPath = Environment.getExternalStorageDirectory() + File.separator + DATABASE_NAME; cop

安卓读取SD卡的容量

在开发中,我们经常会用到SD卡,那么在对SD卡进行读写的时候,我们经常需要判断SD卡的剩余容量是否足够.因此,这次我们来写写获取SD卡容量的程序. 该注意的地方,我都在程序里面有注明了.看程序基本就懂了哈. 先来看看运行结果截图吧. 布局文件 activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://sche

Genymotion中SD卡目录在Eclipse中查看,以及创建SDCard

今天写的Android程序在用Google自带的AVD4.4版本运行的,其中sd卡目录在/storage/sdcard/中,但是呀,自己的机器不给力启动一下5分多,程序员的时间是宝贵的,5分多可以敲好几十行代码了,所以就切回Eclipse边写代码边等它启动起来,但是有个东西在后面挂着,这敲着代码老是惦记着它启动了没有,后来忍受不了了,这就开始使用Genymotion这个模拟器了. 接下来就是找到Android Genymotion 模拟器的SD卡的目录. 这模拟器说真的就是启动快,和真机没啥太大

使用SQLiteOpenHelper管理SD卡中的数据库

本人在网上找了好多大牛的资料,研究了几天终于调试出来了.以下是笔记: SQLiteOpenHelper是Android框架为我们提供的一个非常好的数据库打开.升级与关闭的工具类.但是这个工具类会自动把db文件创建到“ /data/data/com.*.*(package name)/” 目录下,这么做可能是与Android文件系统的设计思路有关. 但是在实战过程中,我们可能有各种原因需要自定义db文件路径(例如db文件较大放到sd卡更安全等等),相信很多人都遇到了这个需求,网上也有很多解决方法,

当心!程序员在职业生涯中最易犯的7个错误

当心!程序员在职业生涯中最易犯的7个错误 概述:本文的作者是软件开发领域著名的职业规划导师,他的工作是通过对程序员当前职业状况的了解,向他们提出改进职业规划发展的建议.在与程序员们长期的接触之后,他总结了程序员们最易犯的7个错误. 1.没有明确的职业目标 没有目标的人生,就像无根的浮萍,水流到哪里就飘到哪里,一生漂泊. 如果你想要在软件开发领域获得真正的成功,那么就必须知道该何去何从.或许面对遥远的未来,你已经有了一个粗略的目标了.但是除了这点还不够,你应当坚实自己的目标--清楚的定义在实现过程

解决安卓微信浏览器中location.reload 或者 location.href失效的问题

出自:http://www.cnblogs.com/joshua317/p/6163471.html 在移动wap中,经常会使用window.location.href去跳转页面,这个方法在绝大多数浏览器中都不会 存在问题,但早上测试的同学会提出了一个bug:在安卓手机的微信自带浏览器中,这个是失效的,并没有跳转: 原来的代码: window.location.reload(location.href); 初步判断可能是缓存的问题,首先想到的解决办法就是在要跳转的url后面加个时间戳,告知浏览器