Android7.0使用PendingIntent传递自定义对象:ClassNotFoundException reading a Serializable obje

最近项目中场景中使用了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的异常,这个我们很熟悉啊,就是类无法再类加载器里面找到嘛!那为何找不到呢?我们来猜想一下:

  1. 是不是代码混淆的时候,忘记过滤了AlarmInfo了?
  2. 是不是这个自定义对象没有正确的序列化?
  3. 是不是类加载器(ClassLoader)所在的进程不一致,导致内存无法共享?
  4. 是不是系统版本的固件或者Rom原因导致无法反序列化对象?

作出猜想后,接下来我们就来验证了:

  1.    检查代码的混淆后发现AlarmInfo是正确的被过滤的,所以第一个疑问排除。
  2. 代码里面我们自定义类AlarmInfo是实现的Serializable接口,而且自定义对象里面没有包含其他自定义对象,所以第二个疑问排除。代码如下:
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、如果是固件的问题我们如何验证呢?这里目前我无法进行验证,但是通过查询资料发现有类似的解决方案:参考文献

这里面提到了固件的问题,分析如下

Android7.0使用PendingIntent传递自定义对象:ClassNotFoundException reading a Serializable obje_第1张图片

我们试着去验证这个方案是否可行,继续撸码

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的机制差异化的存在导致了种种问题,我今天遇到的问题只是冰山一角,还需要多了解里面的差异化和兼容性的问题,才会使我们的代码行云流水。

以上是个人研究的观点,如有错误,请纠正,谢谢!

 

你可能感兴趣的:(Android)