2010年10月2日土曜日

Android Home キーが押されたことを検知する。

Android で ホームキー (Home Key) を検知する方法はないものかと

Activity のメソッドを見ていて

 protected void onUserLeaveHint()

というのを見つけました。

------

Called as part of the activity lifecycle when an activity is about to go into the background as the result of user choice. For example, when the user presses the Home key, onUserLeaveHint() will be called, but when an incoming phone call causes the in-call Activity to be automatically brought to the foreground, onUserLeaveHint() will not be called on the activity being interrupted. In cases when it is invoked, this method is called right before the activity's onPause() callback.

This callback and onUserInteraction() are intended to help activities manage status bar notifications intelligently; specifically, for helping activities determine the proper time to cancel a notfication.

---

Activity のライフサイクルの一部として、Activity が ユーザーの選択の結果 background に行くときに呼ばれる。例えば、ユーザーが Home key を押したとき onUserLeaveHint() が呼ばれる。しかし、着信によって in-call Activity が自動で foreground に来るような場合は onUserLeaveHint() は呼ばれない。onUserLeaveHint() は onPause() の直前に呼ばれる。

このコールバックと onUserInteraction() は Activity がステータスバーの notification を賢く処理するために意図されたものである。特に、Activity が notification をキャンセルする適切な時間を決定するための指標となる。

---

で、試してみた。


-- Launcher から起動 --

onApplyThemeResource(Resources.Theme theme, int resid, boolean first)

onCreate()

onWindowAttributesChanged (WindowManager.LayoutParams params)

onContentChanged()

onStart()

(*onRestoreInstanceState(Bundle))

onPostCreate(Bundle)

onResume()

onPostResume()

onAttachedToWindow()

onWindowFocusChanged (boolean hasFocus) : true

-- home key 押下 --

onUserInteraction()

onUserLeaveHint()

onSaveInstanceState()

onResume()

onPostResume()

onWindowFocusChanged (boolean hasFocus) : false

onCreateDescription() : null

onStop()

-- Home key 長押しから起動 --

onRestart()

onStart()

onResume()

onWindowFocusChanged (boolean hasFocus) : true

-- Back key で終了 --

onUserInteraction()

onResume()

onWindowFocusChanged (boolean hasFocus) : false

onStop()

onDestroy()

onDetachedFromWindow()

-- Launcher から起動 --

onApplyThemeResource() : resid = 16973829, first = true

onCreate()

onWindowAttributesChanged (WindowManager.LayoutParams params)

onContentChanged()

onStart()

onResume()

onAttachedToWindow()

onWindowFocusChanged (boolean hasFocus) : true




 確かに Home key を押したときは呼ばれていて、
 Back key を押したときは呼ばれていない。


 

Android Drawable Mutations

Drawable Mutations

訳&試してみた
サンプル部分は全入れ替えですw

-----

Android の drawable はアプリを作る上でとても便利である。

Drawable は一般的に View に関連付けられている拡張可能な描画用コンテナであり、画像を表示するための BitmapDrawable や 図形やグラデーションなどを描画するための ShapeDrawable などがある。(Drawable Resources の一覧)これらを組み合わせて複雑なレンダリングを実現することができる。

Drawable を使うことで、簡単に widget のレンダリングをカスタマイズできる。実際、この機能はすごく便利なため、ほとんどの標準 Android apps と widgets は drawables を使って作られており、Android framework のコアには約700の drawables が使われている。

drawables はシステム内で非常によく使われているため、Android は resource から読み込むときにこれらを最適化している。例えば、Button を作成するたびに新しい drawable が framework resoruces (android.R.drawable.btn_default) から読み込まれる。これはアプリ中のすべてのボタンが異なる drawable instance をその背景として使うことを意味している。しかし、これらすべての drawable は "constant state" と呼ばれる通常の状態を共有している。

この通常状態は、使用する drawable のタイプによって変わるが、一般的には resource によって定義可能なすべてのプロパティを含む。Button の場合、通常状態は bitmap 画像を含む。このように、アプリ中のすべてのボタンは同じ bitmap を共有し多くのメモリを節約している。

以下は、2つの異なる view の background に同じ画像をリソースを割り当てたときに、どのエンティティが生成されるかを表したダイアグラムである。2つの drawable が生成されるが、それらは同じ constant state を共有する。



この状態共有の機能は、メモリの無駄遣いを大幅に節約できるが、個々の drawable のプロパティを編集しようとした場合に問題が起こる。

ListView の各要素に星画像★が付いているアプリがあるとする。例えば、お気に入りの場合は★を不透明にし、そうでない場合は透明にしたいとする。そのために、ListView の getView() を次のように実装したとする。
# めんどいので、position が奇数は不透明、偶数は透明にする


public View getView(int position, View convertView, ViewGroup parent)

ViewHolder holder;

if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item,
parent, false);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);

convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

Drawable mIcon = mContext.getResources()
.getDrawable(R.drawable.ic_menu_star);

if ((position & 1) == 1) {
mIcon.setAlpha(255); // opaque
} else {
mIcon.setAlpha(70); // translucent
}

holder.text.setText(DATA[position]);
holder.icon.setImageDrawable(mIcon);

return convertView;
}


残念ながら、このコードだとすべての drawables が同じ透明度になる。




この結果は constant state で説明できる。リストの各アイテムに対して新しい drawable instance を取得したとしても、BitmapDrawable の場合、constant state は同じままになる。このように、1つの drawable instance の透明度を変化させると、他のすべての instance の透明度も変化する。この問題を Android 1.0 と 1.1 で関係するのは簡単ではなかった。

Android 1.5 以上なら、mutate() を使うことで、とても簡単にこの問題を解決できる。 このメソッドを drawable に対して発行すると、drawable の constant state が多重化されるので、他の drawable へ影響させずに個々のプロパティを変えることができる。ただし、drawable を mutate した後であっても、bitmap は共有されたままになる。

drawable で mutate() を呼び出すと次のようになる。



mutate() を使って、先のコードを変更する。

Drawable mIcon = mContext.getResources()
.getDrawable(R.drawable.ic_menu_star);

if ((position & 1) == 1) {
mIcon.mutate().setAlpha(255); // opaque
} else {
mIcon.mutate().setAlpha(70); // translucent
}


mutate() は自身の drawable を返すため、chain method call が可能である。ただし、これは新しいインスタンスを生成するわけではない。これで正しく表示されるようになる。




-----
全コードはこちら


public class MainActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.drawablemutate);
}

static class ViewHolder {
TextView text;
ImageView icon;
}

private class MyAdapter extends BaseAdapter {
private Context mContext;
private LayoutInflater mInflater;

private String[] DATA = { "Henry IV (1)", "Henry V",
"Henry VIII", "Richard II",
"Richard III", "Merchant of Venice",
"Othello", "King Lear" };

public MyAdapter(Context context) {
mContext = context;
mInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
}

public int getCount() {
return DATA.length;
}

public Object getItem(int position) {
return position;
}

public long getItemId(int position) {
return position;
}

public View getView(int position, View convertView, ViewGroup parent)

ViewHolder holder;

if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item,
parent, false);
holder = new ViewHolder();
holder.text = (TextView) convertView.findViewById(R.id.text);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);

convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}

Drawable mIcon = mContext.getResources()
.getDrawable(R.drawable.ic_menu_star);

if ((position & 1) == 1) {
mIcon.setAlpha(255); // opaque
} else {
mIcon.setAlpha(70); // translucent
}

holder.text.setText(DATA[position]);
holder.icon.setImageDrawable(mIcon);

return convertView;
}
}

2010年10月1日金曜日

Chrome To Libraroid

 C2DM (Cloud to Device Messaging) を使って
 Libraroid に組み込んでみました。

 動画です。



 C2DMの invitation mail に

> Please note that by default, all new sender accounts
> are held to a development-only quota. This quota is
> sufficient for development and exploratory purposes.
> Before you launch a C2DM-enabled application to a
> large audience, please reply to this email to discuss a
> production-level quota.

 とあったので、かってにマーケットに解き放つのは
 ダメなのだろうと思って公開してません。


 GAE for Java + Google Chrome Extension + Android app
 という構成です。

 基本的には公開されている ChromeToPhone のソースコード
 をそのまま流用してます。
 http://code.google.com/p/chrometophone/
 Auth Token が若干面倒だったくらいです。

 C2DM server からの Intent を ChromeToLibraroid という
 Android app で受信して、取り出したキーワードで
 Libraroid に ACTION.SEARCH を投げています。
 こうすると、対象のアプリ(この場合の Libraroid)は変更せずに
 2.2 以上の端末のみ、機能として拡張できるのでメンテが楽です。

 ChromeToPhone のコードを Slim3 を使って
 変更したやつを近々公開よてい(ホントかw)