Subversion + Dropbox の使用 その3

前回の続きです。SVNリポジトリー・エクスプローラー パースペクティブの SVNリポジトリ ビューで、

うまくいけば、前回 svn importでコミットしたディレクトリ群が表示されます
最後に、Javaパースペクティブに戻り、プロジェクトをコミットします。

この段階ではeclipseのプロジェクトがコミットされただけで ソースコードや設定ファイルは管理対象外です。なので管理対象とするファイルorディレクトリを パッケージエクスプローラで右クリック → チーム → バージョン管理に追加 として コミットします。
無事 同期がされるとオレンジのマーク?が付きます

Subversion + Dropbox の使用 その2

前回の続きです*1。Dropboxで同期しているディレクトリに、Subversionのリポジトリを作成します。

$ svnadmin create ~/Dropbox/svn/

/tmp ディレクトリにプロジェクトのディレクトリを作り、リポジトリにインポートします

$ mkdir /tmp/svn
$ cd /tmp/svn
$ mkdir FooProject
$ mkdir FooProject/trunk
$ mkdir FooProject/tags
$ mkdir FooProject/branches
$ svn import . file:///home/foo/Dropbox/svn/ --message 'ディレクトリ初期構成'

次に、eclipseからSubversionにアクセスするためのプラグイン、Subclipseを導入します。まずは、subclipse: Wiki: JavaHLを参考に

$ sudo apt-get install libsvn-java

として libsvn-javaをインストールします。次にeclipseの起動設定ファイル eclipse.ini に下記の行を追加します。

-Djava.library.path=/usr/lib/jni

これらの設定はSubclipseがfile://にアクセスするために必要です。続けてeclipsewiki.net - このウェブサイトは販売用です! -&nbspeclipsewiki リソースおよび情報を参考にSubclipseをインストールします。更新URLは http://subclipse.tigris.org/update_1.6.x です。
インストールが完了すると、「SVN リポジトリー・エクスプローラー」パースペクティブが選択できるようになります

*1:これ以降はDropboxとか関係なく、Subversion一般の設定手順になってます

Subversion + Dropbox の使用 その1

そろそろソースのどこをどのようにいじったか記憶するのが難しくなってきたので、バージョン管理システムを使うことにします。今ですとGit/GitHubなんかがいいのかもしれませんが、どうせユーザは私一人なので 使い慣れたSubversionを導入します。せっかくなので出先でもプロジェクトにアクセスできるよう、SubversionのリポジトリをDropboxの管理下に置いてみます

Subversionの導入

$ sudo apt-get install subversion libsvn1

aptで簡単にインストールできます。

Dropboxの導入

Dropbox for Linux を GNOME 以外で利用 - 暇つぶし【Linux/いろいろ】 と Dropbox without Gnome :: antrix.netを参考にさせて頂きます。

  • 事前にDropboxのアカウントを取得しておく
  • http://www.getdropbox.com/download?plat=lnx.x86_64からdropbox-lnx.x86_64-0.7.110.tar.gz をダウンロード
  • ~/.dropbox-dist へ展開
  • 展開された~/.dropbox-dist/dropboxd を、Xfce 4 設定マネージャの自動開始アプリケーション に加える
    • メニュー Applications → 設定 → Xfce 4 設定マネージャ を選択
    • セッションと起動 を選択
    • 自動開始アプリケーション タブを選択
    • 追加 ボタンを押し、名前:Dropbox daemon、説明:<空欄>、コマンド:~/.dropbox-dist/dropboxd と入力し、OKボタン
    • 閉じるボタンを押し、ログアウト→ 再ログイン
  • Dropboxの初回設定画面が表示されるので、アカウント情報と 同期するディレクトリを指定する
  • 同期が開始される。通知トレイにアイコンが表示される(はず)

ActivityのライフサイクルとObserver

とりあえず

  • onDestroy()でObserverの登録を解除する
  • onDestory()以外のonFoo()メソッドでは Observerの登録を解除しない
  • Modelからの通知を受け取ったとき、Activity#getWindow()#isActive()を使って 表示する/しないなどの判断をする

という指針を置いてやってみようかと思います。Window#isActive()は そういう使い方をしてよいものなのか全然自信がない(実際、onResume()ではtrue/false どっちの状態もありうるみたい)ですが、困ったら後で考えるということで。

Activityのライフサイクル

Activity  |  Android Developersを参考にします。
基本、Control/Viewは

  1. 自分が現在アクティブでなくても、今後アクティブになる可能性がある限りはModelからの通知を受け取っておきたい
  2. 今後アクティブになる可能性がゼロであれば、Modelからの通知を受け取らないようにしたい
  3. Modelの状態が変わらない間は そもそも通知がされないため、通知を受け取っても受け取らなくてもよい

ということだと思います。
finish()でActivityを終了させようとするとonDestroy()が必ず呼ばれますが、これは2に該当するのでObserver登録を削除するようにします。
さらに、onPause()が呼ばれた後はいつでもActivityがkillされる可能性があり、ここでもObserver登録を削除したほうがいいのかもしれませんが、onPause()からはonResume()を経て再度アクティブになることがあるのでそうともいいきれません。つまりonPause()実行後からonStop()実行後までは1とも2ともつかないため、3を満たすようにする必要があります。
今はModelを操作するActivityが1つしかないので3は簡単に満たせますが、画面遷移のためにActivityを増やしていけばそうも言ってられなくなるような。ということは3は諦めて onDestroy()でObserver登録を削除(=2)、それ以外のとき(=1)はActivityの状況を動的に判断して例えばバックグラウンドならToastを表示しないとか、通知は受け取るがその後どうするかは状況を見て決める とすべきなんでしょうか。混乱してきました…

Observerの削除

DB読み込み機能を作っていて、奇妙なこと?に気がつきました。1回しか実行していないはずのToast表示が、複数回出てくることがあるのです。
登録ボタンを押すと、

  1. Control : CharacterCreateActivity#onClick()
  2. Model : CharacterList#save()
  3. View : CharacterCreateActivity#onSaved() ←ここでToast

という順番で実行されますが、ボタン1回につきToastが3〜4回くらい出てきてるような…?
調べたところ何のことはない、CharacterList(Subject)へのCharacterCreateActivity(Observer)の登録が重複していたのでした。

  • アプリ実行開始
  • CharacterCreateActivityのインスタンス作成
  • CharacterList#addListener()でObserver登録
  • ...
  • アプリ実行終了
  • アプリ再実行開始
  • CharacterCreateActivityのインスタンス作成
  • CharacterList#addListener()でObserver登録(2個目)
  • ...

という流れで徐々にAcitivityのインスタンスが増えていき、その全てに対してイベント通知していたのでToastが何回も出てきていました。CharacterListもActivityもfinish()でアプリを終了させただけでは破棄されないようなので、Activityのライフサイクルを勉強して、重複しないように適宜Observerを削除する必要があります。二日間 更新を飛ばしてしまったうえに脱線してしまい恐縮ですが、明日以降 Activityのライフサイクルについて掘り下げようと思います

SQLiteの使用 その2

DBへの保存を実行するために、さらに下記を追加します
CharacterList.OnUpdatedListenerインタフェース : 保存したことを通知するための抽象メソッドonSaved()を追加

	public interface OnUpdatedListener {
		void onAdded(Character character);

		void onRemoved(Character character);

		void onSaved(CharacterList characterList);
	}

CharacterListクラス : 保存したことを通知するメソッドsendSaved()と、Listのサイズを返すメソッドsize()を追加

	public void save(Application application) {
		if (dbHelper == null) {
			dbHelper = new DbHelper(application);
		}

		for (Character character : characterList) {
			dbHelper.insertName(character.getName());
		}
		this.sendSaved();
	}
...

	private void sendSaved() {
		for (OnUpdatedListener listener : this.onUpdatedListenerList) {
			listener.onSaved(this);
		}
	}
...
	public int size() {
		return this.characterList.size();
	}

main.xml : 保存ボタンの追加、終了ボタンのレイアウト変更

	<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	    android:orientation="horizontal"
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    >
		<EditText
			android:id="@+id/charName"
			android:layout_width="wrap_content"
			android:width="160dp"
			android:layout_height="wrap_content"
			/>
		<Button
			android:id="@+id/create"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="登録"
			/>
		<Button
			android:id="@+id/save"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="保存"
			/>
		<Button
			android:id="@+id/finish"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:text="終了"
			/>
	</LinearLayout>

CharacterCreateActivityクラス : 保存ボタンの追加、押下時にCharacterList#save()を呼ぶ、onSaved()で保存したキャラクタの人数をToast表示

...
	private Button savedButton;
...
	public void onCreate(Bundle savedInstanceState) {
...
		saveButton = (Button) this.findViewById(R.id.save);
		saveButton.setOnClickListener(this);
	}
...
	@Override
	public void onClick(View v) {
		if (v.equals(createButton)) {
...
		} else if (v.equals(saveButton)) {
			charaList.save(getApplication());
		}
	}
...
	@Override
	public void onSaved(CharacterList characterList) {
		Toast.makeText(this, characterList.size(), Toast.LENGTH_SHORT);
	}

明日はDBからの読込を追加する予定です