【Android】安卓Q适配指南-相册

碎碎念

本来每次安卓版本升级都是非常期待的事情,但是开发者就吃苦了!!!

尤其是从Q开始,应用采用沙盒模式,即各种公共文件的访问都会受到限制。。。

所以适配Q成了当务之急,然鹅网上关于适配的资料少之又少(可能是我太菜了)

主要出现的问题:

根据图片的绝对路径无法正常加载图片,同时使用File.delete删除也是失效

直到我看到oppo开发者平台的开发指南:Android Q版本应用兼容性适配指导,才解决了这个问题!

特此记录一下。

权限申请(都是权限惹的祸)

安卓6.0以上动态申请权限,这里就写简单一点:

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

动态申请:

 private void checkPermission() {
        int readExternalStoragePermissionResult = checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
        if(readExternalStoragePermissionResult != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},1);
        }
    }

遍历图片

这里仍然使用ContentProvider来进行图片的获取。

首先我们要确定我们需要的内容,大概是图片路径、图片显示名称、图片ID、图片创建时间。

创建相应的Bean类:

public class PhotoBean {
    private String path;
    private String name;
    private int ID;
    private long createDate;


    public int getID() {
        return ID;
    }

    public void setID(int ID) {
        this.ID = ID;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public long getCreateDate() {
        return createDate;
    }

    public void setCreateDate(long createDate) {
        this.createDate = createDate;
    }

}

遍历图片:

 private List<PhotoBean> mPics = new ArrayList<>();

private void initData(){
        mPics.clear();
        ContentResolver contentResolver = getContentResolver();
        Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        Cursor query = contentResolver.query(uri,new String[]{
                MediaStore.Images.Media.DATA,
                MediaStore.Images.Media.DISPLAY_NAME,
                MediaStore.Images.Media.DATE_ADDED,
                MediaStore.Images.Media._ID},null,null,null,null);
        while(query.moveToNext()) {
            PhotoBean photoItem = new PhotoBean();
            photoItem.setPath(query.getString(0));
            //这里的下标跟上面的query第一个参数对应,时间是第2个,所以下标为1
            photoItem.setCreateDate(query.getLong(1));
            photoItem.setName(query.getString(2));
            photoItem.setID(query.getInt(query.getColumnIndex(MediaStore.MediaColumns._ID)));
            mPics.add(photoItem);
        }
        query.close();
    }

这样我们就取到了相册所有图片的信息,主要是查到这个ID

此时我们如果直接使用path来创建Bitmap去加载或者File、第三方框架均不能正确加载图片。

下面讲一下如何使用Uri来加载图片

获取Uri并加载图片

我们可以在PhotoBean中增加这个方法

 public Uri getUri(){
        Uri baseUri = Uri.parse("content://media/external/images/media");
        return Uri.withAppendedPath(baseUri, "" + ID);
    }

如果没有获取ID,只有photpath也是可以的,但影响效率:需要根据path再去查一遍

public static Uri getImageContentUri(Context context, String path) {
        Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                new String[] { MediaStore.Images.Media._ID }, MediaStore.Images.Media.DATA + "=? ",
                new String[] { path }, null);
        if (cursor != null && cursor.moveToFirst()) {
            int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
            Uri baseUri = Uri.parse("content://media/external/images/media");
            return Uri.withAppendedPath(baseUri, "" + id);
        } else {
            if (new File(path).exists()) {
                ContentValues values = new ContentValues();
                values.put(MediaStore.Images.Media.DATA, path);
                return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            } else {
                return null;
            }
        }
    }

使用第三方控件如Glide加载

拿到Uri以后就可以直接使用

 Glide.with(context).load(photoBeanList.get(position).getUri()).into(imageView);

这样就没有问题了。

选择图片并拷贝到私有目录下进行加载

那么,既然安卓Q针对应用私有数据不受任何限制,那么我们可以提前把用户选择的图片拷贝一份到自己的私有目录下,那么直接进行读取删除操作就不会受到限制了。

选择一张图片:

Intent intent = new Intent(Intent.ACTION_PICK);
                intent.setType("image/*");
                startActivityForResult(intent, 2);

在onActivityResult中接收

if (data != null) {
  Uri originalUri = data.getData(); // 获得图片的uri
  String path= ImageHelper.getPrivatePath(SettingActivity.this,originalUri);
  if(path!=null&&!path.equals("")){
    //TODO
  }
}

getPrivatePath的操作就是将文件拷贝一份到私有目录下并返回绝对路径

/**
     * 根据Uri直接获取图片
     * @param context 上下文
     * @param uri 图片的uri
     * */
    public static String getPrivatePath(Context context,Uri uri){
        try {
            Bitmap bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri);
            File file=compressImage(context,bitmap);
            return file.getAbsolutePath();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

compress是精简的意思,不知道我怎么想的就写成了这个,自己注意一下

 /**
     * 把bitmap写入app私有目录下
     * @param context 上下文
     * @param bitmap 这个bitmap不能为null
     * @return File
     * 适配到4.4
     * */
    private static File compressImage(Context context, Bitmap bitmap) {
        String filename;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
            SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
            Date date = new Date(System.currentTimeMillis());
            //图片名
            filename = format.format(date);
        }else {
            Date date=new Date();
            filename=date.getYear()+date.getMonth()+date.getDate()+date.getHours()+date.getMinutes()+date.getSeconds()+"";
        }

        final File[] dirs = context.getExternalFilesDirs("Documents");
        File primaryDir = null;
        if (dirs != null && dirs.length > 0) {
            primaryDir = dirs[0];
        }
        File file = new File(primaryDir.getAbsolutePath(), filename + ".png");
        try {
            FileOutputStream fos = new FileOutputStream(file);
            try {
                fos.write(baos.toByteArray());
                fos.flush();
                fos.close();
            } catch (IOException e) {

                e.printStackTrace();
            }
        } catch (FileNotFoundException e) {

            e.printStackTrace();
        }

        // recycleBitmap(bitmap);
        return file;
    }

后面如果正常使用的话就是直接用path加载即可。

/**
     * 根据私有路径加载
     * @param context 上下文
     * @param path 这个路径一定是私有路径,即应用自己的目录下(data/包名)
     * @return Drawable 用来设置背景什么的
     * */
    public static Drawable getByPrivatePath(Context context,String path){
        Bitmap bitmap = BitmapFactory.decodeFile(path);
        Drawable drawable = new BitmapDrawable(context.getResources(), bitmap);
        return drawable;
    }

图片删除操作

以前我们在删除的时候,需要开发者自己添加一个确认删除的功能,现在谷歌已经帮我们完成了。

File.delete也就失效了,相对来说比较安全吧。

同样的,我们需要使用Uri来进行操作。

 @TargetApi(29)
    public void deleteUri(Uri imageUri) {
        ContentResolver resolver = getContentResolver();
        OutputStream os = null;
        try {
            if (imageUri != null) {
               resolver.delete(imageUri,null,null);
            }
        } catch (RecoverableSecurityException e1) {
            Log.d(TAG,"get RecoverableSecurityException");
            try {
                this.startIntentSenderForResult(
                        e1.getUserAction().getActionIntent().getIntentSender(),
                        100, null, 0, 0, 0);
            } catch (IntentSender.SendIntentException e2) {
                Log.d(TAG,"startIntentSender fail");
            }
        }
    }

RecoverableSecurityException在谷歌文档中是这样解释的:This exception is only appropriate where there is a concrete action the user can take to recover and make forward progress, such as confirming or entering authentication credentials, or granting access.即对于图片的修改、删除操作都需要用户的允许,即也是一种权限,故需要抛出该异常并去申请获得该权限。

如图所示:

总结

图片在加载的过程中,有一些图片能用uri查到,但是通过uri获取图片抛出文件不存在异常,故完善一下代码。

这里提供的解决思路是将选择的图片拷贝一份到私有目录,这样无论是读取还是修改图片都不会受到影响。

public class ImageHelper {
    /**
     * 通过绝对路径获取图片的私有存储路径
     * 将图片复制到私有目录下,下次加载、删除啥的就没有影响了
     * 但是注意删除的仅是app私有的数据,并不是真正删除相册的图片
     * 存在部分图片能查到uri但是无法正常加载,故需要判断一下
     * 如果返回路径为空,则跳过该图片,并提示用户手动在系统相册中将图片添加至相册再重试
     * @param context 上下文
     * @param path 图片绝对路径(直接获取到的)
     * @return String 返回一个复制到私有路径下相同的图片路径
     * */
    public static String coverFromBitmap(Context context, String path){
        Bitmap bitmap=SuperSuitWay(context,path);
        if(bitmap==null){
            return "";
        }
        return compressImage(context,bitmap).getAbsolutePath();
    }

    /**
     * 把bitmap写入app私有目录下
     * @param context 上下文
     * @param bitmap 这个bitmap不能为null
     * @return File
     * */
    private static File compressImage(Context context,Bitmap bitmap) {
        String filename;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
        Date date = new Date(System.currentTimeMillis());
        //图片名
        filename = format.format(date);
        final File[] dirs = context.getExternalFilesDirs("Documents");
        File primaryDir = null;
        if (dirs != null && dirs.length > 0) {
            primaryDir = dirs[0];
        }
        File file = new File(primaryDir.getAbsolutePath(), filename + ".png");
        try {
            FileOutputStream fos = new FileOutputStream(file);
            try {
                fos.write(baos.toByteArray());
                fos.flush();
                fos.close();
            } catch (IOException e) {

                e.printStackTrace();
            }
        } catch (FileNotFoundException e) {

            e.printStackTrace();
        }

        // recycleBitmap(bitmap);
        return file;
    }
    /**
     * 通过绝对路径获取bitmap
     * 适配安卓Q使用绝对路径无法正确加载的问题
     * @param context 上下文
     * @param path 绝对路径
     * @return Bitmap
     * */
    private static Bitmap SuperSuitWay(Context context,String path){
        Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
        Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                new String[] { MediaStore.Images.Media._ID }, MediaStore.Images.Media.DATA + "=? ",
                new String[] { path }, null);
        Uri imageUri = null;
        if (cursor != null && cursor.moveToFirst()) {
            imageUri = ContentUris.withAppendedId(external, cursor.getLong(0));
            cursor.close();
        }
        ParcelFileDescriptor pfd = null;
        if (imageUri != null) {
            try {
                pfd = context.getContentResolver().openFileDescriptor(imageUri, "r");
                if (pfd != null) {
                    Bitmap bitmap = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor());
                    return bitmap;
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (pfd != null) {
                        pfd.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

}

---------------------------------------------------------------------------------------------------------------------

啊,自己好菜_(¦3」∠)_

觉得文章好的欢迎点赞哦~

这里推荐一篇文章,也是最近我找到的,拜读一下:点我跳转

原文地址:https://www.cnblogs.com/robotpaul/p/12347231.html