1. 程式人生 > >android4.4上sd卡的讀寫許可權

android4.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, nullnull);  
  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. }  
[HOWTO] Write to media card (secondary storage) from an app under Android 4.4 KitKat In Android 4.4 KitKat, Google/AOSP made the following change to the API specification, much to the detriment of app developers and users:

Quote:
"The WRITE_EXTERNAL_STORAGE permission must only grant write access to the primary external storage on a device. Apps must not be allowed to write to secondary external storage devices, except in their package-specific directories as allowed by synthesized permissions."
Source: http://source.android.com/devices/te...age/index.html

You can read my rather unhappy write-up about it here:https://plus.google.com/108338299717...ts/gjnmuaDM8sn

This only applies to dual-storage devices, i.e., devices with a user-writable internal flash storage AND a removable SD card. But if your device has both, as of Android 4.4, apps will no longer be able to write arbitrarily to the "secondary" storage (the SD card). 

There is however still an API exposed that will allow you to write to secondary storage, notably the media content provider. This is far from an ideal solution, and I imagine that someday it will not be possible.

I've written a tiny bit of code to let your applications continue to work with files on the SD card using the media content provider. This code should only be used to write to the secondary storage on Android 4.4+ devices if all else fails. I would strongly recommend that you NEVER rely on this code. This code DOES NOT use root access.

The class:
Code:
/*
 * Copyright (C) 2014 NextApp, Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

package nextapp.mediafile;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
import android.provider.MediaStore;

/**
 * Wrapper for manipulating files via the Android Media Content Provider. As of Android 4.4 KitKat, applications can no longer write
 * to the "secondary storage" of a device. Write operations using the java.io.File API will thus fail. This class restores access to
 * those write operations by way of the Media Content Provider.
 * 
 * Note that this class relies on the internal operational characteristics of the media content provider API, and as such is not
 * 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
 * access, so all bets are off.
 * 
 * If you're forced to use this class, it's because Google/AOSP made a very poor API decision in Android 4.4 KitKat.
 * Read more at https://plus.google.com/+TodLiebeck/posts/gjnmuaDM8sn
 *
 * Your application must declare the permission "android.permission.WRITE_EXTERNAL_STORAGE".
 */
public class MediaFile {

    private final File file;
    private final ContentResolver contentResolver;
    private final Uri filesUri;
    private final Uri imagesUri;

    public MediaFile(ContentResolver contentResolver, File file) {
        this.file = file;
        this.contentResolver = contentResolver;
        filesUri = MediaStore.Files.getContentUri("external");
        imagesUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
    }

    /**
     * Deletes the file. Returns true if the file has been successfully deleted or otherwise does not exist. This operation is not
     * recursive.
     */
    public boolean delete()
            throws IOException {
        if (!file.exists()) {
            return true;
        }

        boolean directory = file.isDirectory();
        if (directory) {
            // Verify directory does not contain any files/directories within it.
            String[] files = file.list();
            if (files != null && files.length > 0) {
                return false;
            }
        }

        String where = MediaStore.MediaColumns.DATA + "=?";
        String[] selectionArgs = new String[] { file.getAbsolutePath() };

        // Delete the entry from the media database. This will actually delete media files (images, audio, and video).
        contentResolver.delete(filesUri, where, selectionArgs);

        if (file.exists()) {
            // If the file is not a media file, create a new entry suggesting that this location is an image, even
            // though it is not.
            ContentValues values = new ContentValues();
            values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
            contentResolver.insert(imagesUri, values);

            // Delete the created entry, such that content provider will delete the file.
            contentResolver.delete(filesUri, where, selectionArgs);
        }

        return !file.exists();
    }

    public File getFile() {
        return file;
    }

    /**
     * Creates a new directory. Returns true if the directory was successfully created or exists.
     */
    public boolean mkdir()
            throws IOException {
        if (file.exists()) {
            return file.isDirectory();
        }

        ContentValues values;
        Uri uri;

        // Create a media database entry for the directory. This step will not actually cause the directory to be created.
        values = new ContentValues();
        values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
        contentResolver.insert(filesUri, values);

        // Create an entry for a temporary image file within the created directory.
        // This step actually causes the creation of the directory.
        values = new ContentValues();
        values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath() + "/temp.jpg");
        uri = contentResolver.insert(imagesUri, values);

        // Delete the temporary entry.
        contentResolver.delete(uri, null, null);

        return file.exists();
    }

    /**
     * Returns an OutputStream to write to the file. The file will be truncated immediately.
     */
    public OutputStream write()
            throws IOException {
        if (file.exists() && file.isDirectory()) {
            throw new IOException("File exists and is a directory.");
        }

        // Delete any existing entry from the media database.
        // This may also delete the file (for media types), but that is irrelevant as it will be truncated momentarily in any case.
        String where = MediaStore.MediaColumns.DATA + "=?";
        String[] selectionArgs = new String[] { file.getAbsolutePath() };
        contentResolver.delete(filesUri, where, selectionArgs);

        ContentValues values = new ContentValues();
        values.put(MediaStore.Files.FileColumns.DATA, file.getAbsolutePath());
        Uri uri = contentResolver.insert(filesUri, values);

        if (uri == null) {
            // Should not occur.
            throw new IOException("Internal error.");
        }

        return contentResolver.openOutputStream(uri);
    }
}
Source download (or cut/paste the above): http://android.nextapp.com/content/m...MediaFile.java
Eclipse project with test app: http://android.nextapp.com/content/m...MediaWrite.zip
APK of test app: http://android.nextapp.com/content/m...MediaWrite.apk

The test project is currently configured to target the path /storage/extSdCard/MediaWriteTest (this is correct for a Note3, at least on 4.3...make sure you don't have anything there). Edit MainActivity.java in the Eclipse project to change it.

And again, let me stress that the above code might not work in the future should Google dislike it. I wouldn't recommend that the average app developer make use of this code, but if you're writing a file manager (or something else that competes with any of my other apps) , it might be useful to you. And actually at the time of writing, this functionality is NOT in FX File Explorer or WebSharing.

1.對多個sd卡支援

從4.4開始android已經支援多了sd卡(之前由廠商自己實現)

可通過以下方法獲取

Context.getExternalFilesDirs(), 返回多個sd卡的該應用私有資料區的files目錄 

/storage/sdcard0/Android/data/<包名>/files

/storage/s