Android 11适配实战:从‘分区存储’到‘软件包可见性’,一个老项目的踩坑与填坑全记录
Android 11适配实战从存储权限到应用交互的全面升级指南当我们的开发团队第一次将老项目的targetSdkVersion升级到30时就像打开了一个潘多拉魔盒——各种意想不到的问题接踵而至。这个已经稳定运行了三年的应用在Android 11设备上突然出现了图片无法保存、第三方分享失效、视频播放崩溃等一系列问题。本文将分享我们在适配过程中遇到的真实挑战和解决方案特别适合那些正在维护历史包袱较重的Android应用的开发者。1. 存储权限的深度适配策略在Android 11上存储权限的变化无疑是最大的挑战之一。我们项目中有大量文件操作代码从用户头像保存到日志文件输出都需要重新审视。经过两周的密集测试我们总结出三种主要适配方案每种都有其适用场景和潜在风险。1.1 MediaStore API的全面应用对于媒体文件图片、视频、音频MediaStore API是最规范的访问方式。我们发现直接使用File API虽然在某些情况下仍然有效但会带来明显的性能损耗——随机读写速度下降约50%。以下是典型的图片保存代码示例public Uri saveImageToGallery(Context context, Bitmap bitmap) throws IOException { String displayName IMG_ System.currentTimeMillis() .jpg; ContentValues values new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, displayName); values.put(MediaStore.Images.Media.MIME_TYPE, image/jpeg); values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES /MyApp); ContentResolver resolver context.getContentResolver(); Uri uri resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); try (OutputStream out resolver.openOutputStream(uri)) { bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out); } return uri; }关键注意事项使用RELATIVE_PATH指定子目录避免文件散乱记得检查返回的Uri是否为null存储空间不足时可能发生对于批量操作考虑使用批量插入API提高性能1.2 MANAGE_EXTERNAL_STORAGE权限的谨慎使用我们的应用有一个文件清理功能需要扫描整个存储空间。这种情况下我们不得不考虑使用MANAGE_EXTERNAL_STORAGE权限。但要注意Google Play对这项权限的审核非常严格。申请流程代码示例public static void requestStorageManagementPermission(Activity activity) { if (Build.VERSION.SDK_INT Build.VERSION_CODES.R !Environment.isExternalStorageManager()) { Intent intent new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); activity.startActivityForResult(intent, REQUEST_CODE_STORAGE_PERMISSION); } }使用建议仅在绝对必要时申请此权限准备充分的理由说明为什么MediaStore和SAF无法满足需求国内应用市场可能要求较低但仍应保持克制1.3 私有目录与共享目录的选择我们发现很多开发者忽略了Android/media目录的特殊性——它既可以被应用私有访问又能被其他应用通过MediaStore读取。这对于需要跨应用共享但又不想完全公开的文件非常有用。// 获取应用专属的共享媒体目录 File sharedMediaDir context.getExternalMediaDirs()[0]; File outputFile new File(sharedMediaDir, temp_video.mp4);2. 软件包可见性对应用交互的影响Android 11引入的软件包可见性限制对我们应用中第三方登录、支付和分享功能造成了严重影响。最典型的问题是我们无法再通过常规方式检测微信是否安装。2.1 精确声明需要的包名在AndroidManifest.xml中添加queries元素是最规范的解决方案manifest xmlns:androidhttp://schemas.android.com/apk/res/android packagecom.example.myapp queries !-- 微信 -- package android:namecom.tencent.mm / !-- 支付宝 -- package android:namecom.eg.android.AlipayGphone / !-- 微博 -- package android:namecom.sina.weibo / /queries ... /manifest2.2 通用Intent的声明方式如果不需要特定包名而是想查询所有能处理某类Intent的应用可以这样声明queries intent action android:nameandroid.intent.action.SEND / data android:mimeTypeimage/* / /intent /queries2.3 避免过度声明我们发现有些开发者倾向于声明QUERY_ALL_PACKAGES权限或列出几十个包名这会导致Google Play审核风险不必要的隐私疑虑未来维护困难最佳实践只声明确实需要的包名对于不常见的应用考虑使用Intent直接启动而不预先检查定期审查queries列表移除不再使用的声明3. 权限模型的精细化适配Android 11对权限系统做了多项调整我们需要特别注意以下几点3.1 单次权限的生命周期管理位置、麦克风和摄像头权限现在支持单次授权。我们需要正确处理权限的临时性Override protected void onResume() { super.onResume(); if (checkSelfPermission(Manifest.permission.CAMERA) PackageManager.PERMISSION_GRANTED) { // 权限可能已被撤销需要重新检查 initCamera(); } }3.2 后台位置权限的独立申请Android 11要求先获取前台位置权限再单独申请后台权限。我们实现了分步申请流程private void requestLocationPermissions() { if (checkSelfPermission(ACCESS_FINE_LOCATION) ! PERMISSION_GRANTED) { // 第一步申请前台权限 requestPermissions(new String[]{ACCESS_FINE_LOCATION}, REQUEST_FOREGROUND_LOCATION); } else if (checkSelfPermission(ACCESS_BACKGROUND_LOCATION) ! PERMISSION_GRANTED) { // 第二步单独申请后台权限 new AlertDialog.Builder(this) .setMessage(需要后台位置权限以持续跟踪) .setPositiveButton(确定, (d, w) - { requestPermissions(new String[]{ACCESS_BACKGROUND_LOCATION}, REQUEST_BACKGROUND_LOCATION); }) .show(); } }3.3 权限自动重置的处理长时间未使用的应用会被系统自动重置权限。我们添加了检测逻辑if (Build.VERSION.SDK_INT Build.VERSION_CODES.R !getPackageManager().isAutoRevokeWhitelisted()) { showPermissionResetWarning(); }4. 其他关键变更与疑难问题4.1 前台服务类型的强制声明访问摄像头或麦克风的前台服务现在需要明确声明类型service android:name.CameraService android:foregroundServiceTypecamera /4.2 安装APK的行为变更我们发现Android 11上安装APK时应用会被杀死这影响了我们的自动更新流程。解决方案是public static void installApk(Context context, File apkFile) { Intent install new Intent(Intent.ACTION_VIEW); Uri uri FileProvider.getUriForFile(context, AUTHORITY, apkFile); install.setDataAndType(uri, application/vnd.android.package-archive); install.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 启动安装前保存关键状态 saveApplicationState(); context.startActivity(install); }4.3 指针标记导致的Native崩溃我们使的某个视频播放SDK在Android 11上频繁崩溃日志显示是内存访问问题。临时解决方案是在AndroidManifest中添加application android:allowNativeHeapPointerTaggingfalse ... /application但需要注意这只是权宜之计长期应该更新SDK版本。5. 调试与测试工具5.1 兼容性框架调试工具Android 11新增的兼容性调试工具让我们能够逐个测试各项变更而不必立即升级targetSdkVersion。使用方法在开发者选项中启用应用兼容性变更选择要测试的应用针对特定变更开启/关闭开关5.2 无线调试的实践虽然Android 11的无线调试功能理念很好但我们的体验并不理想连接稳定性差传输速度慢锁屏后容易断开建议重要调试工作仍使用有线连接无线调试仅作为辅助手段。