Android BluetoothGattService - 异常无法编组
Android BluetoothGattService - exception cannot marshall
我遇到了以下问题。
我的应用架构大致由三个主要部分组成:
- 设备视图(显示可用的 BLE 设备)
- BLE 服务(处理 BLE 连接和数据)
- 服务视图(显示特定设备的服务)
我的问题是第二次将 BluetoothGATTService 传递给 Intent(第一次有效)。
或多或少的动作是这样的:
- 选择要为其显示服务的设备
- 将设备意图和要执行的操作发送到 BLEService
- BLEService 执行服务发现并发送带有服务的意图
- BroadcastReceiver 正确接收 intent 并使用接收到的 intent 中的数据开始新的 activity。
在最后一步出现问题,因为我无法将 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
我遇到了以下问题。
我的应用架构大致由三个主要部分组成:
- 设备视图(显示可用的 BLE 设备)
- BLE 服务(处理 BLE 连接和数据)
- 服务视图(显示特定设备的服务)
我的问题是第二次将 BluetoothGATTService 传递给 Intent(第一次有效)。
或多或少的动作是这样的:
- 选择要为其显示服务的设备
- 将设备意图和要执行的操作发送到 BLEService
- BLEService 执行服务发现并发送带有服务的意图
- BroadcastReceiver 正确接收 intent 并使用接收到的 intent 中的数据开始新的 activity。
在最后一步出现问题,因为我无法将 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