2011年10月5日水曜日

Android Localeに対応した曜日や月の表記一覧を取得する

特定の Locale に対応した、曜日や月などの表記の一覧配列を DateFormatSymbols を使って取得できるので、Android で設定言語を変えた場合を取得してみた。

public class DateFormatSymbolSampleActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); DateFormatSymbols symbols = new DateFormatSymbols(); TableLayout table = (TableLayout) findViewById(R.id.symbols); addTableRow(inflater, table, "getAmPmString", Arrays.toString(symbols.getAmPmStrings())); addTableRow(inflater, table, "getEras", Arrays.toString(symbols.getEras())); addTableRow(inflater, table, "getMonths", Arrays.toString(symbols.getMonths())); addTableRow(inflater, table, "getShortMonths", Arrays.toString(symbols.getShortMonths())); addTableRow(inflater, table, "getWeekdays", Arrays.toString(symbols.getWeekdays())); } private void addTableRow(LayoutInflater inflater, TableLayout table, String text1, String text2) { TableRow row = (TableRow)inflater.inflate(R.layout.row, table, false); ((TextView)row.findViewById(R.id.text1)).setText(text1); ((TextView)row.findViewById(R.id.text2)).setText(text2); table.addView(row); } }

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TableLayout android:id="@+id/symbols" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>

<?xml version="1.0" encoding="utf-8"?> <TableRow xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="5dip" > <TextView android:id="@+id/text1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="5dip" /> <TextView android:id="@+id/text2" android:layout_width="0dip" android:layout_weight="1" android:layout_height="wrap_content" android:padding="5dip" /> </TableRow>

英語ロケール


日本語ロケール


ドイツ語ロケール


フランス語ロケール


韓国語ロケール


ちなみに、1.6 だと、曜日名と月名がとれないバグがあるそうです。
Issue 3985 - android - DateFormatSymbols bugs in Japanese locale sdk1.6 - Project Hosting on Google Code

2011年10月4日火曜日

Android Apache HTTP Client と HttpURLConnection どっちを使うべき?

Android Developer's Blog にちょっと面白い記事があったのでまとめてみた。

Android Developers Blog: Android’s HTTP Clients -

Android のフレームワークは HTTP 通信をするための HTTP client の実装が2つあります。

1つが HttpURLConnection (java.net パッケージ) で、もう1つのが Apache HTTP Client (org.apache.http パッケージ) です。

Apache HTTP Client

 DefaultHttpClient とそれのサブクラスの AndroidHttpClient は Web ブラウザに適応した拡張された HTTP Client です。そのため、大きくてフレキシブルな API を持っています。これらの実装は安定していますが、いくつかのバグも含まれています。

巨大な API は互換性を破らずに改善することがとても難しく、Android チームは Apache HTTP Client に対してアクティブに活動していません。


HttpURLConnection

 HttpURLConnection ほとんどのアプリケーションに適応する汎用的で軽量な HTTP Client です。API が絞られているので、徐々に改善していきやすいです。

Froyo 以前では、HttpURLConnection には幾つかのイライラするバグがありました。とくに、close() を読み込み可能な InputStream で呼ぶことで connection pool を汚染することがありました。connection pooling を無効にすることでこの問題を回避するには
private void disableConnectionReuseIfNecessary() { // HTTP connection reuse which was buggy pre-froyo if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) { System.setProperty("http.keepAlive", "false"); } }
のようにします。

Gingerbread では、transparent response compression が追加されました。HttpURLConnection は自動的にリクエストにヘッダを追加し、対応するレスポンスを処理します。

Accept-Encoding: gzip

Web server が圧縮レスポンスを利用できるクライアントに対してそれを返すように設定することで、この利点を生かすことができます。もし圧縮レスポンスで問題がある場合は、クラスのドキュメントに無効にする方法がのっています。

HTTP の Content-Length ヘッダは圧縮されたサイズを返すので、非圧縮データ用のバッファサイズとして getContentLength() で取得した値を使うとエラーになります。InputStream.read() が -1 を返すまで読みようにしてください。

Gingerbread での HTTPS も幾つか改善されています。HttpsURLConnection は IP アドレスを共有する複数の HTTPS host を許可する Servier Name Indication (SNI) に接続を試みます。これは圧縮とセッションチケットを有効にします。コネクションが失敗した場合は、自動でこれらの機能を省いてリトライします。これは HttpsClientConnection が過去のサーバーと互換性を保ちながら最新のサーバーと接続するのに効果的です。

Ice Cream Sandwich では、レスポンスキャッシュが追加されます。インストールされたキャッシュに対して、次の3つのなかのいずれかの方法で HTTP リクエストが完了します。

  • 完全にキャッシュされたレスポンスがローカルストレージから直接提供される。ネットワーク接続がない場合、このようなレスポンスがすぐに返ってくることが必要であるため。
  • 条件付きでキャッシュされたレスポンスはウェブサーバーによって新しさが検証されます。クライアントは "もし機能から変更されていたら /foo.png をください” のようなリクエストを送り、サーバーはアップデートがあるかどうかに応じて 304 Not Modified status かコンテンツを返します。つまり、コンテンツが変わっていなければダウンロードされません!
  • キャッシュされていないレスポンスは Web から提供されます。これらのレスポンスは後でレスポンスキャッシュに保存されます。


HTTP レスポンスのキャッシュをサポートしている端末で有効にするにはリフレクションを使います。例えば、以前のリリースに影響を与えずに、Ice Create Sandwich の端末でキャッシュ機能を有効にするには次のようにします。

private void enableHttpResponseCache() { try { long httpCacheSize = 10 * 1024 * 1024; // 10 MiB File httpCacheDir = new File(getCacheDir(), "http"); Class.forName("android.net.http.HttpResponseCache") .getMethod("install", File.class, long.class) .invoke(null, httpCacheDir, httpCacheSize); } catch (Exception httpResponseCacheNotAvailable) { } }

もちろん、HTTP レスポンスにキャッシュヘッダをセットするよう Web server を設定する必要があります。

Which client is best? どっちのクライアントがいい?

Eclair と Froyo では Apache HTTP client のほうがバグがすくないので、こちらがベストです。

Gingerbread 以降では、HttpURLConnection がベストです。API がシンプルでサイズも小さいので Android に適しています。Transparent compression とレスポンスキャッシュはネットワークの利用を減らし、速度やバッテリーのもちを改善します。

---

# さらっと Ice Cream Sandwich のことが書かれてたね。

PreferenceActivity の CheckBox マークを変える

PreferenceActivity の各UI は Android のフレームワークの theme.xml に定義されています。

/frameworks/base/core/res/res/values/themes.xml 194 <!-- Preference styles --> 195 <item name="preferenceScreenStyle">@android:style/Preference.PreferenceScreen</item> 196 <item name="preferenceCategoryStyle">@android:style/Preference.Category</item> 197 <item name="preferenceStyle">@android:style/Preference</item> 198 <item name="preferenceInformationStyle">@android:style/Preference.Information</item> 199 <item name="checkBoxPreferenceStyle">@android:style/Preference.CheckBoxPreference</item> 200 <item name="yesNoPreferenceStyle">@android:style/Preference.DialogPreference.YesNoPreference</item> 201 <item name="dialogPreferenceStyle">@android:style/Preference.DialogPreference</item> 202 <item name="editTextPreferenceStyle">@android:style/Preference.DialogPreference.EditTextPreference</item> 203 <item name="ringtonePreferenceStyle">@android:style/Preference.RingtonePreference</item> 204 <item name="preferenceLayoutChild">@android:layout/preference_child</item>
ここをそれぞれオリジナルテーマに置き換えればOK

もともとはこうなってる

/frameworks/base/core/res/res/values/styles.xml 769 <!-- Preference Styles --> 770 771 <style name="Preference"> 772 <item name="android:layout">@android:layout/preference</item> 773 </style> 774 775 <style name="Preference.Information"> 776 <item name="android:layout">@android:layout/preference_information</item> 777 <item name="android:enabled">false</item> 778 <item name="android:shouldDisableView">false</item> 779 </style> 780 781 <style name="Preference.Category"> 782 <item name="android:layout">@android:layout/preference_category</item> 783 <!-- The title should not dim if the category is disabled, instead only the preference children should dim. --> 784 <item name="android:shouldDisableView">false</item> 785 <item name="android:selectable">false</item> 786 </style> 787 788 <style name="Preference.CheckBoxPreference"> 789 <item name="android:widgetLayout">@android:layout/preference_widget_checkbox</item> 790 </style> 791 792 <style name="Preference.PreferenceScreen"> 793 </style> 794 795 <style name="Preference.DialogPreference"> 796 <item name="android:positiveButtonText">@android:string/ok</item> 797 <item name="android:negativeButtonText">@android:string/cancel</item> 798 <item name="android:widgetLayout">@android:layout/preference_dialog</item> 799 </style> 800 801 <style name="Preference.DialogPreference.YesNoPreference"> 802 <item name="android:positiveButtonText">@android:string/yes</item> 803 <item name="android:negativeButtonText">@android:string/no</item> 804 </style> 805 806 <style name="Preference.DialogPreference.EditTextPreference"> 807 <item name="android:dialogLayout">@android:layout/preference_dialog_edittext</item> 808 </style> 809 810 <style name="Preference.RingtonePreference"> 811 <item name="android:ringtoneType">ringtone</item> 812 <item name="android:showSilent">true</item> 813 <item name="android:showDefault">true</item> 814 <item name="android:widgetLayout">@android:layout/preference_dialog</item> 815 </style>
これを置き換え。

android:widgetLayout というのが左側に配置される部品(チェックボックスとか、○に下三角のやつ)

結論。CheckBoxPreference のチェックボックスと EditTextPreference の下三角は変更できる。
ただし、EditTextPreference のダイアログのレイアウトは変更できたけど、レイアウトファイル内の @+android:id/edittext_container が public ではないため、入力ボックスがでなくなっちゃうのでダメだった。



PreferenceDialog を継承したクラスを作ったほうが早そう。

res/xml/prefs.xml <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceCategory android:title="Setting"> <CheckBoxPreference android:key="checkbox_value" android:title="Setting1" android:summary="sample of checkbox setting" /> <EditTextPreference android:key="edittext_value" android:title="Setting2" android:summary="sample of edittext setting" android:dialogTitle="Setting edittext" android:dialogMessage="please set values" /> </PreferenceCategory> </PreferenceScreen> res/values/styles.xml <?xml version="1.0" encoding="utf-8"?> <resources> <style name="CustomTheme" parent="@android:style/Theme.Light"> <item name="android:windowBackground">@color/window_bg</item> <item name="android:colorBackground">@color/window_bg</item> <item name="android:checkBoxPreferenceStyle">@style/CustomCheckBoxPreference</item> <item name="android:editTextPreferenceStyle">@style/CustomDialogPreference.CustomEditTextPreference </item> </style> <style name="CustomCheckBoxPreference"> <item name="android:widgetLayout">@layout/preference_widget_checkbox</item> </style> <style name="CustomDialogPreference.CustomEditTextPreference"> <item name="android:dialogLayout">@layout/preference_dialog_edittext</item> </style> <style name="CustomDialogPreference"> <item name="android:positiveButtonText">@android:string/ok</item> <item name="android:negativeButtonText">@android:string/cancel</item> <item name="android:widgetLayout">@layout/preference_dialog</item> </style> <style name="CustomDialogPreference.YesNoPreference"> <item name="android:positiveButtonText">@android:string/yes</item> <item name="android:negativeButtonText">@android:string/no</item> </style> </resources> res/layout/preference_widget_checkbox.xml <?xml version="1.0" encoding="utf-8"?> <CheckBox xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+android:id/checkbox" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="4dip" android:layout_gravity="center_vertical" android:button="@drawable/btn_check" android:focusable="false" android:clickable="false" /> res/layoutpreference_dialog.xml <?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="6dip" android:layout_marginTop="@dimen/margin_check" android:layout_marginLeft="@dimen/margin_check" android:layout_marginBottom="@dimen/margin_check" android:layout_gravity="center_vertical" android:background="@drawable/btn_circle" android:src="@drawable/ic_btn_round_more" android:scaleType="center" /> res/layoutpreference_dialog_edittext.xml <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:id="@*android:id/edittext_container" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="5dip" android:orientation="vertical"> <TextView android:id="@android:id/message" style="?android:attr/textAppearanceMedium" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textColor="@color/color2" /> </LinearLayout> </ScrollView> res/values/dimens.xml <?xml version="1.0" encoding="utf-8"?> <resources> <dimen name="size_check">32dip</dimen> <dimen name="size_more">32dip</dimen> <dimen name="size_radio">32dip</dimen> <dimen name="margin_check">3dip</dimen> <dimen name="size_circle">14dip</dimen> </resources> res/values/colors.xml <?xml version="1.0" encoding="utf-8"?> <resources> <color name="color1">#FDF3DE</color> <color name="color2">#9C0C0A</color> <color name="color3">#E88833</color> <color name="color4">#8F6031</color> <color name="color5">#3B3013</color> <color name="color6">#690806</color> <color name="color7">#dfd3d0</color> <color name="window_bg">@color/color1</color> <color name="component_frame_normal">@color/color3</color> <color name="component_frame_pressed">@color/color2</color> <color name="component_frame_pressed_top">@color/color2</color> <color name="component_frame_pressed_bottom">@color/color6</color> <color name="component_frame_disabled">@color/color7</color> <color name="component_text_normal">@color/color6</color> <color name="component_text_pressed">@color/color1</color> <color name="component_button_top">#fff</color> <color name="component_button_bottom">@color/color7</color> </resources> res/drawable/btn_check_bg.xml <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <padding android:left="38dip" android:right="0dip" /> <solid android:color="#0000" /> </shape> res/drawable/btn_bg.xml <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_checked="true" android:state_pressed="true" android:state_enabled="true"> <layer-list> <item android:top="@dimen/margin_check" android:bottom="@dimen/margin_check" android:left="@dimen/margin_check" android:right="@dimen/margin_check"> <shape android:shape="rectangle"> <corners android:radius="5dip" /> <gradient android:angle="270" android:startColor="@color/component_frame_pressed_top" android:endColor="@color/component_frame_pressed_bottom" /> <size android:width="@dimen/size_check" android:height="@dimen/size_check" /> <stroke android:width="2dip" android:color="@color/component_frame_pressed" /> </shape> </item> <item android:top="10dip" android:bottom="10dip" android:left="8dip" android:right="8dip" android:drawable="@drawable/check_on" /> </layer-list> </item> <item android:state_checked="false" android:state_pressed="true" android:state_enabled="true"> <layer-list> <item android:top="@dimen/margin_check" android:bottom="@dimen/margin_check" android:left="@dimen/margin_check" android:right="@dimen/margin_check"> <shape android:shape="rectangle"> <corners android:radius="5dip" /> <gradient android:angle="270" android:startColor="@color/component_frame_pressed_top" android:endColor="@color/component_frame_pressed_bottom" /> <size android:width="@dimen/size_check" android:height="@dimen/size_check" /> <stroke android:width="2dip" android:color="@color/component_frame_pressed" /> </shape> </item> <item android:top="10dip" android:bottom="10dip" android:left="8dip" android:right="8dip" android:drawable="@drawable/check_off" /> </layer-list> </item> <item android:state_checked="true" android:state_focused="true" android:state_enabled="true"> <layer-list> <item android:top="@dimen/margin_check" android:bottom="@dimen/margin_check" android:left="@dimen/margin_check" android:right="@dimen/margin_check"> <shape android:shape="rectangle"> <corners android:radius="5dip" /> <gradient android:angle="90" android:startColor="#ffffff" android:endColor="#fefefe" /> <size android:width="@dimen/size_check" android:height="@dimen/size_check" /> <stroke android:width="2dip" android:color="@color/component_frame_pressed" /> </shape> </item> <item android:top="10dip" android:bottom="10dip" android:left="8dip" android:right="8dip" android:drawable="@drawable/check_on" /> </layer-list> </item> <item android:state_checked="false" android:state_focused="true" android:state_enabled="true"> <layer-list> <item android:top="@dimen/margin_check" android:bottom="@dimen/margin_check" android:left="@dimen/margin_check" android:right="@dimen/margin_check"> <shape android:shape="rectangle"> <corners android:radius="5dip" /> <gradient android:angle="90" android:startColor="#ffffff" android:endColor="#fefefe" /> <size android:width="@dimen/size_check" android:height="@dimen/size_check" /> <stroke android:width="2dip" android:color="@color/component_frame_pressed" /> </shape> </item> <item android:top="10dip" android:bottom="10dip" android:left="8dip" android:right="8dip" android:drawable="@drawable/check_off" /> </layer-list> </item> <item android:state_checked="false" android:state_enabled="true"> <layer-list> <item android:top="@dimen/margin_check" android:bottom="@dimen/margin_check" android:left="@dimen/margin_check" android:right="@dimen/margin_check"> <shape android:shape="rectangle"> <corners android:radius="5dip" /> <gradient android:angle="90" android:startColor="#ffffff" android:endColor="#fefefe" /> <size android:width="@dimen/size_check" android:height="@dimen/size_check" /> <stroke android:width="2dip" android:color="@color/component_frame_normal" /> </shape> </item> <item android:top="10dip" android:bottom="10dip" android:left="8dip" android:right="8dip" android:drawable="@drawable/check_off" /> </layer-list> </item> <item android:state_checked="true" android:state_enabled="true"> <layer-list> <item android:top="@dimen/margin_check" android:bottom="@dimen/margin_check" android:left="@dimen/margin_check" android:right="@dimen/margin_check"> <shape android:shape="rectangle"> <corners android:radius="5dip" /> <gradient android:angle="90" android:startColor="#ffffff" android:endColor="#fefefe" /> <size android:width="@dimen/size_check" android:height="@dimen/size_check" /> <stroke android:width="2dip" android:color="@color/component_frame_normal" /> </shape> </item> <item android:top="10dip" android:bottom="10dip" android:left="8dip" android:right="8dip" android:drawable="@drawable/check_on" /> </layer-list> </item> <!-- Disabled states --> <item> <layer-list> <item android:top="@dimen/margin_check" android:bottom="@dimen/margin_check" android:left="@dimen/margin_check" android:right="@dimen/margin_check"> <shape android:shape="rectangle"> <corners android:radius="5dip" /> <gradient android:angle="90" android:startColor="#ffffff" android:endColor="#fefefe" /> <size android:width="@dimen/size_check" android:height="@dimen/size_check" /> <stroke android:width="2dip" android:color="@color/component_frame_disabled" /> </shape> </item> <item android:top="10dip" android:bottom="10dip" android:left="8dip" android:right="8dip" android:drawable="@drawable/check_off" /> </layer-list> </item> </selector> res/drawable/btn_circle.xml <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_enabled="true"> <shape android:shape="ring" android:useLevel="false" android:innerRadius="0dip" android:thickness="@dimen/size_circle"> <size android:width="@dimen/size_check" android:height="@dimen/size_check" /> <stroke android:width="2dip" android:color="@color/component_frame_normal" /> <solid android:color="#fff" /> </shape> </item> <item> <shape android:shape="ring" android:useLevel="false" android:innerRadius="0dip" android:thickness="@dimen/size_circle"> <size android:width="@dimen/size_check" android:height="@dimen/size_check" /> <stroke android:width="2dip" android:color="@color/component_frame_disabled" /> <solid android:color="#fff" /> </shape> </item> </selector> ic_btn_round_more.xml は SDK プラットフォームのリソースのものを利用。