I have got following problem.
My application architecture more or less consits of 3 main parts:
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)
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;
}
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
}
There are two issues in play here: one causes everything to work fine when local broadcasts are used, another one makes serialization of BluetoothGattService
fail.
In order to pass data between two processes, it must be serialized (written to Parcel
). Bundle contents are serialized/deserialized lazily — as long as Bundle
object does not cross process boundaries or get stored on disk, all contained objects will be stored in memory (within a HashMap) and won't get serialized.
Local broadcasts never cross process boundaries, thus do not serialize/deserialize Intent extras. Inter-process communications (global broadcasts, asking ActivityManager to start an Activity) do.
In order to be passed between processes, Intent extras must be either Parcelable or Serializable, otherwise Android may treat them as opaque objects under some circumstances and will attempt to determine approach to their serialization (as Serializable/Parcelable/String etc.) at runtime (see writeValue) — and fail.
As for BluetoothGattService — it clearly didn't implement Parcelable after initial public release and was retroactively changed to implement Parcelable in API 24. This means, that there are devices in the wild, where that class does not implement Parcelable (even if it does in the source code of the latest Android version). Adding a parent to inheritance chain is not technically a breaking change as far as binary compatibility is concerned — Java Classloaders won't complain. But any code, that relies on parcelability of such class, will either fail during bytecode validation or cause ClassCastException/ArrayStoreException on older versions.
Java generics are not reified — when compiled to binary code, ArrayList<String>
and ArrayList<Parcelable>
look to ClassLoader the same: as ArrayList<?>
. You are not casting BluetoothGattService to Parcelable and neither is ArrayList (internally it stores it's contents in Object[]
). In order to serialize ArrayList, HashMap and similar classes, Android have to use reflection, and that fails in your case, because the runtime type of BluetoothGattService does not implement Parcelable on the device. Just add a code, that explicitly makes a cast (such as placing BluetoothGattService in Parcelable[]
) and you will see.
Also, even on devices, where it does happen to implement Parcelable prior to API 24, trying to serialize a BluetoothGattService is still bad idea (that wasn't officially public API, wasn't covered by CTS, and thus is completely untested).
I recommend you to refrain from passing BluetoothGattService via parcelization and find other way to approach your task (such as passing it's contents or storing an instance in singlton).