最近项目中场景中使用了PendingIntent去绑定闹钟服务的广播接收器,代码如下:
初始化闹钟:
private void initAlarmManager(Context context, AlarmInfo alarmInfo, int id) {
Intent intent = new Intent(context, MyReceiver.class);
Bundle bundle = new Bundle();
bundle.putSerializable("alarmInfo", alarmInfo);
intent.putExtras(bundle);
PendingIntent sender = PendingIntent.getBroadcast(context.getApplicationContext(), id, intent, 0);
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
am.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + alarm.getDistTime(), sender);
} else {
am.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + alarm.getDistTime(), sender);
}
}
闹钟服务唤醒广播接收器:
@Override
public void onReceive(Context context, Intent intent) {
WakefulIntentService.acquireStaticLock(context);
AlarmInfo alarm = (Alarm) intent.getSerializableExtra("alarmInfo");
if (alarm != null) {
//TODO you something
}
}
上述的处理方式在Android7.0以前是没有任何问题的,但是在Android7.0以后就出现了Crash:导致了闹钟功能无法正常使用
ClassNotFoundException reading a Serializable obje
这是什么情况?我自定义的对象不能反序列化成功?还是ClassNotFound。。。老规矩,出现问题不可怕,可怕的是不知道如何下手(啰嗦一下)。好了,既然程序抛出了ClassNotFound的异常,这个我们很熟悉啊,就是类无法再类加载器里面找到嘛!那为何找不到呢?我们来猜想一下:
作出猜想后,接下来我们就来验证了:
public class AlarmInfo implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String aTypeName;
private String bTypeName;
private String name;
private String time;
private String period;
private Boolean isRemind;
private Long distTime;
3.如果是类加载器不在同一个进程下无法共享的原因,那我们可以用代码验证:
private void initAlarmManager(Context context, AlarmInfo alarmInfo, int id) {
Intent intent = new Intent(context, MyReceiver.class);
Bundle bundle = new Bundle();
bundle.putSerializable("alarmInfo", alarmInfo);
intent.putExtras(bundle);
//把AlarmInfo的类加载器传递过去
intent.setExtrasClassLoader(AlarmInfo.class.getClassLoader());
//省略。。。。
}
运行程序,结果还是一样出现ClassNotFound Crash,说明并不是不同进程导致类加载器无法共享的问题,所以第3点疑问排除。
4、如果是固件的问题我们如何验证呢?这里目前我无法进行验证,但是通过查询资料发现有类似的解决方案:参考文献
这里面提到了固件的问题,分析如下
我们试着去验证这个方案是否可行,继续撸码
Serizlizable的解决方案:
public class SerializableUtils {
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(out);
os.writeObject(obj);
return out.toByteArray();
}
public static Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
ByteArrayInputStream in = new ByteArrayInputStream(data);
ObjectInputStream is = new ObjectInputStream(in);
return is.readObject();
}
}
Intent意图绑定自定义对象
Intent intent = new Intent(context, AlarmReceiver.class);
Bundle bundle = new Bundle();
try {
bundle.putByteArray("alarmInfo",SerializableUtils.serialize(alarmInfo));
} catch (IOException e) {
e.printStackTrace();
}
intent.putExtras(bundle);
广播接收器里面处理:
byte[] alarmBytes = intent.getByteArrayExtra("alarmInfo");
try {
AlarmInfo alarm = (AlarmInfo) SerializableUtils.deserialize(alarmBytes);
if (alarm != null) {
//TODO you something
}
} catch (Exception e) {
LogUtil.error(MyReceiver.class.getSimpleName(),"获取闹钟设置实例失败");
}
Parcelable的解决方案:自己代码的处理方式类似
public class ParcelableUtil {
public static byte[] marshall(Parcelable parceable) {
Parcel parcel = Parcel.obtain();
parceable.writeToParcel(parcel, 0);
byte[] bytes = parcel.marshall();
parcel.recycle();
return bytes;
}
public static Parcel unmarshall(byte[] bytes) {
Parcel parcel = Parcel.obtain();
parcel.unmarshall(bytes, 0, bytes.length);
parcel.setDataPosition(0); // This is extremely important!
return parcel;
}
public static T unmarshall(byte[] bytes, Parcelable.Creator creator) {
Parcel parcel = unmarshall(bytes);
T result = creator.createFromParcel(parcel);
parcel.recycle();
return result;
}
}
上面的处理代码验证通过,并且闹钟服务唤醒的时候能正常发送到广播接收器里面,所以第4点猜想得到了验证。
原理:通过绕过序列化的问题,不管是使用Serizlizable还是Parcelable序列化自定义对象的原理都是把自定义对象转换为字节数组传递到Intent的bundle里面,然后再从广播接收器里面转换成对象流出来。
总结:居于Android系统版本的更新迭代,Android系统的固件升级和Rom的机制差异化的存在导致了种种问题,我今天遇到的问题只是冰山一角,还需要多了解里面的差异化和兼容性的问题,才会使我们的代码行云流水。
以上是个人研究的观点,如有错误,请纠正,谢谢!