I have got following problem.
My application architecture more or less consits of 3 main parts:
My issue is with passing BluetoothGATTService to the intent second time (first time it works).
More or less actions are like this:
And on the last step there is a problem as I cannot put ArrayList to the intent/bundle as it causes exception:
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)
for me it's very very strange, as if the operation is unsupported, then we would get this exception on sending ArrayList of services from BLEService to the BroadcastReceiver, not now.
Source code:
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;
}
This works fine. Now
After recognizing the proper intent to handle this method is invoked
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
}
Can anyone explain me the mistery lying behind this? I just cannot understand why first code is just fine, while second fails with exception.
Already tried doing things in lots of different ways, but all of them failed. I was even exchanging bundles between intents as their contents are same, but this also failed, copying list items makes no difference.
EDIT
refering to the 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
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).