【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」∠)_
觉得文章好的欢迎点赞哦~
这里推荐一篇文章,也是最近我找到的,拜读一下:点我跳转
【Android】安卓Q适配指南-相册的更多相关文章
- 安卓Q | 诸多本地文件找不到?应用文件存储空间沙箱化适配指导
上期我们针对Android Q 版本中对设备存储空间进行的限制.新特性变更引发的兼容性问题及原因分析推出了<安卓 Q | 8大场景全面解析应用存储沙箱化>文章,本期文章我们将手把手指导各位 ...
- Android 7.0+相机、相册、裁剪适配问题
Android 7.0+相机.相册.裁剪适配问题 在manifest中: <provider android:name="android.support.v4.content.File ...
- Android界面设计适配不同屏幕的尺寸和密度解读
Android是运行在各种提供不同的屏幕尺寸和密度的设备.Android系统提供跨设备的统一开发环境和处理大部分的工作,以调整每个应用程序的用户界面,以在其上显示的画面. 同时,该系统提供了API,允 ...
- Unity Android交互过坑指南
Unity Android交互过坑指南 介于网上看过很多unity和Android交互的教程,都或多或少的漏掉了一些部分,导致编译过程中出现各种问题,特此整理一份教程,仅供参考 介绍 本次实现的是在游 ...
- Android刘海屏适配 ----- ImmersionBar
android 4.4以上沉浸式状态栏和沉浸式导航栏管理,适配横竖屏切换.刘海屏.软键盘弹出等问题,可以修改状态栏字体颜色和导航栏图标颜色,以及不可修改字体颜色手机的适配,适用于Activity.Fr ...
- Unity与Android刘海屏适配
本周学习Unity与Android刘海屏适配 关于刘海屏适配部分 网上有很多教程 这里只是做一下整理 https://blog.csdn.net/xj1009420846/article/detail ...
- Android实习生 —— 屏幕适配及布局优化
为什么要进行屏幕适配.对哪些设备进行适配?在近几年的发展当中,安卓设备数量逐渐增长,由于安卓设备的开放性,导致安卓设备的屏幕尺寸大小碎片化极为严重.从[友盟+]2016年手机生态发展报告H1中看截止1 ...
- Android12 新特性及适配指南
Android 12(API 31)于2021年10月4日正式发布,正式版源代码也于当日被推送到AOSP Android开源项目.截止到笔者撰写这篇文章时,国内各终端厂商的在售Android设备,已经 ...
- Android权限管理之RxPermission解决Android 6.0 适配问题
前言: 上篇重点学习了Android 6.0的运行时权限,今天还是围绕着Android 6.0权限适配来总结学习,这里主要介绍一下我们公司解决Android 6.0权限适配的方案:RxJava+RxP ...
随机推荐
- c语言秋季作业3
本周作业头 这个作业属于那个课程 C语言程序设计II 这个作业要求在哪里 作业链接 我在这个课程的目标是 运用C语言编程解决一些简单的数学问题 这个作业在那个具体方面帮助我实现目标 学习if else ...
- Docker三剑客之compose
简介 Compose 项目是 Docker 官方的开源项目,负责实现对 Docker 容器集群的快速编排.从功能上看,跟 OpenStack 中的 Heat 十分类似.其代码目前在 https://g ...
- springIOC源码接口分析(七):ApplicationEventPublisher
一 定义方法 此接口主要是封装事件发布功能的接口,定义了两个方法: /** * 通知应用所有已注册且匹配的监听器此ApplicationEvent */ default void publishEve ...
- python中map()和dict()的用法
map()用法 map()是python的内置函数,会根据提供的函数对指定序列做映射. 语法: map(func, iter, ...) 其中func为一个功能函数,iter表示可迭代参数序列.map ...
- 死磕java(1)
java入门 package com.sougn.new1; public class new1 { /** * @param args */ public static void main ...
- 【动手学pytorch】softmax回归
一.什么是softmax? 有一个数组S,其元素为Si ,那么vi 的softmax值,就是该元素的指数与所有元素指数和的比值.具体公式表示为: softmax回归本质上也是一种对数据的估计 二.交叉 ...
- Windows API 教程(九) 网络编程
茵蒂克丝 基础概念 ip 地址 服务端与客户端 Socket 基础概念 头文件和库文件 常用函数 WSAStartup ( ) 函数 WSACleanup ( ) 函数 Socket ( ) 函数 c ...
- cmd 重定向
关于cmd 命令的重定向输出 2>&1 mycommand >mylog.txt 2>&1 应该是最经典的用法了. 命令的结果可以通过" %> &qu ...
- 如何播放 WAV 文件?
from http://www.vckbase.com/index.php/wv/434 平时,你在多媒体软件的设计中是怎样处理声音文件的呢?使用Windows 提供的API函数 sndPlaySou ...
- 利用视频解析网站免费观看各大平台VIP电影
需求: 观看VIP电影.VIP电视 准备: 1.视频解析网站 2.VIP电影URL 教程开始: 1.百度搜索[视频解析],会索引出大量的视频解析网站,随便选择一个网站. 2.找到想观看的VIP视频,复 ...