此贴是为了记录日常开发过程中,遇到的一些让人难忘的bug。

  • MediaPlayer: surface has already been released
  • kotlin ?.的陷阱与缺陷

surface has already been released

场景:MediaPlayer做跨进程的视频播放发现的,由于有无画面的后台播放场景,所以,MediaPlayer的相关操作放在独立进程的Service中,通过跨进程的调度,进行相关媒体操作,包括设置用于展示画面的Surface,Surface是一个Parcable类,所以是可以通过跨进程传输的。
归因:在播放进程中,用成员变量缓存了通过setSurface设置的surface变量,以便于从后台恢复前台播放时候,可以直接使用,而不需要再次传入surface参数。但是这样做是不可以的,会爆出surface has already been released的错误。
IPlayer.aidl如下:

1
2
3
interface IPlayer {
void setSurface(inout Surface surface);
}

需要注意的是,此处surface的修饰符包括一个inout。这样设置,是为了适配IjkMediaPlayer切换全屏的场景。如果把这个修饰符改成in,则IjkMediaPlayer在通过setSurface多次修改surface时候,也会爆出surface has already been released这样的问题。

kotlin ?.的陷阱与缺陷

?.是kotlin的空安全语法。相比Java的if语句判断,这样做要简洁的多,但是这里边有一种“陷阱”,这并不是kotlin本身的问题,而是使用者容易疏忽的问题。
展示逻辑的伪代码如下:

1
2
3
4
5
方法A(回调) {
结果1 = 动作1
结果2 = 动作2
回调(结果1, 结果2)
}

简单解释一下,一个方法A,带有一个Nullable的回调,需要执行动作1动作2,分别返回了结果1结果2,并且在回调中返回结果。
上述逻辑用kotlin实现如下:

1
2
3
4
5
6
7
8
// 方案一
fun action1(): Int = 1
fun action2(): Int = 2
fun methodA(block: ((Int, Int) -> Unit)? = null) {
val result1 = action1()
val result2 = action2()
block?.invoke(result1, result2)
}

或者

1
2
3
4
5
6
// 方案二
fun action1(): Int = 1
fun action2(): Int = 2
fun methodA(block: ((Int, Int) -> Unit)? = null) {
block?.invoke(action1(), action2())
}

如果你选择了方案二,那么你就掉入”陷阱”了。
因为方法内执行的逻辑,不应该收到回调的影响。如果在methodA中传入了null作为参数,或者直接调用methodA(),这样参数block就为null,那么方案二是不等价于方案一的,我只是不需要知道方法的执行结果,不是让你不执行方法。

本文采用CC-BY-SA-3.0协议,转载请注明出处
Author: boybeak