• 【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」∠)_

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

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

  • 相关阅读:
    前世今生:Hive、Shark、spark SQL
    spark streaming 6: BlockGenerator、RateLimiter
    spark streaming 5: InputDStream
    spark streaming 4: DStreamGraph JobScheduler
    常见css水平自适应布局
    js动态加载以及确定加载完成的代码
    如何判断css是否加载完成
    翻书特效
    jquery 事件冒泡的介绍以及如何阻止事件冒泡
    phonegap之android原生日历调用
  • 原文地址:https://www.cnblogs.com/robotpaul/p/12347231.html
Copyright © 2020-2023  润新知