2012年7月26日木曜日

Android Honeycomb 以降ではダイアログの外部タッチで閉じる、がデフォルトになっている

ダイアログの周りの領域をタッチしたときにダイアログを閉じるかどうかは setCanceledOnTouchOutside() で設定できます。

Honeycomb 以降では、ダイアログの外部をタッチしたときにダイアログが閉じるようになっているので、それが嫌な場合は Dialog のインスタンスに対して setCanceledOnTouchOutside(false) を呼べば OK です。

DialogFragment なら onActivityCreated() で getDialog() で Dialog のインスタンスを取得できます。


なんでこうなってるのかを調べたら意外に深かった。。。

まず、Dialog の setCanceledOnTouchOutSide() は内部では Window に対して setCloseOnTouchOutside() を呼んでいます。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/app/Dialog.java#1059
1059 public void setCanceledOnTouchOutside(boolean cancel) { 1060 if (cancel && !mCancelable) { 1061 mCancelable = true; 1062 } 1063 1064 mWindow.setCloseOnTouchOutside(cancel); 1065 }

Dialog クラス内では setCanceledOnTouchOutside() を呼んでいません。 検索しても呼んでるのは AlertDialog.Builder、TimePickerDialog、SerchDialog のみ。

ちなみに AlertDialog.Builder では create() 時にキャンセル可能なら true をセットしています。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/app/AlertDialog.java#917
912 public AlertDialog create() { 913 final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false); 914 P.apply(dialog.mAlert); 915 dialog.setCancelable(P.mCancelable); 916 if (P.mCancelable) { 917 dialog.setCanceledOnTouchOutside(true); 918 } 919 dialog.setOnCancelListener(P.mOnCancelListener); 920 if (P.mOnKeyListener != null) { 921 dialog.setOnKeyListener(P.mOnKeyListener); 922 } 923 return dialog; 924 }

次に Window の setCloseOnTouchOutside() を見たら

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/view/Window.java#setCloseOnTouchOutside 123 private boolean mCloseOnTouchOutside = false; 124 private boolean mSetCloseOnTouchOutside = false; 812 public void setCloseOnTouchOutside(boolean close) { 813 mCloseOnTouchOutside = close; 814 mSetCloseOnTouchOutside = true; 815 } 816 817 /** @hide */ 818 public void setCloseOnTouchOutsideIfNotSet(boolean close) { 819 if (!mSetCloseOnTouchOutside) { 820 mCloseOnTouchOutside = close; 821 mSetCloseOnTouchOutside = true; 822 } 823 }

mCloseOnTouchOutside に値をセットしている。この変数の初期値は false で、値をセットしているのは setCloseOnTouchOutside() と setCloseOnTouchOutsideIfNotSet() だけ、このどちらかがデフォルトで呼ばれていないと初期値が false なのでつじつまが合わない。

setCloseOnTouchOutside は Dialog と Activity からしか呼ばれていないし、Activity で呼んでいるところは関係なかった。

setCloseOnTouchOutsideIfNotSet で検索したら AlertController と PhoneWindow から呼ばれてる。 PhoneWindow を見ると

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java#2567 2567 if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion 2568 >= android.os.Build.VERSION_CODES.HONEYCOMB) { 2569 if (a.getBoolean( 2570 com.android.internal.R.styleable.Window_windowCloseOnTouchOutside, 2571 false)) { 2572 setCloseOnTouchOutsideIfNotSet(true); 2573 } 2574 }

ビンゴ。ここで HONEYCOMB がでてくる。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/res/res/values/themes.xml#174 <style name="Theme"> ... <item name="windowCloseOnTouchOutside">false</item> </style> <style name="Theme.Dialog"> ... <item name="android:windowCloseOnTouchOutside">@bool/config_closeDialogWhenTouchOutside</item> </style> <style name="Theme.Dialog.NoFrame"> ... <item name="android:windowCloseOnTouchOutside">false</item> </style> <style name="Theme.Toast" parent="@android:style/Theme.Dialog"> ... <item name="android:windowCloseOnTouchOutside">false</item> </style> http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/res/res/values/config.xml#93 <bool name="config_closeDialogWhenTouchOutside">true</bool>

なるほど。

ということは、DialogFragment でオリジナルテーマ設定してるなら

<style name="MyDialogTheme" parent="@android:style/Theme.Holo.Light.Dialog"> ... <item name="android:windowCloseOnTouchOutside">false</item> </style>

とかして

@Override public Dialog onCreateDialog(Bundle savedInstanceState) { return new Dialog(getActivity(), R.style.MyDialogTheme); }

とすれば、このテーマを使ってるもろもろの DialogFragment を一括で変更できたー!



2012年7月20日金曜日

Google I/O 2012 セッションまとめ : Multi-Versioning Android User Interfaces

Multi-Versioning Android User Interfaces

どうして"最新の Android だけに対応する"ということができないのでしょうか?

円グラフを見ると、ICS 以降の部分は少なく、大部分のユーザーを取り込むには
 Froyo + Gingerbread + ICS + Jelly Bean
に対応しないといけません

(個人的には、このグラフをみると 2.1 も対応してもいいような気もするけど、、、)

進化を完全に避ける開発者がいます。新しいパーツが用意されていてもそれを忘れて古いパーツを使い続けます。 アプリが一貫性を持つのは大事なことですが、その一貫性を理由にするのはナンセンスです(その他の場所ではその一貫性(古いパーツのこと)は廃止されているので)。

バージョン毎の APK を用意するのはどうでしょうか?

multiple APK は普遍的によくないアプローチだと言っているわけではありません。この機能は各バージョン毎の APK を持つために乱用されるべきではない、ということです。

なるべく1つの APK にした方がいいことがわかったが、レイアウトやリソースをバージョン毎に分ける開発者がいます。
バージョン毎にレイアウトやコードを分けるのはクレイジーな方法です。我々はこれを推奨しません。
(メジャーなバージション毎にレイアウトやコードを分けるのはいいですが、全部に対してやる、やりすぎるのはよくないという意味)




■ Parallel Activity Pattern

基本的な multiversioning skills として parallel activity と呼ばれる非常に一般的な方法があります。 Intent i = null; if(android.os.Build.VERSION>SDK_INT >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { i = new Intent(this, ShinyCoolActivity.class); } else { i = new Intent(this, LegacyAnctivity.class); } プラットフォームのバージョンによって起動する Activity を変える簡単でシンプルな方です。

これは Activity が本当に本当に多くの新しいプラットフォームの機能に依存していて、他のバージョン用のまともなフォールバック(代替)を持ちたい場合にはいい方法です。 ICS 以前・以後のようなメジャーなプラットフォームの境界をまたぐ必要がある場合のみ使うべきです。なぜなら、あまりにも多くの分岐があると急速に複雑になるからです。


複雑さについて話をすると、ときどき開発者は特定の機能を複数のプラットフォームバージョンに実装するために別々のクラスに書きますが、ほとんど同じコードで異なるのはほんの数行だったりします。


こういう状況では、あなたのコードと正気を同時に維持するのは不可能です。選択しなければなりません。 これはいいアイディアではありません。


if を使うのは恥ずかしいことではありません。if は素晴らしいです。


これらのプラットフォームバージョンの定数が古いプラットフォームバージョンで壊れる(なぜならその時にはその定数はなかったから)ことを心配していますか? その通り。これは確かに心配することの1つです。

しかしこの場合は問題ありません。なぜならこれらはコンパイル時に純粋な数字に置き換わるからです。 1つめのアドバイスは if を使うことを恐れないこと。2つめのアドバイスは if を恐れることです。 これらのコードの分岐があまりにも大きくなったら、よりよいアプローチが必要です。


■ lazy

lazy loading をつかいます。
数年前に Android Developer's blog にこのアイディアの詳細を書きました(How to have your (Cup)cake and eat it too)。

Abstraction and lazy loading
・abstract class
・implementations
・load when needed

まず、アプリケーションが行いたいことを記述する abstract class もしくは interface を定義するところから始めます。 つぎに、新しい機能が使えるようになったプラットフォームバージョン毎にそのインタフェースの実装を書きます。

シンプルなファクトリーメソッドや、それに似たメカニズムで、プラットフォームバージョンにあった正しい実装を取得するようにします。こうすると、アプリの中で読んでいるコードはバージョンの違いを考慮しなくてすむようになります。 public abstract class VersionedLoremIpsum { public abstract String doLorem(); public abstract int doIpsum(); } public class EclairLoremIpsum extends VersionedLoremIpsum { public String doLorem() { // do lorem, Eclair-style } public abstract int doIpsum() { // deliver ipsum, a la Eclair } } 古いバージョン用のフォールバックでは、合理的にエミュレートやバックポートできない機能を落とすことがあります。また、合理的にできることがない場合、これらの互換性実装がただのスタブや no ops になってもいいでしょう。 public class FroyoLoremIpsum extends EclairLoremIpsum { public string doLorem() { String l = super.doLorem(); // additional processing; return l; } public abstract int doIpsum() { ... 新しいバージョンのプラットフォームでは、ユーザー体験を拡張する付加的な機能を加えることができます。ここでは super class の実装を呼んでいます。実装の重複を減らして、ベースの振る舞いに付加機能を重ねるのにいい方法です(特に、新しいものに全体を入れ替える場合でないのなら)。 VersionedLoremIpsum li; int sdk = Build.VERSION.SDK_INT; if (sdk <= Build.VERSION_CODES.ECLAIR) { li = new EclairLoremIpsum(); } else if (sdk <= Build.VERSION_CODES.FROYO) { li = new FroyoLoremIpsum(); } else { li = new GingerbreadLoremIpsum(); } 簡潔なバージョンチェックで正しいバージョンのインスタンスを生成することができ、残りのコードはバージョンの違いを無視することができます。

Android Support library はまさにこれと同じ戦術を使っています。

これらの、非常に焦点をあてている特別な場合(利用できる場合に、より高度な機能を重ねる場合)には、このセッションで紹介した multiversioning のアプローチを使って提供します。 もし、これらをテクニックをアプリのより小さく独立したコンポーネントに適用したい場合でも、多くの重複を含む大きな包括的アプローチを使うよりはずっと簡単に利用できます。


■ resource system

-vN リソース識別子を使う。

res/layout-v11/foo.xml

res/layout/foo.xm

layouts, drawables, styles などをプラットフォームバージョンの応じて切り替えますが、これを使うには加減が必要です。やり過ぎると多くの XML の重複が起こります。これはコードの重複と同じように悪いことです。

これを避ける戦略はなにかあるでしょうか?

最初の戦略は boolean リソースを使うことです。

res/values-v14/bools.xml true false values-v14 なので、ICS 以降はこのリソースを使います。

res/values/bools.xml false true 残り、つまり ICS 未満ではこのリソースを使います。

次のようのコードをの切り替えにも使えますが、プラットフォームバージョンに応じてコンポーネントの有効・無効を切り替えるのに使うことができます。 Resources res = getResources(); boolean postICS = res.getBoolean(R.bool.postICS); if(postICS) { // do something cool and cutting-edge } else { // do something old-school but elegant! } 例えば AppWidget に使えます。AppWidget は基本的には特別なデータを持っているブロードキャストレシーバーです。 <receiver android:name="MyAppWidget"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/my_appwidget_info" /> /> このように書くと、全てのプラットフォームバージョンで同じ見た目、同じ振るまいになります。 しかしバージョンによって異なるものを提供したい場合はどうしたらいいでしょう?ここで boolean リソースを使います。 <receiver android:name="MyPreICSAppWidget" android:enabled="@bool/preICS"> <intent-filter ... /> <meta-data android:name="android.appwidget.provider" android:resource="@xml/my_pre_ics_info" /> /> <receiver android:name="MyPostICSAppWidget" android:enabled="@bool/postICS"> <intent-filter ... /> <meta-data android:name="android.appwidget.provider" android:resource="@xml/my_post_ics_info" /> /> デバイスで動いているプラットフォームのバージョンによって自動的にこれらのブロードキャストレシーバーの有効・無効が切り替わります。


■ Activity UI fallbacks

ICS 以降では Switch を使い、それ以前では Checkbox を使いたい場合はどうすればいいでしょう?



res/layout/main.xml include を使ってバージョンに依存する部分だけ分割するようにします。

res/layout/desserts.xml <merge ...> <CheckBox .../> <CheckBox .../> <CheckBox .../> ... </merge> res/layout-v14/desserts.xml <merge ...> <Switch .../> <Switch .../> <Switch .../> ... </merge> (セッションのスライドでは layout-v11 となっているが、Switch は API Level 14 から)

CheckBox も Switch も CompoundButton を継承しているので、次のように単一のコードで対応できます。 CompoundButton cb = (CompoundButton) findViewById(R.id.myoption); checked = cb.isChecked(); include は重要なテクニックです。プラットフォームのバージョンだけでなく画面サイズなどいろいろな場合に使えます。


■ Multiversioning Themes

値をハードコーディングしないこと。文字サイズ、余白などなど。

× android:textSize="20sp"

○ android:textAppearance="?android:attr/textAppearanceLarge"

これでもいいですが、テーマを使えばデフォルトの値を設定することができます。


テーマでは元のスタイルを継承しましょう。

× <style name="MyButtonStyle">

○ <style name="MyButtonStyle" parent="@android:style/Widget.Holo.Button">

問題は Holo と DeviceDefault テーマは Honeycomb 以前には存在しないということです。

次のように ICS 以前と以後で見た目を変えたい場合、テーマの継承先を変えます。



res/values/styles.xml res/values-v11/styles.xml これだと各コンポーネントを定義しないといけなくなります。よりよい方法は、ベーステーマで継承先を変えることです。

res/values/styles.xml res/values-v11/styles.xml res/values/styles.xml


■ creating your own theme attributes

以前のバージョンでは存在しない属性を利用したい場合、独自の属性を使って切り替えるようにします。 res/values/attrs.xml res/values/themes.xml @drawable/fallback_item_background res/values-v11/themes.xml ?android:attr/selectableItemBackground

どのテーマを継承すべきでしょうか?

・Holo - stable/easy
・Device Default - tighter integration
・Activity Content: holo-ish


■ Support Library and Beyond

App Compat は support library に新しく追加される部分です。既存の v4 と v13 のコンポーネントに追加されます。
これまでにリリースしている support library はあなたのアプリを multiversion 対応にするための abstract tools を提供しています。
App Compat のエレメントは、複数のプラットフォームにまたがって Android Design ガイドラインに沿う手助けをするようになっています。

support library を使うと ICS 以前でも Fragment を使うことができます。
Fragment を継承したクラスを作る場合や、レイアウト XML で Fragment を定義する方法は Honeycomb 以降と support library で同じです。
ただし、Fragment を使う Activity は注意が必要です。
support library では Activity ではなく、FragmentActivity を継承する必要があります。


■ Notification

Notification は新しいバージョンではさまざまな機能が追加されています。

Honeycomb 以前は直接パラメータをセットし、manager にセットしていました。 Notification notif = new Notification(icon, tickerText, when); notif.setLatestEventInfo(context, contentTitle, contentText, contentIntent); mNotificationManager.notify(MY_NOTIF_ID, notif);

Honeycomb 以降は、より複雑になっていて、Notification.Builder を使って作成します。

Notification notif = new Notification.Builder(this) .setSmallIcon(R.drawable.ic_stat_notify_example) .setAutoCancel(true) .setTicker(getString(R.string.notification_text)) .setContentIntent(myContentIntent) .setNumber(7) .setContentTitle(getString(R.string.app_name)) .setContentText(getString(R.string.notif_text)) .getNotification();

support library の NotificationCompat.Builder を使えば Honeycomb 以前でも Notification.Buidler を使うことができます。

android.support.v4.app.NotificationCompat.Builder

しかし JellyBean についてはどうでしょうか?

JellyBean では Notification に多くの機能が追加されています。ボタンが追加できるようになっていたり、スタイルがセットできるようになっています。

builder.setStyle(style);

・Notification.BigTextStyle
・Notification.InboxStyle
・Notification.BigPictureStyle

他にも
・setPriority
・setUsesChronometer
・setSubText
・etc.

残念ながら、Jelly Bean の Notification 機能はまだ support library に入っていません。よって Jelly Bean のいいところを使いつつ、後方互換性も確保するなら、次のようにバージョンに応じて使い分ける必要があります。

・if version >= JellyBean
  ・use Notification.Builder
・else
  ・use NotificationCompat.Builder

Notification の他の重要なポイントとしては、アイコンがあります。



Foryo 以前は黒い背景に白でした、Gingerbread では灰色、Honeycomb 以降では白です。 Android Asset Studio (j.mp/androidassetstudio)を使うのがいい方法です。


■ Action Bar

Acition Bar は2つの部分からなります。1つめが Navigation、ここは通常タブやスピナー、up ボタンなどです。2つめ Actions です。Actions は action ボタンに分かれます。これらはバーの上に常に表示されており、action overflow は右側の3つのドットボタンの後ろに隠れています。

デザインガイドラインの FIT ルールを思い出してください。frequent(頻繁に使う)、important(重要)、typical(典型的な機能)なものは画面上に表示するべきです。これに当てはまらない場合は常に、画面上の貴重な場所を取らない、どこか他の場所に挟み込めないか考えてください。overflow はそのための場所の1つです。

Honeycomb 以前では ActionBar を使うことができません。

自分でそのバージョン用のを実装するときのいいスタートポイントは action bar の compat sample です。 Action Bar Sherlock のような third-party library を使うこともできます。
いいニュースがあります。まもなく action bar を Honeycomb 以前でも作れるようになる AppCompat library が使えるようになります。support library で Fragment を使うのと同じくらい簡単になります。



FragmentActivity と同じような方法で使います。ActionBarActivity は FragmentActivity を継承しており、これらの間のコンフリクトなどを心配する必要はありません。



Action Bar は App Compat library のパーツとして利用できるようにしようと我々が計画しているものの始まりの1つにすぎません。

・Styles and themes
・Common layouts
・Extra theme attributes to query

我々が今日話したことは便利ですが、心からコードの重複を望んでいる人はいません。これらをまとめて、単にアプリに追加して利用できるようにしたいのです。


■ Miscellaneous tips

Option panel を変えない



Option panel はシステムで一貫して使われるものなので、スタイルが異なるとユーザーが混乱してしまいます。 システムが用意したスタイルに合うアセットを常に提供してください。


Options menu は Action Overflow です。 ほとんどの新しいデバイスは Menu キーがありません。すべての action は FIT ルールに沿うかレイアウト上においてください。それ以外はプラットフォームのバージョンに応じて、 options panel か action overflow に置いてください。


アンカーとなるエレメントについてです。ダイアログはその1つです。これはシステムレベルのインタラクションを示しています。クリティカルな yes/no などです。これらを使う場合は控えめにしてください。ポップアップはすごくうるさいものです。ダイアログがユーザー体験の中心部分になってはいけません。プラットフォームに応じて適用されるデフォルトのスタイルを変えないでください。

特定の理由によってダイアログのスタイルを変える誘惑に逆らえなかった場合、本来ダイアログですべきではないことをダイアログで達成しようとしていることを示しているのかもしれません。 それは実際は Activiy の一部であったり、別の Activity であるべきものでしょう。つまり、ダイアログのスタイルを変えないでくださいということです。

ICS 以前と以後で positive ボタンと negative ボタンの順番が変わっています。



後方互換性を保つためには、プラットフォームでのボタンの順番と同じになるように気をつけてください。 これらのボタンの順番の問題は、App Compat library で我々が提供したいと思っているものの1つです。 システムが提供しているダイアログとカスタムレイアウトのダイアログを使っているアプリなどでは、アプリ内で一貫性が保たれていないことがあります。


■ set min and target SDK versions

・android:minSdkVersion
・android:targetSdkVersion

常に minSdkVersion と targetSdkVersion を指定するようにしてください。minSdkVersion にはアプリが動く最小のバージョンを指定してください。
targetSdkVersion は指定するのを忘れる人や、古すぎるバージョンを指定する人が多いです。しかし target SDK はアプリが動作するのに必要な 最小の SDK には影響しません。targetSdkVersion にはアプリが動くことをテスト済みの最新のバージョンを指定してください。つまり今なら JellyBean(16)です。 システムは targetSdkVersion に応じて多くの互換性パーツの有効・無効を切り替えるからです。例えばデフォルトのテーマやメニューキーなどに影響します。


2012年7月12日木曜日

Google I/O 2012 セッションまとめ : So You've Read the Design Guide; Now What?

So You've Read the Design Guide; Now What?

デザインガイドラインを実際に使ってアプリをよりよくする方法についてのセッション


■ Standardize in DPs
  • mdpi で 1px = 1dp (mdpi : 160, hdpi : 240, xhdpi : 320)
  • xhdpi は mdpi の 2倍(Galaxy Nexus は xhdpi)

  • Galaxy Nexus : 360 x 640 dp
  • Nexus 7 : 600 x 960 dp
  • Xoom : 800 x 1280dp

  • dp 単位でコミュニケーションするようにする(デザイナー、開発者間)と間違いが減る
  • 4dp Grid(Metrics and Grids
  • ステンシルを使う(ここからダウンロードできる)
  • Android Design Preview tool : デスクトップ上の領域をデバイス上にミラーリングできる

  • LinearLayout のネストをさける
    RelativeLayout や GridLayout(support package に backport された) を使う
  • LinearLayout での weight の使い方
    weight で分配されるのは android:width や android:height の実際のサイズの残り


■ Holoテーマのカスタマイズ
  • ベースのテーマを選ぶ(Light, Dark, Light with Dark Action Bar)
  • Action Bar の色を変える(ハイライトと影を忘れずに)
  • アプリのアイコンではなくロゴを使うという選択肢
  • ボタンやチェックボックスをカスタマイズする際は state に対応することをを忘れずに
    Normal, Focused, Pressed それぞれの on/off で6スタイルは最低用意すること
  • 全てのコンポーネントをカスタマイズすることができるが、手間がかかる(システムは様々な解像度用の画像などをあらかじめ用意している)
  • オススメはメイン画面を変えること(例えば YouTube アプリはほとんどがデフォルトの Holo テーマだが、HDボタンの下線、動画のシークバー、タブに赤色を使っている)
  • システムデフォルトのカラーを押せる部分の背景に使うには selectableItemBackground 属性を利用する android:background="?android:attr/selectableItemBackground"
  • Focused 状態のテストにはキーボードを使うとよい(Bluetooth キーボードとか)


■ Iconography
  • Icon Pack (ここからダウンロードできる。ベクター形式になってる)
  • 各解像度用のアイコンを用意するのがよいが、それをしたくない場合は一番高い解像度のアイコンを用意する

  • 新しい Icon wizard を使う([New] - [Other] (もしくは Command + N)-[Android] - [Android Icon Set])
  • Android Icon Set で Notification アイコンを作成しようとすると、version に応じて適切な背景色や文字色にしてくれる


  • Lint ツールで画像の重複や画像のサイズ(ActionBar のアイコンとして適切なサイズかなど)をチェックしてくれる


■ Fragment
  • 7inch は tablet と phone の中間。どっちのように振る舞ったら良い?

    縦画面では phone のように、横画面では tablet (2 pane) のようにすると、画面内のレイアウト的にはよくなるが、画面を回転したときに 2 pane → 1 pane になるとユーザーが元いた場所を見失ってしまう。
    タブレットでは(例えばカーナビとして使ってるなど)回転はよくおこることなので、回転時に pane 数を変えないようにする(このコンセプトを rotational stability と呼んでいる)。
    People アプリでは縦画面のときには左側の pane が狭くなるようにサムネイルが出ないようにして 2 pane でも使えるレイアウトになっている。


■ Testing layouts
  • 大きいデバイスで小さいデバイスをシミュレートする
    Zoom で :
    adb shell am display-size 1024x768

    Nexus7 で :
    adb shell am display-size 852x480

    Reset するには :
    adb shell am display-size reset

    ・density は変えられない

    ・Android はラインタイム中の画面サイズ変更をネイティブにサポートしているわけではないので、いくつかのアプリケーションはクラッシュする可能性がある(最悪の場合にはホームボタンへのアクセスできなくなる)

    ・デバイスのバケットを切り替えないほうがいい (システムのハードキーがないデバイスでハードキーが必要なデバイスのシミュレートは危険) (10 inch から 9inch にするのはいいが、phone サイズにするとシステムは多くのことを再構成しないといけないのでクラッシュする可能性が高い)

    ・Bluetooth キーボードを使っていれば、Back や Home キーがあるのでデバイスを使うことができる

  • Emulator をつかう

    AVD で既存のハードウェアの解像度等を指定できる Device: という項目が増えた
    (でも ADT20 にしてもこの項目でてこないよ。。。)

  • Eclipse で Graphical Layout 上で複数の画面サイズをサムネイルでチェックできる機能が増えた
    (でも ADT20 にしてもこの機能でてこないよー。。。本当は Graphical Layout の上部の default 部分のドロップダウンを開くと Preview Al Screen Sizes とかがあるそうなのだが、現状ではまだ Create New... しかない)

    本当はこうなるらしい


    けど、まだこの状態


■ targetSdkVersion
  • targetSdkVersion はアプリが動作する最小の SDK version ではない。それは minSdkVersion。 maxSdkVersion というのもあるが、通常は使わないことが推奨されている。なぜなら、ユーザーがデバイスをアップデートしたときにアプリを利用できなくなる可能性があるから。 targetSdkVersion は対象としてテストした最新の SDK のバージョン。 デバイスのバージョンよりも古いバージョンを targetSdkVersion に指定している場合、システムは様々な部分に対してコンパチビリティモードを追加したり切り替える。例えば、targetSdkVersion="8" のアプリを Honeycomb タブレットで動かすと、Notification の色を変えたり、Dialog のテーマを古いものにしたり、Zoom mode を有効にしたり、Menu キーを表示したりする


■ QA

Q. theme の attribute の補間はどうやったらでますか?

A. ADT20 でサポートを追加したが、コード補間はまだ動いてなかったと思う。次のバージョンで入るかもしれない。


Q. ベータバージョンのツールは利用できますか?

A. ツールはオープンになっているし、adt-dev Google group でディスカッションできる。我々は外部の Gerrit AOSP ツリーにチェックインしているが、ここで見せた multi-configuration 編集はまだチェックインされていない。でもすぐに使えるようになるよ。


Q. Google+ の左上のメニューを押したときのようなレイアウトが作るのに困っています

A. 次のセッションにその質問に最適の人がでるので、そこで聞いてみてください


Q. どういうスタイルのコラボレーションがいいですか(開発者とデザイナー間で)?セッションで見せたツールは開発者が使うべき?それとも開発者とデザイナー両方?例えば Photoshop ファイルをメールでやりとりするようなことはしたくないのです。

A. うーん。我々は近くに座って、それぞれの作成物を互いに見せ合います。我々のエンジニアのほとんどは Photoshop の使い方を知らないです。ほとんどは GIMP です。よく見る結果は GIMP の出力ですw 本当に本当に利益をもたらすのは、一緒に座って一緒に働くことです。そして自分のデザインワークを見せてつねに話し合うことです。そうすれば、より短いイテレーションサイクルをとることができるでしょう。tool team が開発しているツールはほとんど開発者向けですが、ギャップを埋めるようなものにしたいと思っています。Photophop のパーツと 9 patch ファイルの間のような。

デザイナーがデザインからパーツ(assets)を取り出す実際のプロセスを開発者に示すのもオススメです。 バージョンコントロールツールを使うのは良い手ですが、問題はデザイナーにGIMPの使い方を教えないといけない点です。


Q. ガイドラインに載っている他のカラーでのパーツは stencil として用意しないのですか?

A. できるけど、リリースするのはすごく大変なので、、、この二人(スピーカーで開発者の二人)がコードで解決するとかw


Q. runtime で selector ファイルを作るとうまく動かないです

A. runtime で作成しないで XML で作ってください


Q. ダイアログの Ok, Cancel がバージョンで変わるのにどう対応すればいいですか?

A. Lint のチェック機能があります。残念ながら自動で配置してくれる機能はないです。独自のレイアウトを作る場合は v14 以降と以前で順番を変えてください。


Q. Photoshop で最初につくるキャンバスサイズはいくつですか?

A. すごい大きいです。2560x1600。さまざまな解像度のデバイス用のパーツを一度に配置できるようにしてます。ビギナーは mdpi で初めてなれてきたら xhdpi を基準に作るといいと思います。いいツールを今用意しているところです。


Q. 画像とXMLで生成した場合(shapeのこと)とではどちらのパフォーマンスがいいですか?

A. 通常は bitmap です。runtime 時にすることが少ないからです。でも具体的にどうするかに依存すると思います。


Q. 開発者です。デザイナーによくレイアウトXMLの Graphical Layout を見せるのですが、テキストや画像の多くが runtime に配置されるので、そこになにも出ないことがあります。いい方法はありますか?

A. いま取り組んでいることは、Fragment や ListView のようなウィジェットで、実際の runtime 時に配置されるデータを Graphical Layout で置くことです。実際の text 属性には置きたくないが、デザイン時のプレビューとして文字列が見たいという要望は受けています。我々がやりたいことの一つです。今あなたができることで簡単な方法はビューのサブクラスを作って View の isInEditMode() を利用するとツールでは動きます。でもすごくいい方法というわけではありません。


Q. ListView や GridView で実際の子要素のレイアウトを Graphical Layout で見たいのですが

A. デザインのときだけのレイアウトを作って、プレビューレンダリングとして利用できます。


Q. Launcher アプリが portlait しかサポートしてないのはなぜですか?

A. いい答えをもってないです。すいません。



2012年7月4日水曜日

Google I/O 2012 セッションまとめ : What's New in Android?

What's New in Android?

■ パフォーマンス

 スムースな操作感を実現
 基本的にはパフォーマンスの問題
 VSync(ヴィーシンクと発音する) による描画とアニメーション
  - 新しい Choreographer と TimeAnimator クラス
 ストリーミング入力
 より速い invalidation と描画
 必要に応じてトリプルバッファリング


■ フレームワークの機能

 Widget と Wallpaper に機能追加

 Widget
  • 3 party ランチャーから AppWidgetManager.ACTION_APPWIDGET_BIND で直接 AppWidget を bind するリクエストをユーザーに投げられる
  • AppWidgetProvider.onAppWidgetExtrasChanged() で AppWidget をサイズが変更されたことを検知できる
  • remote views に対して縦横のレイアウトが使えるようになった
  • remote views にメモリーリミットが付いた(画面サイズの関数)
  • GridLayout と ViewStub が remote で使えるようになった(つまり AppWidget で使える)
 Wallpaper
  • アプリの wallpapers を見るための新しい Intent である ACTION_CHANGE_LIVE_WALLPAPER が追加された(今までは live wallpaper アプリを作ったら、「ホームに行って〇〇してください」というようなインストラクションを表示するだけのアプリで対処していたが、アプリから直接 wallpaper を選択するピッカーを開くことができるようになった)


 Animation と LayoutTransition

 ViewPropertyAnimator
  • withLayer() メソッドが追加された
    これにより、複数の複雑なアニメーションを組み合わせたときや、複雑なレイアウトのビューをアニメーションさせるときによりよいパフォーマンスを発揮できる

    ハードウェアレイヤー ON → アニメーション → ハードウェアレイヤー OFF

    は boilerplate code なのでこのメソッドとして用意したとのこと view.animate().rotationY(180).withLayer()
  • withStartAction() と withEndAction() メソッドが追加された
    これまでは Animation のリスナーをセットしていたが、より簡単に記述できるようになった。
view.animate().translationX(0).withStartAction(new Runnable() { public void run() { view.setTranslationX(-view.getWidth()); } }); view.animate().alpha(0).withEndAction(new Runnable() { public void run() { parent.removeView(view); } });  LayoutTransition
  • 新しい Transition タイプの Changing を有効にできるようになった
LayoutTransition t = new LayoutTransition(); t.enableTransitionType(LayoutTransition.CHANGING); container.setLayoutTransition(t);

 その他
  • Non-editable TextView のメモリ使用が改善された & 長いテキストに対するパフォーマンスがあがった
  • ClipData でスタイルされた文字をサポートするようになった(太字や斜線などの情報もこぴぺできる)
  • システム全体のメモリ管理がよくなった
  • ActivityManager.getMyMemoryState()
  • ComponentCallback2.TRIM_*
  • GridLayout と TextureView が support library に追加され ICS 以前でも使えるようになった
  • データベースクエリをキャンセルできるようになった
  • Cursors, ContentProviders, Loaders
signal = new CancellationSignal(); signal.setOnCancelListener(new OnCancelListener() { @Override public void onCancel() { // Cancelled contacts query } } Cursor c = r.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null, signal); signal.cancel();

 Activity の Animation
  • ActivityOptions
    Activity を開始するときのアニメーションをよりリッチにできる(例えば、Jelly Bean では Recent Apps から起動するとき、Recent Apps のサムネイルがそのままスケールアップするアニメーションになっている)
ActivityOptions opts; opts = ActivityOptions.makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight()); startActivity(intent, opts.toBundle());

 Navigation
  • <activity> タグに android:parentActivityName 属性をつけることで、自動で 'UP' ナビゲーションをつけてくれる
  • support library で利用可能 ...
  • TaskStackBuilder
        合成したタスクスタックの簡単につくれる
        Notification や AppWidget からアプリを起動してバックキーで戻ったときにどこに戻るかのスタックをつくるのに便利
TaskStackBuilder.create(this) .addParentStack(ContentActivity.class) .addNextIntent(new Intent(this, ContentActivity.class) .putExtra(ContentActivity.EXTRA_TEXT, "From Notification") .startActivities();

 Internationalization
  • 新しいロケールとフォント : Arabic, Hebrew
  • right-to-left(RTL) text のサポートの改善 : TextView, EditText, StaticLayout, Canvas
  • RTL text のパフォーマンスの改善


 Accessibility
  • ジェスチャーによる画面上の全エレメントの探索
    AccessibilityService.GESTURE_SWIPE_UP, GESTURE_SWIPE_DOWN,...
    ナビゲーションボタン(Home, back, recent, notification)を含む
  • 現在のエレメントをダブルタップでアクティブ化
  • 単語、行、段落ごとのテキストリーディング
  • 標準のViewで動く : 特殊な場合には View.setImportantForAccessibility()
  • セマンティック構造でのカスタムビュー : View.getAccessibilityNodeProvider() を Override
  • Support library で使える


 External Storage Access
  • 外部ストレージの読み込み用パーミッション : android.permission.READ_EXTERNAL_STORAGE
    現在はこのパーミッションがなくても動くが、将来のリリースではこのパーミッションを必要とする予定
    Developer options の Protect USB storage にチェックをいれると、このパーミッションを正しく使っているかをチェックできる


 Limiting Costly Downloads
  • ConnectivityManager.isActiveNetworkMetered() : 現在の通信が測定されているか(ダウンロードにリミットが課されているか)をアプリから検知する
 


■ グラフィックスとメディア

 Renderscript Compute
  • NEON instruction のサポート
  • Texture sampling : rsSample()
  • x86エミュレータとx86デバイス上でのデバッグ
  • 1つのファイルで複数の root()-style カーネル


 Media
  • low-level コーデックデータへの API アクセス
  • "Media Codec APIs in Android Jelly Bean" を見てね


 Audio Latency Improvements
  • OpenSL, soundpool, tone generator のレイテンシの改善
  • いくつかのデバイスでの重大な warm latency の改善(デバイス依存)
  • ほかいろいろ


 NFC
  • Bluetooth を介して大きいデータをやりとり(タップしてファイルをデバイス間で移動)
  • Bluetooth ハンドセットやスピーカーとタップしてペアリング



■ Google Play

 user comments への返信(現在はまだ全員ができるわけではなく、top developer などバッチがある開発者から)

 in-app subscriptions

 その他
  • 新しい販売可能国
  • Android Developer Console へのチームアクセス
  • 販売レポート
  • Android Expansion Files : 50MB までの APK, 4GB までの Expansion files
  • 最適化された APK アップデート
  • Unlocked デバイスが Google Play から購入可能



■ SDK と Tools

 x86 エミュレータ : Android 2.3, Android 4.0

 エミュレータでの GPU サポート
  • Android 4.0.3+
  • OpenGL/ES 1.x + 2.x をサポート
  • ハードウェアアクセラレーションをつかったアプリケーションをサポート
 物理的な Android デバイスを介したセンサーとマルチタッチのサポート

 New tool
  • lint
  • Tracer for OpenGL ES
  • Device Monitor(DDMS の置き換え、より UI がリッチで hierarchyview などいくつかのツールが含まれている)
  • System Trace


 ADT
  • 新しい tools の統合 : lint, Tracer for OpenGL ES など
  • アプリケーションテンプレート
  • NDK サポートの改善 : Build, Debug
  • レイアウトエディターの改善 : 新しいプロパティシート
  • 多くのUIとパフォーマンスの改善



■ Notification(通知)

 Priority
  • Notification.priority
  • Notification.Builder.setPriority(int)
  • PRIORITY_MAX = 2, PRIORITY_HIGH = 1, PRIORITY_DEFAULT = 0, PRIORITY_LOW = -1, PRIORITY_MIN = -2
  • システム UI へのヒント
  • priority に応じて位置や見え方が変わる
  • 上部に配置するために FLAG_ONGOING を使うのやめること!変わりに高い priority を使う
  • ステータスバーに表示されない notification を作れるようになった(PRIORITY_MIN)


 Priority buckets
  • MAX : very urgent/active tasks : 着信、ターンバイターン方式の誘導、緊急アラート
  • HIGH : 重要なメール、SMS/チャット、"widgets"
  • DEFAULT : その他(JB以前の Notification はこれになる)
  • LOW : ジャンクメール、chatty apps
  • MIN : 終了したイベント、提案、詳細なステータス、アプリのプロモート


 bing CongtentView
  • contentView : 以前と同じ 64dip 高("1U")
  • bigContentView : 最大 256dip 高("4U")


 Action
  • 3つまでボタンを配置できる
  • 異なる Activity に遷移する/バックグラウンドでなにかアクションをする
  • すでに実行されたアクションを表示するために、intent に null をセットして re-notify() する
new Notification.Builder(this) .setContentTitle("Missed call") .setContentText("vvakame") .setLargeIcon(bitmap) .setSmallIcon(R.drawable.missed_call) .setPriority(Notification.PRIORITY_HIGH) .setContentIntent(callLogIntent) .addAction(R.drawable.call, "Call Back", callMeMaybe) .addAction(R.drawable.text, "Message", sendSms) .build();

 BigTextStyle
  • 複数行の TextView
  • contentText と bigText で同じ文字列を使えるが、bigText は ticker には使われない
  • スタイルと改行は使える
new Notification.Builder(this) .setSmallIcon(...) .setLargeIcon(...) .setContentTitle(senderName) .setTicker(tickerText) .addAction(...) .setContentText(msgText) .setStyle(new Notification.BigTextStyle().bigText(msgText)) .build();

 BigPictureStyle
  • 画像を大きく表示するスタイル
  • 画像は中心部にクロップされる
  • 画像の幅は 450dip より小さく、〜2:1 のアスペクト比がよい(Bitmapの外側はクロップされるので RAM の無駄遣いになる)
new Notification.Builder(this) ... .setStyle(new Notification.BigPictureStyle().bigPicture(photoBits)) .build();

 InboxStyle
  • メッセージ、ヘッドライン、カレンダーのアジェンダ、バッチ処理の通知などリストを表示するスタイル
  • setSummaryText() を使うことでアカウント名や "+5 more" などを追加できる
new Notification.Builder()this) .setContentTitle("3 new messages") .setContentText("You have mail!") .setSubText("test.hoge@gmail.com") .setNumber(3) .setSmallIcon(R.drawable.email) .setStyle(new Notification.InboxStyle() .addLine(styledLine1) .addLine(styledLine2) .addLine(styledLine3)) .build();

 Accountability
  • Notification をロングタップすることでアプリの情報(設定アプリのアプリ詳細画面)を見られるようになった(つまり、この Notification がどのアプリから来ているかを見ることができるようになった)
  • ここからアプリをアンインストールしたり、このアプリからの Notification を無効にできる
  • つまりうるさいアプリはここからすぐアンインストールされちゃうよ、ということ





Android 3rd party のランチャーで直接 AppWidget を bind する

Jelly Bean で AppWidget 関係にいろいろ手が入りました。

これまで 3rd party のランチャーに AppWidget を bind するには、AppWidgetManager.ACTION_APPWIDGET_PICK を呼び出して、AppWidget を選択する標準のダイアログを表示するしかありませんでした。 private void pickAppWidget() { int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); startActivityForResult(intent, 1); }


ちなみに Jelly Bean でこの Intent を呼ぶとこうなります。
ちょっとどうなのよ。。。



一方で、ICS 以降のデフォルトの Launcher アプリでは、Launcher 内に AppWidget のプレビューを表示し、そこからドラッグ&ドロップして選択されたものを直接 bind しています(つまり、上記のダイアログを呼び出さない)。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/packages/apps/Launcher2/src/com/android/launcher2/Launcher.java#1509 1520 int appWidgetId = getAppWidgetHost().allocateAppWidgetId(); 1521 AppWidgetManager.getInstance(this).bindAppWidgetId(appWidgetId, info.componentName); 1522 addAppWidgetImpl(appWidgetId, info);
この bindAppWidgetId() メソッドは ICS までは 3rd party からも呼べました(@hide ではなかった)。 呼べますが、android.permission.BIND_APPWIDGET パーミッションが必要で、かつこのパーミッションは system レベルなので 3rd party から呼ぶと SecurityException で落ちます。 (でも BIND_APPWIDGET のドキュメントにはその情報がないので、ちょっと不親切。まぁ、ちょっとぐぐればでるけどね http://stackoverflow.com/questions/3520564/security-exception-while-calling-bindappwidgetid とか。)

例えば、以下のコードは上記のパーミッションを宣言していても SecurityException で落ちます。

private void bindAppWidget() { // this will be fail because of SecurityException ComponentName componentName = new ComponentName( "com.example.android.mybatterywidget", "MyBatteryWidgetProvider"); int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); AppWidgetManager.getInstance(self).bindAppWidgetId(appWidgetId, componentName); }

Jelly Bean では新しくメソッドが追加され、3rd party のランチャーでも直接 AppWidget を bind することができるようになっています。それに伴って bindAppWidgetId() メソッドはなくなり(たぶん @hide になったのだと思われるが、現状2012/7/4ではコードがまだ公開されていないのでわからない。)、bindAppWidgetIfAllowed() メソッドが追加されました。

このメソッドを呼んで bind が成功した場合は true が返ってきます。これまでの標準 Launcher のように android.permission.BIND_APPWIDGET パーミッションがあるアプリは成功します。3rd party アプリから呼んだ場合は、ユーザーがこのコンポーネントに対して常に AppWidget の bind を有効にしている場合は成功します。有効になっていない場合は失敗するので false が返ってきます。この場合はユーザーに有効にしてくださいとお願いするための Intent を呼びます。それが AppWidgetManager.ACTION_APPWIDGET_BIND です。

こんな感じで使います。

private void bindAppWidget() { ComponentName componentName = new ComponentName( "com.example.android.mybatterywidget", "com.example.android.mybatterywidget.MyBatteryWidgetProvider"); int appWidgetId = mAppWidgetHost.allocateAppWidgetId(); boolean allowed = AppWidgetManager.getInstance(self) .bindAppWidgetIdIfAllowed(appWidgetId, componentName); if (!allowed) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, componentName); startActivityForResult(intent, 2); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == 2) { AppWidgetManager mAppWidgetManager = AppWidgetManager .getInstance(this); if (resultCode == RESULT_OK) { int appWidgetId = data.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { AppWidgetProviderInfo appWidget = mAppWidgetManager .getAppWidgetInfo(appWidgetId); // 必要であれば AppWidget の configure を呼びだしたり、ビューを自分のアプリに追加したりする Log.d("appWidgetId", appWidgetId + ""); } } else if (resultCode == RESULT_CANCELED) { int appWidgetId = data.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { // キャンセルされたので削除 mAppWidgetHost.deleteAppWidgetId(appWidgetId); } } return; } super.onActivityResult(requestCode, resultCode, data); }

AppWidgetManager.ACTION_APPWIDGET_BIND を呼ぶとこんな感じの確認ダイアログがでます。



ここでチェックボックスにチェックを入れて Create にすると、この 3rd party ランチャーに常に AppWidget を bind する許可を与えることになります(つまり bindAppWidgetIfAllowed() が成功します)。このデフォルト設定は設定アプリから Clear defaults で外すことができます。

注意!
AppWidgetManager.ACTION_APPWIDGET_BIND のドキュメントには

The system will respond with an onActivityResult call with the following extras in the intent:

EXTRA_APPWIDGET_ID The appWidgetId that you supplied in the original intent.

のように extras として EXTRA_APPWIDGET_ID というキーで AppWidget の Id が返ってくる、とあるのですが、なぜか試したみたところ onActivityResult() に返ってくる Intent が null なのです(上記のコードだと data という変数が null になるので data.getIntExtra() のところで落ちる)。 よって startActivity() するときに現状の AppWidgetId を持っておいてそれを使うしかない感じです。

うーん。。。