> 经过检测,我们识别到您的应用,目前未适配安卓11(API30),请您关注适配截止时间,尽快开展适配工作,避免影响应用正常发布和经营。
> targetSdkVersion30 升级适配工作参考文档:(小米开放平台)[TargetSdk
> 30上架适配指南](https://dev.mi.com/distribute/doc/details?pId=1737)
> 截至日期为2023年12月4日,请问您可以如期适配嘛?
主要处理内容
- 请求运行时权限
- 存储机制更新(强制执行分区存储)
其他
隐私设置
1.1强制执行分区存储
1.2闲置应用权限自动重置
1.3后台位置访问
1.4应用包可见性 二、安全
2.1堆指针标记
2.2消息框的更新 网络连接
3.1限制对APN数据库的读取访问 、无障碍服务
4.1在清单文件中声明与TTS引擎
4.2在元数据文件中声明"无障碍"… 五、相机
5.1媒体intent操作需要系统默认 应用打包和安装
6.1压缩的资源文件
6.2现在需要APK签名方案v2 ……
十、非SDK接口限制
十一、Google Android 11适配信息…
权限申请可以遵循官方的最佳做法实践指导
fun requestPermission(context: FragmentActivity, sureBlock: ()->Unit, permissions: List<String>){
PermissionX.init(context).permissions(permissions)
.onForwardToSettings { scope, deniedList ->
scope.showForwardToSettingsDialog(PermissionToSettings(context, deniedList))
}
.request { allGranted, _, _ ->
if (allGranted) {
sureBlock.invoke()
}
}
}
implementation 'com.guolindev.permissionx:permissionx:1.5.0'
直接执行新库,会报错
:app:checkPpDebugAarMetadata 7 errors
1 sec, 436 ms
org.gradle.workers.internal.DefaultWorkerExecutor$WorkExecutionException: occurred while executing com.android.build.gradle.internal.tasks.CheckDuplable
java.lang.RuntimeException: Duplicate class androidx.lifecycle.ViewModelLamodules jetified-lifecycle-viewmodel-ktx-2.3.1-runtime (androidx.lifecycleodel-ktx:2.3.1) and lifecycle-viewmodel-2.5.0-runtime (androidx.lifecycle:
org.gradle.apii.internal.tasks.execution.ExecuteActionsTaskExecuter$MultipFailures: Multiple task action failures occurred:
java.lang.RuntimeException: The minCompileSdk (32) specified in a
java.lang.RuntimeException: The minCompileSdk (31) specified in a
java.lang.RuntimeException: The minCompileSdk (32) specified in a
java lang RuntimeException: The minCompileSdk (31) specified in a
分析原因是项目所适配的最低版本和sdk的不适配。
因此,不能直接用1.7.1;改用了1.5.0.
后续等项目适配到Android13之后,可将新库的版本更新到最新即可。
1.1 强制执行分区存储
背景
为了让用户更好地管理自己的文件并减少混乱,以 Android 10(API 级别
29)及更高版本为目标平台的应用在默认情况下被授予了对外部存储空间的分区访问权限(即分区存储)。此类应用只能访问外部存储空间上的应用专属目录,以及本应用所创建的特定类型的媒体文件。在 Android 11 上运行但以 Android 10(API 级别 29)为目标平台的应用仍可请求
requestLegacyExternalStorage
属性。应用可以利用此标记暂时停用与分区存储相关的变更,例如授予对不同目录和不同类型的媒体文件的访问权限。当您将应用更新为以 Android
11 为目标平台后,系统会忽略 requestLegacyExternalStorage 标记。
兼容影响
当您将应用更新为以 Android 11 为目标平台后,您将无法使用requestLegacyExternalStorage,而且也没有其他标记可以提供停用分区存储。
分区存储对于App访问存储方式、App数据存放以及App间数据共享,都产生很大影响。
而Environment.getExternalStorageDirectory() 在 API Level 29 开始已被弃用,开发者应迁移至 Context#getExternalFilesDir(String), MediaStore, 或Intent#ACTION_OPEN_DOCUMENT。
适配建议
如果您将应用专属文件存储在外部存储空间中,则可以将这些文件存放在外部存储空间中的应用专属目录内,以便更加轻松地采用分区存储。这样,在启用分区存储后,您的应用将可以继续访问这些文件。
如需让您的应用适合分区存储,请参阅存储用例和最佳实践指南。
具体适配参考:
----------------------------------------------------------------------------------------------------.
需要重点适配的内容:
关键步骤
涉及到文件储存的功能。
通过官方推荐的方式来替换掉关键代码:
Environment.getExternalStorageDirectory()
如果直接使用以上代码进行存储的话,会报一下错误。
FileNotFoundException xxxxxxxxxx open failed: EPERM (Operation not permitted)
可以使用
context.getExternalFilesDir(null)?.absolutePath)
来存储,目录为:/storage/emulated/0/Android/data/com.xxx/files
resizeBmp = BitmapFactory.decodeFile(file.getPath(), opts);
场景:当通过相册拿到外部文件路径之后,使用以上代码获取bitmap,会偶现失败。检查权限相关无异常,但是会偶尔报这个错误
Unable to decode stream: java.io.FileNotFoundException: /storage/emulated/0/DCIM/com.xxx/1699410185196.jpg: open failed: EACCES (Permission denied)
通过调试和看日志,没能找到相关获取bitmap失败的其他日志。根据以上的这条error日志,判断是否鸿蒙系统对这块的适配有问题。因此,在华为的包中,添加一下代码:
<manifest ... >
<application android:requestLegacyExternalStorage="true" ...>
...
</application>
</manifest>
虽然在官方文档中,表示:
当您将应用更新为以 Android 11
为目标平台后,您将无法使用requestLegacyExternalStorage,而且也没有其他标记可以提供停用分区存储。
但是,通过这种方式进行设置,确实能避免鸿蒙系统会存在偶现失败的场景发生。
Permission to access file: /storage/emulated/0/Mob/comm/locks/.dhlock is denied uid = 10242
java.lang.SecurityException: com.yishouapp.fumi has no access to content://media/external_primary/file/1000000118
at com.android.providers.media.MediaProvider.enforceCallingPermissionInternal(MediaProvider.java:10152)
at com.android.providers.media.MediaProvider.enforceCallingPermission(MediaProvider.java:10049)
at com.android.providers.media.MediaProvider.checkAccess(MediaProvider.java:10177)
at com.android.providers.media.MediaProvider.checkIfFileOpenIsPermitted(MediaProvider.java:9068)
报错日志:
Unable to decode stream: java.io.FileNotFoundException:
/storage/emulated/0/DCIM/com.xxx/1699410185196.jpg: open
failed: EACCES (Permission denied)
'com.github.LuckSiege.PictureSelector:picture_library:v2.6.0'
因为项目中用到了camera-view这个库。因此,这个库的相关代码调用,也需要做代码上的适配,会有一些api上的改动。
参考:
报错日志:
com.android.providers.media.module Permission to access file:
/storage/emulated/0/Mob/comm/lockss/.dhlock is denied uid = 10242
java.lang.SecurityException: com.yishouapp.fumi has no access to
conttent://media/external_primary/file/100000000000118 at
com.android.providers.media.MediaProvider.enforceCallingPermissiionInternal(MediaProvider.java:10152)
at
com.android.providers.media.MediaProvider.enforceCallingPermission(MediaProvider.java:10049)
at
com.android.providers.media.MediaProvider.checkAccess(MediaProvider.java:10177)
at com.android.
roviders.media.MediaProvider.checkIfFileOpenIsPermitted(MediaProvider.java:9068)
at
com.android.providers.media.MediaProvider.onFileOpenForFusse(MediaProvider.java:9181)
FATAL EXCEPTION: ModernAsyncTask #1 com.yishouapp.fumi Process:
com.yishouapp.fumi, PID:30137 java.lang.RuntimeException: An error
occurred while executing doInBackground( at
androidx.loader.content.ModernAsyncTask 3. d o n e ( M o d e r n A s y n c T a s k . j a v a : 164 ) C a u s e d b y : j a v a . l a n g . I l l e g a l A r g u m e n t E x c e p t i o n : I n v a l i d c o l u m n C O U N T ( ∗ ) A S c o u n t < 6 i n t e r n a l l i n e s > a t a n d r o i d . d a t a b a s e . D a t a b a s e U t i l s . r e a d E x c e p t i o n F r o m P a r c e l ( D a t a b a s e u t i l s . j a v a : 172 ) a t a n d r o i d . d a t a b a s e . D a t a b a s e U t i l s . r e a d E x c e p t i o n F r o m P a r c e l ( D a t a b a s e u t i l s . j a v a : 142 a t a n d r o i d . c o n t e n t . C o n t e n t P r o v i d e r P r o x y . q u e r y ( C o n t e n t P r o v i d e r N a t i v e j a v a : 481 ) a t a n d r o i d . c o n t e n t . C o n t e n t R e s o l v e r . q u e r y ( C o n t e n t R e s o l v e r . j a v a : 1221 a t a n d r o i d . c o n t e n t . C o n t e n t R e s o l v e r . q u e r y ( C o n t e n t R e s o l v e r . j a v a : 1152 a t a n d r o i d x . c o r e . c o n t e n t . C o n t e n t R e s o l v e r C o m p a t . q u e r y ( C o n t e n t R e s o l v e r C o m p a . j a v a : 81 a t a n d r o i d x . l o a d e r . c o n t e n t . C u r s o r L o a d e r . l o a d I n B a c k g r o u n d ( C u r s o r L o a d e r . j a v a : 63 ) a t c o m . z h i h u . m a t i s s e . i n t e r n a l . l o a d e r . A l b u m L o a d e r . l o a d I n B a c k g r r o u n d ( A l b u m L o a d e r . j a v a : 98 a t c o m . z h i h u . m a t i s s e . i n t e r n a l . l o a d e r . A l b u m L o a d e r . l o a d I i n B a c k g r o u n d ( A l b u m L o a d e r . j a v a : 34 ) a t a n d r o i d x . l o a d e r . c o n t e n t . A s y n c T a s k L o a d e r . o n L o a d I n B a c k g r o u n d ( A s y n c T a s k L o a d e r . j a v a : 307 ) a t a n d r o i d x . l o a d e r . c o n t e n t . A s y n c T a s k L o a d e r 3.done(ModernAsyncTask.java:164) Caused by: java.lang.IllegalArgumentException: Invalid column COUNT(*) AS count <6 internal lines> at android.database.DatabaseUtils.readExceptionFromParcel(Databaseutils.java:172) at android.database.DatabaseUtils.readExceptionFromParcel(Databaseutils. java:142 at android.content.ContentProviderProxy.query (ContentProviderNativejava:481) at android.content.ContentResolver.query(ContentResolver.java:1221 at android.content.ContentResolver.query(ContentResolver.java:1152 at androidx.core.content.ContentResolverCompat.query (ContentResolverCompa.java:81 at androidx.loader.content.CursorLoader.loadInBackground(CursorLoader.java:63) at com.zhihu.matisse.internal.loader.AlbumLoader.loadInBackgrround(AlbumLoader.java:98 at com.zhihu.matisse.internal.loader.AlbumLoader.loadIinBackground(AlbumLoader.java:34) at androidx.loader.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:307) at androidx.loader.content.AsyncTaskLoader 3.done(ModernAsyncTask.java:164)Causedby:java.lang.IllegalArgumentException:InvalidcolumnCOUNT(∗)AScount<6internallines>atandroid.database.DatabaseUtils.readExceptionFromParcel(Databaseutils.java:172)atandroid.database.DatabaseUtils.readExceptionFromParcel(Databaseutils.java:142atandroid.content.ContentProviderProxy.query(ContentProviderNativejava:481)atandroid.content.ContentResolver.query(ContentResolver.java:1221atandroid.content.ContentResolver.query(ContentResolver.java:1152atandroidx.core.content.ContentResolverCompat.query(ContentResolverCompa.java:81atandroidx.loader.content.CursorLoader.loadInBackground(CursorLoader.java:63)atcom.zhihu.matisse.internal.loader.AlbumLoader.loadInBackgrround(AlbumLoader.java:98atcom.zhihu.matisse.internal.loader.AlbumLoader.loadIinBackground(AlbumLoader.java:34)atandroidx.loader.content.AsyncTaskLoader.onLoadInBackground(AsyncTaskLoader.java:307)atandroidx.loader.content.AsyncTaskLoaderLoadTask.doInBackground(AsyncTaskLoader.java:60
at
androidx.loader.content.AsyncTaskLoader$LoadTask.doInBackground(AsyncTaskLoader.java:48
at
androidx.loader.content.ModernAsyncTask$2.call(ModernAsyndTask.java:141)
<4 internal lines
该库已经从19年开始就不再维护了。因此,本次调整,将替换掉这个库。
/分享的图片集合
val imageUris = ArrayList<Uri>()
bitmaps.forEach {
imageUris.add(SaveUtils.saveBitmapToAlbumForShare(activity, it))
}
//分享到微信好友
activity.startActivity(
Intent().apply {
component = ComponentName("com.tencent.mm", "com.tencent.mm.ui.tools.ShareImgUI")
action = Intent.ACTION_SEND_MULTIPLE
type = "image/*"
putExtra(Intent.EXTRA_STREAM, imageUris)
})
// 启动 ocr 识别,识别类型为身份证正面
OcrSDKKit.getInstance().startProcessOcr(MainActivity.this, OcrType.IDCardOCR_FRONT, customConfigUi, new ISDKKitResultListener() {
@Override
public void onProcessSucceed(String response, String srcBase64Image, String requestId) {
popTip(response, "Succeed"); // 展示 ocr 识别结果
}
@Override
public void onProcessFailed(String errorCode, String message, String requestId) {
popTip(message, errorCode); // 展示 ocr 识别错误信息
}
});
总结:存储这块,很多项目中使用到的第三方库都没有做很好兼容。需要去仔细排查然后去做适配或者废弃替换工作。整体还是存在风险的,因此不会全量覆盖所有渠道,逐步覆盖了。
注意一个点,如果要处理保存图片/视频,并同步到相册的话。则不能直接将文件资源存储在私有目录中,私有目录的文件无法共享到相册中。可以通过MediaStore来操纵,具体可以看文档:
访问共享存储空间中的媒体文件