Androidの開発環境プラットフォーム

Androidの開発環境としてMac(Snow Leopard)とWindows (XP/7)を比較してみた。

Macの場合

  • UNIX系のコマンドラインツールや、javaとかがプリインストールされているので開発環境の構築が楽。足りなければmacportsでインストール
  • ○USBドライバのインストール無しに、殆どのAndroid端末が認識される。
  • ×Eclipseが全般的に遅い。特にAndroidxmlエディタを開いていると、ビューの切り替えに4〜5秒かかるようになる。

Windowsの場合

  • ×開発環境の構築が面倒。
  • ×新しいAndroid端末が出たら、ベンダーIDを調べてUSBドライバの設定ファイルに追記しないと認識されない。
  • ○Eclipeが速い。殆どのタスクの切り替えが1秒未満で収まる。

Eclipeの遅さが致命的で、それさえ解消されればMacAndroid開発環境を全面的に移行したい。

Macbook Airを注文

購入しようか悩んでいるのが面倒になったので、MBA 13inch 1.86GHz/4GB/128GB/US を注文した。今週末には届いているかと思う。
気にしていた液晶ディスプレイの反射も、実機を触ったところ気にならなかった。
11inchではなく13inchにした理由は、下記になる。

  • 解像度の大きさ(1440x900)。Eclipse使うなら1440ないときつい。
  • キータッチが違和感なかった(11inchは、自分の手の大きさだと窮屈だった)
  • そこそこ使えるようなら、メインマシンにしたい。

問題は、今あるMBP 15inch Core i7/8GB/160GB SSD/US/1680x1050 をどうするか。重量以外では、MBPに軍配があがるわけで。
重量の問題も、MBP 2.5kgを持ち運ぶのが週末にある勉強会くらいなもので毎日持ち運ぶわけではないので特に困らない。
Macは開発にしか使用していない(メール端末は、Thinkpad X201s)ので、できるだけスペックが高いほうがありがたい。
そうするとMBAの使い道が無い。1週間ほど検証(主にEclipseAndroid開発)して使い道が無いようなら中古で売ると思う。
殆ど同スペックのMBP 13inchを使っていたならば、悩むことなく即乗換だったのが。

AndroidでHTTP Cache(失敗編)

コードレビューの会で発表した資料。AndroidでHTTP通信する際に、Webブラウザと同じようなCache機能を使いたいと思い、Apache HttpClient-Cahceを検証してみた。結果は、思ったより性能が出ない(Cacheあり>Cahceなし)。HTTP Responseのシリアライズに時間がかかっているので、そこを改善できるといいのかな。Protocol Bufferでも使う?
汎用性ならApache HttpClient-Cahceがいいけど、必要なHTTP ResponseだけCacheするandroid.webkit形式を独自で実装してもいいかもしれない。Http Headers(Expires, Last-Modified, Etag, mime-type)はDBに保存、bodyはシリアライズしてファイルに保存すれば、そこそこパフォーマンス出ると思う。

検証用のソースコードは、http://www1.axfc.net/uploader/Sc/so/154474.zip で公開。

Broadcast Intentを"am"コマンドから投げる

Android端末をCar Dockに載せたりする場合の動きをテストしたい場合、エミュレータやハードウェアが無いとテストできない場合がある。
この場合、コマンドラインから"am"コマンドでBroadcast Intentを投げると同様の動きをエミュレートできる。
"am"コマンドの使い方は、下記のコマンドを実行する。

$ adb shell am

Broadcast Intentの一覧は、Intent  |  Android Developers を参照。
コマンドラインからBroadcast Intentを投げる場合の引数は、下記になる。

  • ACTION_BOOT_COMPLETED: 再起動と同様の処理(実際には再起動はしない)
adb shell am broadcast -a android.intent.action.BOOT_COMPLETED
  • DOCK_EVENT: (Desk|Car) Dock 抜き挿し
# Desk Dock に挿す (※Android 2.2で成功。2.1だと無視)
adb shell am broadcast -a android.intent.action.DOCK_EVENT --ei android.intent.extra.DOCK_STATE 1
# Car Dock に挿す (※Android 2.2で成功。2.1だと無視)
adb shell am broadcast -a android.intent.action.DOCK_EVENT --ei android.intent.extra.DOCK_STATE 2
# (Desk|Car) Dock から抜く (※Android 2.2で成功。2.1だと無視)
adb shell am broadcast -a android.intent.action.DOCK_EVENT --ei android.intent.extra.DOCK_STATE 0
  • ACTION_TIME_TICK: current time has changed
adb shell am broadcast -a android.intent.action.TIME_TICK
  • ACTION_TIME_CHANGED: The time was set.
adb shell am broadcast -a android.intent.action.TIME_SET
  • ACTION_TIMEZONE_CHANGED: The timezone has changed.
adb shell am broadcast -a android.intent.action.TIMEZONE_CHANGED --es time-zone GMT+09:00
  • ACTION_PACKAGE_ADDED:

(未検証)

  • ACTION_PACKAGE_CHANGED:

(未検証)

  • ACTION_PACKAGE_REMOVED:

(未検証)

  • ACTION_PACKAGE_RESTARTED:

(未検証)

  • ACTION_PACKAGE_DATA_CLEARED:

(未検証)

  • ACTION_UID_REMOVED:

(未検証)

  • ACTION_BATTERY_CHANGED:

(未検証)

  • ACTION_BATTERY_LOW: 電池が少なくなってきた場合の通知
adb shell am broadcast -a android.intent.action.BATTERY_LOW
  • ACTION_POWER_CONNECTED: 充電開始
# 動作確認できていない
adb shell am broadcast -a android.intent.action.ACTION_POWER_CONNECTED
  • ACTION_POWER_DISCONNECTED: 充電終了
# 動作確認できていない
adb shell am broadcast -a android.intent.action.ACTION_POWER_DISCONNECTED
  • ACTION_SHUTDOWN: シャットダウン
# システムによって保護されているので、このコマンドは無視される。
adb shell am broadcast -a android.intent.action.SHUTDOWN

[Streak] Dell Streak のUSBドライバ on Windows

Dell StreakをWindows XP/7にUSB接続して認識させる場合、"android-sdk-windows\usb_driver\android_winusb.inf" に下記の項目を追加します。

[Google.NTx86]
; [Google.NTx86] セクションに下記の3行を追加
;DELL Streak
%SingleAdbInterface%        = USB_Install, USB\VID_413C&PID_B007
%CompositeAdbInterface%     = USB_Install, USB\VID_413C&PID_B007&MI_01

[Google.NTx86]
; [Google.NTamd64] セクションに下記の3行を追加
;DELL Streak
%SingleAdbInterface%        = USB_Install, USB\VID_413C&PID_B007
%CompositeAdbInterface%     = USB_Install, USB\VID_413C&PID_B007&MI_01

カスタムNumberPickerの作成

AndroidのDatePickerやTimePickerは日付・時刻の入力を行うためのコンポーネントであるが、数値を1単位でインクリメント・デクリメントするための機能しかない。
例えば、時刻を入力するとき、1分間隔で入力することは少なく5分とか10分間隔で入力することが多いが、標準のTimePickerで実現できなかった。
そのため、TimePicker内部で使用しているNumberPickerを自作して使用していた。
このとき、下記の問題が発生していた。

  • apkファイルの肥大化(NumberPickerが内部で使用するリソースファイルをapkファイルに持つため、apkファイルのサイズが大きくなる)
  • Android SDKからコピーしたリソースファイルを流用しているので、カスタムUIを使用している端末(Xperia、Desire等)と同じにならない(背景色とか)。

この問題を解決するために、プライベートリソースの指定(例:res/layout/*.xmlandroid:drawable="@*android:drawable/timepicker_down_normal" でカスタムUIが持っているシステムリソースを参照する方法を試したが、AndroidアプリのSDKバージョンとAndroid端末のバージョンが一致しないとリソースを取得できないため、使用を諦めていた。

しかし、KazzzさんのNumberPickerを再作成する - Kazzzの日記 のblogと yanzmさんのY.A.M の 雑記帳: Android Get color of status barのblogを参考にさせていただいて、リフレクションを使用してAndroid標準のNumberPickerからリソースを取得すればいいことが分かったので、早速適用してみた。

注意点は、NumberPickerのパッケージがAndroid 2.1以下とAndroid 2.2以上で違うので、SDKバージョンを見てリフレクションのターゲットclassを切り替えている。

リフレクションのターゲットclassの切替とカスタムNumberPickerへのリソースの適用ソースは、下記になる。

// source file: com.example.android.customnumberpicker.widget.NumberPicker

public class NumberPicker extends LinearLayout implements OnClickListener, OnFocusChangeListener,
        OnLongClickListener {

    private static final String NUMBER_PICKER_CLASS_NAME;

    static {
        final int sdkVersion = Build.VERSION.SDK_INT;
        // 8=Build.VERSION_CODES.FROYO
        if (sdkVersion < 8) {
            NUMBER_PICKER_CLASS_NAME = "com.android.internal.widget.NumberPicker";
        } else {
            // Android 2.1 or higher
            NUMBER_PICKER_CLASS_NAME = "android.widget.NumberPicker";
        }
    }

    protected void setWidgetResource() {
        ....
        
        try {
            final Context context = getContext();
            final ClassLoader cl = context.getClassLoader();
            final Class<?> clazz = cl.loadClass(NUMBER_PICKER_CLASS_NAME);
            final Constructor<?> constructor = clazz.getConstructor(Context.class);
            final Object obj = constructor.newInstance(context);
            final Class<?> c = obj.getClass();

            {
                final Field field = c.getDeclaredField("mIncrementButton");
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                final ImageButton internalIncrementButton = (ImageButton) field.get(obj);
                mIncrementButton.setBackgroundDrawable(internalIncrementButton.getBackground());
            }

            {
                final Field field = c.getDeclaredField("mText");
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                final EditText internalText = (EditText) field.get(obj);
                mText.setBackgroundDrawable(internalText.getBackground());
                mText.setTextColor(internalText.getTextColors());
                // TextSizeを適用すると Android 2.2 800x480(hdpi) で、テキストが大きくなってしまうので、コメントアウト。
                //mText.setTextSize(internalText.getTextSize());
            }

            {
                final Field field = c.getDeclaredField("mDecrementButton");
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                final ImageButton internalDecrementButton = (ImageButton) field.get(obj);
                mDecrementButton.setBackgroundDrawable(internalDecrementButton.getBackground());
            }
            mButtonsBackgroundInitilized = true;
        } catch (final Exception ex) {
            Log
                .e(
                    TAG,
                    "com.android.internal.widget.NumberPicker internal button background resource got not.",
                    ex);
        }
    }
    
    ....
}

サンプルとして、n分間隔でインクリメント・デクリメントするTimePickerを作成した。下記のURLからダウンロード可能。
http://www1.axfc.net/uploader/Sc/so/154123.zip

上記のサンプルを、各Adnroid端末で実行したときのスクリーンショットが下記になる。
左がカスタムTimePicker、右がTimePickerになる。
縦幅が若干違うのと日付アイコンが違うが、殆ど同じイメージになった。



  • HTC Desire(Android 2.1, 800x480 (hdpi))




Nexus OneにCyanogenMod-6.0.0から不要な標準アプリを削除

CyanogenModに限らず、Androidの標準アプリは、アプリケーションの管理から削除できない。
root権限を取得していれば、Android端末にsh接続して削除できる。
以下、その手順。

最初に書き込み権限ありでファイルシステムをマウント

$ adb remount

次に、Android端末にsh接続して、不要なアプリを削除

$ adb shell
# cd /system/app
# rm CarHomeGoogle.apk	# Car Home は日本では使えない
# rm CarHomeLauncher.apk
# rm Development.apk	# 開発用のツールだけど、殆ど使わない
# rm Email.apk	# gmail only used.
# rm Facebook.apk	# リア充じゃないので
# rm Protips.apk
# rm SpareParts.apk		# 開発用のツールだけど、殆ど使わない
# rm Twitter.apk	# 他のTwitter APを使用しているので
# rm VoiceDialer.apk	# 使わない
# rm com.amazon.mp3.apk	# 使わない
# rm Talk.apk
# rm PassionQuickOffice.apk
# rm CMWallpapers.apk
# rm Mms.apk
# rm FM.apk
# rm AndroidTerm.apk
# rm MagicSmokeWallpapers.apk
# rm VisualizationWallpapers.apk