Android Q(10) 文件存储适配

Posted 夜辉疾风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Q(10) 文件存储适配相关的知识,希望对你有一定的参考价值。

Android Q官方文档

Android Q 分区存储

  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)

以上是关于Android Q(10) 文件存储适配的主要内容,如果未能解决你的问题,请参考以下文章

Android 10适配总结

Android 10适配总结

总结系列-Android10适配-分区存储

总结系列-Android10适配-分区存储

Android 启动图适配

Android 国际化适配总结