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+设备上,如果其他方式写入失败,则将数据写入二级存储设备。 





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
15.   
16. import
17. import
18. import
19.   
20. import
21. import
22. import
23. import
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
40.   
41. private final
42. private final
43. private final
44. private final
45.   
46. public
47. this.file = file;  
48. this.contentResolver = contentResolver;  
49. "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
58. throws
59. if
60. return true;  
61.         }  
62.   
63. boolean
64. if
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. "=?";  
73. new
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
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. new
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
90.     }  
91.   
92. public
93. return
94.     }  
95.   
96. /**
97.      * Creates a new directory. Returns true if the directory was successfully created or exists.
98.      */
99. public boolean
100. throws
101. if
102. return
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. new
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. new
116. "/temp.jpg");  
117.         uri = contentResolver.insert(imagesUri, values);  
118.   
119. // Delete the temporary entry.
120. null, null);  
121.   
122. return
123.     }  
124.   
125. /**
126.      * Returns an OutputStream to write to the file. The file will be truncated immediately.
127.      */
128. public
129. throws
130. if
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. "=?";  
137. new
138.         contentResolver.delete(filesUri, where, selectionArgs);  
139.   
140. new
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
150.     }  
151. }