适配全Android版本(8/9/10/11/12等)与全品牌设备(小米/三星/一加等)的图片存储方案求助
全兼容Android各版本&品牌的无权限存储方案(对标WhatsApp)
终于搞定了这个头疼的存储兼容问题!下面这套方案亲测覆盖Android 8到12+所有版本,小米、三星、一加等主流品牌都能正常工作,完全不需要特殊权限就能把图片/视频/文档存在storage/emulated/0/Android/media/[你的包名]目录下,和WhatsApp的存储逻辑一致,还能保证文件在文件管理器里正常可见。
核心逻辑拆解
为什么选Android/media目录?
- Android 10(API 29)及以上:这个目录属于应用专属公共媒体目录,系统默认开放读写权限,不需要申请
WRITE_EXTERNAL_STORAGE - Android 8/9(API 26-28):需要申请
WRITE_EXTERNAL_STORAGE权限,但只要用户同意,就能正常读写这个目录 - 所有版本都可以通过系统API直接获取目录路径,避免硬编码带来的品牌适配问题
完整代码实现
我封装了一个工具类MediaStorageUtils,直接复制用就行:
1. 权限处理(仅针对Android 8/9)
先处理旧版本的权限申请,新版本直接跳过:
// 检查是否需要申请存储权限 fun needStoragePermission(context: Context): Boolean { return Build.VERSION.SDK_INT in Build.VERSION_CODES.O..Build.VERSION_CODES.P && ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED } // 申请存储权限 fun requestStoragePermission(activity: Activity, requestCode: Int = 1001) { ActivityCompat.requestPermissions( activity, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), requestCode ) }
2. 获取Android/media下的应用专属目录
这个方法是核心,不管什么版本什么品牌,都能拿到正确的目录:
// 获取Android/media/[包名]目录 fun getAppMediaDir(context: Context): File? { // getExternalMediaDirs()返回的第一个元素就是主存储的Android/media/包名目录 val mediaDirs = context.getExternalMediaDirs() if (mediaDirs.isNotEmpty()) { val appMediaDir = mediaDirs[0] // 确保目录存在 if (!appMediaDir.exists()) { appMediaDir.mkdirs() } return appMediaDir } return null }
3. 保存图片到目录并通知系统
保存后必须通知媒体库,不然三星等设备的文件管理器看不到新文件:
// 保存Bitmap到Android/media目录 fun saveBitmapToMediaDir(context: Context, bitmap: Bitmap, fileName: String): Boolean { val appMediaDir = getAppMediaDir(context) ?: return false val file = File(appMediaDir, "$fileName.png") return try { val outputStream = FileOutputStream(file) bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream) outputStream.flush() outputStream.close() // 通知媒体库更新,让文件管理器可见 MediaScannerConnection.scanFile( context, arrayOf(file.absolutePath), arrayOf("image/png") ) { _, _ -> /* 扫描完成后的回调,可忽略 */ } true } catch (e: Exception) { e.printStackTrace() false } } // 如果是保存文件流(比如下载的图片/文档),用这个方法 fun saveStreamToMediaDir(context: Context, inputStream: InputStream, fileName: String, mimeType: String): Boolean { val appMediaDir = getAppMediaDir(context) ?: return false val file = File(appMediaDir, fileName) return try { val outputStream = FileOutputStream(file) inputStream.copyTo(outputStream) outputStream.flush() outputStream.close() // 通知媒体库 MediaScannerConnection.scanFile( context, arrayOf(file.absolutePath), arrayOf(mimeType) ) { _, _ -> } true } catch (e: Exception) { e.printStackTrace() false } }
踩坑提示(重点解决三星等品牌问题)
- 绝对不要硬编码路径:比如不要写
/storage/emulated/0/Android/media/com.your.package,不同品牌可能有自定义存储路径,必须用getExternalMediaDirs()获取 - 必须调用MediaScannerConnection:三星设备对媒体库更新很敏感,不扫描的话文件管理器里看不到新保存的文件
- Android 10+不需要权限:如果你的targetSdkVersion是30+,直接去掉
WRITE_EXTERNAL_STORAGE权限申请逻辑也可以,系统会自动授权 - 处理目录创建失败的情况:比如用户存储已满,要给用户友好提示
内容的提问来源于stack exchange,提问作者Urvish Vocsy
相关产品推荐
相关产品推荐

