背景
最近在项目中着手做Android10
和Android11
适配时候,期间遇到了不少的坑。之前有专门写过qq、微信分享的适配。但是此次在针对偏业务侧适配工作的时候还是碰到了一些新的问题。记录下来,方便以后查阅,希望能帮到碰到此问题的相关同学。
如果对存储分区概念不是很熟悉的话,可以参考之前写的文章:
Android Q (10) 分区存储 微信、qq分享 适配
一、 私有目录下资源访问
存在这样一个场景:我们要分享一张图片到qq或者微信,首先第一步是要是得到这个bitmap(通过本地生成或者网络加载),然后存储到本地sd卡上,最后把存储的图片的绝对路径传给qq或者微信即可。
在以上的场景中,涉及到了这些关键点:
- 把图片存储到sd卡
- 把绝对路径path传递给qq或者微信
1.1 直接访问sd卡的根目录
通过FileOutPutStream来完成,在Android10以下都没问题。路径如下:
/storage/emulated/0/demo/sharePicture/1637048769163_share.jpg
但是在Android10及以上
,就会存在会报错:
java.io.FileNotFoundException: /storage/emulated/0/demo/sharePicture/1637048769163_share.jpg: open failed: EACCES (Permission denied)
//其实存储权限是同意了的
这是因为,我们被存储分区限制了,不能直接访问外部目录。因此,我们需要修改存储路径为scope的App-specific目录。
1.2 改为App-specific私有目录
该目录自己访问不需要权限,如果第三方访问需要权限! 因此,我们后面通过FileProvider
去临时授权即可。 如果对 FileProvider
不熟悉,可参考篇头的文章。
/storage/emulated/0/Android/data/com.demo.test/files
当你再通过FileOutPutStream
来存储图片时候,是成功的。
private fun saveImage(bitmap: Bitmap, storePath: String, filePath: String): Boolean {
val appDir = File(storePath)
if (!appDir.exists()) {
appDir.mkdirs()
}
val file = File(filePath)
if (file.exists()) {
file.delete()
}
var fos: FileOutputStream? = null
try {
fos = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
fos.flush()
return true
} catch (e: IOException) {
e.printStackTrace()
} catch (e: FileNotFoundException) {
e.printStackTrace()
} finally {
fos?.close()
}
return false
}
经过测试,在29的下和29 的设备下,分享qq、微信都成功了。
1.3 分享原理总结
分享的本质就是把图片路径
给qq或微信
访问,让他们能够访问到我们的图片。分区之前是存储在外部sd卡,都没有问题。
分区后,qq或微信
没法访问的我们的私有目录App-specific
。因此,我们需要通过 fileprovider
转换成 content:// 格式
去分享,临时授权给 qq或微信
来访问我们的图片。
qq是内部自己做了 fileprovider
适配,因此,我们只需要传入绝对路径 file://
格式即可,而微信是需要接收 content://
格式,所以需要我们外部自己来转换。
具体的适配逻辑参考篇头的文章~
二、公共目录下资源访问
Google建议我们采用 mediaStore
或者 SAF
去访问。在Android10
上公共目录下的图片无法通过file:// 格式
去访问,提示找不到路径。如glide加载、图片选择库、裁剪框架等等都会收到影响。
但是,这里有个坑: 在Android10上不行,在Android11上又可以!!为什么?
因为Google改回来了,让Android11支持file://
格式了。。。。 (wtf? 我谢谢你啊~~)
**我这里说的 Android10
和 android 11
是指 targetSdkVersion
哦 **
2.1 往公共目录插入一张图片
只能通过mediaStore方式:
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DESCRIPTION, "This is an image");
values.put(MediaStore.Images.Media.DISPLAY_NAME, "Image.png");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
values.put(MediaStore.Images.Media.TITLE, "Image.png");
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/test");
Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver resolver = context.getContentResolver();
//这里就能拿到这个insertUri
Uri insertUri = resolver.insert(external, values);
LogUtil.log("insertUri: " + insertUri);
OutputStream os = null;
try {
if (insertUri != null) {
os = resolver.openOutputStream(insertUri);
}
if (os != null) {
final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
bitmap.compress(Bitmap.CompressFormat.PNG, 90, os);
// write what you want
}
} catch (IOException e) {
LogUtil.log("fail: " + e.getCause());
} finally {
try {
if (os != null) {
os.close();
}
} catch (IOException e) {
LogUtil.log("fail in close: " + e.getCause());
}
}
2.2 content uri转file格式路径
public static String getFilePathFromContentUri(Uri selectedVideoUri,
ContentResolver contentResolver) {
String filePath;
String[] filePathColumn = {MediaStore.MediaColumns.DATA};
Cursor cursor = contentResolver.query(selectedVideoUri, filePathColumn, null, null, null);
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
filePath = cursor.getString(columnIndex);
cursor.close();
return filePath;
}
2.3 根据图片名来获取file格式路径
String imageName="test";
Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver resolver = BaseApp.getContext().getContentResolver();
String selection = MediaStore.Images.Media.TITLE + "=?";
String[] args = new String[] {imageName};
String[] projection = new String[] {MediaStore.Images.Media._ID};
Cursor cursor = resolver.query(external, projection, selection, args, null);
// 这里的得到content 格式的uri
Uri imageUri = null;
//content://media/external/images/media/318952
if (cursor != null && cursor.moveToFirst()) {
imageUri = ContentUris.withAppendedId(external, cursor.getLong(0));
cursor.close();
}
拿到绝对路径后,在Android11上都 glide、qq分享、第三方的图片选择框架等都可以正常访问。
三、终极适配方案
- 在Android10上
开启标志位 :android:requestLegacyExternalStorage="true"
来开启兼容模式,关闭分区适配,相当于targetSdkVersion=29
的时候还是以旧的方式运行,完全没问题。完美避开无法访问公共目录的坑!!!
- 在Android11上
以上标志会自动失效。因此,应用存储的东西还在放在App-specific目录下。分享私有目录可以通过fileprovider
方式适配。 要分享公共目录,因为支持File api
直接访问公共目录,因此,可以直接把content格式
转成file格式
即可,具体可回看文中的第二部分。
最后,我还想问两个问题:
1. targetSdk=30,android:requestLegacyExternalStorage=”false”运行在Android10的设备上 会咋么样?
答: 肯定会碰到权限问题。因为,Android10
的设备还是以Android10
的兼容模式运行的。所以要改成true
。
2. targetSdk=30,android:requestLegacyExternalStorage=”false”运行在Android11的设备上 会咋么样?
答: 如果按照上面正常适配,肯定完全没得问题!
以上是自己适配经验,难免有疏忽之处,如果文章有问题或者更好的建议,欢迎评论指正~
最后
分享给大家一份面试题合集。
下面的题目都是在Android交流群大家在面试时遇到的,如果大家有好的题目或者好的见解欢迎分享,楼主将长期维护此帖。
参考解析:郭霖、鸿洋、玉刚、极客时间、腾讯课堂…
内容特点:条理清晰,含图像化表示更加易懂。
内容概要:包括 Handler、Activity相关、Fragment、service、布局优化、AsyncTask相关
、Android 事件分发机制、 Binder、Android 高级必备 :AMS,WMS,PMS、Glide、 Android 组件化与插件化等面试题和技术栈!
Handler 相关知识,面试必问!
常问的点:
Handler Looper Message 关系是什么?
Messagequeue 的数据结构是什么?为什么要用这个数据结构?
如何在子线程中创建 Handler?
Handler post 方法原理?
Android消息机制的原理及源码解析
Android Handler 消息机制
Activity 相关
启动模式以及使用场景?
onNewIntent()和onConfigurationChanged()
onSaveInstanceState()和onRestoreInstanceState()
Activity 到底是如何启动的
启动模式以及使用场景
onSaveInstanceState以及onRestoreInstanceState使用
onConfigurationChanged使用以及问题解决
Activity 启动流程解析
Fragment
Fragment 生命周期和 Activity 对比
Fragment 之间如何进行通信
Fragment的startActivityForResult
Fragment重叠问题
Fragment 初探
Fragment 重叠, 如何通信
Fragment生命周期
Service 相关
进程保活
Service的运行线程(生命周期方法全部在主线程)
Service启动方式以及如何停止
ServiceConnection里面的回调方法运行在哪个线程?
startService 和 bingService区别
进程保活一般套路
关于进程保活你需要知道的一切
Android布局优化之ViewStub、include、merge
什么情况下使用 ViewStub、include、merge?
他们的原理是什么?
ViewStub、include、merge概念解析
Android布局优化之ViewStub、include、merge使用与源码分析
BroadcastReceiver 相关
注册方式,优先级
广播类型,区别
广播的使用场景,原理
Android广播动态静态注册
常见使用以及流程解析
广播源码解析
AsyncTask相关
AsyncTask是串行还是并行执行?
AsyncTask随着安卓版本的变迁
AsyncTask完全解析
串行还是并行
Android 事件分发机制
onTouch和onTouchEvent区别,调用顺序
dispatchTouchEvent, onTouchEvent, onInterceptTouchEvent 方法顺序以及使用场景
滑动冲突,如何解决
事件分发机制
事件分发解析
dispatchTouchEvent, onTouchEvent, onInterceptTouchEvent方法的使用场景解析
Android View 绘制流程
简述 View 绘制流程
onMeasure, onlayout, ondraw方法中需要注意的点
如何进行自定义 View
view 重绘机制
- Android LayoutInflater原理分析,带你一步步深入了解View(一)
- Android视图状态及重绘流程分析,带你一步步深入了解View(二)
- Android视图状态及重绘流程分析,带你一步步深入了解View(三)
- Android自定义View的实现方法,带你一步步深入了解View(四)
Android Window、Activity、DecorView以及ViewRoot
Window、Activity、DecorView以及ViewRoot之间的关系
Android 的核心 Binder 多进程 AIDL
常见的 IPC 机制以及使用场景
为什么安卓要用 binder 进行跨进程传输
多进程带来的问题
- AIDL 使用浅析
- binder 原理解析
- binder 最底层解析
- 多进程通信方式以及带来的问题
- 多进程通信方式对比
Android 高级必备 :AMS,WMS,PMS
AMS,WMS,PMS 创建过程
- AMS,WMS,PMS全解析
- AMS启动流程
- WindowManagerService启动过程解析
- PMS 启动流程解析
Android ANR
为什么会发生 ANR?
如何定位 ANR?
如何避免 ANR?
什么是 ANR
如何避免以及分析方法
Android 性能优化之 ANR 详解
Android 内存相关
注意:内存泄漏和内存溢出是 2 个概念
什么情况下会内存泄漏?
如何防止内存泄漏?
- 内存泄漏和溢出的区别
- OOM 概念以及安卓内存管理机制
- 内存泄漏的可能性
- 防止内存泄漏的方法
Android 屏幕适配
屏幕适配相关名词解析
现在流行的屏幕适配方式
- 屏幕适配名词以及概念解析
- 今日头条技术适配方案
Android 缓存机制
LruCache使用极其原理
- Android缓存机制
- LruCache使用极其原理述
Android 性能优化
如何进行 内存 cpu 耗电 的定位以及优化
性能优化经常使用的方法
如何避免 UI 卡顿
- 性能优化全解析,工具使用
- 性能优化最佳实践
- 知乎高赞文章
Android MVC、MVP、MVVM
好几种我该选择哪个?优劣点
任玉刚的文章:设计模式选择
Android Gradle 知识
这俩篇官方文章基础的够用了
必须贴一下官方文档:配置构建Gradle 提示与诀窍
Gradle插件 了解就好
Gradle 自定义插件方式
全面理解Gradle – 执行时序
- Gradle系列一
- Gradle系列二
- Gradle系列三
RxJava
使用过程,特点,原理解析
RxJava 名词以及如何使用
Rxjava 观察者模式原理解析
Rxjava订阅流程,线程切换,源码分析 系列
OKHTTP 和 Retrofit
OKHTTP完整解析
Retrofit使用流程,机制详解
从 HTTP 到 Retrofit
Retrofit是如何工作的
最流行图片加载库: Glide
郭神系列 Glide 分析
Android图片加载框架最全解析(一),Glide的基本用法
Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程
Android图片加载框架最全解析(三),深入探究Glide的缓存机制
Android图片加载框架最全解析(四),玩转Glide的回调与监听
Android图片加载框架最全解析(五),Glide强大的图片变换功能
Android图片加载框架最全解析(六),探究Glide的自定义模块功能
Android图片加载框架最全解析(七),实现带进度的Glide图片加载功能
Android图片加载框架最全解析(八),带你全面了解Glide 4的用法
Android 组件化与插件化
为什么要用组件化?
组件之间如何通信?
组件之间如何跳转?
Android 插件化和热修复知识梳理
为什么要用组件化
- Android彻底组件化方案实践
- Android彻底组件化demo发布
- Android彻底组件化-代码和资源隔离
- Android彻底组件化—UI跳转升级改造
- Android彻底组件化—如何使用Arouter
插件化框架历史
深入理解Android插件化技术
Android 插件化和热修复知识梳理
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
机房租用,北京机房租用,IDC机房托管, http://www.fwqtg.net