Android Q(10) 文件存儲適配

  1. Android Q文件存儲機制修改成了沙盒模式,和IOS神似
  2. 應用只能訪問自己沙盒下的文件和公共媒體文件
  3. 對於Android Q以下,還是使用老的文件存儲方式

權限

Android Q不再需要申請文件讀寫權限,默認可以讀寫自己沙盒文件和公共媒體文件。所以,Q以上不需要再動態申請文件讀寫權限。

沙盒存儲/讀寫

獲取沙盒指定文件夾

//廢棄方法
//不再用以下代碼獲取文件根目錄了
Environment.getExternalStorageDirectory();
Environment.getExternalStoragePublicDirectory();

//獲取沙盒下的文件目錄
//沙盒下的圖片文件夾
File filePictures = getExternalFilesDir(Environment.DIRECTORY_PICTURES);

//Environment下的文件夾名稱常量
//只需要調用,不需要創建,如果手機中沒有對應的文件夾,則系統會自動生成
//以下爲源碼中的各個文件夾名稱描述
 /**
     * Standard directory in which to place any audio files that should be
     * in the regular list of music for the user.
     * This may be combined with
     * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS},
     * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series
     * of directories to categories a particular audio file as more than one
     * type.
     */
    public static String DIRECTORY_MUSIC = "Music";

    /**
     * Standard directory in which to place any audio files that should be
     * in the list of podcasts that the user can select (not as regular
     * music).
     * This may be combined with {@link #DIRECTORY_MUSIC},
     * {@link #DIRECTORY_NOTIFICATIONS},
     * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series
     * of directories to categories a particular audio file as more than one
     * type.
     */
    public static String DIRECTORY_PODCASTS = "Podcasts";

    /**
     * Standard directory in which to place any audio files that should be
     * in the list of ringtones that the user can select (not as regular
     * music).
     * This may be combined with {@link #DIRECTORY_MUSIC},
     * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS}, and
     * {@link #DIRECTORY_ALARMS} as a series
     * of directories to categories a particular audio file as more than one
     * type.
     */
    public static String DIRECTORY_RINGTONES = "Ringtones";

    /**
     * Standard directory in which to place any audio files that should be
     * in the list of alarms that the user can select (not as regular
     * music).
     * This may be combined with {@link #DIRECTORY_MUSIC},
     * {@link #DIRECTORY_PODCASTS}, {@link #DIRECTORY_NOTIFICATIONS},
     * and {@link #DIRECTORY_RINGTONES} as a series
     * of directories to categories a particular audio file as more than one
     * type.
     */
    public static String DIRECTORY_ALARMS = "Alarms";

    /**
     * Standard directory in which to place any audio files that should be
     * in the list of notifications that the user can select (not as regular
     * music).
     * This may be combined with {@link #DIRECTORY_MUSIC},
     * {@link #DIRECTORY_PODCASTS},
     * {@link #DIRECTORY_ALARMS}, and {@link #DIRECTORY_RINGTONES} as a series
     * of directories to categories a particular audio file as more than one
     * type.
     */
    public static String DIRECTORY_NOTIFICATIONS = "Notifications";

    /**
     * Standard directory in which to place pictures that are available to
     * the user.  Note that this is primarily a convention for the top-level
     * public directory, as the media scanner will find and collect pictures
     * in any directory.
     */
    public static String DIRECTORY_PICTURES = "Pictures";

    /**
     * Standard directory in which to place movies that are available to
     * the user.  Note that this is primarily a convention for the top-level
     * public directory, as the media scanner will find and collect movies
     * in any directory.
     */
    public static String DIRECTORY_MOVIES = "Movies";

    /**
     * Standard directory in which to place files that have been downloaded by
     * the user.  Note that this is primarily a convention for the top-level
     * public directory, you are free to download files anywhere in your own
     * private directories.  Also note that though the constant here is
     * named DIRECTORY_DOWNLOADS (plural), the actual file name is non-plural for
     * backwards compatibility reasons.
     */
    public static String DIRECTORY_DOWNLOADS = "Download";

    /**
     * The traditional location for pictures and videos when mounting the
     * device as a camera.  Note that this is primarily a convention for the
     * top-level public directory, as this convention makes no sense elsewhere.
     */
    public static String DIRECTORY_DCIM = "DCIM";

    /**
     * Standard directory in which to place documents that have been created by
     * the user.
     */
    public static String DIRECTORY_DOCUMENTS = "Documents";

    /**
     * Standard directory in which to place screenshots that have been taken by
     * the user. Typically used as a secondary directory under
     * {@link #DIRECTORY_PICTURES}.
     */
    public static String DIRECTORY_SCREENSHOTS = "Screenshots";

    /**
     * Standard directory in which to place any audio files which are
     * audiobooks.
     */
    public static String DIRECTORY_AUDIOBOOKS = "Audiobooks";

沙盒裏新建文件夾和新建文件

private String signImage = "signImage";
//將文件保存到沙盒中
//注意:
//1. 這裏的文件操作不再需要申請權限
//2. 沙盒中新建文件夾只能再系統指定的子文件夾中新建
public void saveSignImageBox(String fileName, Bitmap bitmap) {
    try {
        //圖片沙盒文件夾
        File PICTURES = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        File imageFileDirctory = new File(PICTURES + "/" + signImage);
        if (imageFileDirctory.exists()) {
            File imageFile = new File(PICTURES + "/" + signImage + "/" + fileName);
            FileOutputStream fileOutputStream = new FileOutputStream(imageFile);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileOutputStream);
            fileOutputStream.flush();
            fileOutputStream.close();
        } else if (imageFileDirctory.mkdir()) {//如果該文件夾不存在,則新建
            //new一個文件
            File imageFile = new File(PICTURES + "/" + signImage + "/" + fileName);
            //通過流將圖片寫入
            FileOutputStream fileOutputStream = new FileOutputStream(imageFile);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileOutputStream);
            fileOutputStream.flush();
            fileOutputStream.close();
        }
    } catch (Exception e) {
    }
}

沙盒中查詢指定文件

//查詢沙盒中的指定圖片
//先指定哪個沙盒子文件夾,再指定名稱
public Bitmap querySignImageBox(String environmentType,String fileName) {
    if (TextUtils.isEmpty(fileName)) return null;
    Bitmap bitmap = null;
    try {
        //指定沙盒文件夾
        File picturesFile = getExternalFilesDir(environmentType);
        if (picturesFile != null && picturesFile.exists() && picturesFile.isDirectory()) {
            File[] files = picturesFile.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.isDirectory() && file.getName().equals(signImage)) {
                        File[] signImageFiles = file.listFiles();
                        if (signImageFiles != null) {
                            for (File signImageFile : files) {
                                String signFileName = signImageFile.getName();
                                if (signImageFile.isFile() && fileName.equals(signFileName)) {
                                    bitmap = BitmapFactory.decodeFile(signImageFile.getPath());
                                }
                            }
                        }
                    }
                }
            }
        }
    } catch (Exception e) {
    }
    return bitmap;
}

公共文件的增、刪、改、查

公共文件的操作需要用到ContentResolver和Cursor

向公共文件夾添加文件

//將文件保存到公共的媒體文件夾
//這裏的filepath不是絕對路徑,而是某個媒體文件夾下的子路徑,和沙盒子文件夾類似
//這裏的filename單純的指文件名,不包含路徑
public void saveSignImage(String filePath,String fileName, Bitmap bitmap) {
    try {
        //設置保存參數到ContentValues中
        ContentValues contentValues = new ContentValues();
        //設置文件名
        contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
        //兼容Android Q和以下版本
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            //android Q中不再使用DATA字段,而用RELATIVE_PATH代替
            //RELATIVE_PATH是相對路徑不是絕對路徑
            //DCIM是系統文件夾,關於系統文件夾可以到系統自帶的文件管理器中查看,不可以寫沒存在的名字
            contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, "DCIM/signImage");
            //contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, "Music/signImage");
        } else {
            contentValues.put(MediaStore.Images.Media.DATA, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getPath());
        }
        //設置文件類型
        contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/JPEG");
        //執行insert操作,向系統文件夾中添加文件
        //EXTERNAL_CONTENT_URI代表外部存儲器,該值不變
        Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
        if (uri != null) {
            //若生成了uri,則表示該文件添加成功
            //使用流將內容寫入該uri中即可
            OutputStream outputStream = getContentResolver().openOutputStream(uri);
            if (outputStream != null) {
                bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
                outputStream.flush();
                outputStream.close();
            }
        }
    } catch (Exception e) {
    }
}

查詢公共文件夾下的文件 

//在公共文件夾下查詢圖片
//這裏的filepath在androidQ中表示相對路徑
//在androidQ以下是絕對路徑
public Bitmap querySignImage(String filePath) {
    try {
        //兼容androidQ和以下版本
        String queryPathKey = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q ? MediaStore.Images.Media.RELATIVE_PATH : MediaStore.Images.Media.DATA;
        //查詢的條件語句
        String selection = queryPathKey + "=? ";
        //查詢的sql
        //Uri:指向外部存儲Uri
        //projection:查詢那些結果
        //selection:查詢的where條件
        //sortOrder:排序
        Cursor cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                new String[]{MediaStore.Images.Media._ID, queryPathKey, MediaStore.Images.Media.MIME_TYPE, MediaStore.Images.Media.DISPLAY_NAME},
                selection,
                new String[]{filePath},
                null);

        //是否查詢到了
        if (cursor != null && cursor.moveToFirst()) {
            //循環取出所有查詢到的數據
            do {
                //一張圖片的基本信息
                int id = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media._ID));//uri的id,用於獲取圖片
                String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.RELATIVE_PATH));//圖片的相對路徑
                String type = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE));//圖片類型
                String name = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));//圖片名字
                //根據圖片id獲取uri,這裏的操作是拼接uri
                Uri uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "" + id);
                //官方代碼:
                 Uri contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
                if (uri != null) {
                    //通過流轉化成bitmap對象
                    InputStream inputStream = getContentResolver().openInputStream(uri);
                    return BitmapFactory.decodeStream(inputStream);
                }
            } while (cursor.moveToNext());
        }
        if (cursor != null)
            cursor.close();
    } catch (Exception e) {
    }
    return null;
}

 

其他操作
增刪改查操作都是使用:ContentResolver去操作

官方文檔中也說明了,想要操作公共目錄,使用ContentResolver去進行一切增刪改查:

在 Android 9(API 級別 28)及更低版本中,保存到外部存儲設備上的所有文件都顯示在名爲 external 的單個卷下。但是,Android Q 爲每個外部存儲設備都提供唯一的卷名稱。這一新的命名系統可幫助您高效地整理內容並將內容編入索引,還可讓您控制新內容的存儲位置。

主要共享存儲設備始終稱爲 VOLUME_EXTERNAL_PRIMARY。您可以通過調用 MediaStore.getExternalVolumeNames() 發現其他卷。

要查詢、插入、更新或刪除特定卷,請將卷名稱傳遞到 MediaStore API 中的任何 getContentUri() 方法,如以下代碼段中所示:

// Publish an audio file onto a specific external storage device.
val values = ContentValues().apply {
put(MediaStore.Audio.Media.RELATIVE_PATH, “Music/My Album/My Song”)
put(MediaStore.Audio.Media.DISPLAY_NAME, “My Song.mp3”)
}
// Assumes that the storage device of interest is the 2nd one
// that your app recognizes.
val volumeNames = MediaStore.getExternalVolumeNames(context)
val selectedVolumeName = volumeNames[1]
val collection = MediaStore.Audio.Media.getContentUri(selectedVolumeName)
val item = resolver.insert(collection, values)
 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章