2011/10/30

レイアウトファイルにおけるincludeタグの動作を検証

上手くレイアウトを分割・コンポーネント化することで、レイアウトファイル
の可読性や再利用性を向上させることができます。

端的に言えば、includeタグで別レイアウトファイルを指定すると、
指定したレイアウトファイルの内容で置換されるようになります。

includeタグにはandroid:idを指定することができますので、それ自体にリソ
ースIDを指定することができます。
下記の例をみてます。

layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <include android:id="@+id/includer" layout="@layout/include" />
</LinearLayout>


layout/include.xml
<?xml version="1.0" encoding="utf-8" ?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <TextView 
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:text="@string/hello"/>
</FrameLayout>

ポイントはmain.xmlのinclude要素にリソースID(includer)が指定されていること。
上記の内容で、setContentView(main.xml)とした場合のレイアウト階層をみる
と、include先(include.xml)のFrameLayoutのリソースIDに"includer"が割り
当てられます。


では、include.xmlにあるFrameLayoutが既にリソースID割り当て済みだとした
らどちらが優先されるのでしょうか?下記の内容で検証してみます。

layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <include android:id="@+id/includer" layout="@layout/include" />
</LinearLayout>


layout/include.xml
<?xml version="1.0" encoding="utf-8" ?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/includee"
    >
    <TextView 
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:text="@string/hello"/>
</FrameLayout>

ポイントはmainのinclude要素にリソースID"includer"が、include.xmlの
FrameLayout要素にリソースID"includee"が指定されていること。
先の内容では、include要素のリソースIDがinclude先(include.xml)の
FrameLayoutのリソースIDに割り当てられましたが、今回はFrameLayoutに
はすでにリソースID"includee"が割り当てられています。
includerとincludeeどちらが優先されるのでしょうか?

結果は
    includer > includee
となりました。
両方定義されている場合はincluderが優先。
includeeのみ定義されている場合はincludeeとなります。

以上です。

2011/10/29

Androidエミュレータを再起動する方法

下記のコマンドを実行するとエミュレータの再起動ができる。
(正しい方法かどうかは未確認)

adb shell kill -9 zygoteのpid

Androidの基幹プロセスに含まれるzygoteのPIDは下記で確認できる。

adb shell ps

... 略 ...
root      35    1     114620 39328 ffffffff 40010794 S zygote
... 略 ...

左から、
USER/PID/PPID/VSIZE/RSS/WCHAN/PC/NAME
上記の例ではzygoteのPIDは35となる。
この場合

adb shell kill -9 35

とすればエミュレータは再起動する。

apkからマニフェストの内容を読み取る方法

apkはaaptツールを使用することで、マニフェストの大まかな内容を知ることができる。
aaptの実行ファイルはSDKのplatform-toolsに含まれている。

) 実行方法
aapt dump xmltree hoge.apk AndroidManifest.xml > foo.txt

上記でhoge.apkのマニフェスト内容がfoo.txtに出力される。

不具合を解析する際に「apkのみでソースコードがない」といった場合に、
期待するパーミッションが宣言されているか、intent-filterの値はどうか等の確認できる。
2011/10/24

マーケットアプリで「読み込んでいます...」から進まない場合の対処方法

技術ネタ以外から少々...

3か月ほど前にAndroidマーケットアプリがリニューアルされましたが、
「読み込んでいます...」から全く進まなくなる場合があります。

下記を試したところ現象が解消することがあったのでメモ。

・タスクマネージャからマーケットアプリプロセスを終了する
・端末再起動

プロセスを終了させる方法が良いと思います。
それでもダメなら再起動で。

2011/10/22

Android4.0向けのADTをインストールする


SDK-r14が公開されました。
http://developer.android.com/intl/ja/sdk/index.html

SDK-r14をインストールした場合はEclipseでAndroidを開発するためのプラグイン
ADTも更新する必要があります。
更新方法は下記になります。
※日本語化されていないEclipseでの手順です。
日本語化パッチを適用している方は読み替えてください。
※EclipseのバージョンはHelios

1. メインメニューより[Help]>[Install New Software...]
2. work withに下記のURLを指定
https://dl-ssl.google.com/android/eclipse/
3. 入力したらインストールパッケージがリストに表示される
Developer Tools
にチェック
4. あとは[Next>]で進めてインストール完了

手順2を入力してもリストが更新されない場合は下記のURLで試してみる。
http://dl-ssl.google.com/android/eclipse/

以上です。

Activityのライフサイクルが保証されないケース


以前紹介したLowMemoryKiller関係で1つ。
→以前の記事

バックグラウンドにあるアプリはkillされやすいことを調査しましたが、
注意しないといけないのはLowMemoryでkillされた場合はActivityのライフサイクルが
保証されないということです。

フォアグラウンドでもkillされる可能性はありますが、バックグラウンドの場合はその
可能性がぐんとあがります。
※FOREGROUND = 空きメモリ約8MB以下でkill / HIDDEN = 空きメモリ約29MB以下でkill

空きメモリが20MBの状態でアプリ起動された場合、フォアグラウンド中はいつも通りに
操作できますが、他アプリ割り込みなどによりバックグラウンドに回った途端にこのア
プリはLowMemoryKillerのkill対象になってしまいます。
つまりはLowMemoryでkillされるため、onStop~onDestoryが呼ばれない可能性があるとい
うことです。

最近はハイスペック端末も増えて、一般には気にすることではないかもしれませんが、
・メモリ上ではなくデータベースなど永続的なストレージでフラグON/OFFを管理している
・フラグのON/OFFがActivityのライフサイクルに依存している
上記のようなアプリは期待しない状態に陥る可能性があります。

アプリが起動しなくなるような状態を避けるためにも、ライフサイクルがすべて正しく
コールされることは必ずしも保証されないことを覚えておいた方がよいでしょう。

onSavedInstanceはonStopの前に呼ばれます。これをうまく使えばkillされる直前のデータ
をうまく退避させることも可能です。

2011/10/10

最新のツイートを取得する方法



指定ユーザの最新ツイートを取得する方法を調査しました。

まず、最新のツイートを指すURIは下記となります。
http://api.twitter.com/1/statuses/user_timeline/<ユーザ名>.<形式>?count=1

<ユーザ名>・・・取得したユーザ名(ユーザID)
<形式>・・・JSON形式の場合は.json、xml形式の場合は.xml等
※count=1は一件取得する場合のクエリ。

例)Yuki_312の最新ツイートを1件JSON形式で取得した場合
http://api.twitter.com/1/statuses/user_timeline/Yuki_312.json?count=1

上記URIから、HttpURLConnectionなどを使用してツイートを取得する。
※HttpURLConnectionを使用したHTTP通信の方法は割愛

最新のツイートをbyte配列で取得した場合、下記でJSONArray型に変換すると
便利。
JSONArray json = new JSONArray(
        new String(/*最新のツイート*/recentTweet(), "UTF-8"));


JSONArray型に変換できればあとは順々にデータを取得すればOKです。
ツイート日付の取得はちょっとコツが必要。
int len = json.length();
for (int i = 0; i < len; i++) {    
    JSONObject tweet = json.getJSONObject(i);    
    String text = tweet.getString("text");    
    
    // ツイート日付の取得    
    SimpleDateFormat df = new SimpleDateFormat("EEE MMM d HH:mm:ss Z yyyy", Locale.ENGLISH);    
    df.setTimeZone(TimeZone.getTimeZone("GMT"));    
    Date created_at_en = df.parse(tweet.getString("created_at"));    
    String created_at_ja = DateFormat.getDateInstance(DateFormat.FULL, Locale.JAPAN)    
            .format(created_at_en);    
    
    JSONObject user = tweet.getJSONObject("user");    
    String screen_name = user.getString("screen_name");    
}


2011/10/08

Android:adb logcat


端末のログ情報を取得できるlogcatツールについてのTipsです。

・ログに出力時間を含める
adb logcat -v time

・ログにスレッドIDも出力
adb logcat -v long

・ファイルに出力
adb logcat > log.txt

・radioログを取得
adb logcat -b radio

・ログレベルによるフィルタ(例はInfo以上のみ出力)
adb logcat *:I

・指定タグへのログレベルフィルタ(例はタグTestに対するフィルタ, 他タグは全出力)
adb logcat Test:I

・指定タグのみ出力する場合(Sはサイレント指定)
adb logcat Test:V *:S

・USBを抜いた後もログを取り続ける場合
adb logcat > log.txt &

最後の方法はUSB未挿入状態でのみ不具合が発生する場合に便利です。

2011/10/03

LowMemoryKillerによりkillされる閾値について


アプリがLowMemoryKillerによってkillされる閾値について調査しました。

LowMemoryKillerは端末上メモリの空き容量が少なくなると、容量確保のため、
決まったルールに従ってアプリをkill(強制終了)する機構です。

LowMemoryでkillされる閾値(メモリの空き容量)は固定ではなくアプリの状態で
上下します。
閾値は下記より求めることができます。

(1)閾値の算出
Androidのプロパティには下記が定義されている。
(実際にはinit.rcで設定される)
[ro.FOREGROUND_APP_ADJ]: [0]
[ro.VISIBLE_APP_ADJ]: [1]
[ro.HOME_APP_ADJ]: [6]
[ro.HIDDEN_APP_MIN_ADJ]: [7]
[ro.EMPTY_APP_ADJ]: [15]

[ro.FOREGROUND_APP_MEM]: [2048]
[ro.VISIBLE_APP_MEM]: [3072]
[ro.HOME_APP_MEM]: [6144]
[ro.HIDDEN_APP_MEM]: [7168]
[ro.EMPTY_APP_MEM]: [8192]

[XXXX_XXX_ADJ]は「システム内で生かすべき優先順位」です。
より値が小さいほどシステムからkillされづらくなります。
つまりは、値が大きければ閾値がよりシビアであり、値が小さければその逆と
なります。

XXXX_XXXにあたる部分はそのアプリの状態を表します。
状態の詳細は下記のコードコメントから予想できると思います。
// This is the process running the current foreground app.  We'd really
// rather not kill it! Value set in system/rootdir/init.rc on startup.
final int FOREGROUND_APP_ADJ;


// This is a process only hosting activities that are visible to the
// user, so we'd prefer they don't disappear. Value set in
// system/rootdir/init.rc on startup.
final int VISIBLE_APP_ADJ;

// This is a process holding the home application -- we want to try
// avoiding killing it, even if it would normally be in the background,
// because the user interacts with it so much.
final int HOME_APP_ADJ;

// This is a process only hosting activities that are not visible,
// so it can be killed without any disruption. Value set in
// system/rootdir/init.rc on startup.
final int HIDDEN_APP_MAX_ADJ;
static int HIDDEN_APP_MIN_ADJ;

// This is a process without anything currently running in it.  Definitely
// the first to go! Value set in system/rootdir/init.rc on startup.
// This value is initalized in the constructor, careful when refering to
// this static variable externally.
static int EMPTY_APP_ADJ;


ここでもう一度プロパティを見てみると、アプリのどの状態がkillされづらい
(値が小さい)かがわかります。
フォアグラウンドであればよりkillされづらく、背面にいればkillされやすい
ようです。
また、HOMEアプリが特別扱いされているのもわかります。


次に、killされる閾値について見ていきます。
[XXXX_XXX_MEM]はアプリ動作に最低限必要な空きページ数です。
この値を下回るとアプリはkillされることになります。

この値はページ数であり、最低限必要な空きメモリ量ではないことに注意してください。
LowMemoryでkillされる閾値(空きメモリ量)は下記で求めることができます。
ページ数([XXXX_XXX_MEM]) × ブロックサイズ(1ページあたりのサイズ)

ブロックサイズは各端末により異なります。
下記のシェルコマンドで端末のブロックサイズを求めることができます。
# df /system

実行結果)
Filesystem             Size   Used   Free   Blksize(ブロックサイズ)
/system                 86M    86M     0K   4096

例えば、[ro.FOREGROUND_APP_MEM]: [2048]でブロックサイズが4096の場合、
2048(ページ数)×4096(ブロックサイズ)=8388608Byte
となり、killされる閾値は約8MBということになります。

下記まとめとなります。
※Blksizeやページ数を上記とした場合

アプリの状態 = 閾値
FOREGROUND = 約8MB
VISIBLE = 約12MB
HOME = 約25MB
HIDDEN = 約29MB
EMPTY = 約33MB

HOMEアプリは空きメモリ容量約25MBを下回るとkillされるのに対して、
FOREGROUNDなアプリは空きメモリ容量約8MBを下回らないとkillされない。

以上です。

2011/10/02

PreferenceListで選択されたPreferenceを判別する方法


アプリ設定の読込みや保存に利用できるPreference。
Preferenceの編集にはPreferenceActivityを利用することができます。

今回はPreferenceActivityを使用する前提で、ユーザが選択したPreferenceを
判定する方法です。

1. Preference選択時のコールバックを拾う
PreferenceActivityの下記メソッドをオーバーライドする
android.preference.PreferenceActivity.onPreferenceTreeClick(PreferenceScreen, Preference)

2. 選択したPreferenceを判別する
ユーザが選択したPreferenceの判別には下記のメソッドが使用できる
android.preference.Preference.getKey()

getKey()で返ってくる値は、Preferenceのリソース(下記メソッドで指定するxml)
で指定するandroid:keyの値となる。
android.preference.PreferenceActivity.addPreferencesFromResource(int)

一連のソースは下記

・Preference(config.xml)
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
	xmlns:android="http://schemas.android.com/apk/res/android">
	<PreferenceCategory
		<EditTextPreference 
			android:key="follow_user"
			...
	</PreferenceCategory>
</PreferenceScreen>

・Preference設定画面
public class TwitterWidgetConfigure extends PreferenceActivity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		addPreferencesFromResource(R.xml.config);
	}

	@Override
	public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
			Preference preference) {
		// key"follow_user"が返される
		android.util.Log.e("yuki", "yuki key=" + preference.getKey());
		return true;
	}

以上です。
2011/10/01

Android.gitからダウンロードしたソースをEclipseで紐付けする方法



前回ダウンロードしたAndroidのソースコードをEclipseに取り込むことで、
framework層以下のソースコードを[呼出し階層を開く]や、宣言へのジャンプ
機能で開けるようにできます。

方法は下記。
※xcopyコマンド(windows)で実現しようとしましたが、上手くいかなかったの
で、cpコマンド(cygwin)でやりました。

export PATH_GIT_BASE=~/android_src/frameworks/base
export PATH_JAR_BASE=~/jar

cp -R $PATH_GIT_BASE/core/java $PATH_JAR_BASE
cp -R $PATH_GIT_BASE/core/java $PATH_JAR_BASE
cp -R $PATH_GIT_BASE/graphics/java $PATH_JAR_BASE
cp -R $PATH_GIT_BASE/keystore/java $PATH_JAR_BASE
cp -R $PATH_GIT_BASE/location/java $PATH_JAR_BASE
cp -R $PATH_GIT_BASE/media/java $PATH_JAR_BASE
cp -R $PATH_GIT_BASE/opengl/java $PATH_JAR_BASE
cp -R $PATH_GIT_BASE/sax/java $PATH_JAR_BASE
cp -R $PATH_GIT_BASE/telephony/java $PATH_JAR_BASE
cp -R $PATH_GIT_BASE/vpn/java $PATH_JAR_BASE
cp -R $PATH_GIT_BASE/wifi/java $PATH_JAR_BASE
cp -R $PATH_GIT_BASE/services/java $PATH_JAR_BASE


・PATH_GIT_BASEはandroid.gitからダウンロードしたソースフォルダのルートデ
ィレクトリ。
・PATH_JAR_BASEはコピー先のディレクトリ。

Eclipseに取り込む前に下記フォルダ(sources)を作成します。
%ANDROID_SDK_ROOT%\platforms\android-XX\sources
※android-XXはAndroid API Levelなので、Eclipseで作成しているプロジェクト
が参照するAPI Levelを指定

PATH_JAR_BASEにコピーしたファイルのjavaディレクトリ下を、作成したsources
フォルダに移動します。

ここまでで、sourcesディレクトリの階層は下記のようになっているはずです。
sources
 |+android
 |+com
 |+javax
 |Android.mk ...などなど

上記の状態でEclipseを起動すると、Activity.classファイルなどを開く際に
コピーしたjavaファイルがアタッチされて参照できるようになっているはずです。

足りないファイルがある場合は同じ要領でsources下にソースコードを持って
くればOKです。

以上です。