0
点赞
收藏
分享

微信扫一扫

Android 10 和Android 11 适配采坑 实践篇


背景

最近在项目中着手做Android10Android11 适配时候,期间遇到了不少的坑。之前有专门写过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? 我谢谢你啊~~)

**我这里说的 Android10android 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 组件化与插件化等面试题和技术栈!

Android 10 和Android 11 适配采坑 实践篇_apache

Handler 相关知识,面试必问!

常问的点:
Handler Looper Message 关系是什么?
Messagequeue 的数据结构是什么?为什么要用这个数据结构?
如何在子线程中创建 Handler?
Handler post 方法原理?
Android消息机制的原理及源码解析
Android Handler 消息机制

Android 10 和Android 11 适配采坑 实践篇_apache_02

Activity 相关

启动模式以及使用场景?
onNewIntent()和onConfigurationChanged()
onSaveInstanceState()和onRestoreInstanceState()
Activity 到底是如何启动的
启动模式以及使用场景
onSaveInstanceState以及onRestoreInstanceState使用
onConfigurationChanged使用以及问题解决
Activity 启动流程解析

Android 10 和Android 11 适配采坑 实践篇_apache_03

Fragment

Fragment 生命周期和 Activity 对比
Fragment 之间如何进行通信
Fragment的startActivityForResult
Fragment重叠问题
Fragment 初探
Fragment 重叠, 如何通信
Fragment生命周期

Android 10 和Android 11 适配采坑 实践篇_android_04

Service 相关

进程保活
Service的运行线程(生命周期方法全部在主线程)
Service启动方式以及如何停止
ServiceConnection里面的回调方法运行在哪个线程?
startService 和 bingService区别
进程保活一般套路
关于进程保活你需要知道的一切

Android 10 和Android 11 适配采坑 实践篇_Android_05

Android布局优化之ViewStub、include、merge

什么情况下使用 ViewStub、include、merge?
他们的原理是什么?
ViewStub、include、merge概念解析
Android布局优化之ViewStub、include、merge使用与源码分析

Android 10 和Android 11 适配采坑 实践篇_ide_06

BroadcastReceiver 相关

注册方式,优先级
广播类型,区别
广播的使用场景,原理
Android广播动态静态注册
常见使用以及流程解析
广播源码解析

Android 10 和Android 11 适配采坑 实践篇_android_07

AsyncTask相关

AsyncTask是串行还是并行执行?
AsyncTask随着安卓版本的变迁
AsyncTask完全解析
串行还是并行

Android 10 和Android 11 适配采坑 实践篇_java_08

Android 事件分发机制

onTouch和onTouchEvent区别,调用顺序
dispatchTouchEvent, onTouchEvent, onInterceptTouchEvent 方法顺序以及使用场景
滑动冲突,如何解决
事件分发机制
事件分发解析
dispatchTouchEvent, onTouchEvent, onInterceptTouchEvent方法的使用场景解析

Android 10 和Android 11 适配采坑 实践篇_apache_09

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 10 和Android 11 适配采坑 实践篇_ide_10

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 10 和Android 11 适配采坑 实践篇_android_11

Android 内存相关

注意:内存泄漏和内存溢出是 2 个概念

什么情况下会内存泄漏?
如何防止内存泄漏?

  • 内存泄漏和溢出的区别
  • OOM 概念以及安卓内存管理机制
  • 内存泄漏的可能性
  • 防止内存泄漏的方法

Android 屏幕适配

屏幕适配相关名词解析
现在流行的屏幕适配方式

  • 屏幕适配名词以及概念解析
  • 今日头条技术适配方案

Android 缓存机制

LruCache使用极其原理

  • Android缓存机制
  • LruCache使用极其原理述

Android 性能优化

如何进行 内存 cpu 耗电 的定位以及优化
性能优化经常使用的方法
如何避免 UI 卡顿

  • 性能优化全解析,工具使用
  • 性能优化最佳实践
  • 知乎高赞文章

Android MVC、MVP、MVVM

好几种我该选择哪个?优劣点

任玉刚的文章:设计模式选择

Android 10 和Android 11 适配采坑 实践篇_Android_12

Android Gradle 知识

这俩篇官方文章基础的够用了
必须贴一下官方文档:配置构建Gradle 提示与诀窍

Gradle插件 了解就好
Gradle 自定义插件方式
全面理解Gradle - 执行时序

  • Gradle系列一
  • Gradle系列二
  • Gradle系列三

RxJava

使用过程,特点,原理解析
RxJava 名词以及如何使用
Rxjava 观察者模式原理解析
Rxjava订阅流程,线程切换,源码分析 系列

Android 10 和Android 11 适配采坑 实践篇_ide_13

OKHTTP 和 Retrofit

OKHTTP完整解析
Retrofit使用流程,机制详解
从 HTTP 到 Retrofit
Retrofit是如何工作的

Android 10 和Android 11 适配采坑 实践篇_android_14

最流行图片加载库: Glide

郭神系列 Glide 分析
Android图片加载框架最全解析(一),Glide的基本用法
Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程
Android图片加载框架最全解析(三),深入探究Glide的缓存机制
Android图片加载框架最全解析(四),玩转Glide的回调与监听
Android图片加载框架最全解析(五),Glide强大的图片变换功能
Android图片加载框架最全解析(六),探究Glide的自定义模块功能
Android图片加载框架最全解析(七),实现带进度的Glide图片加载功能
Android图片加载框架最全解析(八),带你全面了解Glide 4的用法

Android 10 和Android 11 适配采坑 实践篇_android_15

Android 组件化与插件化

为什么要用组件化?
组件之间如何通信?
组件之间如何跳转?
Android 插件化和热修复知识梳理
为什么要用组件化

  • Android彻底组件化方案实践
  • Android彻底组件化demo发布
  • Android彻底组件化-代码和资源隔离
  • Android彻底组件化—UI跳转升级改造
  • Android彻底组件化—如何使用Arouter

插件化框架历史
深入理解Android插件化技术
Android 插件化和热修复知识梳理

Android 10 和Android 11 适配采坑 实践篇_Android_16

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

举报

相关推荐

0 条评论