背景
在Android上使用MediaPlayer时发现某些情况下的会导致莫名的NPE:
java.lang.NullPointerException
at android.media.MediaPlayer$EventHandler.handleMessage(MediaPlayer.java:2398)
at android.os.Handler.dispatchMessage(Handler.java:110)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:5331)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:832)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:648)
at dalvik.system.NativeStart.main(Native Method)
但是这些机型都是非原生ROM,因此不好从源码判断是哪些出了问题,想到一个比较极端的方式,就是替换MediaPlayer内部的EventHandler的实例,然后try/catch住handleMessage方法。
一、首先看MediaPlayer的源码:
http://androidxref.com/4.4.2_r2/xref/frameworks/base/media/java/android/media/MediaPlayer.java#2181
发现MediaPlayer.EventHandler类是一个private的成员类
对于private的成员访问方式一种是反射,一种是通过API欺骗。但是这里因为要继承MediaPlayer.EventHandler,所以只能通过API欺骗。
二、构造SafeEventHandler
package android.media;
import android.os.Looper;
import android.os.Message;
public class SafeEventHandler extends android.media.MediaPlayer.EventHandler {
public SafeEventHandler(MediaPlayer mp, Looper looper) {
mp.super(mp, looper);
}
public void handleMessage(Message msg) {
try {
super.handleMessage(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:mp.super(mp, looper); 是用宿主类的实例去调用super来构造实例,写法比较特殊
三、替换MediaPlayer的mEventHandler
这里替换逻辑很简单,反射赋值即可。
一切看似很合理,但是一运行,在load class SafeEventHandler时直接抛出异常:superclass not accessible,字面意思就是父类无法访问。
第一反应是:API欺骗有问题了?于是写了简单的demo,在电脑上直接运行,一切正常。因此可以判断是DVM对Class Loader过程做了手脚。
于是全局grep了这个异常,发现异常抛出代码是:
http://androidxref.com/4.4.2_r2/xref/dalvik/vm/oo/Class.cpp#2677
} else if (!dvmCheckClassAccess(clazz, clazz->super)) {
ALOGW("Superclass of '%s' (%s) is not accessible",
clazz->descriptor, clazz->super->descriptor);
dvmThrowIllegalAccessError("superclass not accessible");
goto bail;
}
意思就是dvmCheckClassAccess验证失败
http://androidxref.com/4.4.2_r2/xref/dalvik/vm/oo/AccessCheck.cpp#125
bool dvmCheckClassAccess(const ClassObject* accessFrom,
const ClassObject* clazz)
{
if (dvmIsPublicClass(clazz))
return true;
return dvmInSamePackage(accessFrom, clazz);
}
dvmIsPublicClass函数在http://androidxref.com/4.4.2_r2/xref/dalvik/vm/oo/Object.h#734中定义。
首先会判断父类是否是public的,显然这里不是,因此进入下一个判断条件。
http://androidxref.com/4.4.2_r2/xref/dalvik/vm/oo/AccessCheck.cpp#39
/* quick test for intra-class access */
if (class1 == class2)
return true;
/* class loaders must match */
if (class1->classLoader != class2->classLoader)
return false;
首先判断是否同一个类,显然这里不是,再判断是否是相同的ClassLoader,这里有待确认。 因此直接打印出MediaPlayer.EventHandler的ClassLoader和SafeEventHandler的ClassLoader比较一下,发现,果然是因为ClassLoader不同导致的。 进一步分析发现,在Java中默认的缺省ClassLoader是java.lang.BootClassLoader,系统的库,比如Android的Framework的class都是缺省的ClassLoader,但是Android工程中自己写的Class,也就是最终放入Dex中的Class都是通过dalvik.system.PathClassLoader来loader的。因此当我们尝试去继承系统的private类的时候,在class load检查中会失败。
解决方案
既然已经找到失败原因了,解决起来就有了方向。
第一次尝试使用同一个ClassLoader去load两个类,但是失败了。因为两个MediaPlayer并不是在dex中,因此无法通过PathClassLoader去load。
那么是否可以修改MediaPlayer.EventHandler的ClassLoader和SafeEventHandler保持一致呢。
于是通过源码发现Dalvik的Class实现和ART的Class实现有所不同:
ART
http://androidxref.com/4.4.2_r2/xref/libcore/libart/src/main/java/java/lang/Class.java
Dalvik
http://androidxref.com/4.4.2_r2/xref/libcore/libdvm/src/main/java/java/lang/Class.java
ART中classloader就是一个成员变量,因此可以通过反射去修改,但是Dalvik中的classLoader是一个native方式,因此无法修改。
至于一次不完美的系统类class注入就完成了。