You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi 12 Android复制文件到SharedRingtonesPath失败,寻求解决办法

修复Android 13下Delphi 12复制MP3到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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 10:15:07