Delphi 12 Android复制文件到SharedRingtonesPath失败,寻求解决办法
1. 适配Android 13的媒体权限
Android 13对媒体文件访问权限做了细分,废弃了旧的READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE权限,需替换为针对性的音频权限:
- 在Delphi项目的
Project Options -> Application -> Permissions中勾选READ_MEDIA_AUDIO和WRITE_MEDIA_AUDIO - 或手动修改
AndroidManifest.template.xml,添加权限声明:
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <uses-permission android:name="android.permission.WRITE_MEDIA_AUDIO" />
2. 改用MediaStore API写入公共铃声目录
Android 13限制了直接对公共媒体目录的文件操作,必须通过MediaStore API完成写入,而非传统的文件复制。核心Delphi代码示例:
procedure CopyAudioToRingtones(const AInternalFilePath: string); var LContentResolver: JContentResolver; LValues: JContentValues; LUri: JUri; LInputStream, LOutputStream: JInputStream; LBuffer: TJavaArray<Byte>; LBytesRead: Integer; begin LContentResolver := TAndroidHelper.Context.getContentResolver; LValues := TJContentValues.JavaClass.init; // 设置媒体元数据 LValues.put(TJMediaStore_Audio_Media.JavaClass.TITLE, StrToJCharSequence('CustomRingtone')); LValues.put(TJMediaStore_Audio_Media.JavaClass.DISPLAY_NAME, StrToJCharSequence('CustomRingtone.mp3')); LValues.put(TJMediaStore_Audio_Media.JavaClass.MIME_TYPE, StrToJCharSequence('audio/mpeg')); LValues.put(TJMediaStore_Audio_Media.JavaClass.RELATIVE_PATH, StrToJCharSequence('Ringtones/')); LValues.put(TJMediaStore_Audio_Media.JavaClass.IS_RINGTONE, TJavaBoolean.JavaClass.valueOf(True)); // 插入媒体条目获取URI LUri := LContentResolver.insert(TJMediaStore_Audio_Media.JavaClass.EXTERNAL_CONTENT_URI, LValues); if LUri <> nil then begin try // 获取应用内部文件输入流 LInputStream := TAndroidHelper.Context.getAssets.open(StringReplace(AInternalFilePath, 'file:///android_asset/', '', [rfIgnoreCase])); // 获取媒体库输出流 LOutputStream := LContentResolver.openOutputStream(LUri); if (LInputStream <> nil) and (LOutputStream <> nil) then begin LBuffer := TJavaArray<Byte>.Create(8192); try while (LInputStream.read(LBuffer) > 0) do begin LOutputStream.write(LBuffer); end; LOutputStream.flush; finally LBuffer.Free; LInputStream.close; LOutputStream.close; end; // 通知媒体库刷新 TAndroidHelper.Context.sendBroadcast(TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_MEDIA_SCANNER_SCAN_FILE, LUri)); end; except // 异常时清理无效条目 LContentResolver.delete(LUri, nil, nil); raise; end; end; end;
3. 校验Delphi 12的路径API正确性
Delphi 12可能调整了SharedRingtonesPath的实现,可通过日志输出实际路径,对比Android 13标准铃声目录/storage/emulated/0/Ringtones/是否一致。若路径错误,可手动构建可靠路径:
var LRingtonePath: string; begin LRingtonePath := TPath.Combine(TPath.GetSharedDownloadsPath, '../Ringtones'); end;
4. 验证文件访问基础条件
- 确认应用内部MP3文件存在:调用
FileExists(AInternalFilePath)检查 - 确保目标目录可访问:通过
ForceDirectories(LRingtonePath)确保目录存在(公共目录通常已存在,但自定义ROM可能例外)
5. 调整通知渠道与播放逻辑
通知渠道设置提示音时,需使用MediaStore返回的URI而非文件路径;直接播放时也需基于URI操作,示例:
procedure PlayRingtone(const ARingtoneUri: JUri); var LRingtone: JRingtone; begin LRingtone := TJRingtone.JavaClass.getRingtone(TAndroidHelper.Context, ARingtoneUri); if LRingtone <> nil then LRingtone.play; end;
内容的提问来源于stack exchange,提问作者Bart Kindt
相关产品推荐
相关产品推荐

