背景
由于一些xxxxoooo的原因,我需要修复一个跨进程Service可能会被外部传入intent数据导致拒绝服务的问题。当被告知这个问题的时候,我是一脸懵逼的。
复现这个问题的测试代码如下:
1 | Intent i = new Intent(); |
我看到的报错信息大概是这样的:java.lang.RuntimeException: Parcelable encountered ClassNotFoundException reading a Serializable object,堆栈崩在了Service的onStartCommand方法中。于是我开始对着onStartCommand里的一小段代码冥思苦想:
1 |
|
和Intent相关的就这些了。根据我看到的堆栈崩溃行数,就是intent.putExtra方法出现了ClassNotFoundException。实在搞不清原因,只能追踪一下看看了。
追根溯源
首先是putExtra方法:
1 | public Intent putExtra(String name, String value) { |
继续看putString方法:
1 | public void putString(@Nullable String key, @Nullable String value) { |
map的put应该没什么问题,那有可能有问题的就是unparcel方法了。继续看下去:
1 | /* package */ void unparcel() { |
如果是这个方法出问题的话,问题应该出在readArrayMapInternal方法上。继续查看这个方法(这个方法在Parcel类中):
1 | /* package */ void readArrayMapInternal(ArrayMap outVal, int N, |
可以看到,这里做的操作实际上是循环读值。因为是ClassNotFoundException,所以问题应该出在和ClassLoader有关的方法上,这里唯一使用了ClassLoader的就是readValue了。那么看一下readValue方法做了什么:
1 | public final Object readValue(ClassLoader loader) { |
由于我测试代码是构造了一个空实现的Serializable对象塞进来,所以相关的方法应该是readSerializable,继续看readSerializable做了什么事:
1 | private final Serializable readSerializable(final ClassLoader loader) { |
重头戏来了!崩溃日志在这里出现了!这里通过传进来的ClassLoader寻找是否有我传进来的空数据类,显然不可能有,于是ClassNotFoundException就非常稳定的出现了。
至此,问题的根源已经找到了。
总结
修复方式也简单,在对外暴露(跨进程)的Activity、Service或者Broadcast等需要用到intent的地方,不管是putXXX还是getXXX(getXXX也需要执行unparcel方法)操作,都加上try-catch抓住这个错误就可以了。说实话,这个问题我第一次遇到,这是一个安全性问题,算是让我记忆深刻了,以后不能再踩这个坑了。