背景
最近在我的项目中着手做Android10
和Android11
适配时候,期间遇到了不少的坑。之前有专门写过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的设施上 会咋么样?
答: 如果依照下面失常适配,必定齐全没得问题!
以上是本人适配教训,不免有忽略之处,如果文章有问题或者更好的倡议,欢送评论斧正~
本文转自 https://juejin.cn/post/7032525748686553095,如有侵权,请分割删除。