安卓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.   
  14. package nextapp.mediafile;  
  15.   
  16. import java.io.File;  
  17. import java.io.IOException;  
  18. import java.io.OutputStream;  
  19.   
  20. import android.content.ContentResolver;  
  21. import android.content.ContentValues;  
  22. import android.net.Uri;  
  23. import android.provider.MediaStore;  
  24.   
  25. /** 
  26.  * Wrapper for manipulating files via the Android Media Content Provider. As of Android 4.4 KitKat, applications can no longer write 
  27.  * to the "secondary storage" of a device. Write operations using the java.io.File API will thus fail. This class restores access to 
  28.  * those write operations by way of the Media Content Provider. 
  29.  *  
  30.  * Note that this class relies on the internal operational characteristics of the media content provider API, and as such is not 
  31.  * 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 
  32.  * access, so all bets are off. 
  33.  *  
  34.  * If you're forced to use this class, it's because Google/AOSP made a very poor API decision in Android 4.4 KitKat. 
  35.  * Read more at https://plus.google.com/+TodLiebeck/posts/gjnmuaDM8sn 
  36.  * 
  37.  * Your application must declare the permission "android.permission.WRITE_EXTERNAL_STORAGE". 
  38.  */  
  39. public class MediaFile {  
  40.   
  41.     private final File file;  
  42.     private final ContentResolver contentResolver;  
  43.     private final Uri filesUri;  
  44.     private final Uri imagesUri;  
  45.   
  46.     public MediaFile(ContentResolver contentResolver, File file) {  
  47.         this.file = file;  
  48.         this.contentResolver = contentResolver;  
  49.         filesUri = MediaStore.Files.getContentUri("external");  
  50.         imagesUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;  
  51.     }  
  52.   
  53.     /** 
  54.      * Deletes the file. Returns true if the file has been successfully deleted or otherwise does not exist. This operation is not 
  55.      * recursive. 
  56.      */  
  57.     public boolean delete()  
  58.             throws IOException {  
  59.         if (!file.exists()) {  
  60.             return true;  
  61.         }  
  62.   
  63.         boolean directory = file.isDirectory();  
  64.         if (directory) {  
  65.             // Verify directory does not contain any files/directories within it.  
  66.             String[] files = file.list();  
  67.             if (files != null && files.length > 0) {  
  68.                 return false;  
  69.             }  
  70.         }  
  71.   
  72.         String where = MediaStore.MediaColumns.DATA + "=?";  
  73.         String[] selectionArgs = new String[] { file.getAbsolutePath() };  
  74.   
  75.         // Delete the entry from the media database. This will actually delete media files (images, audio, and video).  
  76.         contentResolver.delete(filesUri, where, selectionArgs);  
  77.   
  78.         if (file.exists()) {  
  79.             // If the file is not a media file, create a new entry suggesting that this location is an image, even  
  80.             // though it is not.  
  81.             ContentValues values = new ContentValues();  
  82.             values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());  
  83.             contentResolver.insert(imagesUri, values);  
  84.   
  85.             // Delete the created entry, such that content provider will delete the file.  
  86.             contentResolver.delete(filesUri, where, selectionArgs);  
  87.         }  
  88.   
  89.         return !file.exists();  
  90.     }  
  91.   
  92.     public File getFile() {  
  93.         return file;  
  94.     }  
  95.   
  96.     /** 
  97.      * Creates a new directory. Returns true if the directory was successfully created or exists. 
  98.      */  
  99.     public boolean mkdir()  
  100.             throws IOException {  
  101.         if (file.exists()) {  
  102.             return file.isDirectory();  
  103.         }  
  104.   
  105.         ContentValues values;  
  106.         Uri uri;  
  107.   
  108.         // Create a media database entry for the directory. This step will not actually cause the directory to be created.  
  109.         values = new ContentValues();  
  110.         values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());  
  111.         contentResolver.insert(filesUri, values);  
  112.   
  113.         // Create an entry for a temporary image file within the created directory.  
  114.         // This step actually causes the creation of the directory.  
  115.         values = new ContentValues();  
  116.         values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath() + "/temp.jpg");  
  117.         uri = contentResolver.insert(imagesUri, values);  
  118.   
  119.         // Delete the temporary entry.  
  120.         contentResolver.delete(uri, nullnull);  
  121.   
  122.         return file.exists();  
  123.     }  
  124.   
  125.     /** 
  126.      * Returns an OutputStream to write to the file. The file will be truncated immediately. 
  127.      */  
  128.     public OutputStream write()  
  129.             throws IOException {  
  130.         if (file.exists() && file.isDirectory()) {  
  131.             throw new IOException("File exists and is a directory.");  
  132.         }  
  133.   
  134.         // Delete any existing entry from the media database.  
  135.         // This may also delete the file (for media types), but that is irrelevant as it will be truncated momentarily in any case.  
  136.         String where = MediaStore.MediaColumns.DATA + "=?";  
  137.         String[] selectionArgs = new String[] { file.getAbsolutePath() };  
  138.         contentResolver.delete(filesUri, where, selectionArgs);  
  139.   
  140.         ContentValues values = new ContentValues();  
  141.         values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());  
  142.         Uri uri = contentResolver.insert(filesUri, values);  
  143.   
  144.         if (uri == null) {  
  145.             // Should not occur.  
  146.             throw new IOException("Internal error.");  
  147.         }  
  148.   
  149.         return contentResolver.openOutputStream(uri);  
  150.     }  
  151. }  

你可能感兴趣的:(Android开发,应用,SD卡)