此贴是为了记录日常开发过程中,遇到的一些让人难忘的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,那么方案二
是不等价于方案一
的,我只是不需要知道方法的执行结果,不是让你不执行方法。