在Fragment/标签页中用SoundPool开发音效板应用遇AudioManager获取问题
问题排查与修复方案
我帮你梳理了代码里的几个关键问题,这些应该是导致应用无法正常运行的核心原因:
主要问题点
- Fragment调用
getSystemService方式错误:Fragment本身没有getSystemService方法,必须通过getActivity()来获取系统服务,直接调用会引发运行时异常。 getView()调用时机过早:在onCreateView中还未返回view时调用getView()会返回null,导致空指针崩溃,应该用已inflate的view对象来查找控件。- 重复设置
SoundPool加载完成监听器:代码中两次设置OnLoadCompleteListener,第二次会覆盖第一次,属于冗余操作。 - 自定义方法覆盖系统方法:你写的空实现
setVolumeControlStream覆盖了Fragment继承的系统方法,导致无法正确设置音量控制流,需要删除这个自定义方法。 - Context初始化时机错误:在Fragment构造方法中调用
getContext()会返回null,因为此时Fragment还未依附到Activity上,应在生命周期方法内获取Context。 - AudioManager常量引用错误:
audiomanager.STREAM_MUSIC是错误的写法,这个是静态常量,应该用AudioManager.STREAM_MUSIC。
修正后的完整代码
public class FragmentQuiz extends android.support.v4.app.Fragment { ListView listView; int[] soundArray; private SoundPool soundPool; private int MAX = 1; View view; boolean loaded = false; public FragmentQuiz() { // 构造方法中不要初始化Context或调用依赖Activity的方法 } @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { view = inflater.inflate(R.layout.quiz_fragment, container, false); // 通过Activity设置音量控制流 getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC); // 初始化SoundPool soundPool = new SoundPool(MAX, AudioManager.STREAM_MUSIC, 0); soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() { @Override public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { loaded = true; } }); // 加载音效文件 soundArray = new int[3]; // 你只加载了3个音效,数组长度设为3更合理 soundArray[0] = soundPool.load(getActivity(), R.raw.bird, 1); soundArray[1] = soundPool.load(getActivity(), R.raw.rooster, 1); soundArray[2] = soundPool.load(getActivity(), R.raw.elephant, 1); // 获取AudioManager(通过Activity调用getSystemService) AudioManager audioManager = (AudioManager) getActivity().getSystemService(Context.AUDIO_SERVICE); float actualVolume = (float) audioManager.getStreamVolume(AudioManager.STREAM_MUSIC); float maxVolume = (float) audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); final float volume = actualVolume / maxVolume; // 通过已inflate的view查找ListView listView = view.findViewById(R.id.list); String[] values = new String[]{ "Android Lollipop", "beta", "marshmello" }; ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(), android.R.layout.simple_list_item_1, android.R.id.text1, values); listView.setAdapter(adapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { int itemPosition = position; String itemValue = (String) listView.getItemAtPosition(position); Toast.makeText(getActivity(), "position: " + itemPosition + " ListItem: " + itemValue, Toast.LENGTH_SHORT).show(); // 播放音效前先判断是否加载完成 if (loaded && position < soundArray.length) { soundPool.play(soundArray[position], volume, volume, 1, 0, 1f); } } }); return view; } // 删除自定义的setVolumeControlStream方法,使用系统提供的方法 }
额外优化点
- 我在播放音效前增加了
loaded状态和数组边界的判断,避免音效未加载完成或点击位置超出数组长度时引发异常。 - 调整了
soundArray的长度,因为你只加载了3个音效,原来的长度5会造成不必要的内存浪费。 - 简化了Context的使用,直接用
getActivity()即可,不需要额外调用getApplicationContext(),在Fragment中使用宿主Activity的Context更合适。
内容的提问来源于stack exchange,提问作者Pranesh Nangare
相关产品推荐
相关产品推荐

