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

Android Studio中修复外部存储写入权限申请不弹窗问题

外部存储权限申请不弹窗,完善音频文件生成权限流程

我做了一个点击按钮生成音频文件的功能,但申请外部存储写入权限时,权限申请对话框不弹出。现有代码已写了部分权限申请逻辑,需要完善实现正确的权限申请流程。


问题分析

原代码存在以下核心问题:

  • 调用顺序混乱:点击按钮后同时触发TTS初始化和文件创建,权限未确认就执行后续逻辑
  • 缺少权限回调处理:未实现onRequestPermissionsResult方法,无法响应用户的授权操作
  • 未处理权限拒绝后的反馈:用户拒绝权限后没有提示,也没有二次申请的合理性说明
  • 异步操作提示不准确:TTS合成音频是异步的,直接提示"保存成功"不符合实际执行逻辑

修复后的完整代码

1. 定义常量与成员变量

private static final int WRITE_EXTERNAL_STORAGE_CODE = 100;
private TextToSpeech speech;
private File folder;
private String mAudioFilename;
private final String mUtteranceID = "tts_audio";
// 假设以下变量已在其他地方定义:
// String translatedText, languageCode, translateTo;
// Locale selectedLocale;

2. 修改按钮点击事件

generateAudioBtn.setOnClickListener(view -> {
    // 先检查存储权限,再执行后续流程
    checkStoragePermission();
});

3. 权限检查与申请逻辑

private void checkStoragePermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        int permissionStatus = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
        if (permissionStatus != PackageManager.PERMISSION_GRANTED) {
            // 如果用户之前拒绝过权限,先说明权限用途
            if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                Toast.makeText(getContext(), "需要存储权限来保存生成的音频文件", Toast.LENGTH_SHORT).show();
            }
            // 发起权限申请
            ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, WRITE_EXTERNAL_STORAGE_CODE);
        } else {
            // 权限已授予,执行音频生成流程
            proceedToGenerateAudio();
        }
    } else {
        // Android 6.0以下无需动态申请,直接执行
        proceedToGenerateAudio();
    }
}

4. 权限申请回调处理

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == WRITE_EXTERNAL_STORAGE_CODE) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            proceedToGenerateAudio();
        } else {
            Toast.makeText(getContext(), "未授予存储权限,无法保存音频文件", Toast.LENGTH_SHORT).show();
        }
    }
}

5. 音频生成核心逻辑

private void proceedToGenerateAudio() {
    // 先创建文件路径(确保权限已获取)
    createFile();
    // 初始化TTS并生成音频
    initializeTextToSpeech(selectedLocale);
}

private void initializeTextToSpeech(Locale selectedLocale) {
    speech = new TextToSpeech(getContext(), status -> {
        if (status == TextToSpeech.SUCCESS) {
            int result = speech.setLanguage(selectedLocale);
            if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
                Log.e(TAG, "Language not supported");
                Toast.makeText(getContext(), "不支持该语言", Toast.LENGTH_SHORT).show();
            } else {
                Log.i(TAG, "Text-to-Speech initialized");
                saveAudioToFile(translatedText);
            }
        } else {
            Log.e(TAG, "Text-to-Speech initialization failed");
            Toast.makeText(getContext(), "语音合成初始化失败", Toast.LENGTH_SHORT).show();
        }
    });
}

private void createFile() {
    folder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "Text To Speech Audio");
    if (!folder.exists()) {
        boolean isDirectoryCreated = folder.mkdirs();
        if (!isDirectoryCreated) {
            Toast.makeText(getContext(), "无法创建音频保存目录", Toast.LENGTH_SHORT).show();
            return;
        }
    }
    // 生成唯一文件名
    mAudioFilename = folder.getAbsolutePath() + "/" + languageCode + "-" + translateTo + mUtteranceID + System.currentTimeMillis() + ".wav";
}

private void saveAudioToFile(String textToSpeak) {
    if (speech == null || TextUtils.isEmpty(mAudioFilename)) {
        Toast.makeText(getContext(), "音频生成准备失败", Toast.LENGTH_SHORT).show();
        return;
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        speech.synthesizeToFile(textToSpeak, null, new File(mAudioFilename), mUtteranceID);
    } else {
        HashMap<String, String> hm = new HashMap<>();
        hm.put(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, mUtteranceID);
        speech.synthesizeToFile(textToSpeak, hm, mAudioFilename);
    }

    // 监听TTS合成状态,确保保存完成后再提示
    speech.setOnUtteranceProgressListener(new UtteranceProgressListener() {
        @Override
        public void onStart(String utteranceId) {}

        @Override
        public void onDone(String utteranceId) {
            getActivity().runOnUiThread(() -> {
                Toast.makeText(getContext(), "音频已保存至: " + mAudioFilename, Toast.LENGTH_LONG).show();
            });
        }

        @Override
        public void onError(String utteranceId) {
            getActivity().runOnUiThread(() -> {
                Toast.makeText(getContext(), "音频保存失败", Toast.LENGTH_SHORT).show();
            });
        }
    });
}

AndroidManifest配置说明

需要在清单文件中声明权限,并针对Android 10(API29)添加兼容配置:

<!-- 声明存储权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<application
    ...
    <!-- 针对API29,启用旧版存储模型(可选,若使用MediaStore则无需此配置) -->
    android:requestLegacyExternalStorage="true">
    ...
</application>

内容的提问来源于stack exchange,提问作者AKD2022

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 01:57:05