こんにちはsekitakaです。
Android開発のPendingIntent周りで少しハマったので現象と解決方法を共有します。
現象
明確なソースを見つけられていないのですが、Android 7系で以下のコードのように独自のSerializableクラスのインスタンスをputExtraで渡し,PendingIntentにしてAlarmManagerに登録したところ、getSerializableExtraではnullが返ってきてしまいます。
Android 4,5,6では動作していたので、Android 7特有の問題かなと思います。
PendingIntent登録
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登録
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登録
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します。
// 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で公開しています。