Android BluetoothGattService - 异常无法编组

Android BluetoothGattService - exception cannot marshall

我遇到了以下问题。

我的应用架构大致由三个主要部分组成:

我的问题是第二次将 BluetoothGATTService 传递给 Intent(第一次有效)。

或多或少的动作是这样的:

在最后一步出现问题,因为我无法将 ArrayList 放入 intent/bundle,因为它会导致异常:

E/AndroidRuntime: FATAL EXCEPTION: main Process: com.projects.dawid.gattclient, PID: 4133 java.lang.RuntimeException: Parcel: unable to marshal value android.bluetooth.BluetoothGattService@ef62956 at android.os.Parcel.writeValue(Parcel.java:1337) at android.os.Parcel.writeList(Parcel.java:711) at android.os.Parcel.writeValue(Parcel.java:1284) at android.os.Parcel.writeArrayMapInternal(Parcel.java:638) at android.os.BaseBundle.writeToParcelInner(BaseBundle.java:1313) at android.os.Bundle.writeToParcel(Bundle.java:1096) at android.os.Parcel.writeBundle(Parcel.java:663) at android.content.Intent.writeToParcel(Intent.java:7838) at android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:2530)

对我来说这很奇怪,好像不支持该操作,然后我们会在从 BLEService 向 BroadcastReceiver 发送服务的 ArrayList 时得到这个异常,而不是现在。

源代码:

广播接收器代码的 BLE 服务

private void notifyServicesDiscovered(BluetoothGatt gatt) {
    Intent intent = new Intent();
    intent.setAction(BLEService.RESPONSE);
    intent.putExtra(BLEService.RESPONSE, BLEService.Responses.SERVICES_DISCOVERED);
    intent.putExtra(BLEService.Responses.DEVICE, gatt.getDevice());
    intent.putParcelableArrayListExtra(BLEService.Responses.SERVICES_LIST, getArrayListServices(gatt));
    LocalBroadcastManager.getInstance(mServiceContext).sendBroadcast(intent);
}

@NonNull
    private ArrayList<BluetoothGattService> getArrayListServices(BluetoothGatt gatt) {
        ArrayList<BluetoothGattService> services = new ArrayList<>();
        services.addAll(gatt.getServices());
        return services;
    }

这很好用。现在

向新的 activity 代码广播接收器

识别正确的意图后调用此方法

private void createViewWithServices(Intent intent) {
    Log.i(TAG, "creating new activity!");

    BluetoothDevice device = intent.getParcelableExtra(BLEService.Responses.DEVICE);
    ArrayList<BluetoothGattService> services =  intent.getParcelableArrayListExtra(BLEService.Responses.SERVICES_LIST);

    Intent serviceShowIntent = new Intent(mActivityContext, ServiceShowActivity.class);
    serviceShowIntent.putExtra(BLEService.Responses.DEVICE, device);
    serviceShowIntent.putParcelableArrayListExtra(BLEService.Responses.SERVICES_LIST, services); //this line causes error
    mActivityContext.startActivity(serviceShowIntent); // here exception is thrown
}

谁能给我解释一下这背后的奥秘?我只是不明白为什么第一个代码很好,而第二个代码却异常失败。

已经尝试过很多不同的方法,但都失败了。我什至在意图之间交换包,因为它们的内容相同,但这也失败了,复制列表项没有区别。

编辑

参考AndroidRuntime error: Parcel: unable to marshal value error. Difference is, that I am using objects of classes provided by android itself, that is BluetoothGATTService, which do implement parcelable interface refering to the https://developer.android.com/reference/android/bluetooth/BluetoothGattService.html

这里有两个问题:一个导致使用本地广播时一切正常,另一个导致 BluetoothGattService 的序列化失败。


为了在两个进程之间传递数据,必须进行序列化(写入Parcel)。 Bundle 内容是 serialized/deserialized 惰性的——只要 Bundle 对象不跨越进程边界或存储在磁盘上,所有包含的对象都将存储在内存中(在 HashMap 中)并且不会被序列化。

本地广播永远不会跨越进程边界,因此不会 serialize/deserialize Intent extras。进程间通信(全局广播,要求 ActivityManager 启动一个 Activity)。

为了在进程之间传递,Intent extras 必须是 Parcelable 或 Serializable,否则 Android 在某些情况下可能会将它们视为不透明对象,并将尝试确定其序列化的方法(如 Serializable/Parcelable/String 等)在运行时(参见 writeValue)——然后失败。


至于 BluetoothGattService — 它在初始 public 发布后显然没有实现 Parcelable 并且 was retroactively changed 在 API 24 中实现了 Parcelable。这意味着,有设备在wild,其中 class 没有实现 Parcelable(即使它在最新的 Android 版本的源代码中实现)。就二进制兼容性而言,将父级添加到继承链在技术上并不是重大更改 — Java 类加载器不会抱怨。但是任何依赖于这种 class 的可打包性的代码都将在字节码验证期间失败,或者在旧版本上导致 ClassCastException/ArrayStoreException。

Java 泛型是 not reified — 当编译为二进制代码时,ArrayList<String>ArrayList<Parcelable> 看起来与 ClassLoader 相同:与 ArrayList<?> 相同。您没有将 BluetoothGattService 转换为 Parcelable,也没有将 ArrayList 转换为(它在内部将其内容存储在 Object[] 中)。为了序列化 ArrayList、HashMap 和类似的 classes,Android 必须使用反射,但在您的情况下会失败,因为 BluetoothGattService 的运行时类型未在设备上实现 Parcelable。只需添加一段代码,明确进行转换(例如将 BluetoothGattService 放在 Parcelable[] 中),您就会看到。

此外,即使在 API 24 之前确实实现了 Parcelable 的设备上,尝试序列化 BluetoothGattService 仍然是个坏主意(这不是正式的 public API, 未被 CTS 覆盖,因此完全未经测试)。

我建议您不要通过 parcelization 传递 BluetoothGattService 并找到其他方法来处理您的任务(例如传递它 contents 或将实例存储在 singlton 中)。

可以通过LocalBroadcastReceiver发送BluetoothGattService