这个故事需要从头说起,Jack接到了一个需求,要对Android系统增加开机铃声,
系统中没有设置开机铃声的地方,修改在native层开机动画启动的地方时,时间点不好控制,会出现铃声播放结束了,但是还没有开机,手机还是停留在开机动画上,这个时候,就是尴尬的等待。怎么还不开机~~
Jack 查看了开机流程,很机灵的想到了一个主意,在系统启动完成的时候,去调用音频播放。
效果还不错。在动画结束,画面切换到使用界面的时候,铃声一下子就开始播放了。这个用户体验还可以。毕竟铃声只有短短的几秒钟。
后来Jack跳槽了,。
当系统升级的时候,采用同样的方法,
开机铃声出现了截断,这个问题转手了几次,很不幸的让我来处理了
Jack是在AMS的SystemReady方法中调用播放方法playbootaudio,为了不影响系统进程,Jack还启动了一个线程来执行这个调用
public void systemReady(final Runnable goingCallback, BootTimingsTraceLog traceLog) {
...
new Thread(new Runnable(){
public void run(){
playbootaudio();
}
}).start();
...
}
Jack写的playbootaudio()方法看上去也没有问题,在其他地方调用这个方法进行测试,声音可以正常播放
private void playbootaudio(){
MediaPlayer mp = new MediaPlayer();
mp.setOnCompletionListener(new OnCompletionListener(){
public void onCompletion(MediaPlayer mpCallback) {
Log.d(TAG, "====== audio play onCompletion called");
mpCallback.release();
}
});
try {
Log.d(TAG, "====== audio play called");
mp.setDataSource("/system/media/audio.mp3");
mp.setAudioStreamType(AudioManager.STREAM_ALARM);//STREAM_MUSIC STREAM_ALARM
mp.prepare();
mp.start();
}catch (Exception e) {
Log.e(TAG, "===== playbootaudio error");
e.printStackTrace();
}
}
添加log进行测试,抓取开机时的log,发现Log.d(TAG, "====== audio play onCompletion called");并没有打印
也就是说onCompletion方法没有调用。
奇怪了,怎么会这样,
这个时候,多年的编程经验终于起了点作用,
难道是对象被释放了?看上去好像有这个可能。
于是在MediaPlayer的finalize()方法中添加log,
结果,还真是调到这里了
01-01 12:19:46.625528 1012 1020 D MediaPlayer: =========== MediaPlayer, finalize() called
现在就好办了,
代码修改为
private MediaPlayer mp;
private void playbootaudio(){
// MediaPlayer mp = new MediaPlayer();
mp = new MediaPlayer();
mp.setOnCompletionListener(new OnCompletionListener(){
public void onCompletion(MediaPlayer mpCallback) {
Log.d(TAG, "====== audio play onCompletion called");
mpCallback.release();
mp = null;
}
});
try {
Log.d(TAG, "====== audio play called");
mp.setDataSource("/system/media/bootaudio.mp3");
mp.setAudioStreamType(AudioManager.STREAM_ALARM);//STREAM_MUSIC STREAM_ALARM
mp.prepare();
//Log.d(TAG, "====== audio play mp.start()");
mp.start();
}catch (Exception e) {
Log.e(TAG, "===== playbootaudio error");
e.printStackTrace();
}
//Log.e(TAG, "===== end of playbootaudio()");
}
原来是局部变量被回收,导致不能播放铃声,
这是个非常经典的案例,
一般情况下,想找到这种例子都是比较困难的
踏破铁鞋无觅处得来全不费工夫
再来看看MediaPlayer的start方法
public
void
start()
throws
IllegalStateException {
//FIXME use lambda to pass startImpl to superclass
final
int
delay =
getStartDelayMs();
if (
delay ==
0) {
startImpl(); }
else {
new
Thread() {
public
void
run() {
try {
Thread.
sleep(
delay); }
catch (
InterruptedException e) { e.
printStackTrace(); }
baseSetStartDelayMs(
0);
try {
startImpl(); }
catch (
IllegalStateException e) {
// fail silently for a state exception when it is happening after
// a delayed start, as the player state could have changed between the
// call to start() and the execution of startImpl() } } }.
start(); } }
调用到startInmp方法
对应的JNI方法为
static
void
android_media_MediaPlayer_start(
JNIEnv *
env,
jobject
thiz){
ALOGV(
"start");
sp<
MediaPlayer>
mp =
getMediaPlayer(
env,
thiz);
if (
mp ==
NULL ) {
jniThrowException(
env,
"
java
/
lang
/
IllegalStateException
",
NULL);
return; }
process_media_player_call(
env,
thiz,
mp->
start(),
NULL,
NULL );}
这里没有使用
NewGlobalRef对Java层对象进行保留操作