Android向Java Server发送序列化对象时触发ClassNotFoundException
我之前做跨端对象传输时也踩过类似的坑,你的问题核心是Java默认序列化机制的类路径绑定导致的,下面给你几个可行的解决思路,还有带Observable列表的对象传输建议:
一、解决ClassNotFoundException的核心方案
1. 共用统一的模型类模块(最推荐)
Java默认序列化会把类的全限定包名+类名写入数据流,所以服务器端会尝试加载Android端包路径下的ClienteFree,但服务器端的ClienteFree是自己包下的,自然找不到。
解决办法是把ClienteFree这类需要跨端传输的类,放到一个独立的纯Java模块(比如叫common-models)里,让Android项目和服务器项目都依赖这个模块。这样两端的类完全是同一个:全限定名一致、字段结构一致、serialVersionUID也相同,序列化和反序列化时就不会有路径问题了。
比如用Gradle的话,Android端和服务器端的build.gradle里都加上:
implementation project(':common-models')
这个方案最稳定,后续修改模型类也只需要改一次,两边同步更新。
2. 改用JSON序列化(灵活兼容)
如果不想共用模块,完全可以抛弃Java默认的序列化,改用JSON这种跨平台的格式,比如Gson、Jackson。这种方式彻底避开类路径的问题,甚至以后换其他平台(比如iOS)也能兼容。
举个简单的例子:
Android端序列化发送:
// 把ClienteFree转成JSON字符串 Gson gson = new Gson(); String clienteJson = gson.toJson(yourClienteFreeInstance); // 通过Socket输出流发送字符串 outputStream.write(clienteJson.getBytes(StandardCharsets.UTF_8));
服务器端接收解析:
// 读取Socket输入流的字符串 String receivedJson = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); // 转成服务器端的ClienteFree实例 Gson gson = new Gson(); ClienteFree cliente = gson.fromJson(receivedJson, ClienteFree.class);
3. 自定义序列化映射(不推荐,太麻烦)
如果一定要用Java默认序列化,可以在服务器端的ClienteFree类里实现readResolve()方法,手动处理类名的映射,但这个方式容易出错,维护成本高,除非特殊情况不建议用。
二、包含Observable列表的对象传输建议
直接传输Observable对象不是个好主意,因为Observable是和Java的观察者模式绑定的,跨端后里面的观察者逻辑完全失效,而且如果Observable的子类包含平台相关的状态,还会导致序列化失败。
正确的做法是:
- 把
Observable里的原始数据提取出来,用普通的List或者自定义的DTO(数据传输对象)来传输,比如把Observable<SomeData>换成List<SomeData>。 - 两端收到数据后,再各自把数据包装成
Observable实例。比如Android端用RxJava的话:
服务器端同理,根据自己的观察者框架来转换。List<SomeData> dataList = ...; // 从JSON解析得到的列表 Observable<SomeData> observable = Observable.fromIterable(dataList);
额外注意事项
- 如果坚持用Java默认序列化,一定要保证两端的类:全限定名完全相同、字段结构一致(包括字段名、类型、访问修饰符)、serialVersionUID完全相同。如果
serialVersionUID不一样,会直接抛出InvalidClassException。 - 不要在序列化类里包含平台相关的对象(比如Android的
Context、View,服务器端的ServletContext等),这些类无法跨端反序列化,要把它们标记为transient,或者在序列化前移除。
内容的提问来源于stack exchange,提问作者Flavio501

