Subversion + Dropbox の使用 その3
前回の続きです。SVNリポジトリー・エクスプローラー パースペクティブの SVNリポジトリ ビューで、
- 新規SVNリポジトリーの追加 を選択
- ロケーションのURLに、file:///home/foo/Dropbox/svn を入力(svnadmin create や svn importで指定したディレクトリ)
うまくいけば、前回 svn importでコミットしたディレクトリ群が表示されます
最後に、Javaパースペクティブに戻り、プロジェクトをコミットします。
- パッケージ・エクスプローラで プロジェクトを右クリック → チーム → プロジェクトの共用
- リポジトリータイプで SVN を選択、次へ
- 既存のリポジトリーロケーション(ラジオボタン)と file:///home/foo/Dropbox/svn を選択し、次へ
- 指定したフォルダ名(ラジオボタン)を選択し、フォルダ名に
/trunk/ を入力、次へ - コミットコメントを適当に入力し、完了
この段階では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 - このウェブサイトは販売用です! - eclipsewiki リソースおよび情報を参考にSubclipseをインストールします。更新URLは http://subclipse.tigris.org/update_1.6.x です。
インストールが完了すると、「SVN リポジトリー・エクスプローラー」パースペクティブが選択できるようになります

*1:これ以降はDropboxとか関係なく、Subversion一般の設定手順になってます
Subversion + Dropbox の使用 その1
そろそろソースのどこをどのようにいじったか記憶するのが難しくなってきたので、バージョン管理システムを使うことにします。今ですとGit/GitHubなんかがいいのかもしれませんが、どうせユーザは私一人なので 使い慣れたSubversionを導入します。せっかくなので出先でもプロジェクトにアクセスできるよう、SubversionのリポジトリをDropboxの管理下に置いてみます
Dropboxの導入
Dropbox for Linux を GNOME 以外で利用 - 暇つぶし【Linux/いろいろ】 と Dropbox without Gnome :: antrix.netを参考にさせて頂きます。
ActivityのライフサイクルとObserver
とりあえず
- onDestroy()でObserverの登録を解除する
- onDestory()以外のonFoo()メソッドでは Observerの登録を解除しない
- Modelからの通知を受け取ったとき、Activity#getWindow()#isActive()を使って 表示する/しないなどの判断をする
という指針を置いてやってみようかと思います。Window#isActive()は そういう使い方をしてよいものなのか全然自信がない(実際、onResume()ではtrue/false どっちの状態もありうるみたい)ですが、困ったら後で考えるということで。
Activityのライフサイクル
Activity | Android Developersを参考にします。
基本、Control/Viewは
- 自分が現在アクティブでなくても、今後アクティブになる可能性がある限りはModelからの通知を受け取っておきたい
- 今後アクティブになる可能性がゼロであれば、Modelからの通知を受け取らないようにしたい
- 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表示が、複数回出てくることがあるのです。
登録ボタンを押すと、
- Control : CharacterCreateActivity#onClick()
- Model : CharacterList#save()
- 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からの読込を追加する予定です
