Superdry Memorandom :-p

旧「superdry memorandum :-D」です

Xperia rayのCamera#getParameterの値

モデル名は「SO-03C」、APIレベル10です。で、arcとかacroとかと同じ感じですねー。

picture-size-values=3264x2448,3264x1836,2592x1944,2048x1536,1632x1224,1920x1080,1280x960,1280x720,640x480,480x320;
zoom=0preview-fps-range=1000,30000;
min-exposure-compensation=-6;
antibanding=auto;
vertical-view-angle=360;
zoom-supported=true;
horizontal-view-angle=360;
whitebalance=auto;
jpeg-thumbnail-height=120;
scene-mode=auto;
jpeg-quality=85;
smooth-zoom-supported=true;
preview-format-values=yuv420sp;
jpeg-thumbnail-quality=100;
focus-mode=macro;
preview-format=yuv420sp;
preview-size=640x480;
focal-length=4.1;
video-frame-format=yuv420sp;
picture-format-values=jpeg;
max-exposure-compensation=6;
flash-mode-values=off,torch;
exposure-compensation=0;
preview-frame-rate-values=7,10,15,20,24,30;
exposure-compensation-step=0.333333;
preview-frame-rate=30;
flash-mode=off;
effect-values=none,mono,negative,solarize,sepia,posterize;
focus-mode-values=auto,infinity,macro,fixed,continuous-video;
picture-size=2048x1536;
max-zoom=40;
effect=none;
jpeg-thumbnail-width=160;
whitebalance-values=auto,incandescent,fluorescent,daylight,cloudy-daylight;
scene-mode-values=auto,portrait,landscape,night,night-portrait,beach,snow,sports,party,barcode;
picture-format=jpeg;
focus-distances=Infinity,Infinity,Infinity;
preview-fps-range-values=(1000,7000),(1000,10000),(1000,15000),(1000,20000),(1000,24000),(1000,30000);
jpeg-thumbnail-size-values=160x120,0x0;
zoom-ratios=100,107,115,123,132,141,152,162,174,187,200,214,230,246,264,283,303,325,348,373,400,429,459,492,528,566,606,650,696,746,800,857,919,985,1056,1131,1213,1300,1393,1493,1600;
preview-size-values=1280x720,864x480,640x480,480x320,352x288,320x240,176x144;
antibanding-values=auto,off,50hz,60hz

Mac OS Xでデバッグできないときに

Android開発環境にMacつかってると面倒なドライバのインストールとかいらないのでほんと超便利なのですが、ごくごくたまにUSBデバッグができない端末に出会うときがあります。具体的には以下のような状態です。

  • 端末側ではUSBデバッグモードになっている
  • ターミナルでadb devicesしても端末リストに現れない

こういう状態の場合、この端末のUSBベンダIDがサポート外である可能性が高いです。サポート外の場合、対象のUSB vender IDを調査し、明示的に登録してやる必要があります。USB vender IDを調査するには、Max OS Xではターミナルから

$ system_profiler SPUSBDataType

とすると現在接続中のUSBの情報を取得できます。ここで調べたUSB vender IDを$HOME/.android/adb_usb.iniに記述し、adbサーバを再起動すればOK。

以下のソースを参考にさせてもらいました。ありがとうございます。

Android Developer Blog の Custom Class Loading in Dalvik をてきとう翻訳してみたよ。

ちょっと興味深かったので勉強メモがてら、てきとう翻訳してみました。十分に理解してるとは言いがたいので訳に変なとこがあったらご指摘ください。

今回の記事ではFred Chungさんという方が書いているのですが、最後の方書くの飽きちゃったのかかなり投げ出した感がありました(笑)。Android Developer Blogって毎回書く人が違うんですが、毎回その人なりの個性がでててとても面白いです。

Dalvikでのカスタムクラスローディング

DalvikVMは開発者にカスタムクラスをロードできるようにしています。デフォルトの位置からDalvik実行可能ファイル(dexファイル)をロードするかわりに、アプリが内部ストレージまたはネットワーク越しからロードすることが可能です。

このテクニックは全てアプリケーションに役立てるものではありません。 実際ほとんどがこのテクニックなしでも問題ないです。しかし、カスタムクラスをローディングする状況で役に立ちます。ここに2つの利点を示します。

  • メソッド参照を64k以上含めることができます。これはdexファイルでサポートされる最大数です。この制限を逃れるため、開発者は複数のセカンダリdexファイルにプログラムを分割し、ランタイムでそれらをロードします。
  • ランタイムでロードされるダイナミックコードによって拡張可能なロジックに設計することが可能です。

クラスロードとdexファイルの分割についてデモ用サンプルアプリを作成しました。(下記に示される理由により、ADTのEclipseプラグインではサンプルアプリをビルドできません。中に含まれるantビルドのスクリプトを使います。詳細はReadme.txtを参照。)

このアプリには、Toastを表示するためにライブラリコンポーネントを呼び出す簡単なActivityがあります。 Activityとそのリソースはデフォルトのdexで保持されますが、ライブラリコードはAPKにバンドルされたセカンダリdexに格納されます。それには下記に詳細を示す通りビルドブロセスを変える必要があります。

ライブラリメソッドを呼び出し可能にする前に、アプリはセカンダリdexファイルをロードしなければなりません。 関連箇所の実装を見ていきましょう。

コードの構成

アプリは以下の3つのクラスで構成されています。

  • com.example.dex.MainActivity: ライブラリを起動するUIコンポーネント
  • com.example.dex.LibraryInterface: ライブラリのInterface定義クラス
  • com.example.dex.lib.LibraryProvider: ライブラリの実装

ライブラリはセカンダリdexでパッケージ化されます。残りのクラスはデフォルト(プライマリ)dexファイルに含まれます。下記の章の「ビルドプロセス」はこれを実現するための方法を示しています。もちろんパッケージングをどのようにするかは、開発者がどのような経緯で取り組んでいるか、その状況に依存します。

クラスローディングとメソッド呼び出し

LibraryProviderを含むセカンダリdexファイルはアプリケーションのassetとして保持されます。最初に、クラスローダーのパスを提供できるようストレージの場所をコピーする必要があります。サンプルアプリでは内部ストレージ領域を使っています。(技術的には外部ストレージでも可能ですが、そこにアプリケーションバイナリをおくことのセキュリティについて考える必要があります)

下記はMainActivityのスニペットです。標準的なファイルI/Oでコピーを実行しています。

  // DexClassLoaderがセカンダリdexファイルを処理する前に、
  // まずはじめに、assetリソースからストレージの場所をコピーします。
  File dexInternalStoragePath = new File(getDir("dex", Context.MODE_PRIVATE), SECONDARY_DEX_NAME);
  ...
  BufferedInputStream bis = null;
  OutputStream dexWriter = null;

  static final int BUF_SIZE = 8 * 1024;
  try {
      bis = new BufferedInputStream(getAssets().open(SECONDARY_DEX_NAME));
      dexWriter = new BufferedOutputStream(new FileOutputStream(dexInternalStoragePath));
      byte[] buf = new byte[BUF_SIZE];
      int len;
      while((len = bis.read(buf, 0, BUF_SIZE)) > 0) {
          dexWriter.write(buf, 0, len);
      }
      dexWriter.flush();
      dexWriter.close();
      bis.close();
      
  } catch (. . .) {...}

次に、セカンダリdexファイルからライブラリを抽出しロードするためにDexClassLoaderインスタンス化します。このようにロードされたクラスのメソッドを呼び出す方法は色々あります。今回の例では、クラスインスタンスをメソッドが直接呼ばれるインタフェースにキャストします。

もう一つの方法として、リフレクションAPIをつかってメソッドを呼び出す方法があります。リフレクションを使う利点は、どんなインタフェースを実装するのもセカンダリdexファイルを必要としないことです。 しかし、リフレクションは冗長で遅いことも意識しておかないといけません。

  // DexClassLoaderが最適化dexファイルを書き出す内部ストレージへのパス
  final File optimizedDexOutputPath = getDir("outdex", Context.MODE_PRIVATE);

  DexClassLoader cl = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
                                         optimizedDexOutputPath.getAbsolutePath(),
                                         null,
                                         getClassLoader());
  Class libProviderClazz = null;
  try {
      // ライブラリをロード
      libProviderClazz = cl.loadClass("com.example.dex.lib.LibraryProvider");
      // インタフェースのメソッドを直接呼び出すことができるように、
      // 返されたオブジェクトをライブラリのインタフェースにキャスト
      // 別の方法として、リフレクションでメソッドを呼び出すことができる(ただし冗長になる)
      LibraryInterface lib = (LibraryInterface) libProviderClazz.newInstance();
      lib.showAwesomeToast(this, "hello");
  } catch (Exception e) { ... }
ビルドプロセス

2個の別々のdexファイルを作るためには、標準的なビルドプロセスを少し変える必要があります。それは、プロジェクト内のAnt用のファイルbuild.xmlのなかで、「-dex」ターゲットを変更するだけです。

以下の手順でターゲット「-dex」を変更します。

2つのデフォルトdexファイルとセカンダリdexファイルに変換される.classファイルを保存するために用意した2つのディレクトリを作成します。PROJECT_ROOT/bin/classesの中から選択した.classファイルを用意しておいたディレクトリにコピーします。

<!-- プライマリdexファイルにはライブラリ実装以外がふくまれる -->
<copy todir="${out.classes.absolute.dir}.1" >
    <fileset dir="${out.classes.absolute.dir}" >
        <exclude name="com/example/dex/lib/**" />
    </fileset>
</copy>
<!-- セカンダリdexには実際のライブラリ実装が含まれる -->
<copy todir="${out.classes.absolute.dir}.2" >
    <fileset dir="${out.classes.absolute.dir}" >
        <include name="com/example/dex/lib/**" />
    </fileset>
</copy>     

この2つのディレクトリの中の.classファイルを個別のdexファイルに変換します。セカンダリdexファイルをjarファイルに追加します(これはDexClassLoaderに対するインプットとなります)。最後にプロジェクトのassetsフォルダへjarファイルをおきます。

<!-- apkのassetsフォルダにアウトプットをパッケージ化しておいておく -->
    <jar destfile="${asset.absolute.dir}/secondary_dex.jar"
            basedir="${out.absolute.dir}/secondary_dex_dir"
            includes="classes.dex" />

ビルドを開始するために、プロジェクトのルートディレクトリでant debug(もしくは ant release)を実行します。

どうですか?正しい状況で、ダイナミッククラスローディングを役立ててください。

Applicationのライフサイクル

AndoridのApplicationオブジェクトにも4つのhandlerがあって、それぞれオーバーライドすることもあるのですが、今まで適当にしか理解してなかった。これを機にきちんと理解し直してみました。

  • onCreate
    • Applicationの生成時に呼ばれる。
    • ここでアプリケーションのシングルトンを初期化する
    • ここでアプリケーションの状態変数や共有リソースなどを生成したり初期化したりする。
  • onTerminate
    • Applicationオブジェクトが終了するときに呼ばれる。
    • 確実に呼ばれる保証はない。
      • 他のアプリのためにリソースを解放する目的でkernelから終了させられる場合は、Warningも出さずonTerminateも呼ばれずプロセスは終了される。
  • onLowMemory
    • リソースが逼迫してる状況で、メモリを解放するタイミングを提供するメソッド。
    • 一般的にはバックグラウンドで稼働しているプロセスがシステムに終了されて、なおかつ前面のapplicationのメモリにまだ余裕のない場合に呼ばれる。
    • ここでキャッシュをクリアしたり、不必要なリソースを解放したりする処理を実装する。
  • onConfigurationChanged
    • ActivityとちがってApplication オブジェクトはconfiguration changedがとんでくる度にkillされたりrestartしたりしないので、ここにはconfiguration changedの際のApplicationレベルの実装を書く。

ソフトキーボードの表示を検知してレイアウトを変更する

入力時にはEditTextにフォーカスがあたりソフトキーボードが起動しますが、その挙動に影響されて、以下のようにEditTextがつぶれて使いづらくなることがあります。意外とリリースされてるソフトでも結構見かけます。
 こういうレイアウトで、
 文字入力しようとすると…あらら
 変換候補が表示されたあかつきには…ぐぬぬ

対処方法

このような場合、色々な対処法がありますが、ソフトキーボードの表示/非表示を検知してレイアウトを変えるというのも一つの手です。現状ソフトキーボードの表示/非表示を検知するようなAPIはありません。一般的にはカスタムViewを作成しその縦幅からソフトキーボードの表示/非表示を判断し、レイアウトを変更するのが通例のようです。意外と簡単なのでココにメモします。

構成

  • /src/org/superdry/sample/gui/DetectableSoftKeyLayout.java
    • カスタムレイアウトクラス
  • /src/org/superdry/sample/gui/MainActivity.java
    • 起動Activity
  • /res/layout/main.xml
    • MainActivityのレイアウトxml
  • AndroidManifest.xml
DetectableSoftKeyLayout.java

今回はLinearLayoutを拡張し、カスタムレイアウトを作成しました。このレイアウトの縦幅を監視するリスナーを実装します。ここでは例として、ソフトキーボードの表示によってLinearLayoutの縦幅100px縮んだ場合、ソフトキーボードが表示されているとして、onSoftKeyShownにtrueをセットしてます。

package org.superdry.sample.gui;

import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.LinearLayout;

public class DetectableSoftKeyLayout extends LinearLayout {

	public DetectableSoftKeyLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public interface OnSoftKeyShownListener {
		public void onSoftKeyShown(boolean isShown);
	}

	private OnSoftKeyShownListener listener;

	public void setListener(OnSoftKeyShownListener listener) {
		this.listener = listener;
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// (a)Viewの高さ
		int viewHeight = MeasureSpec.getSize(heightMeasureSpec);
		// (b)ステータスバーの高さ
		Activity activity = (Activity) getContext();
		Rect rect = new Rect();
		activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
		int statusBarHeight = rect.top;
		// (c)ディスプレイサイズ
		int screenHeight = activity.getWindowManager().getDefaultDisplay()
				.getHeight();
		// (a)-(b)-(c)>100ピクセルとなったらソフトキーボードが表示されてると判断
		//(ソフトキーボードはどんなものでも最低100ピクセルあると仮定)
		int diff = (screenHeight - statusBarHeight) - viewHeight;
		if (listener != null) {
			listener.onSoftKeyShown(diff > 100);
		}
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}
}
MainActivity.java

ここではリスナーの登録と、ソフトキーボードが表示の場合/非表示の場合の処理を実装しています。ここでは例としてソフトキーボード表示の場合はPostボタンを非表示にしています。

package org.superdry.sample.gui;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import org.superdry.sample.gui.R;

public class MainActivity extends Activity {

	private Button post;
	private DetectableSoftKeyLayout layout;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		layout = (DetectableSoftKeyLayout) findViewById(R.id.detectable_layout);
		layout.setListener(listner);
		post = (Button) findViewById(R.id.post_btn);

	}

	DetectableSoftKeyLayout.OnSoftKeyShownListener listner = new DetectableSoftKeyLayout.OnSoftKeyShownListener() {
		@Override
		public void onSoftKeyShown(boolean isShown) {
			if (isShown) {
				// ソフトキーボードが表示されている場合
				// postボタンを非表示にする
				post.setVisibility(View.GONE);
			} else {
				// ソフトキーボードが表示されてなければ、表示する
				post.setVisibility(View.VISIBLE);
			}
		}
	};
}
main.xml

MainActivityのレイアウトファイルです。

<?xml version="1.0" encoding="utf-8"?>
<org.superdry.sample.gui.DetectableSoftKeyLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent" android:orientation="vertical"
	android:layout_height="match_parent" android:id="@+id/detectable_layout">
	<TextView android:id="@+id/title" android:layout_width="wrap_content"
		android:layout_height="wrap_content" android:text="Input Message"
		android:textSize="20dp"/>
	<EditText android:id="@+id/edittext" android:inputType="textMultiLine"
		android:layout_height="match_parent" android:layout_width="match_parent"
		android:layout_weight="1">
	</EditText>
	<Button android:text="Post" android:id="@+id/post_btn"
		android:layout_width="wrap_content" android:layout_height="wrap_content" >
		<requestFocus /></Button>
</org.superdry.sample.gui.DetectableSoftKeyLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="org.superdry.sample.gui"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="4" />

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name="org.superdry.sample.gui.MainActivity"
                  android:label="@string/app_name" android:windowSoftInputMode="adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest>

対応後

こんな挙動になります。EditTextがつぶれなくなった!わーい!
 

Activityの4つの状態

初心者の人に教えるにあたって、Activityのライフサイクルをどう説明したらいいかで迷ってたのですが、ちょっといい説明があったのでメモってみました。

Activity Stackの図

Activityは以下のようなスタックで管理されている。

状態

  • Active
    • ActivityがActivity Stackの一番上にある。
    • 最前面のvisibleでfocusedでユーザ入力を受けられる状態。
    • Systemからkillされる優先度はPausedより低い。
  • Paused
    • ActivityがActivity Stackの一番上にある。
    • visibleだけどfocusは当たってない状態。例えば透過Activityやフル画面でないActivityが対象のActivityより上に表示された場合など。
    • unfocusedなのでユーザ入力は受け付けられない。
    • この状態でシステムからkillされることは滅多にないがまれにある。
    • Systemからkillされる優先度はStoppedより低い。
  • Stopped
    • unvisibleな状態だけどメモリ上には残っている。
    • この状態でデータセーブしたり、UI状態を保存したりする。
    • Systemからはまず最初にkillされる。
  • Inactive
    • Activityがkillされて再起動する前の状態。
    • この状態になるとActivityStackからは削除される

Activityの遷移


各メソッドについて

  • onCreate
    • Activityの初期化を行う。UIをinflateしたり変数定義やサービス、スレッドの生成など。Activity生成時に1回だけ呼ばれる。
  • onDestory
    • onCreate()で生成した全てのリソースを解放する。主に外部との接続(ネットワークやデータベースリンク)を閉じる処理をココで実装。
    • 必ずシステムから呼び出されるわけではないことに注意。
  • onRestoreInstanceState
    • UIの保存したい情報をBundleに入れて、onSaveInstanceStateメソッドをオーバライドしたメソッド内で保存する。この保存した前の画面の状態を読み出すメソッド。
  • onStop
    • Visible→Invisibleに遷移するとき呼ばれる。見えなくなるので、アニメーションとか、GPS、TimerやService、Broadcast ReceiverなどUIの更新に使われるようなプロセスを終了させる。必ずシステムに呼び出されるわけではないことに注意。
  • onStrat/onRestart
    • Invisible→Visible状態になったとき、onStartまたはonRestartでonStopで停止したUIを更新するようなプロセスを開始する。
    • onRestartはonStartより前に呼び出される。Activityが一旦Invisibleになったあと、Visibleになったときのみ実行される。
  • onPause
    • アプリのレスポンスに影響するので、ココはなるべく軽く速くなるように実装する。
    • onPauseの前に、onSaveInstanceState()が実行される。このメソッドはUI状態をBundleオブジェクトにつめてActivityのUI情報(例えば、入力途中の文字列とか、保存前のcheckboxの状態とか、フォーカス状態など)を保存する。なのでUI情報の保存はココで実装すべきじゃない。
    • ここでスレッドやプロセス、Broadcast Receiverを一時停止する。
    • 終了するときはシステムから必ず呼ばれる。
  • onResume
    • アプリのレスポンスに影響するので、ココはなるべく軽く速くなるように実装する。
    • onPause()で一時停止したスレッドやプロセス、Broadcast Receiverを再開させるような処理を実装する。
    • UI情報のリロードはココで実装すべきじゃない。

Androidの意外と知られていないツールlayoutopt

AndroidSDKにはいろんなツール類が付属されていますが、Androidのレイアウトxmlファイルの最適化を検証するlayoutoptについては、意外に使っていない人が多かったので紹介します。以下のAndroid Developerのページにも説明があります。

Layoutの最適化はパフォーマンスに直結するのでとても大事です。

  • 不必要なLayoutを使うことはさける
  • 大量のViewをつかうことはさける(View80個が目安)
  • 階層が深くなりすぎることをさける(10階層ぐらいが目安)

最適化すべき箇所を指摘してくれるツールが、layoutoptです。layoutopt自体はSDKのホームディレクトリの下のtoolsフォルダに入ってます。使い方はターミナルから、以下のように実行します。

layoutopt <file_or_directory> ...

例として、とあるプロジェクトで実行してみました。

$ cd sample_project_home
$ layoutopt ./res/layout*
./res/layout/note_edit.xml
	31:31 Use an android:layout_width of 0dip instead of wrap_content for better performance
	40:40 Use an android:layout_height of 0dip instead of wrap_content for better performance
./res/layout/notepad.xml
./res/layout-port/notepad.xml
$ 

ここではnote_edit.xmlの31行目のandroid:layout_widthと40行目のandroid:layout_heightの設定値を、"wrap_content"から"0dip"に変更した方がパフォーマンス的によいよ!と教えてくれてます。ちょう便利!

@zaki50さんのブログ記事みて、最適化考えるの超めんどくさいと思った人は多用するといいとおもいました。