2016年2月28日日曜日

conent_available を true にすると foreground のときしか onMessageReceived() が呼ばれない

GcmListenerService では、特定の条件の場合 onMessageReceived() を呼ばずに自分で Notification を出すような処理になっています。

この条件が Google Play Services のバージョンによって異なり、7.8.0 から 8.4.0 に変更したときにはまったので調べた結果をまとめておきます。
(調べたのは 7.8.0 と 8.4.0 で、この間の他のバージョンは調べていません。)

7.8.0



7.8.0 では、送信する JSON に "notification" payload があり、その中の "icon" の値が null じゃない場合は onMessageReceived() が呼ばれずに GcmListenerService が Notification を出します。このときの Notification には "notification" payload で指定された "title" や "body" が使われます。"title" の値が空のときはアプリ名が利用されます。

notification payload の形式については https://developers.google.com/cloud-messaging/http-server-ref#notification-payload-support 参照

つまり
"notification" の "icon" == null → onMessageReceived()
"notification" の "icon" != null → GcmListenerService が Notification 発行


8.4.0



8.4.0 では条件が大きく変わります。
"content_available" が false (指定がない場合も false)かつ "notification" の "icon" == null の場合 onMessageReceived() が呼ばれます。そうでない場合、アプリが foreground にあれば onMessageReceived() が呼ばれ、background なら GcmListenerService が Notification を出します。

つまり
"content_available" == false && "notification" の "icon" == null → onMessageReceived()
"content_available" == true || "notification" の "icon" != null →
  アプリが foreground → onMessageReceived()
  アプリが background → GcmListenerService が Notification 発行

"content_available" を true にしていると、7.8.0 から 8.4.0 に変更したときに今まで background でも onMessageReceived() が呼ばれていたのに呼ばれなくなります。全然ドキュメントにも書いてないしはまりました。。。


7.8.0 のコード

GcmListenerService private void zzt(Bundle var1) { var1.remove("message_type"); var1.remove("android.support.content.wakelockid"); if(zza.zzu(var1)) { zza.zzay(this).zzv(var1); // "notification" のデータを使って Notification を発行している } else { String var2 = var1.getString("from"); var1.remove("from"); this.onMessageReceived(var2, var1); } } zza static boolean zzu(Bundle var0) { return zzb(var0, "gcm.n.icon") != null; } "gcm.n.icon" は "notification" payload の "icon" に対応している。


8.4.0 のコード

GcmListenerService private void zzq(Intent var1) { Bundle var2 = var1.getExtras(); var2.remove("message_type"); var2.remove("android.support.content.wakelockid"); if(zzb.zzy(var2)) { if(!zzb.zzaI(this)) { // 同じプロセスの Activity が foreground かチェック、foreground じゃない場合 if の中に入る zzb.zzc(this, this.getClass()).zzA(var2); // "notification" のデータを使って Notification を発行している return; } if(zzx(var1.getExtras())) { zza.zzh(this, var1); } zzb.zzz(var2); } String var3 = var2.getString("from"); var2.remove("from"); zzw(var2); this.onMessageReceived(var3, var2); } zzb static boolean zzy(Bundle var0) { return "1".equals(zze(var0, "gcm.n.e")) || zze(var0, "gcm.n.icon") != null; } "gcm.n.e" は "content_available" に対応している。 "gcm.n.icon" は "notification" payload の "icon" に対応している。

zzb static boolean zzaI(Context var0) { KeyguardManager var1 = (KeyguardManager)var0.getSystemService("keyguard"); if(var1.inKeyguardRestrictedInputMode()) { return false; } else { int var2 = Process.myPid(); ActivityManager var3 = (ActivityManager)var0.getSystemService("activity"); List var4 = var3.getRunningAppProcesses(); if(var4 != null) { Iterator var5 = var4.iterator(); while(var5.hasNext()) { RunningAppProcessInfo var6 = (RunningAppProcessInfo)var5.next(); if(var6.pid == var2) { return var6.importance == 100; } } } return false; } }


2016年2月21日日曜日

講演の準備について

Droid Kaigi 2016 を振り返ってふと講演前に何をしてきたのかを書いてみようと思いました。

Konifar氏に「あー」とか「えー」とかをほとんど言わずあまり噛まないとお褒めいただいたことで、どうしてそうなのか改めて考えてみたことがきっかけです。

1. CFP

直後ではなく、締め切り2週間〜1週間前くらいに出しました。
すでに提出されているCFPを確認して、かぶらないような内容を選びました。
私が確認した時点ですでにCamera2やRxJava、Kotlinなどがかぶっていました。

2. 話の流れを考える

本格的に考え出したのは本番1ヶ月前くらいからです。
細かい内容よりも全体の流れを先に考えます。
風呂に入っているときや寝る前に脳内リハーサル(イメージトレーニング)します。
マインドマップは書きません。以前の講演で試したこともあるのですが合わないのか続きませんでした。

3. 裏取りをする

人に伝える以上間違ったことは話せません。
以前やったことがあることでももう一度実際に書いて試してみます。
また、このときに調べ直して新たに知ったことも多いです。
この作業時にスライドに載せるコードやキャプチャも大まかに決めます。

4. 会場のサイズを知る

スライドの文字サイズを決めるために @mhidaka に担当部屋を下見したときの写真をみせてもらいました。
写真から
- 奥行きがかなりある
- スクリーンが左右に2枚
- スクリーンの下の方は少し見ずらい(このことは講演者へのメールにも記載されていました)
ことがわかりました。

スタッフと知り合いじゃないと難しいと思うので、できれば大きなイベントの開催者は講演者へのメールに会場の写真も添付してもらえると助かる人が多いと思います。

5. 資料作成

白地に黒の方が見やすい

特に文字が細い場合黒字だと会場を暗くしないと見るのが厳しいです。
私はスライドのビジュアル(絵的に美しいかどうか)よりも見やすいかどうかを優先しています。

会場のサイズから文字サイズはかなり悩みました。
最終的に重要なコードは 24pt 以上、文章は 32pt 以上になるようにしました。

私はスライドの作成にはほぼ Keynote を使っています。慣れによるところが大きいです。

発表者ノートを使うこともあります。今回は使っていませんが、エモーショナルなスライド(写真背景 + 格言みたいなやつ)では話す内容のヒントが少ないため、そのような内容の講演だと使うことがあります。

6. 脳内リハーサル(イメージトレーニング)

多分これが噛まない秘訣(?)だと思います。実リハーサルはほぼやらないのですが1週間前から寝る前の頭の中はほぼこれです。頭の中でしゃべってます。

7. 前日

2日目だったので、1日目の最後にお願いして実際の部屋でスライドを映すテストをさせていただけました。
コードに半透明の黄色でハイライトをいれていたのですが、実際に写してみるとハイライトをいれると元の文字が見にくいということがわかり、急遽赤線を入れる方向に変えました。

8. 本番

どうしても一部コードの文字が小さいスライドがあるので、事前にスライドを公開すると決めていました。本番10分前に公開して tweet &会場で始まるまでQRコードとURLをスライドで表示しました。

9. 終わった感想

今回は話の流れの組み立てにけっこう悩みました。AndroidフレームワークのTheme・Styleの機能と AppCompat 独自の機能は別ですが、AppCompat は Material Design の見た目を実現するのにフレームワークの機能を使っています。そこをカスタマイズするときは AppCompat がどのような設定を行っているのかを知る必要があるため、AppCompat の設定値の話が多くなりました。
後から考えるともう少しAndroidフレームワークのTheme・Styleの機能についての説明も入れたほうがよかったなと思いました。(?attr/と@style/とandroid:のありなしについて後で質問されたので)

今回は想定以上にスライドの枚数が多くなり早口だったと思います。42分くらいで話終わってしまったのでもう少しゆっくり話せばよかったと反省です。枚数が多い時の時間配分は難しいですね。


2016年2月19日金曜日

Droid Kaigi 2016 で講演してきました。

2016年2月18日、19日に開催された Droid Kaigi 2016 で講演してきました。

スタッフの皆様、講演を聞きに来てくれた参加者のみなさま、ありがとうございました。

需要の高そうな StatusBar の透明化については別途ブログのエントリーにしました。
StatusBar 透明化の正しい方法 : Y.A.M の雑記帳



StatusBar 透明化の正しい方法

各属性についての説明や、なぜこのような設定になっているのかは Droid Kaigi 2016 の発表資料の 121p 以降 を参照してください。

values/styles.xml <resources> <style name="Theme.AppTheme.TranslucentStatusBar" parent="Theme.AppCompat.Light.NoActionBar" /> </resources> values-v19/styles.xml <resources> <style name="Theme.AppTheme.TranslucentStatusBar" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowTranslucentStatus">true</item> </style> </resources> values-21/styles.xml <resources> <style name="Theme.AppTheme.TranslucentStatusBar" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:statusBarColor">@android:color/transparent</item> </style> </resources> values-23/styles.xml <resources> <style name="Theme.AppTheme.TranslucentStatusBar" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:statusBarColor">@android:color/transparent</item> <item name="android:windowLightStatusBar">true</item> </style> </resources>

public class SplashActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { findViewById(android.R.id.content).setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); } setContentView(R.layout.activity_splash); } } <activity android:name=".SplashActivity" android:theme="@style/Theme.AppTheme.TranslucentStatusBar" />

上記のような指定を行うと次のような見た目になります。

4.3以下


4.4


5系


6系



2016年2月2日火曜日

Theme.NoDisplay は onCreate() で finish() する Activity 以外で使うと Android 6.0 でクラッシュする

Issue 2353: Activity crash with @android:style/Theme.NoDisplay : android-developer-preview

Android 6.0 から onCreate() で finish() していない Activity に Theme.NoDisplay をセットすると startActivity() したときにクラッシュします。 Theme.Translucent や Theme.Translucent.NoTitleBar はクラッシュしません。クラッシュ時の Exception は IllegalStateException で、メッセージは did not call finish() prior to onResume() completing です。

この原因となるテーマ属性は windowNoDisplay です。デフォルトは false ですが、Theme.NoDisplay では true がセットされています。つまり
onCreate() で finish() していない Activity のテーマで windowNoDisplay が true だとクラッシュします。

次のように onCreate() で finish() していればクラッシュしません。 public class NoDisplayActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); finish(); } } <activity android:name=".NoDisplayActivity" android:theme="@android:style/Theme.NoDisplay" /> windowNoDisplay のドキュメントには
if set to true, and this window is the main window of an Activity, then it will never actually be added to the window manager. This means that your activity must immediately quit without waiting for user interaction, because there will be no such interaction coming.
とあるので、正しい挙動になったと言えるのでしょう。

- 関連 - 参考