【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」∠)_
觉得文章好的欢迎点赞哦~
这里推荐一篇文章,也是最近我找到的,拜读一下:点我跳转