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
相关产品推荐
相关产品推荐

