此贴是为了记录日常开发过程中,遇到的一些让人难忘的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如下:

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

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

kotlin ?.的陷阱与缺陷

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

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

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

// 方案一
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)
}

或者

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

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