2012/04/26

Android:静的解析ツールLintを使う

ADT16でAndroid Lint(静的解析ツール)がリリースされました。
Lintを使用することで潜在的な不具合を発見することができます。

発見できる不具合や詳細は下記を参照
・Android Tools Project Site
http://tools.android.com/tips/lint
・Classmethod.dev()
http://dev.classmethod.jp/smartphone/android-tips-8-android-lint-1/
・Bescottee
http://andbrowser.com/development/knowhow/787/android-lint-check-rule-detail/

LintによるレポートはHTMLフォーマットをサポートしています。
下記のコマンドで実行が可能です。
lint <アプリフォルダパス> -–html <レポートファイル名>.html
# adbにパスが通ってない場合は %ANDROID_SDK_HOME%/tools 直下で実行してください。

実行すると、
Wrote HTML report to <レポート出力先パス>
のメッセージが表示され、解析結果がHTMLベースで出力されます。

HTMLフォーマットでは各項目の詳細情報へのアンカーリンクも貼られているので便利です。
レポートの内容は、解決方法までだいたい理解できるようになっています。

以上です。
2012/04/12

Android:Activity状態遷移タイムアウトによるPAUSE状態への遷移


Activityの状態遷移にはタイムアウトが設けられています。
特にPAUSE状態遷移のタイムアウトは500msecとシビアです。
PAUSEはActivityの切替パフォーマンスに直接影響を与えるため、他の状態遷移より短い
タイムアウトが設けられています。
# ActivityStackクラスのPAUSE_TIMEOUT定数辺りが参考になります


タイムアウトが500msecであることで注意すべきポイントはLowMemoryKillerと状態保存です。
ActivityのPAUSE状態は生存優先度が低く、LowMemoryKillerによるkill対象となり易くな
ります。

つまり、onSaveInstance~onPauseのForeground優先度が保証されるのはonSaveInstance
開始から500msec間のみということになります。
onPauseの完走に500msec以上を要する場合、「onPauseの実行途中に突然LowMemoryKiller
にkillされた...」ということが起こりえます。

onPauseで永続化したいデータの保存処理はonSaveInstanceの開始から500msec以内に収め
るのが吉です。

以上です。

2012/04/06

Android:Activityスタックの状態をダンプして確認する方法


下記のコマンドを実行することで、現在動作しているActivityやActivityスタックの状態
をダンプして確認することが可能です。
adb shell dumpsys activity activities
実行すると下記のようなログが大出力されます。
# ダンプログ出力元クラスは下記
# com.android.server.am.ActivityManagerService.dumpHistoryList
#     (FileDescriptor, PrintWriter, List, String, String, boolean, boolean, boolean, String)

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
  Main stack:
  * TaskRecord{411ae508 #2 A com.android.launcher}
    ... 略 ...

  Running activities (most recent first):
    TaskRecord{411ae508 #2 A com.android.launcher}
      Run #1: ActivityRecord{411ab270 com.android.lau...}

    ... 略 ...


【Main stack情報】
ActivityやActivityStackの状態を確認したい場合はMain stack情報が役に立ちます。
情報の内容をみていきましょう。

参照するMain stack情報は下記です。

* TaskRecord{41222188 #12 A yuki.activitystack}
  numActivities=1 rootWasReset=false
  affinity=yuki.activitystack
  intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=yuki.activitystack/.StandardActivity}
  realActivity=yuki.activitystack/.StandardActivity
  askedCompatMode=false
  lastThumbnail=null lastDescription=null
  lastActiveTime=2827820 (inactive for 4s)
  * Hist #1: ActivityRecord{41148798 yuki.activitystack/.StandardActivity}
      packageName=yuki.activitystack processName=yuki.activitystack
      launchedFromUid=0 app=ProcessRecord{412115e8 1007:yuki.activitystack/10043}
      Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=yuki.activitystack/.StandardActivity }
      frontOfTask=true task=TaskRecord{41222188 #12 A yuki.activitystack}
      taskAffinity=yuki.activitystack
      realActivity=yuki.activitystack/.StandardActivity
      base=/data/app/yuki.activitystack-1.apk/data/app/yuki.activitystack-1.apk data=/data/data/yuki.activitystack
      labelRes=0x7f040001 icon=0x7f020000 theme=0x0
      stateNotNeeded=false componentSpecified=true isHomeActivity=false
      config={1.0 310mcc260mnc en_US layoutdir=0 sw480dp w480dp h775dp lrg long port finger qwerty/v/v tball/v s.4}
      compat={160dpi always-compat}
      launchFailed=false haveState=false icicle=null
      state=RESUMED stopped=false delayedResume=false finishing=false
      keysPaused=false inHistory=true visible=true sleeping=false idle=true
      fullscreen=true noDisplay=false immersive=false launchMode=0
      frozenBeforeDestroy=false thumbnailNeeded=false forceNewConfig=false
      thumbHolder=TaskRecord{41222188 #12 A yuki.activitystack}
      lastVisibleTime=+47m8s986ms
      waitingVisible=false nowVisible=true


2行目~8行目:タスクレコードの状態情報です。
 各情報の概要:
  numActivities:このタスクに属しているActivityの数
  rootWasReset:タスクルートのintentがFLAG_ACTIVITY_RESET_TASK_IF_NEEDEDを持
    っている場合にtrueを返す。
  affinity:タスクのアフィニティ名。あるいはnull
  intent:タスクを開始した時のIntent情報
  realActivity:タスク開始時の実Activity情報(エイリアスの場合はtargetComponent名)
  askedCompatMode:このタスクレコードが互換性モードを求められたか?
  lastThumbnail:サムネイル画像情報
  lastDescription:サムネイルのディスクリプション情報
  lastActiveTime:最後に活動していた時間(elapsedRealtime)。
    (Inactive *s)内は非活動時間

9行目:アクティビティレコードインスタンスの情報です。
 フォーマット:* Hist #(A): ActivityRecord{(B) (C)}
  (A):ログ用識別番号
  (B):アクティビティレコードのハッシュ値
  (C):インテントに含まれるコンポーネット名

10行目~28行目:アクティビティレコードの状態情報です。
 各情報の概要:
  packageName:コンポーネントのパッケージ名
  processName:コンポーネントのプロセス名
  launchedFromUid:Activity起動元のUID
  app:アプリケーションプロセス情報
  Intent:Activity生成時に指定されたIntent情報
  frontOfTask:タスクのルートアクティビティかどうか?
  task:このActivityが属するタスクレコード情報
  taskAffinity:アクティビティのタスクアフィニティ情報
  realActivity:コンポーネント名。エイリアスActivityの場合はそのtargetActivity
  base:アプリリソースの配置場所
  data:アプリデータの保存場所
  labelRes:Activityラベルのリソース情報
  icon:Activityアイコンのリソース情報
  theme:Activityテーマのリソース情報
  stateNotNeeded:stateNotNeededフラグの有無
  componentSpecified:明示的Intentによる呼び出しか?
  isHomeActivity:ランチャーActivityか?
  config:最後にActivityに適用されたconfig
  compat:最後に使用したcompatモード
  launchFailed:Activity起動で2ndトライに失敗したか?
  haveState:最後のActivity状態を得たか? #pauseによる状態保存?
  icicle:最後に保存されたActivityの状態
  state:Activity状態(RESUME,PAUSE,DESTROYED等)
  stopped:Activityは停止しているか?
  delayedResume:呼び出しもとが再開の遅延を求めているか?
  finishing:破棄予定であるか?
  keysPaused:キーディスパッチが当該Activityのため停止しているか?
  inHistory:当該Activityがヒストリスタックにあるか?
  visible:Activityのウィンドウは表示中か?
  sleeping:Activity状態はスリープか?
  idle:Activity状態はアイドルか?
  fullscreen:全画面をカバーするか?
  noDisplay:Activityは表示されていないか?
  immersive:FLAG_IMMERSIVEがONか?(割込み拒否モードか?)
  launchMode:Activityの起動モード
    (Standard(Multiple)=0/SingleTop=1/SingleTask=2/SingleInstance=3)
  frozenBeforeDestroy:frozen状態だかまだDestroyされていない状態か?
  thumbnailNeeded:サムネイルを要求されているか?
  forceNewConfig:次回、新たなconfigでActivity再生成されるか?
  thumbHolder:サムネイル情報
  lastVisibleTime:直前のActivityが可視化された時間(uptimeMillis)
  waitingVisible:再表示の待ちか?
  nowVisible:Activityウィンドウは表示されているか?


1/10000の再現率の不具合や再現状況や手順が不明な場合、Activityの状態がわかるこの
ログ情報を活用すれば、再現手順や前提条件が見えてきます。
bugreportによるログ取得でも同様のログが含まれているのですが、出力されるログの量が
膨大なため、手軽にActivity/ActivityStackの状態を確認したい時等に便利です。

Running activitiesの情報は、変数名からなんとなく内容の分かるものばかりなので省略
します。


13/02/09 追記---
ダンプ情報について、例えばテーマ情報(theme)情報はマニフェスト上で定義されたものが反映され、
ソースコード上で動的に設定されたテーマについては反映されません。
---


以上です。
2012/04/05

Android:アプリ起動の高速化 ViewTreeObserver

ユーザへのレスポンスタイムを早めるためのTips。

onCreateやonResumeでの重い処理はレスポンスを悪くします。
重い処理をバックグラウンドスレッドで行うのも手ですが、バックグランド処理の開始を
遅らせることで更にレスポンスを早くすることが可能です。

レスポンスを早くするには素早くUIを構築することです。
ここでのUIは"現在UIを構築中"であることを伝えるものかもしれませんし、
スタブライクなUIかもしれません。

UIを素早く構築するには、UI構築処理に集中しこれを邪魔しないことです。
バックグラウンドでデータベースアクセスをしたり、ネットワークリソースにアクセスす
ることもUIの構築完了まで我慢します。

簡単なサンプルコードを載せます。
private boolean mFirstDraw = true;

@Override
protected void onResume() {
    super.onResume();

    findViewById(R.id.hoge).getViewTreeObserver().addOnPreDrawListener(
        new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                if (mFirstDraw) {
                    mHandler.sendEmptyMessageDelayed(WHAT, 1000);
                    mFirstDraw = false;
                }
                return true;
            }
        }
    );
}
上記のコードではViewの計測と整列を終え、描画の直前状態をフックして初回描画時の
タイミングから1000msec後にバックグラウンドスレッド処理を開始します。

こうすることで、Viewの描画が完了しUIの提供を終えた頃にバックグラウンド処理が開始
されるのでアプリケーションはUI構築に集中できます。

以上です。