From 510bb8439b105bfedcf6c1b9176a2ad93a18c104 Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Wed, 10 Jun 2026 20:22:32 +0800 Subject: [PATCH] =?UTF-8?q?feat(file):=20=E6=B7=BB=E5=8A=A0=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=B8=8B=E8=BD=BD=E6=9D=83=E9=99=90=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=92=8C=E5=9B=9E=E9=80=80=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现 PublicDownloads 目录失败时自动回退到 Sandbox 目录 - 添加 Android 11+ 所有文件访问权限检查功能 - 提供跳转到文件访问权限设置页面的 Intent 方法 - 重构目录解析逻辑为独立的 resolveBaseDir 函数 - 增强文件下载的异常处理和日志记录 - 修复空白文件名处理逻辑,确保默认文件名为 download.bin --- .../main/java/com/xuqm/sdk/file/FileSDK.kt | 84 ++++++++++++++----- 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/sdk-core/src/main/java/com/xuqm/sdk/file/FileSDK.kt b/sdk-core/src/main/java/com/xuqm/sdk/file/FileSDK.kt index c81d2cb..4ea0801 100644 --- a/sdk-core/src/main/java/com/xuqm/sdk/file/FileSDK.kt +++ b/sdk-core/src/main/java/com/xuqm/sdk/file/FileSDK.kt @@ -254,30 +254,68 @@ object FileSDK { android.util.Log.d("XWV", "saveBlobDownload: fileName=$fileName, dest=$destination, b64Len=${base64Data.length}") val bytes = android.util.Base64.decode(base64Data, android.util.Base64.DEFAULT) android.util.Log.d("XWV", "saveBlobDownload: decoded ${bytes.size} bytes") - val baseDir = when (destination) { - FileDownloadDestination.PublicDownloads -> { - val appName = context.applicationInfo.loadLabel(context.packageManager).toString() - File( - android.os.Environment.getExternalStoragePublicDirectory( - android.os.Environment.DIRECTORY_DOWNLOADS - ), - appName, - ).apply { - val created = mkdirs() - android.util.Log.d("XWV", "saveBlobDownload: PublicDownloads dir=$absolutePath, created=$created, exists=${exists()}") - } - } - FileDownloadDestination.Sandbox -> { - (context.getExternalFilesDir(null) ?: context.filesDir).also { - android.util.Log.d("XWV", "saveBlobDownload: Sandbox dir=${it.absolutePath}") - } - } - } - val target = uniqueFile(baseDir, fileName.takeIf { it.isNotBlank() } ?: "download.bin") + + val safeName = fileName.takeIf { it.isNotBlank() } ?: "download.bin" + + // 尝试目标目录,如果 PublicDownloads 失败则回退到 Sandbox + val baseDir = resolveBaseDir(context, destination) + val target = uniqueFile(baseDir, safeName) android.util.Log.d("XWV", "saveBlobDownload: writing to ${target.absolutePath}") - target.writeBytes(bytes) - android.util.Log.d("XWV", "saveBlobDownload: done, size=${target.length()}") - return target + return try { + target.writeBytes(bytes) + android.util.Log.d("XWV", "saveBlobDownload: done, size=${target.length()}") + target + } catch (e: java.io.IOException) { + android.util.Log.w("XWV", "saveBlobDownload: primary dir failed (${e.message}), falling back to Sandbox") + val fallbackDir = resolveBaseDir(context, FileDownloadDestination.Sandbox) + val fallbackTarget = uniqueFile(fallbackDir, safeName) + fallbackTarget.writeBytes(bytes) + android.util.Log.d("XWV", "saveBlobDownload: fallback done, ${fallbackTarget.absolutePath}, size=${fallbackTarget.length()}") + fallbackTarget + } + } + + private fun resolveBaseDir(context: Context, destination: FileDownloadDestination): File = when (destination) { + FileDownloadDestination.PublicDownloads -> { + val appName = context.applicationInfo.loadLabel(context.packageManager).toString() + File( + android.os.Environment.getExternalStoragePublicDirectory( + android.os.Environment.DIRECTORY_DOWNLOADS + ), + appName, + ).apply { mkdirs() } + } + FileDownloadDestination.Sandbox -> { + context.getExternalFilesDir(null) ?: context.filesDir + } + } + + /** + * 检查是否拥有"所有文件访问权限"(MANAGE_EXTERNAL_STORAGE)。 + * Android 11+ 需要此权限才能写入公共 Downloads 目录。 + * Android 10 及以下始终返回 true(使用 WRITE_EXTERNAL_STORAGE)。 + */ + fun isManageStorageGranted(context: Context): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + Environment.isExternalStorageManager() + } else { + true + } + } + + /** + * 返回跳转到"所有文件访问权限"设置页的 Intent。 + * 调用方应使用 startActivityForResult 或 registerForActivityResult 处理返回。 + */ + fun requestManageStorageIntent(context: Context): Intent? { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) return null + return try { + Intent(android.provider.Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply { + data = Uri.parse("package:${context.packageName}") + } + } catch (_: Exception) { + Intent(android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) + } } /**