Android NではPengindIntent経由のカスタムSerializable/ParcelableのインスタンスをgetExtraで取得できない


こんにちはsekitakaです。

Android開発のPendingIntent周りで少しハマったので現象と解決方法を共有します。

# 現象
明確なソースを見つけられていないのですが、Android 7系で以下のコードのように独自のSerializableクラスのインスタンスをputExtraで渡し,PendingIntentにしてAlarmManagerに登録したところ、getSerializableExtraではnullが返ってきてしまいます。
Android 4,5,6では動作していたので、Android 7特有の問題かなと思います。

## PendingIntent登録
```Java
Intent intent = new Intent(this, Receiver.class);
SerializablePlayer serializablePlayer = new SerializablePlayer(1, "Seki");
intent.putExtra("serializable_player", serializablePlayer);
intent.putExtra("result_function_id", 1); // 結果表示関数用
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 23, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = ((AlarmManager) getSystemService(Context.ALARM_SERVICE));
// 1秒後にbroadcastする
alarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000 * 1, pendingIntent);
```

## 結果
```
serializable_player:null
```

もっと悪いことにカスタムSerializable/ParcelableのインスタンスをputExtraすると、他のputExtraの値も失われてしまいます。
次の例では`extra_string`でputした値がnullになってしまいました。

## PendingIntent登録
```Java
Intent intent = new Intent(this, Receiver.class);
SerializablePlayer serializablePlayer = new SerializablePlayer(1, "Seki");
intent.putExtra("serializable_player", serializablePlayer);
intent.putExtra("result_function_id", 2); // 結果表示関数用
intent.putExtra("extra_string", "foo"); // nullになってしまう
intent.putExtra("extra_int", 23); // 値は渡される
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = ((AlarmManager) getSystemService(Context.ALARM_SERVICE));
// 1秒後にbroadcastする
alarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000 * 1, pendingIntent);
```

## 結果
```
serializable_player:null
extra_string:null
extra_int:23
```

# 原因と対策
原因のはっきりしたところは分かりませんが、PendingIntentとしてAlarmManagerに登録されてBroadcastされる過程で、IntentがAndroid OS側で管理されるようなタイミングがあるのかと。その時にアプリの独自のクラスをOSから参照できずにnullとされてしまうのかなと思います。(他の値は失ってほしくないですが)

## 対策
Serializable/Parcelableは使用しないで、一度`byte[]`に変換して`putExtra`します。
さいわいApache Commonsのライブラリに、`Serializable`と`byte[]`の相互変換のメソッドがあります。
それを利用して以下のようにすることで、無事Serializableのオブジェクトを渡すことができました。

### PendingIntent登録
```Java
Intent intent = new Intent(this, Receiver.class);
SerializablePlayer serializablePlayer = new SerializablePlayer(1, "Seki");
// byte[]化する
byte[] byteSerializablePlayer = SerializationUtils.serialize(serializablePlayer);
intent.putExtra("byte_serializable_player", byteSerializablePlayer); // 値は渡される
intent.putExtra("result_function_id", 3); // 結果表示関数用
intent.putExtra("extra_string", "foo"); // 値は渡される
intent.putExtra("extra_int", 23); // 値は渡される
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 3, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager alarmManager = ((AlarmManager) getSystemService(Context.ALARM_SERVICE));
// 1秒後にbroadcastする
alarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 1000 * 1, pendingIntent);
```

### 結果表示コード
deserializeします。

```Java
// deserializeする
Log.d(TAG, "byte_serializable_player:" + SerializationUtils.deserialize(intent.getByteArrayExtra("byte_serializable_player")));
Log.d(TAG, "extra_string:" + intent.getStringExtra("extra_string"));
Log.d(TAG, "extra_int:" + intent.getIntExtra("extra_int", Integer.MAX_VALUE));
```

### 結果
```
byte_serializable_player:{1:Seki}
extra_string:foo
extra_int:23
```

# まとめ
Parcelable/Serializableの値がnullになるのはいいとしても、他の値もnullになってしまうのはちょっといただけないですね。
このせいで`byte[]`までnullになってしまい、なかなか今回の解決方法を見つけることができませんでした。

検証に使用したコードは[GitHub](https://github.com/sekitaka/PendingIntentExtra)で公開しています。