generate sample codeを使わずGWTプロジェクトを組み立ててみる

GAEもOFFにして、最小構成から手順を確認します。

Moduleの作成

New > Moduleで、GWT Moduleを作成します。
パッケージはプロジェクト作成時に作ったパッケージにします。
下記のxmlが*.gwt.xmlで作成されます。
ついでにclientというサブパッケージが追加されます。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.2.0//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.2.0/distro-source/core/src/gwt-module.dtd">
<module>
	<inherits name="com.google.gwt.user.User" />
	<source path="client"/>
</module>

EntryPointの作成

New > EntryPointとすると、勝手にclientの下に作成してくれます。動作確認のために、alertを仕込んでおきます。

public class MyEntryPoint implements EntryPoint {

	@Override
	public void onModuleLoad() {
		Window.alert("onModuleLoad");
	}

}

EntryPointクラスを作成すると、プラグインが自動的にModuleファイルにもEntryPointの情報を追加してくれます。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.2.0//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.2.0/distro-source/core/src/gwt-module.dtd">
<module>
	<inherits name="com.google.gwt.user.User" />
	<source path="client" />
	<entry-point class="com.naokichick.samplegwt.client.MyEntryPoint" />
</module>

HTMLの作成

New > HTMLでhtmlを作成します。
scriptのsrcに、モジュールのパスが設定されます。

<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>mypage</title>
    <script type="text/javascript" language="javascript" src="com.naokichick.samplegwt.MyModule/com.naokichick.samplegwt.MyModule.nocache.js"></script>
  </head>

  <body>

  </body>
</html>

実行する

Run As でホストモードで実行すると、alertが表示されます。また、warフォルダ内にモジュールのパスに対応したフォルダが作成されます。

GWT.getModuleName()はscriptのsrc属性で決定される

scriptのsrc属性は作成したときのmoduleの情報で書き込まれます。なので、後からmoduleのrename-toを書き換えたときは、ここも書き換えないと正しく動作しませんでした。moduleをrename-to="mymodule"としてから、htmlを作成しなおすと、今度は下記のようになりました。

<!doctype html>
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>mypage</title>
    <script type="text/javascript" language="javascript" src="mymodule/mymodule.nocache.js"></script>
  </head>

  <body>

  </body>
</html>

この書き換えの後は、サーバを再起動する必要があるみたいです。

HandlerThreadとHandlerとLooperの関係

IntentServiceを使って非同期処理を行う - Tech Booster
上記で紹介されているIntentServiceについて、どういうものなのか調べようと思ったら、HandlerThreadというものが内部的に使われていました。
気になる名前のクラスなので、こちらを先に押さえることにします。

HandlerThreadを使うと何が出来るのか

まず調べた結果を報告します。HandlerThreadは、Handler経由でメッセージをsendできる拡張スレッド、のようです。下記は調べたメモです。

IntentServiceのソースコード

HandlerThreadを調べる発端となったIntentServiceは、Serviceのサブクラスです。ソースを見てみると、onCreate時にHandlerThreadを生成して、スタートさせています。

IntentService.javaのonCreate
    @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

さて、このHandlerThreadとは何者でしょうか。

HandlerThreadは内部にlooperを持つスレッド

HandlerThreadは、UIスレッドのようなlooperを内部に持つ拡張スレッドのようです。
Handlerは、引数なしで生成すると生成時のスレッドのlooperがsend先になります。

Handler.javaの引数なしコンストラク
    public Handler() {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = null;
    }

HandlerをUIスレッド以外からnewすると「Can't create handler inside thread that has not called Looper.prepare()」とよく怒られていたんですが、やっと合点がいきました。引数なしで生成されたHandlerは問答無用でUIスレッドにメッセージをsendするわけではなくて、newされたスレッドのlooperに対してメッセージをsendしていたんですね。
IntentService#onCreateでやっているように、new Handler(Looper looper)のコンストラクタを使うと、引数で渡されたlooperに対してメッセージをsendできるようです。

HandlerThreadを試してみる

上記を踏まえて、HandlerThreadの動作を試してみます。
まず、UIスレッドにHandler経由でメッセージをsendするコードです。

public class SampleHandlerThreadActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button btn1 = (Button) findViewById(R.id.button1);
        btn1.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				Handler mainHandler = new Handler();
				mainHandler.post(new Runnable() {
					public void run() {
						Log.v("hoge", "thread name:" + Thread.currentThread().getName());
					}
				});
				
			}
		});
    }
}
実行結果

04-10 15:41:58.006: VERBOSE/hoge(902): thread name:main

postされたrunnableが、mainスレッド(つまりUIスレッド)で実行されたことが確認できます。
これを、post先を独自のスレッドになるように書き換えてみます。

独自のスレッドにHandlerからpostする
public class SampleHandlerThreadActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button btn1 = (Button) findViewById(R.id.button1);
        btn1.setOnClickListener(new OnClickListener() {
			public void onClick(View v) {
				HandlerThread hogeThread = new HandlerThread("hogeThread");
				hogeThread.start();
				Handler hogeThreadHandler = new Handler(hogeThread.getLooper());
				hogeThreadHandler.post(new Runnable() {
					public void run() {
						Log.v("hoge", "thread name:" + Thread.currentThread().getName());
					}
				});
				
				
			}
		});
    }
}
実行結果

04-10 15:49:14.966: VERBOSE/hoge(951): thread name:hogeThread

hogeThreadで、postしたrunnableが実行されていることを確認できました。

HandlerThreadはrunの中でLooper.prepare()する

なお、上記のコードではhogeThreadをstartさせてからHandlerをnewしています。HandlerThreadはstartされてからはじめてlooperを自身に設定するためです。
startするまえにgetLooperしてもnullが返るので、new Handler(Looper)は失敗します。

HandlerThreadのrun()
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

おすすめのAndroid

Google Androidプログラミング入門
江川 崇 竹端 進 山田 暁通 麻野 耕一 山岡 敏夫 藤井 大助 藤田 泰介 佐野 徹郎
アスキー・メディアワークス
売り上げランキング: 11701

Xcode4でhello world

iPhoneアプリを作ってみよう(Xcode4編)第1回:じゃんけんゲーム - もとまか日記Z
上記記事を参考にしつつ、helloworldに挑戦します。
画面のボタンを押したら画面上のラベルをhello worldに書き換えるようなアプリにしてみます。
かなりあいまいな理解の部分もあるので、鵜呑みにしないよう気をつけてください。

xcode4は有料

xcode4からは有料なんだそうです。iTunesStoreで\600で買いました。4.5GBあり、DLには結構時間がかかりました。

プロダクト作成

テンプレートView-Based Applicationで作ってみます。


Product Name:HelloWorld
Company Identifer:com.example

Company IdentiferはJavaのパッケージみたいなものなんでしょうか?とりあえず調べるのは後回しにします。

レイアウトはxibファイルで指定

画面上のコンポーネントは、xibファイルに定義するようです。
ラベルやボタンをドラッグアンドドロップで配置してみます。

xibファイルの中身は?

xibファイルをOpen As Source Codeで開いてみると、普通のxmlのように見えます。
が、ひとつのコンポーネントに対応してる感じに見えます。IBUIViewという部分があり、この上にコンポーネントを配置していくのがView-Basedってことでしょうか。

ボタンを押したときの処理を記述する

HelloWorldViewController.xmlに定義したボタンを押したとき(タップ)の処理は、
HelloWorldViewController.hに定義するみたいです。

#import <UIKit/UIKit.h>

@interface HelloWorldViewController : UIViewController {
    
}

// アクションメソッド
-(IBAction) onTouchUpInside:(id)sender;

@end

onTouchUpInsideというメソッドを定義しました。

これを、xibエディタでボタンのTouch up Insideイベントに設定します。ドラッグアンドドロップで設定。
xibファイルのオーナーがHelloWorldViewControllerなので、*.hで定義したアクションメソッドをxibファイルから見れるようになってます。

hファイルで定義したメソッドがアクションメソッドかどうかは、戻り値がIBActionかどうかで判別されてるようです。

interfaceを実装

hファイルで宣言したアクションメソッドはmファイルで実装します。とりあえず空のメソッドを記述しmす。

#import "HelloWorldViewController.h"

@implementation HelloWorldViewController

// アクションメソッドの実装
- (IBAction) onTouchUpInside:(id)sender {
    // ここにボタンが押されたときの処理を書く
}

- (void)dealloc
{
    [super dealloc];
}

// 後略

@end

画面上のコンポーネントをコードから参照する

ボタンが押されたときにラベルを書き換えるには、ラベルへの参照を取得する必要があります。そのためには、ViewControllerがラベルへの参照を保持するフィールドを定義します。
まず、hファイルにラベルの参照を保持するフィールドを定義します。

#import <UIKit/UIKit.h>

@interface HelloWorldViewController : UIViewController {
    IBOutlet UILabel *label;
}

// アクションメソッド
-(IBAction) onTouchUpInside:(id)sender;


@end

ただフィールドを定義するだけではだめで、IBOutletという修飾子が必要です。これをつけて定義したフィールドは、xibファイルから参照できるので、アクションメソッドのようにD&Dで設定します。

設定したら、mファイル内に実装したアクションメソッド内で、ラベルを書き換えるコードを記述します。

// アクションメソッドの実装
- (IBAction) onTouchUpInside:(id)sender {
    label.text = @"hello world!";
}

これでプロダクトをRunさせ、iOSシミュレータ上に表示されたボタンをクリックすると、ラベルが書き変わることを確認できました。

注意

最初にも書きましたが、かなり曖昧な理解で書いている部分もあるので、鵜呑みは禁物です。

PreferenceActivityのレイアウトカスタマイズ

「アカウントと同期の設定」みたいな画面をつくる

「アカウントと同期の設定」画面みたいな画面をつくるにはどうすればよいでしょう。

基本はPreferenceActivityだけど、フッター部分のボタンを追加するには工夫が必要そうです。

自作layoutにpreferenceを載せる方法

PreferenceActivityのレイアウト変更 - テクメモ
上記ページに、自作layoutにpreferenceを載せる方法が紹介されていました。
@android:id/listなListViewを持つlayoutをsetContentViewすると、preferenceが載るListViewを差し替えられるようです。ListViewにlayout_weightを指定しておかないとButtonが表示されませんでした。

SamplePreferenceActivity

public class SamplePrerenceActivity extends PreferenceActivity {
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.linear);

		addPreferencesFromResource(R.xml.pref);
	}
}

linear.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent" android:layout_height="fill_parent"
	android:orientation="vertical">
	<ListView android:id="@android:id/list" android:layout_width="fill_parent"
		android:layout_height="fill_parent" android:layout_weight="1" />
	<LinearLayout android:layout_width="match_parent"
		android:background="#aaaaaa" android:gravity="center_horizontal"
		android:paddingTop="10dip" android:layout_height="wrap_content">
		<Button android:layout_width="wrap_content" android:text="アカウントを追加"
			android:layout_height="wrap_content" />
	</LinearLayout>
</LinearLayout>

実際の表示

上記コードとpref.xmlにより、似たようなレイアウトを実現できました。

買ってよかったAndroidのレイアウト/UI本

Android Layout Cookbook アプリの価値を高める開発テクニック
あんざい ゆき
インプレスジャパン
売り上げランキング: 12365
Android UI Cookbook for 4.0 ICS(Ice Cream Sandwich)アプリ開発術
あんざい ゆき
インプレスジャパン
売り上げランキング: 92209

Wifiに関するbroadcast

イベント broadcast int EXTRA_WIFI_STATE NetworkInfo EXTRA_NETWORK_INFO
WIFIをONにしています WifiManager.WIFI_STATE_CHANGED_ACTION="android.net.wifi.WIFI_STATE_CHANGED" WIFI_STATE_ENABLING **
WIFIをONにしました WifiManager.WIFI_STATE_CHANGED_ACTION="android.net.wifi.WIFI_STATE_CHANGED" WIFI_STATE_ENABLED **
アクセスポイントに接続中 WifiManager.NETWORK_STATE_CHANGED_ACTION="android.net.wifi.STATE_CHANGE" ** isConnected()==false
アクセスポイントに接続しました WifiManager.NETWORK_STATE_CHANGED_ACTION="android.net.wifi.STATE_CHANGE" ** isConnected()==true
WIFIをOFFにしています WifiManager.WIFI_STATE_CHANGED_ACTION="android.net.wifi.WIFI_STATE_CHANGED" WIFI_STATE_DISABLING **
WIFIをOFFにしました WifiManager.WIFI_STATE_CHANGED_ACTION="android.net.wifi.WIFI_STATE_CHANGED" WIFI_STATE_DISABLED **

プロセスとアプリケーションとスレッドの関係

プロセスとアプリケーションとスレッドについての理解が非常にあいまいなため、整理してみます。

プロセスとアプリケーションのライフサイクルの関係

アプリケーション内のサービスやアクティビティが全部終了していても、プロセスは停止しない。
例えばアクティビティ1枚のアプリケーションを起動して、onDestroyさせてもプロセスは生きている。(DDMSのDevicesビューや、設定>アプリケーション>実行中 にアプリケーションが表示されている)
Androidがこうする理由は、プロセスの生成コストによるバッテリー消費を避けるためらしい。

Androidの各アプリケーションは,システム上の1プロセスとして起動される。通常のLinuxディストリビューションであれば,ソフトウエアの終了はプロセスの停止を意味する。しかし,Androidの場合はユーザーがアプリケーションを終了しても,プロセス自体はすぐには停止しない。
 その理由は,一般的なPCとは違い,携帯端末では消費電力が問題になるからだ。
プロセスの生成は一般的な処理に比べて,CPUをはじめとするコンピュータ・リソースの消費量が非常に大きい。ユーザーがアプリケーションを切り替えるたびに,プロセスの終了と生成を繰り返していては,バッテリの持続時間が短くなってしまう。
 そのため,使われなくなったアプリケーションのプロセスも,すぐに終了せず,待機状態にしておく。もし,待機しているアプリケーションの起動が要求されたら,待機中のプロセスを利用し,プロセス生成のコストを低減するのである。

Androidの仕組みを知る(1) - Android徹底解説---内部構造,移植,開発:ITpro http://itpro.nikkeibp.co.jp/article/COLUMN/20091126/341182/?ST=android-dev&P=2

アプリケーションとスレッドの関係

アプリケーションを終了させてもプロセスが生きているということは、アプリケーションからスレッドを起動して、アプリケーションを終了させてもスレッドは終了しないということ。

例えばボタンを押すと別スレッドで5秒後にログを表示させる場合、5秒の間にアプリケーションを終了させてもスレッドに影響はない。(※ただしスレッドの参照に到達できなくなるとgc対象になってしまう?)
影響があるのは、プロセスを強制終了させた場合。

No longer wantなプロセスはkillされる

メモ。
一度アプリを起動すると、他のアプリを起動してもプロセスは生きている。アプリをばんばん起動し続けると、あるタイミングでプロセスが終了される。
その際、LogCatには下記の出力。

03-31 00:15:36.450: INFO/ActivityManager(92): No longer want com.naokichick.sampleprocess (pid 2966): hidden #16