Android MediaPlayer 在 API 21 时失败(但不是 API 19 或 22!)
Android MediaPlayer Fails on API 21 (but NOT API 19 or 22!)
在我的应用程序中,我希望有一个简单的方法使用 MediaPlayer class 播放一次声音资源(如果需要,再播放一次)。
我已经成功地实现了这一点(见下面的代码)并在 API 23 的真实硬件设备上成功地测试了它,还在 API 等级从 19 一直到 P 的模拟器上进行了测试... 除了 API 21,失败的地方。
是不是很奇怪?为什么某些东西适用于 API 19... 和 23,而不是 21?
所以失败是什么意思?那么,在 API 21 上,如果您按下 播放 按钮, 没有声音播放 ,而且似乎什么也没有发生(尽管如果您窥视 logcat,您将看到各种可怕的本机库消息,包括许多 "deaths" 甚至 "tombstone" 或两个)。
如果您继续按 play 第二次,同样的事情会发生,在 logcat... 和第三次中会生成更多内部错误消息,你终于崩溃了。
我能看到的唯一可能与我控制的任何事情相关的错误是 Log.i:
中捕获的异常
"com.example.boober.stackqmediaplayer I/SFX:
error:java.io.IOException: Prepare failed.: status=0x64"
所有其他消息和最终堆栈跟踪均由内部本机库生成。
我用谷歌搜索了这些错误,阅读了 logcat 和 MediaPlayer 文档。希望我只是在做一些根本上愚蠢的事情,有人可以向我指出。
我真的希望有人能看一看。
我已经编写了一个非常简单的问题示例,在 Android Studio 中 cut/paste/rebuild 应该很容易,因此您可以重现该问题:
主要活动:
public class MainActivity extends Activity {
MediaPlayer ourPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ourPlayer = MediaPlayer.create(this, R.raw.soundeffect);
}
public void play(View v) {
try {
ourPlayer.stop();
ourPlayer.prepare();
ourPlayer.start();
} catch (IOException e) {
Log.i("SFX", "error:" + e.toString());
}
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:onClick="play"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="PLAY"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Res 文件夹(使用任何音频文件):
Build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.example.boober.stackqmediaplayer"
minSdkVersion 19
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
根据MediaPlayer's documentation:
ourPlayer
启动时处于initialized状态,而不是idle状态,因为你使用MediaPlayer.create()
.
从状态图中,您可以仅在播放器正在播放或暂停时调用stop()
,但是您的按钮单击方法第一次运行时,您的播放器仅被初始化,因此您的调用 stop()
会使您的播放器处于错误状态,而不是停止状态。
您对prepare()
的调用处于错误状态,IOException
或IllegalStateException
或可能被抛出。文档没有说明前者何时被抛出,但它在您的 try-catch
中被捕获。后者会使您的应用程序在您第三次单击播放按钮时崩溃。
解决方案是仅在播放器处于播放或暂停时调用stop()
,因为这些状态有指向停止状态的直接箭头。
MediaPlayer
有一个 isPlaying()
方法,但是没有 isPaused()
。可能会找到此问题的一些解决方法 here
下面是我提出的解决方案:
public class MainActivity extends Activity {
private MediaPlayer ourPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ourPlayer = MediaPlayer.create(this, R.raw.beer03);
}
@Override
protected void onDestroy() {
super.onDestroy();
ourPlayer.release(); // Free native resources
}
public void play(View ignored) {
try {
if (ourPlayer.isPlaying() || isPlaybackPaused()) {
ourPlayer.stop();
ourPlayer.prepare();
}
player.start();
} catch (IOException e) {
Log.i("SFX", "error:" + e.toString());
}
}
private boolean isPlaybackPaused() {
// TODO
}
}
万一有人和我有完全相同的用例...
只要按下按钮(或触发事件)时,您只需要从头开始播放声音...
我找到了另一个似乎有效的简单 solution/workaround:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ourPlayer = MediaPlayer.create(this, R.raw.soundeffect);
}
public void play(View v) {
if (ourPlayer != null) {
ourPlayer.release(); }
ourPlayer = MediaPlayer.create(this, R.raw.soundeffect);
ourPlayer.start();
}
如果出于某种原因这是个坏主意,请任何人告诉我!谢谢!
在我的应用程序中,我希望有一个简单的方法使用 MediaPlayer class 播放一次声音资源(如果需要,再播放一次)。
我已经成功地实现了这一点(见下面的代码)并在 API 23 的真实硬件设备上成功地测试了它,还在 API 等级从 19 一直到 P 的模拟器上进行了测试... 除了 API 21,失败的地方。
是不是很奇怪?为什么某些东西适用于 API 19... 和 23,而不是 21?
所以失败是什么意思?那么,在 API 21 上,如果您按下 播放 按钮, 没有声音播放 ,而且似乎什么也没有发生(尽管如果您窥视 logcat,您将看到各种可怕的本机库消息,包括许多 "deaths" 甚至 "tombstone" 或两个)。
如果您继续按 play 第二次,同样的事情会发生,在 logcat... 和第三次中会生成更多内部错误消息,你终于崩溃了。
我能看到的唯一可能与我控制的任何事情相关的错误是 Log.i:
中捕获的异常"com.example.boober.stackqmediaplayer I/SFX: error:java.io.IOException: Prepare failed.: status=0x64"
所有其他消息和最终堆栈跟踪均由内部本机库生成。
我用谷歌搜索了这些错误,阅读了 logcat 和 MediaPlayer 文档。希望我只是在做一些根本上愚蠢的事情,有人可以向我指出。
我真的希望有人能看一看。
我已经编写了一个非常简单的问题示例,在 Android Studio 中 cut/paste/rebuild 应该很容易,因此您可以重现该问题:
主要活动:
public class MainActivity extends Activity {
MediaPlayer ourPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ourPlayer = MediaPlayer.create(this, R.raw.soundeffect);
}
public void play(View v) {
try {
ourPlayer.stop();
ourPlayer.prepare();
ourPlayer.start();
} catch (IOException e) {
Log.i("SFX", "error:" + e.toString());
}
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button"
android:onClick="play"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:text="PLAY"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
Res 文件夹(使用任何音频文件):
Build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.example.boober.stackqmediaplayer"
minSdkVersion 19
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support.constraint:constraint-layout:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
根据MediaPlayer's documentation:
ourPlayer
启动时处于initialized状态,而不是idle状态,因为你使用MediaPlayer.create()
.从状态图中,您可以仅在播放器正在播放或暂停时调用
stop()
,但是您的按钮单击方法第一次运行时,您的播放器仅被初始化,因此您的调用stop()
会使您的播放器处于错误状态,而不是停止状态。您对
prepare()
的调用处于错误状态,IOException
或IllegalStateException
或可能被抛出。文档没有说明前者何时被抛出,但它在您的try-catch
中被捕获。后者会使您的应用程序在您第三次单击播放按钮时崩溃。
解决方案是仅在播放器处于播放或暂停时调用stop()
,因为这些状态有指向停止状态的直接箭头。
MediaPlayer
有一个 isPlaying()
方法,但是没有 isPaused()
。可能会找到此问题的一些解决方法 here
下面是我提出的解决方案:
public class MainActivity extends Activity {
private MediaPlayer ourPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ourPlayer = MediaPlayer.create(this, R.raw.beer03);
}
@Override
protected void onDestroy() {
super.onDestroy();
ourPlayer.release(); // Free native resources
}
public void play(View ignored) {
try {
if (ourPlayer.isPlaying() || isPlaybackPaused()) {
ourPlayer.stop();
ourPlayer.prepare();
}
player.start();
} catch (IOException e) {
Log.i("SFX", "error:" + e.toString());
}
}
private boolean isPlaybackPaused() {
// TODO
}
}
万一有人和我有完全相同的用例...
只要按下按钮(或触发事件)时,您只需要从头开始播放声音...
我找到了另一个似乎有效的简单 solution/workaround:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ourPlayer = MediaPlayer.create(this, R.raw.soundeffect);
}
public void play(View v) {
if (ourPlayer != null) {
ourPlayer.release(); }
ourPlayer = MediaPlayer.create(this, R.raw.soundeffect);
ourPlayer.start();
}
如果出于某种原因这是个坏主意,请任何人告诉我!谢谢!