乐趣区

关于android:Android-10-和Android-11的适配

背景

最近在我的项目中着手做 Android10Android11 适配时候,期间遇到了不少的坑。之前有专门写过 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 的设施上 会咋么样?

答:如果依照下面失常适配,必定齐全没得问题!

以上是本人适配教训,不免有忽略之处,如果文章有问题或者更好的倡议,欢送评论斧正~

本文转自 https://juejin.cn/post/7032525748686553095,如有侵权,请分割删除。

退出移动版