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 a77fe73..42f7326 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 @@ -251,7 +251,12 @@ object FileSDK { val safeName = fileName.takeIf { it.isNotBlank() } ?: "download.bin" - // 尝试目标目录,如果 PublicDownloads 失败则回退到 Sandbox + // Android 10+ 公共 Downloads 目录使用 MediaStore 写入(兼容华为等设备) + if (destination == FileDownloadDestination.PublicDownloads && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return saveBlobViaMediaStore(context, bytes, safeName) + } + + // 旧版本或 Sandbox 目录直接写文件 val baseDir = resolveBaseDir(context, destination) val target = uniqueFile(baseDir, safeName) android.util.Log.d("XWV", "saveBlobDownload: writing to ${target.absolutePath}") @@ -269,6 +274,44 @@ object FileSDK { } } + /** + * 通过 MediaStore 将文件写入公共 Downloads 目录。 + * 在华为等设备上直接文件 I/O 会失败,MediaStore 通过 ContentResolver 操作,兼容性更好。 + * 返回缓存目录中的副本(真实文件,可被 openFile 使用)。 + */ + private fun saveBlobViaMediaStore(context: Context, bytes: ByteArray, fileName: String): File { + val mimeType = MimeTypeMap.getSingleton() + .getMimeTypeFromExtension(fileName.substringAfterLast('.', "").lowercase()) + ?: "application/octet-stream" + val appName = context.applicationInfo.loadLabel(context.packageManager).toString() + + // 写入 MediaStore + val values = ContentValues().apply { + put(MediaStore.Downloads.DISPLAY_NAME, fileName) + put(MediaStore.Downloads.MIME_TYPE, mimeType) + put(MediaStore.Downloads.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/$appName") + put(MediaStore.Downloads.IS_PENDING, 1) + } + + val resolver = context.contentResolver + val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values) + ?: throw java.io.IOException("MediaStore insert returned null") + + resolver.openOutputStream(uri)?.use { out -> + out.write(bytes) + } ?: throw java.io.IOException("Cannot open output stream for $uri") + + values.clear() + values.put(MediaStore.Downloads.IS_PENDING, 0) + resolver.update(uri, values, null, null) + + android.util.Log.d("XWV", "saveBlobViaMediaStore: saved $fileName, size=${bytes.size}, uri=$uri") + + // 同时写入缓存目录,供 openFile 使用 + val cacheFile = File(context.cacheDir, fileName).apply { writeBytes(bytes) } + return cacheFile + } + private fun resolveBaseDir(context: Context, destination: FileDownloadDestination): File = when (destination) { FileDownloadDestination.PublicDownloads -> { val appName = context.applicationInfo.loadLabel(context.packageManager).toString()