黑马程序员技术交流社区

标题: Android 4.4系统中SD存储卡将成“废品”? [打印本页]

作者: 俞帅明    时间: 2014-2-21 17:05
标题: Android 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
  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. }
复制代码

原文链接:http://www.iteye.com/news/28768-Android-KitKat




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2