2010年12月30日木曜日

Android ダイアログ表示時にソフトキーボードを出す

この方法は別にダイアログ(Dialog)でなくても、どこでも使えます。

showSoftInput メソッドを使います。

getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); を使う方法は gabu さんのブログ EditTextを持つダイアログ(AlertDialog)が表示された時に自動的にソフトキーボードを表示する。 - gabuchanの日記 - にのっています。


以前のエントリでボタンを押したときにソフトキーボードを表示する方法を紹介しました。

Y.A.M の 雑記帳: Android ボタンを押したときにソフトキーボードを消す

このときと同様に InputMethodManager を使います。

showSoftInput メソッドの第1引数で指定する View は EditView などの Editable な View にしてください(たぶん)。
# ちなみに、Button を第1引数に指定したらソフトキーボードは表示されません
# でした。リファレンスに "Explicitly request that the current input method's
# soft input area be shown to the user, if needed."
# とあるので、入力が必要な View を渡されたときだけ表示するんでしょうね。



EditText editText = (EditText)findViewById(R.id.edittext);

InputMethodManager inputMethodManager = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.showSoftInput(editText, 0);


ダイアログが表示されたときに自動でソフトキーボードをだすには、Dialog の onShow() で上記のコードを実行します。onFocusChange() で実行しても表示されません!


final Dialog newDialog = new Dialog(this);
newDialog.setContentView(R.layout.mydialog);
newDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface arg0) {
EditText editText = (EditText)newDialog.findViewById(R.id.edittext);

InputMethodManager inputMethodManager = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.showSoftInput(editText, 0);
}
});
newDialog.show();


res/layout/mydialog.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="30dip"
>
<EditText
android:id="@+id/edittext"
android:layout_width="200dip"
android:layout_height="wrap_content"
android:inputType="text"
/>
</LinearLayout>



# InputMethodManager には showSoftInputFromInputMethod() というメソッドもあるのですが、この人を実行してもなぜか

W/InputManagerService( 109): Ignoring showMySoftInput of uid 10052 token: android.os.BinderProxy@4091e980

# とかいわれて表示されませんでした。うむむ。


 

2010年12月27日月曜日

Android DatePickerDialog をカスタマイズする

DatePickerDialog は AlertDialog を継承しているので、前回のエントリ「Android AlertDialog の背景を変更する」 を応用します。

# 名古屋でお世話になった、じゅんこさんからの宿題です!
# 前回のエントリは前座で、今回のエントリがメインですw

android:alertDialogStyle は設定してある前提です。

で、

DatePickerDialog は theme を指定するコンストラクタが用意されているので、


final DatePickerDialog dialog = new DatePickerDialog(this, R.style.MyDialog, null, 2010, 11, 26);
dialog.show();


とするだけです。

res/values/styles.xml

<style name="MyDialog" parent="@android:style/Theme.Dialog">
<item name="android:windowBackground">@drawable/rect</item>
<item name="android:buttonStyle">@style/CustomButton</item>
<item name="android:textColor">#000</item>
</style>

<style name="CustomButton" parent="@android:style/Widget.Button">
<item name="android:background">@drawable/button_stateful</item>
</style>


res/drawable/rect.xml と res/drawable/button_stateful.xml は前回のエントリ を見てください。



# DatePicker 部分のカスタマイズはまた次のエントリでー。。。

Android AlertDialog の背景を変更する

ダイアログの背景(タイトル、メッセージ、ボタン部分で色が変わっているところ)を変更するには、

android:alertDialogStyle

を設定します。

res/values/styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MyTheme" parent="@android:style/Theme.Black">
<item name="android:alertDialogStyle">@style/AlertDialog</item>
</style>

<style name="AlertDialog">
<item name="android:fullBright">@android:color/transparent</item>
<item name="android:fullDark">@android:color/transparent</item>
<item name="android:topBright">@color/basic_blue1</item>
<item name="android:topDark">@color/basic_blue1</item>
<item name="android:centerBright">@color/basic_blue2</item>
<item name="android:centerDark">@color/basic_blue2</item>
<item name="android:centerMedium">@color/basic_blue2</item>
<item name="android:bottomBright">@color/basic_blue3</item>
<item name="android:bottomDark">@color/basic_blue3</item>
<item name="android:bottomMedium">@color/basic_blue3</item>
</style>
</resources>


AndroidManifest.xml の application タグや activity タグで android:theme="@style/MyTheme" を設定する


res/values/colors.xml

<resources>
<color name="basic_blue1">#5781a9</color>
<color name="basic_blue2">#a4bcc6</color>
<color name="basic_blue3">#995781a9</color>
<color name="basic_orange1">#99eca14f</color>
<color name="basic_orange2">#66eca14f</color>
<color name="basic_white1">#eeffffff</color>
<color name="basic_white2">#99ffffff</color>
</resources>



@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.main);

AlertDialog.Builder builder = new AlertDialog.Builder(this);

builder.setTitle("dialog");
builder.setMessage("test!");
builder.setPositiveButton("OK", null);
builder.show();
}






全体を1つの背景にしたい場合、AlertDialog.Builder クラスには theme を指定する方法がないので、(下記のリンクメモにあるように、master ブランチには theme 付のコンストラクタがあるんだけどなぁ。。。)AlertDialog を extends した独自ダイアログをつくります。


package yanzm.example.alertdialogsample;

import android.app.AlertDialog;
import android.content.Context;

public class MyAlertDialog extends AlertDialog {

public MyAlertDialog(Context context) {
super(context);
}

public MyAlertDialog(Context context, int theme) {
super(context, theme);
}
}



package yanzm.example.alertdialogsample;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.view.View;

public class MyActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.main);
}

// AlertDialog.Builder を使う普通の方法
public void showNormalAlertDialog(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);

builder.setTitle("dialog");
builder.setMessage("test!");
builder.setPositiveButton("OK", null);
builder.show();
}

// 独自ダイアログをつかってテーマを指定
public void showMyDialog(View v) {
MyAlertDialog myDialog = new MyAlertDialog(this, R.style.MyDialog);
myDialog.setTitle("dialog");
myDialog.setMessage("test!");
myDialog.setButton("OK", (OnClickListener)null);
myDialog.show();
}
}



res/values/styles.xml

<style name="MyDialog" parent="@android:style/Theme.Dialog">
<item name="android:windowBackground">@drawable/rect</item>
</style>


res/drawable/rect.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/white" />
<stroke
android:width="5dip"
android:color="@color/basic_blue1"
android:dashWidth="5dip"
android:dashGap="5dip" />
</shape>



showMyDialog



ついでに、ボタンをカスタマイズするには、独自ダイアログのコンストラクタで渡すスタイルで android:buttonStyle を設定します。

res/values/styles.xml

<style name="MyDialog" parent="@android:style/Theme.Dialog">
<item name="android:windowBackground">@drawable/rect</item>
<item name="android:buttonStyle">@style/CustomButton</item>
</style>

<style name="CustomButton" parent="@android:style/Widget.Button">
<item name="android:background">@drawable/button_stateful</item>
</style>


res/drawable/button_stateful.xml

<?xml version="1.0" encoding="utf-8" ?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="@android:color/transparent" />
</shape>
</item>
<item android:top="5dip" android:bottom="8dip" android:left="5dip"
android:right="5dip">
<selector>
<item android:state_pressed="true">
<shape>
<gradient android:startColor="@color/basic_orange1"
android:endColor="@color/basic_white1" android:angle="90" />
<corners android:radius="10dip" />
<stroke android:width="2dip" android:color="@color/basic_blue1" />
<padding android:left="15dip" android:top="15dip"
android:right="15dip" android:bottom="15dip" />
</shape>
</item>
<item android:state_focused="true">
<shape>
<gradient android:startColor="@color/basic_orange2"
android:endColor="@color/basic_white1" android:angle="90" />
<corners android:radius="10dip" />
<stroke android:width="2dip" android:color="@color/basic_blue1" />
<padding android:left="15dip" android:top="15dip"
android:right="15dip" android:bottom="15dip" />
</shape>
</item>
<item>
<shape>
<gradient android:startColor="@color/basic_white2"
android:endColor="@color/basic_white1" android:angle="90" />
<corners android:radius="10dip" />
<stroke android:width="2dip" android:color="@color/basic_blue1" />
<padding android:left="15dip" android:top="15dip"
android:right="15dip" android:bottom="15dip" />
</shape>
</item>
</selector>
</item>
</layer-list>


でーきた!







■リンクめも

 ・AlertController.java

setBackground() メソッドで

537 private void setBackground(LinearLayout topPanel, LinearLayout contentPanel,
538 View customPanel, boolean hasButtons, TypedArray a, boolean hasTitle,
539 View buttonPanel) {
540
541 /* Get all the different background required */
542 int fullDark = a.getResourceId(
543 R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark);
544 int topDark = a.getResourceId(
545 R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark);
546 int centerDark = a.getResourceId(
547 R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark);
548 int bottomDark = a.getResourceId(
549 R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark);
550 int fullBright = a.getResourceId(
551 R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright);
552 int topBright = a.getResourceId(
553 R.styleable.AlertDialog_topBright, R.drawable.popup_top_bright);
554 int centerBright = a.getResourceId(
555 R.styleable.AlertDialog_centerBright, R.drawable.popup_center_bright);
556 int bottomBright = a.getResourceId(
557 R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright);
558 int bottomMedium = a.getResourceId(
559 R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium);


 ・AlertDialog.java

 ここ(master)には、theme を第2引数にもつコンストラクタがあるのに...

 ・http://code.google.com/p/android/issues/detail?id=6278

 なんか関連ありそうな issue

 ・attrs.xml

 AlertDialog っていう styleable がある

 ・styles.xml

 ・themes.xml

  ダイアログのスタイルを設定している

 ・AlertDialog の styleable

 ・obtainStyledAttributes

 ・ContextThemeWrappter


 

2010年12月26日日曜日

Android Onscreen Input Methods

ソフトウェアキーボードによって画面の領域がせまくなってしまうので、それに配慮した設計を行わないと、入力できなーい!なんてことになってしまいます。

そこで、画面上に出てくる入力メソッド(=ほとんとソフトウェアキーボード)についてまとめたいと思います。

元ネタはこちら

Onscreen Input Methods

Android 1.5 から Android プラットフォーム で Input Method Framework (IMF) が提供されるようになりました。これによって、開発者は ソフトキーボードのような画面上からの入力方法を作成することができるようになりました。

 ・ Android input method editors (IMEs) の概要
 ・ IMF, IMEs と一緒に動くアプリケーションにはなにが必要か

について簡単に説明してきます。

What is an input method?

 Android IMF は、ソフトウェアキーボードや手書き認識入力やハードキーボード変換器など、複数の IMEs をサポートするようにデザインされています。しかし、ここではソフトウェアキーボードにフォーカスします。ソフトウェアキーボードによる入力方法が現在のプラットフォームの一部だからです。

 一般的には、ユーザーはテキスト入力するために、入力領域をタップして現在の IME にアクセスします。

 ソフトキーボードは画面の下部に、アプリケーションのウィンドウに被さるように配置されます。アプリケーションと IME 双方が使用可能な領域を作れるように、いくつかのアプローチをとっています。
 
 一つは、pan and scan と呼ばれるものです。これは単純に現在フォーカスされている View が見えるようにアプリケーションウィンドウのまわりをスクロールします。これがデフォルトのモードです。

pan and scan




 pan and scan の次に最も適用されるレイアウトはリサイズです。この場合、アプリケーションウィンドウは全体が見えるようにリサイズされます。たとえば、e-mail メッセージを編集しているときなどはスクロールできるようにリサイズされます。

resize



右側にスクロールバーが出現したのは、この編集領域(3つのEditTextのレイアウト)がリサイズできるような設定だったから(だと思う)

 アプリケーションウィンドウのサイズが変更されるため、IME によって隠される部分がなくなり、アプリケーションと IME 両方にフルアクセスできます。もちろん、アプリケーションが(十分なスペースを作るために)リサイズによって縮小可能な領域をもつ場合にのみ働きますが、このモードでの垂直方向の領域は、横向きで使用可能な領域より小さくなるので、多くのアプリケーションはすでに対応できています。


 もう一つのメジャーなモードが fullscreen or extract mode です。これは、アプリケーションと合理的にスペースを分け合うには IME が大きすぎる場合に使われます。標準の IMEs では、横向き画面のときにのみ遭遇することがあるでしょう。他のIMEでは、必要なときにいつでも自由にこのモードを使うことができます。この場合、アプリケーションウィンドウはそのままの状態で残り、単純に IME がその上にフルスクリーンで表示されます。




入力領域も IME の一部

IME がアプリケーションを覆ってしまうため、アプリケーションに入力される文字を表示するための入力領域を IME 自身が持ちます。そのため、ユーザー体験を向上させるために、アプリケーションがこの領域を限定的にカスタマイズできます。(例えば done ボタンに表示される文字列を変更するなど)



Basic XML attributes for controlling IMEs

 IMEs とアプリケーションをよりよくコンビネーションさせるためにシステムが行っていること

  * デフォルトでは pan and scan モードを使う。
   ただし、list や scroll view などの存在によってリサイズモードが
   働くと合理的に推測できる場合を除く
  * TextView 属性を解析して、ソフトキーボードを適切なキー配置で表示する
  * "next field" や "done" などのデフォルトのアクションを
   fullscreen IME に割り当てる

Specifying each EditText control's input type

 * EditText で android:inputType 属性を使う

(この属性は新しい属性で、android:password, android:singleLine, android:numeric, android:phoneNumber, android:capitalize, android:autoText, and android:editable などの古い属性を置き換える。もし古い属性と新しい属性を両方設定した場合、システムは android:inputType を使い、古い属性は無視する。)


android:inputType 属性の 3 パーツ

  * class : 文字の全体的な解釈
    * text (plain text)
    * number (decimal number)
    * phone (phone number)
    * datetime (a date or time)

  * variation : class をより洗練する
  (通常 class と variant 両方指定する)
    * textEmailAddress : キー配列は'@'を含む(べき)
    * numberSigned : 記号付の数値フィールド

  * flag : さらなる改善を提供する(flag は class に特有のもの)
    * text class のフラグ
      * textCapSentences
      * textAutoCorrect
      * textMultiline

例えば

<EditText android:id="@+id/edtInput"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:inputType="textShortMessage|textAutoCorrect|textCapSentences|textMultiLine"
android:imeOptions="actionSend|flagNoEnterAction"
android:maxLines="4"
android:maxLength="2000"
android:hint="@string/compose_hint"/>



Enabling resize mode and other window features

input method に関連する window の振る舞いを指定する。
AndroidManifest.xml の各 <activity> 定義で android:windowSoftInputMode 属性を指定することで、振る舞いをコントロールできる。いくつかの設定を組み合わせることが可能。

  * ウィンドウの調整モードを指定する
    * adjustResize か adjustPan
    * 常にどちらかを指定することを推奨する

  * Activity が表示されたときに、IME が自動で表示されるか
   どうかコントロールする
    * stateVisible : IME を自動で表示する
    * その他、stateUnspecified, stateUnchanged,
     stateHidden, stateAlwaysHidden, stateAlwaysVisible

例えば

<Activity name="EditContactActivity"
android:windowSoftInputMode="stateVisible|adjustResize">
...
</activity>


Activity ではないウィンドウからこの振る舞いをコントロールするには setSoftInputMode(int) メソッドを使います。


Controlling the action buttons

IME の "action" ボタンをカスタマイズする

2 つの action タイプ

  * 複数行編集 EditText 以外の場合、ソフトキーボード上の
   エンターキーは action にバインドされます。
   例えば、次のフィールドに移動したり、アプリケーションが
   action を実行するために割り込んだり。

  * fullscreen モードでは、一般的なアプリケーションの操作に
   簡単にアクセスするために、IME は付加的なアクションボタンを
   編集テキストの右側に表示します。

これらの操作は TextView の android:imeOptions 属性でコントロールできます。
以下の組み合わせを指定できます。

  * 事前に定義されている action 定数の一つ
    (actionGo, actionSearch, actionSend, actionNext, actionDone)
   指定されていない場合、システムは次にフォーカスできる
   フィールドがあるかどうかで、actionNext か actionDone の
   いずれか選択する。
   action しないように強制するには actionNone を指定する

  * flagNoEnterAction を指定すると、たとえ複数行テキスト
   ではなくても、enter key での action を使用できないようにする。
   例えば、タイピング中に誤って触れることで、送信などの
   回復不可能な action が実行されてしまうのを避けることができる。

  * flagNoAccessoryAction を指定すると、action ボタンを
   テキストエリアから削除できる

  * flagNoExtractUi を指定すると、完全にテキストエリアが
   削除され、背後にアプリケーションが見えるようになる


APIs for controlling IMEs

Context.getSystemService() で取得できる android.view.inputmethod.InputMethodManager class は、グローバルな input method の状態とインタラクトできる。例えば、IME's の入力領域を明示的に隠したり出したりできる。

Window.addFlags() メソッドや Window.setSoftInputMode() メソッドを使ってコントロールすることも可能。

-------------------------------------------------------

 さて、ここからがメインです。



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="To"
android:inputType="textEmailAddress"
/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="Subject"
android:inputType="text"
/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Compose Mail"
android:gravity="top"
android:inputType="textShortMessage|textAutoCorrect|textCapSentences|textMultiLine"
/>
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Send"
/>
</LinearLayout>





AndroidManifest.xml に android:windowSoftInputMode="adjustPan" とすると、コンテンツ部分が上にスクロールできるようになります。





AndroidManifest.xml に android:windowSoftInputMode="adjustResize" とすると、android:layout_weight="1" を指定した部分がリサイズされてスクロールされなくなります。



fullscreen モードの場合、To は次のフォーカスがあたる widget も EditText なので、ボタンの表示が Next になっていて、Composing Mail では次が Button なので Done になっている





android:imeOptions="flagNoAccessoryAction"


android:imeOptions="flagNoExtractUi"


android:imeOptions="actionGo"
右下のキーが Go になる


android:imeOptions="actionSearch"
右下のキーが検索マークになる


android:imeOptions="actionSend"
右下のキーが Send になる



# 横画面で入力フィールドのあるダイアログを表示する場合は注意が必要なのですが、それはまた次のエントリで書きます。。。



 

2010年12月20日月曜日

Android Android 2.3 - StrictMode -

・StrictMode とは、開発者がアプリケーションをモニターし、パフォーマンスを改善するために新しく追加されたシステム機能

・この機能を実装すると、StrictMode はアプリケーションのパフォーマンスを低下させる accidental disk や network activity を捕捉し、開発者にしらせてくれる

・例えば、メインスレッド上での accidental disk や network activity など

・開発者は StrictMode が補足した network や disk usages の問題を評価し、必要ならば修正できる

・これにより、メインスレッドは応答性を維持し、ANRダイアログがユーザーに表示されることを防げる


 - StrictMode
   core class であり、システムとVMを統合する主要なポイント。
   このクラスは、インスタンスに応用するスレッドとVMのポリシーを
   管理するための便利なメソッドを提供する

 - StrictMode.ThreadPolicyStrictMode.VmPolicy
   スレッドとVMのインスタンスに応用するための(自分の)
   ポリシーを保持する  
 
---------------------------------------------------------

New Gingerbread API: StrictMode - Android developers Blog より

・StrictMode は Thread のポリシーに、どの操作を違反とし、違反があったときに何をするかを定義する

・デフォルトでは、全ての操作が許可されている

・thread ポリシーに含むことができるフラグとして、例えば以下のものがある

  ・detect disk writes
  ・detect disk reads
  ・detect network usage
  ・on a violation: log
     > adb logcat で見ることが可能
  ・on a violation: crash
  ・on a violation: dropbox
     DropBoxManager に書き込まれ、後から抽出できる
     > adb shell dumpsys dropbox data_app_strictmode --print
  ・on a violation: show an annoying dialog

・StrictMode の強力な点は、スレッドあたりのポリシーは他の Serivces や Providers によって Binder IPC コールが作成されるたびに伝搬し、スタックトレースは任意の数のプロセスでつなぎあわされたものになること

・メインスレッドでネットワークリクエストをしてはいけない。Honeycomb リリースでは、メインスレッドでのネットワークリクエストは fatal error になるように作られている(らしい)

Android Market にリリースする前に StrictMode を除くのを忘れずに!

StrictMode を使うには、 onCreate() で

public void onCreate() {
if (DEVELOPER_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
}
super.onCreate();
}

もっと簡単に

public void onCreate() {
if (DEVELOPER_MODE) {
StrictMode.enableDefaults();
}
super.onCreate();
}


・Gingerbread 以前の API をターゲットにしたアプリケーションを Gingerbread 以降の端末やエミュータで実行する場合、reflection や 他の方法 を使って StrictMode を有効にできる

・問題が見つかったら、Threadjava.util.concurrent.* に加えて Handler, AsyncTask, AsyncQueryHandler, IntentService なども活用する

・Android 2.3 から SharedPreference.Editor() に apply() メソッドが追加された。commit() の戻りを使わない場合は apply() に置き換えられる。

# commit() は永続化ストレージに設定を同期して書き込むが、apply()は in-memory の SharedPreferences にすぐにコミットし、ディスクへのコミットは非同期に行われる。そのため、UIスレッドをブロックしない。

---------------------------------------------------------



 ■ StrictMode.ThreadPolicy

  特定のスレッドに適用するポリシー

  setThreadPolicy(StrictMode.ThreadPolicy) メソッドで有効にする

  現在のポリシーは getThreadPolicy() で取得する

  注意: 複数のペナルティを設定することは可能だが、検出された異なる
  アクションに異なるペナルティを選択することは現状ではできない。

  StrictMode.ThreadPolicy.Builder を使ってインスタンスを生成する

  ・ Nested Classes
    
    - StrictMode.ThreadPolicy.Builder
       StrictMode.ThreadPolicy インスタンスを生成する

  ・ Constants
    - LAX
       デフォルトのポリシー。lax policy は何も捕捉しない

 ■ StrictMode.ThreadPolicy.Builder

  StrictMode.ThreadPolicy インスタンスを生成する

  "detect" から始まるメソッドで補足したい問題の検出を有効にする

  "permit" から始まるメソッドで捕捉したい問題の検出を無効にする

  "penalty" から始まるメソッドで問題を検出したときにすべきことを指定する

  複数の detect と penalty メソッドを呼ぶことができる
  順番は問題に関係く、全てのペナルティは全ての検出された問題に適用される

  全てを検出して、すべてをログに出すには次のようにする

StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build();
StrictMode.setThreadPolicy(policy);


  ・ Public Methods

    - build ()
       ThreadPolicy instance を生成
       build メソッドが呼ばれるまえに、ペナルティがなにも
       セットされていない場合は、penaltyLog() が暗黙に
       セットされる

    - detectAll ()
       可能性のある候補すべてを検出する
       Gingerbread release では、 SQLiner cursor leaks だけ
       含まれるが、将来の release で拡張される

    - detectDiskReads ()
       disk reads の検出を有効にする

    - detectDiskWrites ()
       disk writes の検出を有効にする

    - detectNetwork()
       ネットワーク操作の検出を有効にする

    - penaltyDeath ()
       違反によって全てのプロセスをクラッシュする
       このペナルティは全ての有効なペナルティの最後に行われる。
       そのため、プロセスが終了する前にログや他の違反を
       取ることが可能

    - penaltyDialog ()

       違反を検出するとダイアログを表示する
       これにより、少しレートが制限される

    - penaltyDropBox ()
       検出された違反のスタックトレースとタイミングデータを
       DropBox にログとして出すことを有効にする
       ベータユーザーのフィールドデータ収集する platform
       integrator を主な対象としている

    - penaltyLog ()
       検出された違反をシステムログに出す

    - permitAll ()
       全ての検出を無効にする

    - permitDiskReads ()
       disk reads の検出を無効にする

    - permitDiskWrites ()
       disk writes の検出を無効にする

    - permitNetwork ()
       ネットワーク操作の検出を無効にする


 ■ StrictMode.VmPolicy

  virtual machine 上のプロセスの全てのスレッドに適用するポリシー
   
  setVmPolicy(StrictMode.VmPolicy) メソッドで有効にする

  StrictMode.VmPolicy.Builder を使ってインスタンスを生成する

  ・ Nested Classes
    
    - StrictMode.VmPolicy.Builder
       StrictMode.VMPolicy インスタンスを生成する

  ・ Constants
    - LAX
       デフォルトのポリシー。lax policy は何も捕捉しない

 ■ StrictMode.Vmpolicy.Builder

  StrictMode.VmPolicy インスタンスを生成する

  "detect" から始まるメソッドで補足したい問題を指定する

  "penalty" から始まるメソッドで問題を検出したときに
  すべきことを指定する

  複数の detect と penalty メソッドを呼ぶことができる
  順番は問題に関係く、全てのペナルティは全ての検出された
  問題に適用される

  全てを検出して、すべてをログに出すには次のようにする

StrictMode.VmPolicy policy = new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build();
StrictMode.setVmPolicy(policy);


  ・ Public Methods

    - build ()
       VmPolicy instance を生成
       build メソッドが呼ばれるまえに、ペナルティがなにも
       セットされていない場合は、penaltyLog() が暗黙に
       セットされる

    - detectAll ()
       可能性のある候補すべてを検出する
       Gingerbread release では、 SQLiner cursor leaks だけ
       含まれるが、将来の release で拡張される

    - detectLeakedSqlLiteObjects ()
       SQLiteCursor や他の SQLite object がクローズされずに
       finalize されたときに検出する
       不要なデータベースの競合と一時的なメモリリークを防ぐ
       ために、SQLite cursor は明示的にクローズするべき

    - penaltyDeath ()
       違反によって全てのプロセスをクラッシュする
       このペナルティは全ての有効なペナルティの最後に行われる。
       そのため、プロセスが終了する前にログや他の違反を
       取ることが可能

    - penaltyDropBox ()
       検出された違反のスタックトレースとタイミングデータを
       DropBox にログとして出すことを有効にする
       ベータユーザーのフィールドデータ収集する platform
       integrator を主な対象としている

    - penaltyLog ()
       検出された違反をシステムログに出す


試しにUIスレッドでインターネットダウンロードしてみました。

package yanzm.example.strictmodesample;

import java.io.IOException;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.StrictMode;

public class MyActivity extends Activity {

private boolean DEVELOPER_MODE = true;

@Override
public void onCreate(Bundle savedInstanceState) {

if (DEVELOPER_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork()
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate(savedInstanceState);

downloadFromInternet();
}

private void downloadFromInternet() {

String url = "https://sites.google.com/site/yukianzm/tmp/image1.png";

try {
Bitmap bmp = null;

final DefaultHttpClient httpClient = new DefaultHttpClient();

HttpGet hg = new HttpGet(url);
HttpResponse httpResponse = httpClient.execute(hg);
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
bmp = BitmapFactory.decodeStream(httpResponse.getEntity().getContent());
hg.abort();
}
}
catch (ClientProtocolException e) {}
catch (IOException e) {}
}
}


Logcat の出力の一部


D/StrictMode( 387): StrictMode policy violation; ~duration=1456 ms: android.os.StrictMode$StrictModeNetworkViolation: policy=23 violation=4
D/StrictMode( 387): at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:758)
D/StrictMode( 387): at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl$SSLInputStream.read(OpenSSLSocketImpl.java:774)
D/StrictMode( 387): at org.apache.http.impl.io.AbstractSessionInputBuffer.fillBuffer(AbstractSessionInputBuffer.java:103)
D/StrictMode( 387): at org.apache.http.impl.io.AbstractSessionInputBuffer.read(AbstractSessionInputBuffer.java:134)
D/StrictMode( 387): at org.apache.http.impl.io.ContentLengthInputStream.read(ContentLengthInputStream.java:174)
D/StrictMode( 387): at org.apache.http.impl.io.ContentLengthInputStream.read(ContentLengthInputStream.java:188)
D/StrictMode( 387): at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:178)
D/StrictMode( 387): at java.io.BufferedInputStream.fillbuf(BufferedInputStream.java:140)
D/StrictMode( 387): at java.io.BufferedInputStream.read(BufferedInputStream.java:324)
D/StrictMode( 387): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
D/StrictMode( 387): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:460)
D/StrictMode( 387): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:515)
D/StrictMode( 387): at yanzm.example.strictmodesample.MyActivity.downloadFromInternet(MyActivity.java:49)
D/StrictMode( 387): at yanzm.example.strictmodesample.MyActivity.onCreate(MyActivity.java:34)
D/StrictMode( 387): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
D/StrictMode( 387): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1586)
D/StrictMode( 387): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1638)
D/StrictMode( 387): at android.app.ActivityThread.access$1500(ActivityThread.java:117)
D/StrictMode( 387): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:928)
D/StrictMode( 387): at android.os.Handler.dispatchMessage(Handler.java:99)
D/StrictMode( 387): at android.os.Looper.loop(Looper.java:123)
D/StrictMode( 387): at android.app.ActivityThread.main(ActivityThread.java:3647)
D/StrictMode( 387): at java.lang.reflect.Method.invokeNative(Native Method)
D/StrictMode( 387): at java.lang.reflect.Method.invoke(Method.java:507)
D/StrictMode( 387): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
D/StrictMode( 387): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
D/StrictMode( 387): at dalvik.system.NativeStart.main(Native Method)




 

Android GridView でアイコンと文字のレイアウト

# メール書こうかなぁと思ったんだけど、なんか解決したみたいなので、こっちに書くことにしたよ。



MyActivity.java

package yanzm.example.gridviewsample;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;

public class MyActivity extends Activity {

public class BindData {
int iconId;
String title;

BindData(int id, String s) {
this.iconId = id;
this.title = s;
}
}

private BindData[] mDatas = {
new BindData(android.R.drawable.ic_menu_camera, "Camera"),
new BindData(android.R.drawable.ic_menu_add, "Add"),
new BindData(android.R.drawable.ic_menu_agenda, "Agenda"),
new BindData(android.R.drawable.ic_menu_call, "Call"),
new BindData(android.R.drawable.ic_menu_crop, "Compass"),
new BindData(android.R.drawable.ic_menu_camera, "Crop"),
};

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

GridView gridview = (GridView) findViewById(R.id.gridview);
gridview.setAdapter(new MyAdapter(this, R.layout.item, mDatas));
}

static class ViewHolder {
TextView textView;
ImageView imageView;
}

public class MyAdapter extends ArrayAdapter<BindData> {
private LayoutInflater inflater;
private int layoutId;

public MyAdapter(Context context, int layoutId, BindData[] objects) {
super(context, 0, objects);
this.inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.layoutId = layoutId;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;

if (convertView == null) {
convertView = inflater.inflate(layoutId, parent, false);
holder = new ViewHolder();
holder.textView = (TextView) convertView.findViewById(R.id.textview);
holder.imageView = (ImageView) convertView.findViewById(R.id.imageview);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
BindData data= getItem(position);
holder.textView.setText(data.title);
holder.imageView.setImageResource(data.iconId);
return convertView;
}
}
}


main.xml

<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/gridview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:numColumns="2"
android:columnWidth="85dip"
android:verticalSpacing="8dip"
android:stretchMode="columnWidth"
android:gravity="center"
/>


item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dip"
android:gravity="center"
>
<ImageView
android:id="@+id/imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>



 

Android SDK tools r8 にして draw9patch が起動しなくなったひとへ

http://stackoverflow.com/questions/4370899/android-draw9patch-throws-classnotfoundexception に解決方法が書いてありますが、

1. swing-worker-1.1.jar をダウンロードする

2. swing-worker-1.1.jar を /path_to_android_sdk/tools/lib/ に入れる。

そんだけ。Issue として報告されてるみたいです。


 

2010年12月19日日曜日

Android Android2.3 - DownloadManager を使う

DownloadManager は long-running HTTP ダウンロード用のシステムサービス

・ダウンロードするファイルのURIを指定してリクエストする

・ダウンロードはバックグラウンドで行われる

・接続が変わったり、リブートしたり、ダウンロードに失敗した場合は再試行される

・getSystemService(String) メソッドの引数に DOWNLOAD_SERVICE を渡してインスタンスを取得する

・このAPIを使ってダウンロードをリクエストするアプリは、ユーザーが notification や downloads UI からダウンロードを開始したことを適切にハンドルするために、ACTION_NOTIFICATION_CLICKED に対する broadcast receiver を登録しておかなければならない



# DownloadManager.Query に渡す URI が https だと怒られた。http じゃないとだめみたい。

# AndoridManifest.xml に <uses-permission android:name="android.permission.INTERNET"/> が必要なのですが、これを書かない時の logcat のエラーメッセージが requires android.permission.ACCESS_ALL_DOWNLOADS or android.permission.ACCESS_ALL_DOWNLOADS (エミュレータで、ですけど。)という(トンチンカン?な internal permission を表示してしまう)ものなので、ちょっと紛らわしいです。


保存先を明示しない場合は、cache にだけ保存されます。

$ adb -e shell
# ls cache
image1-3.png
image1-2.png
image1-1.png
image1.png
backup
lost+found



永続化する場合は、外部メディアのURIを指定します。


// save with uri
File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "image1.png");
mRequest.setDestinationUri(Uri.fromFile(file));


# setDestinationUri() の引数に内部メディアのURIを入れると怒られます。。。


保存先を外部メディアの Application's Directory にした場合は /mnt/sdcard/ とかの下の Android/data/package-name/files/ の下の setDestinationInExternalPublicDir() の第1引数で指定したディレクトリの中になります。


mRequest.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, "image1.png");
File pathExternalDir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
pathExternalDir.mkdirs();



$ adb -e shell
# ls mnt/sdcard/Android/data/yanzm.example.downloadmanagersample/files/Download
image1.png



保存先を外部メディアの Public Directory にした場合は /mnt/sdcard/ とかの下の setDestinationInExternalPublicDir() の第1引数で指定したディレクトリの中になります。


mRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "image1.png");
File pathExternalPublicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
pathExternalPublicDir.mkdirs();



$ adb -e shell
# ls mnt/sdcard/Download
image1.png



# mkdirs() でディレクトリ作っておかないとダウンロードに失敗するので気をつけて。。。


全ソースはこちら

package yanzm.example.downloadmanagersample;

import java.io.File;

import android.app.Activity;
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.View;

public class MyActivity extends Activity {

private int downloadDestination = DESTINATION_EXTERNAL_PUBLICDIR;

private static final int CACHE = 0;
private static final int DESTINATION_URI = 1;
private static final int DESTINATION_EXTERNAL_DIR = 2;
private static final int DESTINATION_EXTERNAL_PUBLICDIR = 3;

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

public void startDownload(View v) {
DownloadManager mDownloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);

Uri uri = Uri.parse("http://sites.google.com/site/yukianzm/tmp/image1.png");

DownloadManager.Request mRequest = new DownloadManager.Request(uri);
mRequest.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE);
// mRequest.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
// mRequest.setAllowedOverRoaming(false);
mRequest.setTitle("Test Download!");
mRequest.setDescription("sample of using download manager");

switch (downloadDestination) {
case CACHE:
break;
case DESTINATION_URI:
// save with uri
File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "image1.png");
mRequest.setDestinationUri(Uri.fromFile(file));
break;
case DESTINATION_EXTERNAL_DIR:
// save in external storage application's directory
mRequest.setDestinationInExternalFilesDir(this,Environment.DIRECTORY_DOWNLOADS, "image1.png");
File pathExternalDir = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
pathExternalDir.mkdirs();
break;
case DESTINATION_EXTERNAL_PUBLICDIR:
// save in external storage public directory
mRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "image1.png");
File pathExternalPublicDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
pathExternalPublicDir.mkdirs();
break;
}

// mRequest.setMimeType(HTTP.OCTET_STREAM_TYPE);
mRequest.setShowRunningNotification(true);
mRequest.setVisibleInDownloadsUi(true);

long downloadId = mDownloadManager.enqueue(mRequest);

Log.d("DownloadManagerSample", "Start Download download Id = " + downloadId);
}
}



# broadcast receiver 部分についてはまた今度。。。

2.3 の Built-in Application の Downloads でみてみるとこんな感じ。






■ Nested Classes
 - DownloadManager.Query
    ダウンロードマネジャークエリーをフィルターするのに使う

 - DwonloadManager.Request
    新しいダウンロードのリクエストに必要な全ての情報を含む


■Constants
 - ACTION_DOWNLOAD_COMPLETE
    ダウンロードが完了したときに download manager によって
    発行される broadcast intent action

 - ACTION_NOTIFICATION_CLICKED
    ユーザーが system notification や downloads UI から
    実行中のダウンロードをクリックしたときに download manager
    から発行される broadcast intent action

 - ACTION_VIEW_DOWNLOADS
    全ダウンロードを表示するための Activity を起動する intent action

 - COLUMN_BYTES_DOWNLOADED_SO_FAR
    これまでのダウンロードバイト数

 - COLUMN_DESCRIPTION
    クライアントが提供するこのダウンロードの詳細

 - COLUMN_ID
    特定のダウンロードの識別子。システム上でユニーク

 - COLUMN_LAST_MODIFIED_TIMESTAMP
    最後にダウンロードが編集されたタイムスタンプ
     (System.currentTimeMillis() UTC)

 - COLUMN_LOCAL_URI
    ダウンロードされたファイルが保存される場所のUri

 - COLUMN_MEDIAPROVIDER_URI
    このダウンロードエントリに対応する MediaProvider でのエントリへのURI

 - COLUMN_MEDIA_TYPE
    このダウンロードファイルの Internet Media Type

 - COLUMN_REASON
    ダウンロードのステータスの詳細を提供する
    ERROR_* か PAUSED_* の定数の1つ

    - ERROR_CANNNOT_RESUME
       一時的なエラーでダウンロードを再開できない

    - ERROR_DEVICE_NOT_FOUND
       外部ストレージデバイスがみつからない

    - ERROR_FILE_ALREADY_EXISTS
       リクエスト先のファイルがすでに存在している
       (download manager はすでに存在している
       ファイルを上書きしない)

    - ERROR_FILE_ERROR
       他のエラーコードに合致しないストレージ問題が起こった

    - ERROR_HTTP_DATA_ERROR
       データを受信もしくは解析中にHTTPレベルでエラーが起こった

    - ERROR_INSUFFICIENT_SPACE
       十分なストレージ領域がない

    - ERROR_TOO_MANY_REDIRECTS
       リダイレクトが多すぎる

    - ERROR_UNHANDLED_HTTP_CODE
       download manager がハンドルできない HTTP code を受信した

    - ERROR_UNKNOWN
       他のエラーコードに合致しないエラーでダウンロードが完了した

    - PAUSED_QUEUED_FOR_WIFI
       ダウンロードが mobile network を介すときのリミットサイズ
       を超えたので、download manager は Wi-Fi 接続による再開を
       待っている

    - PAUSED_UNKNOWN
       ダウンロードは何かの理由で一時停止している

    - PAUSED_WAITING_FOR_NETWORK
       ダウンロードは再開のためにネットワーク接続を待っている

    - PAUSED_WAITING_TO_RETRY
       ネットワークエラーが発生し、download manager はリクエストを
       再試行する前に待っているので、ダウンロードが一時停止している

 - COLUMN_STATUS
    ダウンロードの現在の状態。STATUS_* 定数の1つ

    - STATUS_FAILED
       ダウンロードが失敗したときの値
       (再試行はされない)

    - STATUS_PAUSED
       ダウンロードが再試行か再開を待っているときの値

    - STATUS_PENDING
       ダウンロードが開始されるのを待っているときの値

    - STATUS_RUNNING
       ダウンロードが実行されているときの値

    - STATUS_SUCCESSFUL
       ダウンロードが成功し完了したときの値


 - COLUMN_TITLE
    クライアントが提供するこのダウンロードのタイトル

 - COLUMN_TOTAL_SIZE_BYTES
    ダウンロードのトータルバイト数

 - COLUMN_URI
    ダンロードする URI

 - EXTRA_DOWNLOAD_ID
    ACTION_DOWNLOAD_COMPLETE intents に含まれる
    intent extra
    今完了したダウンロードのID (long値)

■ Public Methods

 - long enqueue(DownloadManager.Request request)
    新しいダウンロードをキューに入れる

 - ParcelFileDescriptor openDonwloadedFile(long id)
    ダウンロードしたファイルを読むために開く

 - Cursor query(DownloadManager.Query query)
    リクエストされているダウンロードについてのクエリ

 - int remove(long... ids)
    ダウンロードをキャンセルし、download manager から削除する

2010年12月18日土曜日

Android setSoftInputMode()をエミュレータで試すときは hw.keyboard を no にしましょう。

以前のエントリで、起動時に自動でソフトキーボードを出したり出さなかったりする方法を書きましたが、これをエミュレータで検証する場合注意が必要です。

Android 自動でソフトキーボードが出るのを防ぐ

ハードキーボードが出ている状態では、

this.getWindow().setSoftInputMode(LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);

という設定をしてもソフトキーボードは出ません。

#ハードキーボードが出てるんだからそりゃそうだ。

で、なにが注意点かというと、デフォルトでAVDを作成するとハードキーボードありのエミュレータになるということです。

つまり、なにも設定しないで、デフォルトでAVD作成して上記が書かれたアプリを実行すると、あれれ?ってなってしまうのです。

もちろん、ハードキーボード無しのAVDを作成して実行すればちゃんとソフトキーボードがでます。

では、ハードキーボード無しのAVDを作る方法です。

 1. Android SDK and AVD Manager を開く
 2. Virtual devices で New... をクリック
 3. Hardware: の New... をクリックし Keyboard support 追加し Value を no に変更
 4. その他を設定
 5. Create AVD をクリック



これで、ハードキーボード無しのAVDが作成されました!
Details... で hw.keyboard: no が表示されていればOKです。



 
 

2010年12月10日金曜日

Android Androidプロジェクトのテンプレート

Preference とか Menu とか毎回コピペするの面倒だし、res の下に毎回フォルダとか XML 作るの面倒だったので、zip ファイルのテンプレートにしました。

構成はこんな感じ。



zip ファイルはこちらから -> AndroidApplicationTemplate.zip

この zip をインポートするには、

[File] -> [Import] -> [Existing Projects into Workspace]

で [Select archive file:] にダウンロードした zip ファイルを指定して
Finish を押します。

あとは、 Alt + Shift + R でプロジェクト名とか、パッケージ名(srcの下のところ)とか変えてください。

gen で生成される Application のパッケージは AndroidManifest.xml の <manifest> タグの package 属性で指定されているパッケージになります。AndroidManifest.xml を直接書き変えてもいいし、
プロジェクトを右クリック -> [Android Tools] -> [Rename application package] で変更することもできます。これだと、依存関係とかをチェックしてくれます。




この場合、src の下のパッケージは変わらないので、適宜変更してください。




MainActivity.java はこんな感じ。他のメソッドも入れたくなったらいれるかも。


package yanzm.template.standard;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;

public class MainActivity extends Activity {

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

@Override
public void onStart() {
super.onStart();
}

@Override
public void onResume() {
super.onResume();
}

@Override
public void onPause() {
super.onPause();
}

@Override
public void onStop() {
super.onStop();
}

@Override
public void onDestroy() {
super.onDestroy();
}

@Override
public boolean onCreateOptionsMenu(Menu menu){
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item){
switch (item.getItemId()){
case R.id.menu1:
return true;
}
return false;
}
}



 

2010年12月8日水曜日

Android Hierarchy Viewer を使ってみた。

SDK Tools r8 で Hierarchy Viewer の UI が新しくなったので使ってみました。

Eclipse の Open Perspective から Hierarchy Viewer を起動できます。



確認してないけど、前のときは root がいるのでデバイスではなくエミュレータでやる必要がありました。今回もそうなのかな?とりあえずエミュレータで HelloWorld の view tree を表示してみました。

まえの無機質な感じから、かなりグラフィカルになってます。



# 黄色とか緑の丸はなんなのかな?

各 View をクリックすると、プロパティが左側に表示されます。



右下には、画面上のどのコンポーネントかが表示されます。

右上には、view tree の全体図が表示されます。


上のアイコンから、画像に export することができます。

左から
 ・Save the tree view as a PNG image
 ・Capture the window layers as a photoshop document



# photoshop 形式で出力ですか。すげー。

 

Android Eclipse から ProGuard を使ってみた。

proGuard を訳そうかな、と思ったんだけど、

Androidとセキュリティ:Android 2.3(Gingerbread) SDKに標準搭載されたProGuardを試す - Android(アンドロイド)情報-ブリリアントサービス -

によくまとまっていたので、補足的な感じにしました。

この機能を使うには ADT8.0.0 以上 + SDK Tools, r8 が必要です


■ proguard.cfg を作成

プロジェクトを作成するとこんな感じでホーム直下に proguard.cfg が自動で作成されます。



中にはデフォルトの設定が入っています。



■ default.properties の設定

この proguard.cfg へのパスを default.properties に追加します。
構文は

proguard.config=/path/to/proguard.cfg

絶対パスか、ホーム直下からの相対パスを指定します。

この場合、proguard.cfg はホーム直下にあるので、

proguard.config=proguard.cfg

でOK





■ Export

プロジェクトを右クリックして、Android Tools -> Export Signed Application Package.. を選択して署名すればOK



# ブリリアントさんのブログにコンソールにログが出るって書いてあったんだけど、出ないんだよねー。なんか設定いるのかな?
→ Lionas さんに教えていただきました!
[Window] - [Preference] - [Android] - [Build] - [Build output] を Verbose にする
(日本語だと "環境設定->Android->ビルドの「ビルド出力」のオプションを「詳細」" らしい)

プロジェクトのあるディレクトリ内に proguard フォルダができてれば OK



中身は、dump.txt, mapping.txt, seeds.txt, usage.txt の4個






■ ProGuard リンク

ProGuard Manual

 ・ Overview of Keep options (-keep オプションの overview)
 ・ Examples section
 ・ Troubleshooting


# すごい簡単。Ant 使うのめんどくてやってなかったんだけど、ある意味まっててよかったw

そうそう。この機能は、SDK Tools のものなので、いわゆる Android のバージョンとは関係ありません。つまり、2.3 じゃなくても、1.6 でも 2.0 でも 2.1 でも 2.2 のアプリを作るときでも、この機能で簡単に ProGuard を使えるようになったってことなんです!

ほら、ちゃんと 1.6 でも proguard.cfg 生成されてるでしょ。








 

2010年12月7日火曜日

Android monkeyrunner を訳して試してみた。

訳しました。

monkeyrunner


-----------------------------------------

monkeyrunner ツールは、Android code 外から Android device やエミュレータをコントロールするプログラムを書くためのAPIを提供します。 monkeyrunner を使って、Android アプケーションやテストパッケージをインストールしたり、実行させたり、キーストロークを送ったり、ユーザーインタフェースのスクリーンショットを取ってワークステーションに保存したりする、Python プログラムを書くことができます。もともと、monkeyrunner ツールはアプリケーションや機能/フレームワークレベルでのデバイステストや、ユニットテストのためにデザインされましたが、他のどんな目的で使おうと自由です。

monkeyrunner ツールは monkey ツールとして知られている、UI/Application Exerciser Monkey とは関係ありません。monkey ツールはデバイスやエミューレータ上の adb shell ディレクトリ内で実行し、ユーザーとシステムの擬似ランダムイベントを発生させます。一方、monkeyrunner ツールは特定のコマンドや API のイベントを送ることで、ワースくテーションからデバイスとエミュレータをコントロールします。

monkeyrunner ツールは次のユニークな機能を Android テストにもたらします:

・マルチデバイスコントロール: monkeyrunner API は1つ以上のテストを複数のデバイスやエミュレータに対して適用することができます。一度、物理的にすべてのデバイスを起動したり、エミュレータをスタートアップしたりすれば、プログラムが順番にそれぞれに接続し1つ以上のテストを実行します。また、プログラムからエミュレータの設定をスタートアップし、1つ以上のテストを実行し、そしてエミュレータをシャットダウンさせることができます。

・機能テスト(Functional testing): monkeyrunner は Android アプリケーションの自動 start-to-finish テストを実行することができます。キーストロークやタッチイベントで入力値を提供し、スクリーンショットとして結果をみることができます。

・回帰テスト(Regression testing): monkeyrunner はアプリケーションを実行し、その出力スクリーンショットを正しいスクリーンショットと比較することで、アプリケーションの安定性をテストすることができます。

・拡張可能な自動化(Extensible automation): monkeyrunner は API toolkit なので、Android デバイスをコントールする Python-based モジュールとプログラムのシステム全体を開発することができます。monkeyrunner のAPIを自体を使用するだけでなく、標準的な Python ossubprocess モジュールを使って、Android Debug Bridgeのような Android tools を呼び出すことができます。

 monkeyrunner API に独自クラスを追加することも可能です。詳しくは Extending monkeyrunner with plugins


monkeyrunner ツールは Jython (Java プログラミング言語を使う Python 実装)を使っています。Jython によって、monkeyrunner API は簡単に Android フレームワークとインタラクトできます。Jythonを使用すると、API の定数,クラス,メソッドにアクセスするために Python の構文を使用することができます。



A Simple monkeyrunner Program

これは、MonkeyDevice オブジェクトを生成し、デバイスに接続する簡単な monkeyrunner プログラムです。MonkeyDevice オブジェクトを使うと、プログラムは Android アプリケーションパッケージをインストールし、そのアクティビティの1つを実行し、アクティビティにキーイベントを送ります。プログラムは結果のスクリーンショットを取り、MonkeyImage オブジェクトを生成します。このオブジェクトから、プログラムはスクリーンショットを含む a.png ファイルを書き出します。


# Imports the monkeyrunner modules used by this program
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice

# Connects to the current device, returning a MonkeyDevice object
device = MonkeyRunner.waitForConnection()

# Installs the Android package. Notice that this method returns a boolean, so you can test
# to see if the installation worked.
device.installPackage('myproject/bin/MyApplication.apk')

# Runs an activity in the application
device.startActivity(component='com.example.android.myapplication.MainActivity')

# Presses the Menu button
device.press('KEYCODE_MENU','DOWN_AND_UP')

# Takes a screenshot
result = device.takeSnapshot()

# Writes the screenshot to a file
result.writeToFile('myproject/shot1.png','png')



The monkeyrunnner API

monkeyrunner API は com.android.monkeyrunner パッケージ内の3つのモジュールに含まれます。

・MonkeyRunner: monkeyrunner プログラムのユーティリティメソッドクラスです。このクラスは monkeyrunnner とデバイスやエミュレータを接続するためのメソッドを提供します。さらに、monkeyrunner プログラムのための UI生成メソッドと、ビルトインヘルプを表示するためのメソッドを提供します。

・MonkeyDevice: デバイスとエミュレータを表します。このクラスはパッケージのインストール・アンインストールするためのメソッド、アクティビティをスタートするためのメソッド、キーボードやタッチイベントをアプリケーションに送るためのメソッドを提供します。また、テストパッケージを実行するためにこのクラスを使うこともできます。

・MonkeyImage: スクリーンキャプチャイメージを表します。このクラスはスクリーンキャプチャをするためのメソッド、ビットマップイメージをいくつかのフォーマットに変換するためのメソッド、2つの MonkeyImage オブジェクトを比較するためのメソッド、イメージをファイルに書き込むためのメソッドを提供します。


Python プログラムでは、Python モジュールとして各クラスにアクセスします。monkeyrunner ツールはこれらのモジュールを自動的にはインポートしません。これらのモジュールをインポートするために、Python の from 構文を使います。


from com.android.monkeyrunner import <module>


<module> にはインポートしたいクラス名を入れます。モジュール名をカンマで区切ることで、同じ from 文内で1つ以上のモジュールをインポートすることができます。



Running monekyrunner

monkeyrunner プログラムは、 a file からでも interactive session で monkeyrunner statements を入力する方法からでも実行することができます。いずれの場合でも、まず (SDK ディレクトリの) tools サブディレクトリ 内の monkeyrunner を実行します。ファイル名を引数で指定した場合、monkeyrunnner はそのファイル内の コンテンツを Python プログラムとして実行します。引数を指定しない場合は interactive session がスタートします。

mokeyrunner コマンドの構文は次のようになってます。


mokeyrunner -plugin <plugin_jar> <program_filename> <program_options>


 -plugin <plugin_jar>  (optional) monkeyrunner のプラグインを含む a.jar ファイルを指定します。複数のファイルを指定するには、複数回引数を含ませます。

 <program_filename>  この引数を指定した場合は、monkeyrunner コマンドはファイルのコンテンツを Python プログラムとして実行します。引数を指定しない場合は、コマンドは interactive session をスタートします。

 <program_options>  (optional) <program_file> 内のプログラムのためのフラグと引数



monkeyrunner Build-in Help

monkeyrunner の API リファレンスを生成するには、次のコマンドを実行します。


monkeyrunner <format> help.py <outfile>


 <format>  text か html を指定する。text は plain text 出力用、html は HTML 出力用
 <outfile>  出力ファイルのパス + 修飾子名



Extending monkeyrunner with Plugins

Java プログラミング言語で記述し、1つ以上の .jar ファイルにビルドしたクラスを使って monkeyrunner API を拡張することができます。独自のクラスを使って monkeyrunner API を拡張したり、既存のクラスを拡張するのにこの機能が使えます。また、monkeyrunner 環境を初期化する機能にも使えます。

プラグインを monkeyrunner に提供するには、-plugin <plugin_jar> 引数で .jar ファイルを指定して monkeyrunner コマンドを実行します。

プラグインコードでは、com.android.monkeyrunner の main monkeyrunner class の MonkeyDevice, MonkeyrImage, MonkeyRunner をインポートし、拡張することができます。(see the monkeyrunner API)

プラグインは Android SDK へのアクセスを提供するものではないことに注意してください。com.android.app などのパッケージをインポートすることはできません。monkeyrunner は framework API 以下のレベルでデバイスエミュレータとインタラクトします。

The plugin startup class

plugin の .jar ファイルはスクリプト処理がスタートする前にインスタンス化されるクラスを指定することができます。このクラスを指定するには、 .jar ファイルのマニフェストに MonkeyRunnerStartupRunner キーを追加してください。この値はスタートアップ時に実行されるクラスの名前でなければなりません。以下は ant build script 内でどのようにすればいいかのスニペットです。


<jar jarfile="myplugin" basedir="${build.dir}">
<manifest>
<attribute name="MonkeyRunnerStartupRunner" value="com.myapp.myplugin"/>
</manifest>
</jar>


monkeyrunner's runtime environment へのアクセスを得るには、スタートアップクラスは com.google.common.base.Predicate<PythonInterpreter> を実装する必要があります。例えば、このクラスは default namespace のいくつかの変数を設定します。


package com.android.example;

import com.google.common.base.Predicate;
import org.python.util.PythonInterpreter;

public class Main implements Predicate {
@Override
public boolean apply(PythonInterpreter anInterpreter) {

/*
* Examples of creating and initializing variables in the monkeyrunner environment's
* namespace. During execution, the monkeyrunner program can refer to the variables "newtest"
* and "use_emulator"
*
*/
anInterpreter.set("newtest", "enabled");
anInterpreter.set("use_emulator", 1);

return true;
}
}


-----------------------------------------

上記のサンプル Python コードをエミュレータで走らせてみました。

注意点

 ・http://developer.android.com/guide/developing/tools/monkeyrunner_concepts.html のコードには誤植があります。上記では修正してあります。
  間違い: device.takeSnapShot
  正解 : device.takeSnapshot()


 ・startActivityに指定するクラスが / を付けないとダメでした。
   起動しなかった : yanzm.products.hoge.Hoge
   起動した : yanzm.products.hoge/.Hoge

 ・エミュレータで試したんだけど、なんかポートのエラーがでる。(でもちゃんと実行される。。。)

 ・アプリケーションの起動が遅いのか、起動する前にメニューボタン押されてスナップショット取られてる気が、、、(保存されるスナップショットが全部ホーム画面になっちゃうんだけど。。。ポートのエラーの問題???)

 ・実行するとこんな感じのログ↓ 

101207 23:02:20.810:I [main] [com.android.monkeyrunner.MonkeyManager] Monkey Command: wake.
101207 23:02:23.866:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] Error starting command: monkey --port 12345
101207 23:02:23.866:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice]com.android.ddmlib.ShellCommandUnresponsiveException
101207 23:02:23.866:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at com.android.ddmlib.AdbHelper.executeRemoteCommand(AdbHelper.java:408)
101207 23:02:23.866:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at com.android.ddmlib.Device.executeShellCommand(Device.java:276)
101207 23:02:23.866:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at com.android.monkeyrunner.adb.AdbMonkeyDevice$1.run(AdbMonkeyDevice.java:89)
101207 23:02:23.866:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471)
101207 23:02:23.866:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
101207 23:02:23.866:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at java.util.concurrent.FutureTask.run(FutureTask.java:166)
101207 23:02:23.866:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
101207 23:02:23.866:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
101207 23:02:23.866:S [pool-1-thread-1] [com.android.monkeyrunner.adb.AdbMonkeyDevice] at java.lang.Thread.run(Thread.java:636)
101207 23:02:52.764:I [main] [com.android.monkeyrunner.MonkeyManager] Monkey Command: press KEYCODE_MENU.
101207 23:02:55.543:I [main] [com.android.monkeyrunner.MonkeyManager] Monkey Command: quit.

 
 ・保存されたスナップショット



















 

Android 2.3 エミュレータを使ってみた。

キャプチャいっぱいとってみました。


起動画面。ステータスバーが黒い!



ホーム画面。ブラウザのアイコンと電話のアイコンが緑だ!



Menuを押してみた。黒っ!
ここに Manage apps が追加されました。便利~。



Manage apps の画面。
下部に Internal storage の使用状況がでるようになりました。



Settings の画面。とくに変化はないかな~。



ダイアログも黒っ!



チェックマークのデザインも黒&緑。



ランチャーはこんな感じ
ブラウザと電話のアイコンが緑になって、新しくDownloads ってアプリが入ってる。



copy カーソル出してみた。



Downloadsアプリを起動してみたけど、なにもなーし。



Speech Recorder ってアプリもあったので起動してみた。



Dev Tools はこんな感じ



Bad Behavior 気になったので起動してみた。
なんか危険なボタンがいっぱいw




とりあえず、いろいろ黒くなってました。