スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Eitan - flashcard 高速学習支援アプリ Android版アップデート

今回、既存のEitanアンドロイド版を大幅に改修し、Ver1.1となりました。

Eitanは、いわゆるフラッシュカードと呼ばれるアプリの部類で、日本では単語帳めくりで
知られていますが、それをシステム化したものです。

特徴としては、間違った問題や、不確かな問題のみを絞り込んで出す機能や、間違った問題を
定期的に出題し、記憶を速やかに定着させる機能を挙げることができます。


<変更点>

【サンプル問題集】
・以下の4つの単語問題集をビルトインで追加しました。
 1.英和(中学生レベル)
 2.英和(高校生レベル)
 3.和英(基礎~中級レベル)
 4.中国語-英語(HSK Level 1-4)

 自分で単語集を作らなくてもすぐに試すことができます。
 あとからこれらの問題集を編集することもできます。

【機能面】
・同じデータを使いながら、問題、答え、備考の位置を自由に変更できるようになりました。
 例えば、英和の問題集であれば、すぐに和英の問題集が作れます。
・データのエクスポートを可能にしました。


[TOP画面でメニューを表示]
top_menu.png

[問題集設定画面]
workbook.png


・Webと連動し、Web辞書で問題や答えの文字を検索でき、その場で問題集を編集可能にしました。
・単語のクリップボードのコピー機能。
・英語については、音声読み上げに対応しました。


[Webリンク設定画面]
web_dic_setting.png

[Webリンク表示例]
naver2.png

[拡張メニュー表示]
lookup.png

・背景色、前景色を変更できます。

[背景色を変更]
qa_color.png



【不具合修正】
・問題集の一部が壊れてしまう問題を修正しました。
・他のアプリを起動して、戻った際に前回の結果等が消えてしまう問題への対応。


【構造面】
・CSVはインポートのみに使用
・内部的なデータの管理はDBを使用
・将来的なサーバ版との連携を考慮して、データの抽象化を推進。
・不明なエラーが発生した際、ログをSDカードにファイル出力

<今後の予定>
・Webとの連動強化
・親しみやすいデザインへの修正

ダウンロードはこちらから
https://play.google.com/store/apps/details?id=happie.eitan&hl=ja
スポンサーサイト

テーマ : 効果的な学習法など
ジャンル : 学校・教育

Android 開発備忘録 (13) 背景色・前景色の変更

長々と続けてきた備忘録だが、まだまだ自分のAndroid道は序盤にすぎない。
(しばらく中断予定)。
何か新しいことをしようとすると、都度勉強が要求される。
こうしたいのだが、と思っても、どうしたらいいのかは調べながら行う必要がある。
これは、どんなプログラム言語で書いても同じことは言えるが、Androidでは特にそれを感じる。
当たり前にできると思っていることにひどく手間取るようになる。

多くの人が言っているように、Androidは開発コストがかかりすぎる。端末の多さも問題になるが、開発者を楽にするコンポーネント類があまりにも貧弱だし、学習コストも高い。

当初、ロジックとビューの分離を頑張っていたが、トータルで見ると、ロジックよりもビューの確認の方がはるかに多くの時間を要するため、分離できたメリットは実はそれほどない。
エミュレータも実機も機動に時間がかかる。起動した後は、実機の方が速かったりする。
この待ち時間は開発効率に影響する。


さて、今回は背景色・前景色の話題。
前景色は、
TextView.setTextColor();
でセットすればよし。
設定をPreferenceに保存して、アプリの起動時に共通の設定としてロードして、同一の設定をする画面から参照できるようにしておく。

背景色が問題。
まず、通常のViewだが、setContentView()でセットしているレイアウトにセットしたいが、それは取れないので、レイアウトxmlで一番外側に定義しているLayoutやViewにidを振っておいて、Java側でそれをfindViewByIdで取り出して、setBackgroundColorで設定する。一番外側のものにしないと、中のLayoutがfill_parentに設定されていても、部分的にしか描画されず、背景色が半分ぐらいしか塗られないことにもなりかねない。

次いで、ダイアローグ。
AlertDialogで表示しているが、このViewに対して背景色を設定すると、何とも中途半端に、表示されてしまう。タイトルやボタン表示位置、さらにボタンとビューの間の部分に線が入ってしまう。
confirm_emu.png
これはちょっと見苦しい。

これに対処するには、Themeを設定する必要があって、非常に苦労した。
以下のページを参考にした。
http://dtmilano.blogspot.jp/2010/01/android-ui-colored-dialogs.html

ただこれを単純に持ってくるだけでは動かなかった。
最初R.attr.tintが参照されず、attrs.xmlも定義していなかっため、いちいち作る必要があるかと思ったら、Rのimport設定の問題だった。つまらないことだが、正直ユーザが定義するものとはクラス名は別にして欲しい。

ついで、このAlertDialogは内部クラスにBuilderを持っているのだが、両方ともサブクラスを作って継承しているがこれが問題を引き起こす。作成したBuilderは親のメソッドを引き継ぐが、設定を行うのは、メンバーに持っているAlertDialogではなく、親が持っているAlertDialogとなる。
そのため、メソッドを呼び出しているのに、反映されないことがあり、何かと盲点となった。
これはデザインパターンでいうところの、AbstractFactoryかBuilderパターンだろうが、継承して使うのは危険が伴う。結局、extendsやOverrideを外して、呼び出し元で親メソッドを呼んでいないかチェックし、それを実装してから、元に戻した。

また、Themeはstyle.xmlに定義するが、Javaから設定できないのが残念。
他の背景色や前景色はJavaで動的に設定したが、styleはそれができないので、いちいちxmlに書いておく必要がある。そのため、設定できる背景色はViewの背景色に比べてかなり少なくした。

Themeを設定するとこんな感じになる。個人的にはボタンの部分はテーマでかぶせなくてもいいのだが、それ以上調査する余力はなかったので、断念。あと、Windowの位置が上に行ってしまう。これには位置の指定やGravityの指定をしたが全く効かず、これまた断念。
confirm_emu2.png


あと、背景色の設定画面だが、色の選択として、PreferenceScreen内のListPreferenceをカスタマイズすればいいだろうと思ったが、これまたやり方がわからないし、よくありがちなのに調べても簡単に見つからない。
結局のところ、これは「Android color pickers」というキーワードで検索すればよかったのだが、結果的にこのコンポーネントは使わず、DialogPreferenceを継承したクラスを作って、単純なリスト形式で選択するようにした。

bgsetting_emu.png
これは、以下のサイトを参考にした。
http://www.ulduzsoft.com/2012/02/colorpreference-class-for-android/


Eitanのダウンロードはこちらから
https://play.google.com/store/apps/details?id=happie.eitan&hl=ja

テーマ : プログラミング
ジャンル : コンピュータ

Android 開発備忘録 (12) 非同期処理

スレッド・非同期処理に関するトピック。

これはandroidに限った話ではないが、重たい処理を別スレッドで行うときの注意事項として、
その処理結果を画面に反映させたい場合、いくら画面コンポーネントのオブジェクトに対する
参照を持っていたとしても、変更はできない。

それをやろうとすると、
Can't create handler inside thread that has not called Looper.prepare()
と怒られてしまう。

UIスレッドではないのに、別スレッドで描画しようとしたのが問題で、対処としては、
Handlerをクラスのメンバーとして、UIスレッド(メインスレッド)がnewするようにしておいて、runの中でそのハンドラーにpostする。

あとは注意事項としては、スレッドがコンポーネント以外の変数を書き換える場合も、実行順序は保証されないため、
きちんと排他制御は考えておく必要がある。

ビューの中で非同期処理を行う場合は、あらかじめ用意されているAsyncTaskを使えばよいが、今回私のようにロジックをPureJavaで別プロジェクトにした場合は、これは使えないので、自前でスレッド処理を行う必要がある。

AsyncTaskが使えるところは使った。以下はその例。
しかし、注意するのは、doInBackground()のメソッドは別スレッドで行われるのでそこで描画を行ってはいけない。エラー発生時にアラート表示をする場合、メインスレッド内で行うようにする。そのため、doInBackgroundの戻りをExceptionにして、onPostExecuteでnullでなければアラートを表示する。

Eitan.setFixedOrientation(ImportCsvForm.this, true);
AsyncTask<Void, Void, Exception> task = new AsyncTask<Void, Void, Exception>() {
@Override
protected void onPreExecute() {
showProgress(ImportCsvForm.this, R.string.msg_process);
}

@Override
protected Exception doInBackground(Void... params) {
try {
importCsv(name.getText().toString().trim(), csvFileName, delete.isChecked());
} catch (Exception e) {
log.e("error", "importCsv error.", e);
return e;
}
return null;
}

@Override
protected void onPostExecute(Exception result) {
dismissProgress();
if (result != null) {
Alert.show(v.getContext(), getString(R.string.msg_error), getString(R.string.msg_error_general));
}
else {
Toast.makeText(v.getContext(), getString(R.string.msg_import), Toast.LENGTH_SHORT).show();
}
Eitan.setFixedOrientation(ImportCsvForm.this, false);
}
};

task.execute((Void)null);

上のsetFixedOrientation()やshowProgress()、dismissProgress()、Alert.show()は共通メソッド化している。大した処理ではないのでここではソースは略。

AsyncTaskを独立したクラスにすることもできるが、実際の処理はさらにそれとは別に書いた方がいいので、上記のように内部クラスにした方がわかりやすいと思う。内部クラスにすれば、変数は、finalのローカル変数を参照すればいいので、パラメータはVoidでいい。

最初、プログレスの表示をAsyncTaskの外に出していて、そのせいで、コピペした際に、二つ入ってしまい、プログレスがなんで消えないのかとかなり悩んだ。(Android特有の動作に散々やり込められていたので、すぐにそれを疑っていたが、自分の書いたものを疑うのも必要)

あとはこれ。

Activity ... has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView that was originally added here


てっきり、スレッドの問題だと思い、散々悩まされた。
結局これは、Alert.show()のあとfinish()していたのが問題。Alert.show()はブロッキングだと思っていたのだが、そうではなくその先のロジックも先に進んでしまう。AlertでOKを押したあとすぐに画面遷移させたい場合は、Alertのあとにロジックを書くのではなく、AlertのOKボタンのイベントリスナで処理するようにすること。


それから、プログレスの表示だが、アプリ固まるのはよくないので、キャンセル可能に設定した。
しかし、そうすると、
バックグラウンドでの処理実行→戻るボタンを押す→プログレスが消える→再度実行、
これを繰り返すと、前の処理は続いたまま、新たな処理が実行され、ずっと連続で処理が実行される。DBに登録・更新する処理だったが、10回ぐらい繰り返したが、SQLiteのエラーにはならない。
さらに戻るボタンでアプリを終了させると、プロセスが停止し、スレッドの処理は停止した。アプリが終了してもスレッドがずっと動き続けると思ったが、このときは問題なかった。ただ、DB更新中だがトランザクションは問題ないだろうか。SQLiteだと少し心配になる。



テーマ : ソフトウェア開発
ジャンル : コンピュータ

Android 開発備忘録 (11) テーブル構造の表示

Androidでテーブル構造のデータを表示したい場合にどのようにするか。

TableLayoutというものが用意されているが、これが非常に遅く、データの表示が1, 2ページ分だけならよいが、
量が多くなると、レンダリングにとてつもない時間がかかる。
しかも、標準では、htmlのテーブルタグのようなボーダーの指定はできない。

なので、通常のListViewを使って表現する方法について述べる。
ただし、いずれにしても限界はあるので、どこかでページングの処理などは必要。

まずlayoutのxml。これはListViewの部分とリスト1行の中身に分ける。
・全体のビュー(前後のコンポーネント略)
	    <ListView
android:id="@+id/listQadata"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fastScrollEnabled="true"/>

・各行のビュー
<?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="wrap_content"
android:orientation="horizontal" >

<TextView
android:id="@+id/data1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>

<TextView
android:id="@+id/data2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:gravity="left"
/>

<TextView
android:id="@+id/data3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:gravity="left"
/>
</LinearLayout>

そして、ビューに対して、Adapterをセットしていく。AdapterはHashMapを使って、列名をキーにして値をセットし、それをListにする。単純にこれを行うと、列が揃わないので、getView()をオーバーライドして、画面の横幅の値に比率をかけて、各行各列に対してwidthをセットする。これは、xmlでweightをセットしても効果がないための苦肉の策である。幸い、縦も横も動的に画面の幅を取得できる。あと、描画する前に値を取得したとしても0になっているので、描画後のタイミングを選ぶ必要がある。

Display disp = ((WindowManager)this.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
int width = disp.getWidth();
List<HashMap<String, String>> qadataMapList = new ArrayList<HashMap<String, String>>();
for (final Qadata qadata : qadataList) {
qadataMapList.add(new HashMap<String, String>(){{
put("data1", qadata.qadata1); put("data2", qadata.qadata2); put("data3", qadata.qadata3);
}});
}
ListView listQadata = (ListView)activity.findViewById(R.id.listQadata);
SimpleAdapter adapter = new SimpleAdapter(activity, qadataMapList, R.layout.sub_qadata,
new String[]{"data1", "data2", "data3"},
new int[]{R.id.data1, R.id.data2, R.id.data3}) {

@Override
public View getView(int i, View convertView, ViewGroup parent) {
if (convertView == null)
{
LayoutInflater inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.sub_qadata, null);
}

HashMap<String, String> item = (HashMap<String, String>) this.getItem(i);
if (item != null) {
TextView text1 = (TextView) convertView.findViewById(R.id.data1);
text1.setText(item.get("data1"));
text1.setWidth((int)(width * 0.3));

TextView text2 = (TextView) convertView.findViewById(R.id.data2);
text2.setText(item.get("data2"));
text2.setWidth((int)(width * 0.3));

TextView text3 = (TextView) convertView.findViewById(R.id.data3);
text3.setText(item.get("data3"));
}
return convertView;
}
};
listQadata.setAdapter(adapter);

listQadata.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ListView listView = (ListView) parent;
HashMap<String, String> item =
(HashMap<String, String>) listView.getItemAtPosition(position);
Intent intent = new Intent(activity, QadataForm.class);
activity.startActivityForResult(intent, 0);
}
});
listQadata.setTextFilterEnabled(true);

結果として、以下のようにきれいに列が揃うようになる。
data_list.png


eitanのダウンロード先はこちら
https://play.google.com/store/apps/details?id=happie.eitan&hl=ja

テーマ : プログラミング
ジャンル : コンピュータ

Android 開発備忘録 (10) アプリケーションのライフサイクル・インスタンス削除

Androidの開発で一番厄介だったのが、このアプリケーションのライフサイクルである。

縦横の切り替えをしたり、バックグラウンドから復活したときに、データが消えてしまい、
前回の状態が表示されない、あるいは部分的にしか表示されていないということがある。
あるいは、エラーがでてアプリケーションが終了してしまう。
今回の開発を通じて、他のアプリで同様の現象が起きる理由がよくわかった。

通常のJavaの開発では、インスタンスへの参照を保持しておけば、プロセスを終了しない限り、
いつでもそのインスタンスの内容を参照することはできる。

ところがAndroidでは、OSの都合で、勝手にインスタンスが削除されたり、クラスがアンロードされたり
する。
そのため再表示された際に、データがなかったり、NullPointerで落ちたりする。

Androidのシステムは、インスタンスを削除する際、あとから復旧できるように、onCreateの引数にある
Bundle(savedInstanceState)とIntentとを保存しておき、Activityを再度Createするときにそれらを
使用する。(どこに保存しているのかは不明。savedInstanceStateやIntentが巨大だとインスタンスを
解放しても空きメモリを増やす目的を遂げられないため、たぶんDiskに書いているのでは?)

保持するべき情報(一時的なもののみ)としては、
 ①Activityのインスタンス変数
 ②参照しているクラスのstatic変数
 ③画面で表示されている内容
 ④ユーザがテキストボックス等で入力した情報
等である。

考えられる事態としては、

1. 表示中のActivityのインスタンスが削除される。
2. Stackに保存されているActivityの情報が削除される(Root Activityを除く)
3. Root ActivityやApplicationインスタンスが削除される。
4. Activityの再現に必要なIntentとsavedInstanceStateが削除される。

があり、いずれもインパクトがある。

なお、4は、2が起きた場合、同時に起きる。というかリストアするActivityがなければ、IntentとsavedInstanceStateは不要となる。そもそも2なしで、4が起きるかというと、多分起きないだろう。
3も仕様上は起きないとなっているが、実際タスクキラーで終了すると、インスタンスは削除されている。
これらを明記しているドキュメントはなさそう。

また2についても、Activityのインスタンスではなく、情報と書いた理由は、Stack中に残っていても、
インスタンス丸ごと残っているわけではないのではないかという懸念から。StackにあるActivityも、
IntentとsavedInstanceStateがあれば、復旧できるのでインスタンス丸ごと残っている必要はない。
実際の動作を見てもそうなっている。

インスタンスの情報は簡単に消える。ローテートしても、一瞬だけバックグラウンドに移行しても消える。
しかし、Stack上のインスタンス情報は残っている。
Activityが生成された段階でStackに入るという説と他のActivityに移った段階で入るという説があるがどちらが正しいのだろうか。


実際に、画面遷移を重ねた状態で、バックグラウンドに移行し、タスクキラーでアプリを終了する。
そうしたとしても、ホームボタン長押しで表示されるアプリの履歴は残っているので、それをクリックする。
すると、すぐに表示していたActivityに行くのではなく、まずApplicationのonCreate()が呼ばれる。
そして次いで、当該ActivityのonCreate()が呼び出される。
Stack上のインスタンスは削除されても、Stackの情報は残っており、戻るボタンを押すと、その前のActivity
がロードされる。しかし、インスタンスは削除されているのでonCreate()が呼ばれる。

また、画面表示情報③④の情報はそれとは別に残っている。

Almost every widget in the Android framework implements this method as appropriate, such that any visible changes to the UI are automatically saved and restored when your activity is recreated.


とはいえ、長い時間が経過し、他でリソースが使われると、Stack情報も削除される。長い時間は、ドキュメント
にある30分とは限らない。1日置いても復旧はできているので、おそらくメモリなどのリソース不足等も加味され
ていると思う。実際、そういうケースでは、メインActivityからスタートする。

まとめると、以下の3つのレベルがある。

(1) 回転、もしくはバックグラウンドの移行後ある程度の時間
 表示しているActivityのインスタンスが削除される。
 それ以外は残っている。
 ある程度の時間というのは状況に依存する。場合によってはバックグラウンドに移行して即となる。

 正確には、バックグラウンドに移行すると、onSaveInstanceState(), onPause(), onStop()が呼ばれる。
 もう少し時間が経つと、状況によっては、Activityのインスタンス変数がクリアされる。Applicationのインスタンスもクリアされている。しかしonDestroy()は呼ばれない。また、そのインスタンスやメンバーのfinalize()が呼ばれる場合と呼ばれないときがある。
 
 こうなると、再開時には、ApplicationのonCreate()から呼び出される。
 しかし、回転すると確実にonDestroy()まで呼ばれる。 

(2) バックグラウンドでしばらくの時間および他でメモリ不足
 全インスタンスが削除されているがスタック情報は残っている
 この状況はシステム側で行うが、同じ状況はタスクキラーでも発生させられる。
 タスクキラーで削除した場合、onDestroy()は呼ばれない。

(3) 2がさらに進んだ場合
 スタック情報も消える。

それぞれについて対策を考える。
まず(3)の場合は対策の打ちようがない。スタック情報がないので、どのActivityを再現するかはわからない。
プログラム上でスタックを記録することはできるかもしれないが、同じ状況を作れるのかどうかは不明。
というか、その場合はもうメインActivityからでいいのでは。当然、永続的に保持する情報は都度保存しておく
必要はあるが。


対策方法:
1. android:alwaysRetainTaskState
 マニフェストファイルのメインActivityにandroid:alwaysRetainTaskState="true"を記載する。
 タスクの状態が保たれるというが全く効果なし。
 インスタンスの状態は保持されない。

2. getLastNonConfigurationInstance()
 onRetainNonConfigurationInstance()とセットで用いる。
 onRetainNonConfigurationInstance()が呼ばれる際に、保存したいオブジェクトをreturnすると、
 getLastNonConfigurationInstance()で取得できるというが、onRetainNonConfigurationInstance()は呼ばれても、
 getLastNonConfigurationInstance()で値がnull以外だった試しがない。やり方が悪いのかもしれないが全く効果なし。

3. Application継承クラスのインスタンスのメンバーに保存
 一番簡単なのは、Applicationのインスタンス変数に値を保存しておき、そこを参照するようにする方法である。
 (1)のケースについては有効だが、結局状況によって(2)の状況が発生し、その場合値はnullになってしまう。
 前回、情報の共有にApplicationを使う話をしたが、これはあくまでも一時的な情報共有で、もし(2)の事態が
 起きたら、再度データをロードする処理を入れる必要がある。

4. savedInstanceStateの使用
 これが(2)のケースでも通用する方法である。バイト配列を使用できるのでオブジェクトをシリアライズして
 保存すれば、同じ状況が再現できる。

	@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(outState);
byte[] buf = Util.toByteArray(qamanager);
if (buf != null) {
savedInstanceState.putByteArray("QAManager", buf);
}
}

public void restoreSavedInstance(Bundle savedInstanceState) {
if (savedInstanceState != null) {
byte[] buf = savedInstanceState.getByteArray("QAManager");
if (buf != null) {
qamanager = (QAManager)Util.toObject(buf);
}
}
}

 ただし、Activityやシリアイズ不可のものをインスタンスのメンバーに加えている場合、それはtransientにしてシリアライズの対象から外し、リストアの際に、再度セットするようにする。

 また、他にもstaticなメンバーなどに値をセットしている場合、再度ロードするような処理が必要である。

 最初、このやり方がよくわからず、どういうときに値が消えるのかについて、私自身混乱しており、
 かなり試行錯誤し、しばらくは、sdcardにオブジェクトをシリアライズしてファイルに保存する方法を
 取った。それをonPause()のタイミングで呼び出すようにしていた。が、結局上記の方法で問題なく
 動作するので不要となった。

 とはいえ、以下のようにonSaveInstanceState()が呼ばれる保証はないので永続データの保存には注意する必要がある。私の今回のアプリでは、セッション情報のみの保存で、永続データは処理がある都度保存していたので、特に、onPause()で処理するものは今のところない。

Note: Because onSaveInstanceState() is not guaranteed to be called, you should use it only to record the transient state of the activity (the state of the UI)?you should never use it to store persistent data. Instead, you should use onPause() to store persistent data (such as data that should be saved to a database) when the user leaves the activity.


あと、エミュレーターだとCTRL + F11, 12で回転ができるが、一番問題の画面で、なぜかデータが消えてしまう。
たぶんエミュレータのバグとしたい。実機ではそのようなことは全くない。

eitanのダウンロード先はこちら
https://play.google.com/store/apps/details?id=happie.eitan&hl=ja

テーマ : プログラミング
ジャンル : コンピュータ

Android 開発備忘録 (9) 画面遷移・情報共有

通常のJavaのGUIを使用するアプリケーションの場合、AWTあるいはSwingのコンポーネントを用いて、
画面にはFrameあるいはJFrameを継承したクラスを用いる。そして、そのインスタンスを生成して、
show()もしくはsetVisible()メソッドを用いて表示する。
そして、画面の表示やイベントの制御はすべて開発者が指定することになり、システム側が勝手に
呼び出すのは、通常のJavaと同じfinalize()ぐらいのもので、それ以外はシステム側で勝手に、
インスタンスを削除したりはしない。

一方、Androidでは、画面表示に、Activityを継承したクラスを用いる。そしてそのインスタンス生成・削除は、システム側に任せられる。特に、削除のタイミングは、Android側が勝手に行うため、これが度々問題となりうる。
イベントの処理等はAWT等と同じように、開発者がリスナーを指定して実装することになるが、それ以外に、システム側が状況に応じて勝手に呼び出すメソッドがいくつかある。Activityの生成・停止・再開・終了時に何らかの処理をしたい場合、決められたメソッドを作って、実装しておく必要がある。しかしいくつかのメソッドは呼び出しを保証してくれるわけではない。
(まあ、通常のJavaでも特定のフレームワークを使用していると、決められたメソッドを実装するように強要され、勝手に呼び出されるのでAndroidに限った話ではないが)。

代表的なものとしては、以下のライフサイクルに従ったメソッドや、onSaveInstanceState()などインスタンスの状態保存のタイミングで呼び出されるようなメソッドがある。

アプリケーション・ライフサイクル

また、通常のJavaの開発では、生成されたインスタンスは参照が消えるまで消されることはない。
逆に、そのインスタンスへの参照を保持しておけば、プロセスを終了しない限り、
いつでもそのインスタンスの内容を参照することはできる。

ところがAndroidではその常識は通用しない。
Androidでは、システム側の都合で、勝手にインスタンスが削除されたり、クラスがアンロードされたり
する。
そのため再表示された際に、データがなかったり、NullPointerで落ちたりする。
この問題にどのように対応するかは、後述するが、まず、どのように画面を表示するか、画面を遷移させる
かについて述べる。

◎一番最初に表示する画面
アプリ起動後一番最初に表示される画面は、AndroidManifest.xmlの中でメインActivityとして指定する。
そうするとアプリ起動時に、システム側で、インスタンスを生成して、onCreate()メソッドを呼び出すことになる。

◎それ以外の画面
Intentを用いて以下のようにする。

intent = new Intent(this, NextActivity.class);
startActivity(intent);


こうすると、同様にシステム側でNextActivityのonCreateを呼び出してくれる。
AWTの場合と比較すると、その違いがよくわかる。

    public static void main(String[] args) {
EitanFrame f = new EitanFrame("eitan") ;
f.setVisible(true) ;
}

AWTの場合、インスタンスの生成があり、その参照を持っているが、Android側では、表示するActivityの
インスタンスは、呼び出し側では持つことができないし、呼び出された側でも、呼び出し側の参照を持つ
ことはできない。あえてやろうとすると、どこかのクラスのstaticメンバーにセットすることだが、
これは推奨できない。なぜなら、システム側で、メモリが不足していると判断されれば、勝手にインスタンスやクラスが消されてしまうからである。

これは、まるでそれぞれのActivityが別のプロセスで動いているようなものともいえるし、クライアント-サーバ型のようなものともいえる。

こうなると、Activity間での情報共有が問題になる。
方法としては、以下のようになる。

1. パラメータ(およびレスポンス)
Intentを送る際に、一緒にパラメータを送ることができる。

Intent intent = new Intent(QadataListForm.this, QadataForm.class);
intent.putExtra("qadataSetId", qadataSetId);
startActivityForResult(intent, 0);


呼び出された側は以下のようにして、パラメータを取得する。

qadata = (Qadata)getIntent().getExtras().getSerializable(key);


ただしパラメータがないかもしれないので、その前にgetIntent().getExtras()のnullチェックは必要。
また次のようにして、呼び出し側に値を返すことができる。
(ただし、これは呼び出す際に、startActivityForResult()で呼び出された場合のみで、
startActivity()の場合は値を受け取れない。)

Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putSerializable("qadata", qadataUpd);
data.putExtras(bundle);
setResult(RESULT_OK, intent);
finish();


そして、呼び出し側で以下のようにonActivityResult()を実装しておくと、呼び出された側が終了した段階で、
コールバックされ、以下のようにして値を受け取ることができる。

    @Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
switch (requestCode) {
case 0:
if (resultCode == RESULT_OK) {
if (intent!= null) {
Bundle bundle = intent.getExtras();
if (bundle.containsKey("qadataSetId")) {
qadataSetId = bundle.getInt("qadataSetId");
}
}
showQadataList();
}
break;
default:
break;
}
}


2. Applicationインスタンスを用いる。
続いて、Activity間での情報共有の方法としては、Applicationを継承したクラスを用いる方法がある。

Androidのアプリケーションの作り方として、Applicationクラスを使用する方法と、すぐにメインのActivityを呼び出すやり方とがある。
前者の場合、Applicationの継承クラスのonCreate()が呼び出された後で、メインActivityのonCreate()が呼び出される。
なので、アプリケーション全体に置ける初期化処理(DBや環境設定等)はApplication継承クラスに書いておいたほうがよい。

用いるには、以下のようにしてAndroidManifest.xmlにApplicationクラスを指定する。

    <application android:name=".Eitan" android:icon="@drawable/eitan0" android:label="@string/app_name">

ちなみに、Applicationクラスの、onTerminate()メソッドは実機では呼ばれないので、ここでリソースの
解放処理等を入れても無駄なので、終了処理を記載したい場合は、メインActivityのonDestroy()で行う方がよい

そして、Applicationのインスタンスは、Activity継承クラスであれば以下のようにして呼び出すことができる。

Eitan eitan = (Eitan)getApplication();


なので、ApplicationインスタンスにActivity間で共有したい情報をセットしておくと共有が可能になる。

これが推奨されるのは、基本的に、ApplicationのインスタンスとメインActivity(root Activity)のインスタンスは、アプリが終了するまで、解放されないからである。Applicationインスタンスが推奨されるのは、どこからでも参照できるからで、メインActivityのインスタンスの場合は、他のActivityからは通常は見えない。
(システムがメモリを必要とすればいつでも解放されるので、再開時に再度値をロードする必要がある。また逆に、あとアプリが終了しても次回の起動コストを減らすために、OSがプロセスをkillせずに残しておく場合が多いので、起動時に変数がすべて初期化されているとは限らない)

3. 任意のクラスのstaticメンバー
これは情報共有の簡単な方法であるが、推奨されない。いつでもシステムによってクラスがアンロードされる可能性があるからである。

4. 永続的領域
これは、SharedPreferencesやDBやファイルなどを用いる方法がある。
しかし、これはアプリケーション終了後も残しておく情報に使用すべきで、一時的なデータを保存する場合、
それらを削除するなり、上書きするなりして、データが不要に増えないように注意すべきである。


画面遷移パターン
次に、画面遷移のパターンについて述べる。
これが特に重要なのは、戻るボタンの動作や、Activityのインスタンスの数等に関係してくるからである。
戻るボタンを押すと、何度か同じ画面が表示されて、やっと前にいた画面に戻ったり、ずっと前の画面に
戻ったりするのは、開発者がこの点について理解していないからである(あるいは意図的な場合も)。

画面遷移のパターンとしては、いろいろあるが基本的には以下の2パターンだと思う。
これが組み合わさったり、条件分岐が加わったりして複雑になっていくが。

① 画面遷移していって戻らない場合
これは一方通行に進んでいくパターンで、最終的に基底に戻るものの、途中では戻りがないパターンである。
画面遷移戻りなし

② 画面遷移して戻ってくる場合
何かをしているときに、一時的にポップアップ表示などをして、さらにその先に進んで、順に戻ってくるパターンである。
画面遷移戻り

ウィザード形式で何かを設定する場合、①のパターンだが、途中で戻って修正可能にするなどした場合、②の要素も
考慮に入れる必要がある。

①の場合、BからC、CからAへの画面の遷移の仕方は以下のようになる。

Intent intent = new Intent(this, ReviewReQForm.class);
startActivity(intent);
finish();


finish()とすることで、そのActivityをスタックに残さないようにする。

②の場合は、BからCへの画面遷移は以下のようになる。

Intent intent = new Intent(QadataListForm.this, QadataForm.class);
startActivityForResult(intent, 0);



またCからBへの戻りは以下のようになる。

Intent intent = new Intent();
Bundle bundle = new Bundle();
intent.putExtras(bundle);
setResult(RESULT_OK, intent);
finish();


戻り値がなければ、finish()だけでよい。

Androidのドキュメントでは、finish()は極力使うなと書いてあるが、こういう画面遷移を明示するためには
必要だと思われる。

背景について説明すると、生成されたActivityは、基底にRoot Activityを置いて、順にスタックに積まれる。
そして、上のActivityがなくなれば、下にあるActivityがポップされて、表示されることになる。
だから、BからCが呼ばれたとき、Cを終了すれば、Bを明示的に呼び出さなくても、Bが表示される。
ここを理解せず、
CからBをIntentを使って呼び出すとどうなるかというと、
A⇒B⇒C⇒Bとなり、Bのインスタンスは重複して積まれることになる。もしこのときCをfinish()していたとすると、
A⇒B⇒Bとなり、Bから戻ってもBが表示されるということになる。

なお、スタックの動作については、Intentで呼び出す際に、Intent#setFlags()メソッドで制御が可能で、
スタック上のActivityを削除したりすることが可能。あるいはマニフェストファイルでandroid:launchModeを指定。
上記の図で、②でかつ、CからAに戻りがある場合、CからAに遷移する際にスタックからBを削除すると、問題はなくなる。

参考:
https://developer.android.com/guide/components/tasks-and-back-stack.html
詳しい仕組みについては以下の方がわかりやすく解説されている。
http://www.usagi1975.com/blog/archives/519#more-519

戻った時の値の反映について
遷移先で値を変更し、その結果を元の画面に反映したい場合、単純に戻るボタンや遷移先でfinish()を呼んだだけでは、
単純にonRestart(), onStart()が呼ばれるだけで、onCreate()は呼ばれないため、データは反映されない。
そのため、データをロードするロジックは共通化し、onActivityResult()の中で、データの反映が必要な場合は、
そのロジックを呼ぶようにして、onCreate()と両方から使えるようにする。
もちろん、全体ではなく部分的な反映であれば、それは共通化しなくてもいいかもしれない。

呼び出し元の判定
同じActivityを複数のActivityから呼び出して使う場合、どのActivityから呼び出されたのかを知りたい場合には、
getCallingActivity()
を使う。ただし名前が取れるだけで参照は取れない。

eitanのダウンロード先はこちら
https://play.google.com/store/apps/details?id=happie.eitan&hl=ja

テーマ : ソフトウェア開発
ジャンル : コンピュータ

Android 開発備忘録 (8) DBその2

前回の対応で、なんとかロジックとビューを別々に切り離して開発できるようになった。

ただ、SQLiteではいろいろと苦労した。

1.参照制約
デフォルトでは外部キー制約が有効にはならないので、
DBをオープンしたら、

db.execSQL("PRAGMA foreign_keys=ON;");

を実行しておく。一度開いている間は有効なのでトランザクションごとに実行しなくてもOK。

ただし、android側はこれでいいが、PC側のJDBCドライバがこれでは効かない。
v3.7.2でもダメ。

SQLiteConfig config = new SQLiteConfig();
config.enforceForeignKeys(true);
Connection conn = DriverManager.getConnection("jdbc:sqlite:", config.toProperties());

でOKとなった。


2.DBが閉じられてしまう。
次のライフサイクルの話とも関連するが、アプリがバックグラウンドから復旧した際に、
閉じられてしまう。
dbのクローズは、Applicationのterminate()に実装してたが、呼ばれることはないので、
クローズしていたのが不思議。dbはnullにはなっていなかったので、Applicationのインスタンスが
削除されたわけではいので、どこでクローズしたか不明。
なので、クエリ発行時に必ず、オープンしているかチェックし、クローズしていた場合に
オープンするようにした。

3.database is locked.
今度は逆に、このエラーが出て困るときがある。
必ずカーソルをとじること。
それからDBアクセスのインスタンスをひとつにして、必ず、それを使うようにすること。
現在まだマルチスレッドで同時更新するような事態にはなっていないが、そうなったときには、
排他制御して同時に1つだけ更新するようにしないといけないかもしれない。

4.更新が遅い
Web上では大量のデータに対して遅いとなっているが、実際1件でも気になるほど遅い。
なので、statementを取っておいて、

SQLiteStatement stmt = stmtMap.get(sql);
if (stmt == null) {
stmt = db.compileStatement(sql)

としようとしたが、これはNG。
Statementをcloseせずにキャッシュしていると、db is lockedとなって次の実行で失敗する。

結局、更新部分をスレッド化することで対処した。

5.副問い合わせ・ジョイン
特にデータ量が多くなると、これらはコストがかかる。
なのでプログレスバーの表示などで、応答がないのをごまかす必要がある。

幸い、不適切な副問い合わせ、不要なジョインだったため、それらを修正すると、7倍速くなった。
それでもPC上で実行すると一瞬で返ってくるのだが。


6.ほか
android.database.sqlite.SQLiteException: not an error:
⇒日本語が含まれるSQLファイルをSJISで保存したのが原因。UTF8にして解決。


eitanのダウンロード先はこちら
https://play.google.com/store/apps/details?id=happie.eitan&hl=ja

テーマ : ソフトウェア開発
ジャンル : コンピュータ

Android 開発備忘録 (7) DB

ビュー側とロジックの分離でうまくいったと思ったら、はまったのがsqlite。

そもそもandroid側で用意しているのは、JDBC Driverではない。
メソッドも、一見使いやすそうでそうでもない、insertやupdateメソッドが用意されている。
PreparedStatementは直接使えないし、パラメータはObject型、結果はすべてStringで受け取る。

最初安直にロジック側にandroid.jarを持ってきて動かしたが、このエラー。

java.lang.RuntimeException: Stub!

そりゃそう、エミュレータや実機上で動くものがWindowsのJVMで動く訳がない。android.jarは
単なるStub!に過ぎず、実装は別。sqliteの接続なんて全部同じだと思っていたが、甘かった。

次に、これまた安直に、JDBCドライバをロジック側に入れて、動かした。当然、ロジック側では
動作する。これをエミュレータで動かそうとすると、

Caused by: java.lang.Error: JVM Specification version: 0.9 is too old. (see org.ibex.util.Platform to add support)

と怒られる。
これを回避するために、

System.setProperty("java.specification.version", "1.6");

と改ざんしたら、通った。しかし、

E/dalvikvm(381): Could not find class 'org.ibex.nestedvm.ClassFileCompiler', referenced from method org.ibex.nestedvm.RuntimeCompiler.runCompiler

などと大量にorg.ibex.nestedvmに関するwarningが出て、時間がかかる。
しかも、jdbcドライバのjarのサイズが巨大なので、インストールに時間がかかる。
実行のたびに、エミュレータにapk全体を転送しているのだからそれは時間がかかる。
しかし、なんとか問題なく動作した。

ただ、これでは目的とは反する。ロジックを単体でテストして効率を上げたのはいいが、ビューの確認のために
ものすごい時間が掛かったのでは元も子もない。

androidに対応したJDBCドライバはないものかと探したが、ない。
そうゆう要望はあって、自分同様に探す人はいたがない。同じ要求を持った人が自らsqldroidというプロジェクトを作って、JDBC互換動作するようにしたのだが、実際入れてみると、単なるandroidのSQLDatabaseのラッパーで
うまく動かない。

結局、ログと同じく、これも自分で自作して、共通のインターフェースを作って、PC用とandroid用の実装クラス
をそれぞれ作った。
基本的に更新文はSQLプラスパラメータで実行し、検索は、引数に渡したクラスのオブジェクトのリストとして
返すようにした。ORマッピングではなく、ROマッピングという類。

ORマッピングに慣れると、いちいちカーソルを回して、オブジェクトにマッピング処理を書くなんてことができなくなってくる。リフレクションやラクダ形式への変換を使えばこの辺は自作するのは全く難しくはない。

そして、ログと同じように、起動時の初期処理の中で、DBUtilの実装クラスをインジェクションする。
ビューワ側での最初のDBの作成には、SQLiteOpenHelperを実装したクラスを使用した。
使うまでもなかったが、今後のアップグレードを考えて、onUpgrade()が自動的に呼び出されるので、
これを使って、テーブルの作成・マスタデータの投入、マイグレーション等をアプリ起動時に行うようにした。

ただ、最終的には、サンプルのデータを最初に大量に入れることにしたので、DBをあらかじめ作っておいて、
初回起動時にコピーするようにしたが。


最終的に、android非依存のロジックを分離することに成功したが、いいことばかりではない。
非同期処理を行なってくれるAsyncTaskなどはandroid側のライブラリにあるため、ロジックに組み入れることは
できず、自前でExecutorsなどを利用して実装することになった。


eitanのダウンロード先はこちら
https://play.google.com/store/apps/details?id=happie.eitan&hl=ja

テーマ : プログラミング
ジャンル : コンピュータ

Android 開発備忘録 (6) ログ

まずはログから。

Androidでは簡単にログを出力する機構がある。
staticなメソッドで、

Log.v("TAG", "message");
Log.d("TAG", "message");
Log.e("TAG", "message");

などと書けば、LogCatでも見れるログを出力でき、これはデバックの際に非常に役立つ。
しかし、これは残念ながらandroidのAPIに含まれるので、ロジック側のプロジェクトでは
使用できない。
ロジック側では単純にsysoutすればいいが、それだとLogCatには出ない。
ロジックの分離で最初に困ったのはこの点である。

解決策として、ログ出力用の共通のインターフェースを作成し、それぞれにロガーを実装することにした。
各クラスに、
public static L log;
をセットし、起動時に初期処理として、ロジック側はJUnitのsetUpの中、ビューワ側はApplicationのonCreate()
から初期化処理を呼び出し、そこで、それぞれのロガーをインジェクトした。
ビューワ側はLog.v()などを呼び出し、ロジック側はSystem.out.println()を呼び出すだけ。
さらに、ビューワ側は、エラーログを残すために、e()メソッドでは、Log.e()の他に、ファイルにエラーログを吐き出すようにした。

問題は、新規クラスを追加するたびに、初期化処理にインジェクションを追加しないといけないことで、これを
忘れて良くNullPoが発生した。
全クラスを走査して、自動的にインジェクションを行えないかと思ったが、パッケージ内クラスを取得するには
jdk6以上のtools.jarが必要であるようで、dalvikでできるかどうかわからないので試してもいない。

次はDB。

eitanのダウンロード先はこちら
https://play.google.com/store/apps/details?id=happie.eitan&hl=ja

テーマ : ソフトウェア開発
ジャンル : コンピュータ

Android 開発備忘録 (5) ロジック単体JUnitテスト

さて、AndroidでUtilやDAOやロジックの単体の開発に入った際、mainメソッドやJUnitから実行しようとする。
サーバーサイドでの開発では、そのスタイルに慣れているので当然Androidでのそのスタイルで行く。
できないエンジニアは、レイア分けが苦手で、ビューにロジックを実装したがる。
Webでも、ロジックはActionに書くなと指示しても書いてしまう。
これは短期的に見ればよくても、規模が大きくなると、かえって開発効率はよくない。

が、ここで詰まる。実行すると、

# A fatal error has been detected by the Java Runtime Environment:
#
# Internal Error (classFileParser.cpp:3075), pid=9260, tid=3292
# Error: ShouldNotReachHere()

となる。
これはAndroidのjarファイルを、Windowsのローカルで実行しようとしたことが原因。
Dalvik VMでなければ動かないので、エミュレータ上でないとダメ。

実行時の構成で、androidをクラスパスに加えなければいいが、実行クラスの中で、
少しでも、android.jarのクラスが含まれていれば、そのロード時にエラーとなる。

なのでロジック・Util関係には、androidのAPIは含まないようにする。
実行時の構成をいちいち変えるのは大変なので、別プロジェクトにして、ビュー側の
プロジェクトから参照するようにする。

ロジック側のプロジェクトは一切androidのAPIは含まれない。実際含むUtilも必要な
部分があるので、そちらはビューワ側のプロジェクトのutilパッケージに入れておく。

こうして切り離せたが、問題があった。

それは、ロジック側で使用しているlibの扱いだ。commons-langとcsv関係のライブラリを
使っていて、ビルドの設定で、それらをExport対象にしていたのだが、ある時は、
実際にapkの生成の際に含まれたり、含まれなかったりするのだ。
これはよくeclipseを再起動したときに起きた。
プロジェクトをクリーンアップしても解決せず、しかたないので、ビューワ側のlibにも
同じjarを入れてビルドする。しかし、そうこうしていると、既にあるのに二重に加えようとする
と怒られる。
libやlibsのjarは勝手に加えられるという話もあるのだが、どうにもよくわからない。
一度成功すると問題ないので、問題が起きたら、ビルドの設定をちょっと変えることで対処。

さて、これでUtilはうまくいったものの、問題はDBとログ。

eitanのダウンロード先はこちら
https://play.google.com/store/apps/details?id=happie.eitan&hl=ja

テーマ : プログラミング
ジャンル : コンピュータ

Android 開発備忘録 (4) 基本的な開発ステップ

さて基本的な環境が揃ったあとで、実際の開発に入る。

設計工程は省略。
DB設計はA5ERを使って実行。ER図を書くのに便利でDDLも吐き出してくれる。
しかしsqliteに対応していないので、postgres用に出力し、
参照制約を加えて、create文を作成。
これはテキストファイルにして、プログラムから読み込めるように、res/rawの下に置いておく。

SQLiteはDBとして、まともに使おうと思うと、これが中々不便である。
列の変更ができないし、制約をあとから追加することもできない。
アプリのアップデートがあることを考えると、これは是非とも欲しいところだが、残念。

列の追加・削除は出来るので、
一時列の追加⇒値のコピー⇒旧列の削除⇒新規列追加⇒値のコピー⇒一時列の削除、あるいは
テーブルの追加⇒値のコピー⇒デーブる削除⇒テーブルのリネーム
という感じで対応するしかなさそう。

パッケージの構成は結果的に以下のようになった。
 common
 dao
 dto
 entity
 logic
 util
 view

規模が大きくなってくると、同じパッケージだと管理が大変なので、さらにサブパッケージを切るか、
プロジェクトを分ける必要がある。
私は、viewの下は、dialogとpartsのパッケージを作った。

開発の基本的な流れは以下のとおり。
1. layout作成
 res/layoutの下に画面レイアウト用のxmlを作成
 eclipseでNew - Android XML Fileで作成してもいいが、すでにあるものをコピペでもいい。
 ファイル名はActivityと関連させる。

 Graphical LayoutでGUIでレイアウトを作成してもいいが、試行錯誤していると、xmlが
 ぐちゃぐちゃになるし、動的要素は表現しきれないので、最終的にはxml直の編集が必要。
 このへんの事情は、Flex BuilderやNetBeansと同じ。

 xmlを使わずにJavaでガリガリ書くこともできるが、xmlの方が入れ子構造が一目瞭然なので
 動的に項目が増えないところが極力xmlで書くようにする。

2. string.xmlの編集
 国際化対応のため、画面に表示する文言やメッセージは、リソースファイルに記述する。
 英語は、res/values/string.xmlに、日本語はres/values-ja/string.xmlに記述。


3. Activityの作成
 onCreate等を実装。詳細は別途。
 イベントリスナは、基本的に無名クラスとして、イベント発生のコントロールにsetXXXListenerの形で
 実装する。
 リスナは、独立したクラスとして作成したり、インスタンスメソッドとして実装できるが、
 無名クラスでやったほうが、イベント発生元と結びついててわかりやすい。ただし、状況に応じて
 そうしない方がいい場合もある。

 ちなみにアプリの起動は、Activityではなく、Applicationを継承したクラスから行い、そこで初期処理等
 をしてから、Activityを呼び出すようにする。

 (このあとのサンプルでActivity名にFormとついているがご了承あれ。VBの痕跡を引きずっています。)

4. AndroidManifest.xmlへの登録
 Activityをここに入れないと実行時にエラーとなる。珍しく、親切にもエラーメッセージで教えてくれる。
 いちいち書くのは面倒なので、アノテーションによる自動登録にでもしてほしいところ。

5. ロジックの実装
 ビューに関係ないロジックは、極力Activityから切り離すようにする。
 DBアクセスやファイルアクセス、文字列操作等共通的なものはutilパッケージの下に置く。
 DBにアクセスする部分は、テーブル単位でDAOを作成する。
 またテーブルの構造に沿ってEntityを作成する。Seasar方式でメンバーはpublicでgetter/setterはつくらない。

 常に問題になるのは、複数テーブル間にまたがる場合どうするかだが、重きが置かれている方に実装する。
 基本的に、Utilはstaticメソッド、DAOはシングルトンで作成。
 DB以外のロジックや状態保持が必要なものは、logicパッケージ以下に作成。

 Entity以外のデータオブジェクトは、dtoとして作成。

これで、各レイヤーの責務が明確になったところで、ビューとロジックを切り離して開発。
上記の順序ではなく、ロジックから開発することも多々ある。

が、ロジック単体の開発で躓くことになる。

eitanのダウンロード先はこちら
https://play.google.com/store/apps/details?id=happie.eitan&hl=ja

テーマ : プログラミング
ジャンル : コンピュータ

Android 開発備忘録 (3) 環境構築

前回に続いて、今回からは私が行なった開発作業について書き連ねる。

Androidの基本的な開発方法については詳細は省略。

必要なのは、
1. SDKインストール
 必要なAPIをアップデートインストール
 AVD(Android Virtual Device)の作成および起動確認
 コマンドプロンプトにadb.exeへのパスを通す。AVDへの接続確認。

2. eclipse&androidプラグインインストール
 SDK Locationの設定

3.プロジェクトの作成
 Android Projectを選択して作成。
  MainのActivityは作成しておく。
 Projectで右クリックし、Run-Android Applicationを選択し、ついで
 エミュレータを選択して実行して、Hello, Worldが表示されればOK。
 libディレクトリの作成
 resの下に以下のディレクトリを作成しておく
  raw
  raw-ja
  values-ja
  xml
 これらは実際のリソースの追加時に作成されるのでそれでもよい。

4. テスト用の実機の設定
 USBデバッグを可能にすること
 USBで接続し、eclipseからの実行時に、Android Device Chooserに表示されること。
 adbでの接続、LogCatの表示、DDMSでの表示ができること。 

あとオプション的に、
4. sqlite expert
 http://www.sqliteexpert.com/download.html
 sqliteのクライアントとしてはこれが一番使いやすい。

eitanのダウンロード先はこちら
https://play.google.com/store/apps/details?id=happie.eitan&hl=ja

テーマ : ソフトウェア開発
ジャンル : コンピュータ

Android開発備忘録(2) 他の開発との比較

Android OS自体のバージョンは最新で4.1で SDKのAPIのバージョンは16で、
私が開発を始めた頃に比べるとかなりバージョンが上がっているものの、
開発フレームワークというのはまだ確立されていないように思える。

そのため、断片的な情報は多いが、どうするのがベストプラクティスかというのは
まとまっていない。
個々の開発者任せなのか、それなりの開発を行なっている会社は自社にノウハウを
溜め込んでいるのか、多分どちらもだと思うが、このへんはGoogleが主導して、
提供して欲しいなと思う。

Googleという会社はつくづく、「優秀な」技術者が中心の会社だな、と感じる。
某フレームワークのように、開発者に対する「やさしさ」が欠如している。
開発のしやすさからして、MSのVisual Studioには全く及ばない。

Seasarのコミュニティが昔のように活発であれば、すぐにS2なんとかで、使いやすいものが
出てきただろうなと思うが、今は廃れてしまっているのが残念。
まあ、Dalvikという特殊なVMでバイトコードを操作できなければ、最大の利点であるAOPは無理
かもしれないが、何か便利にすることぐらいはできるはず。
GuiceもAOPなしでということらしいし。

Androidで苦労するのは、主にビューの部分。
エミュレータは結構重く、ちょっとしたビューの修正確認のためにも時間を要する。
そのほか次回以降で述べるインスタンスの管理等厄介なところがたくさんある。

Android開発(Java)と他の私が主に経験してきた開発言語・手法と比較した。

1. Webアプリケーション(サーバサイド中心)
ビューの部分はhtml、サーバサイドは、struts等MVCモデルやアーキテクチャーパターンで
各レイヤの役割分担が明確で、DI ContainerやORM等を使うのが割と標準的になっている。
ビューの部分はhtmlなので、最初はデザイナとの分業が可能。ビューのみの修正であれば、
ブラウザのF5ボタンで修正確認がすぐにできる。
ビューとロジックの分離をきちんと行なってれば、JUnitを使用してロジックの部分のみを
テストでき、開発効率が上がる。


2. Webアプリケーション(js中心・Flex)
ビューの部分は1と同じ。
ただロジックがjsに移り、サーバサイドの処理をまたなくていいため、その分、ユーザ
エクスペリエンスは上がる。サーバサイドはAPIの形での実装となり、単体での試験は
しやすい。jsはスクリプト言語なので、確認のためのビルドの必要はないが、デバッグは
やりにくい。
ビューにFlexを使った場合も同様。ただこちらはビューの確認にビルドが必要。


3. デスクトップアプリケーション(Swing)
ビューの部分は、Javaのコードになる。なので確認にビルドは必要。
ただし、エミュレータは不要なので、その分の時間はandroidよりも短い。


ビューの部分は、htmlであるのが一番確認しやすい。AndroidもFlexもSwingもデザイナが
いまいちなので、そのへんはVisual Studioには及ばない。
ただ、確認のために、いちいちエミュレータを立ち上げる時間を考えると、Androidが
一番開発効率が悪い。

PhoneGapのように、ビューやビュー関連ロジックをHTML5で開発し、バックエンドのメソッドを
呼び出すようにするのが、Androidでは一番開発効率はいいかもしれない。

今回は、とりあえず、通常のAndroidの開発で進めた。

eitanのダウンロード先はこちら
https://play.google.com/store/apps/details?id=happie.eitan&hl=ja

テーマ : プログラミング
ジャンル : コンピュータ

Android 開発備忘録 (1)

eitanアプリを今回アップデートすることにした。

自分で使っていていくつか不具合や不都合な点があったので直すことにした。
元々携帯版でJ2MEで使っていたコードをかなり流用していたこともあり、
修正は全般にわたり、時間が掛かった。

前のバージョンはCSVを使って、DBを使わず、定期的にCSVを吐き出すようにして
いたが、それでは限界がきて、というか、なぜかたまに改行が正しく出力されない
ことがあり、それでどんどんデータがおかしくなってしまうのだ。

Android特有のバックグラウンドに移行したアプリは容赦なく、強制的にメモリを
解放してしまうのが関係していそうなのだが、原因を追求することはやめて、
元々予定していたDBへの移行を行うことにした。

ある程度仕組みは理解していたつもりだったの、数日でさっと終わると思ったが、
結構はまりどころが多く、Android特有の部分もあって、調べながら行うと、
結局週末の空き時間を利用しながら、1カ月半近くに渡った。

これまでは小手先でとにかく動けばいいということでちょこちょこやっていたが、
今回もう少ししっかりやってみた。いろいろ勉強になったが、忘れないように、
備忘録として残しておこうと思う。

こういう技術ノウハウというのは同じ環境を使用している限りはあとでも役に立つ。
ただ、特定のプラットフォームの上で遊んでいる以上、そのプラットフォームが
変わってしまった場合、全く役に立たなくなる。数年前の常識は通用せず、
散々はまった中で中身に精通して自分が掴んだノウハウは、プラットフォームが
改善されれば不要となる。

とはいえ、しばらくは役立つと思うので残しておこうと思う。

一連の記事の中で間違えを見つけた人は遠慮なく指摘願います。

eitanのダウンロード先はこちら
https://play.google.com/store/apps/details?id=happie.eitan&hl=ja

テーマ : プログラミング
ジャンル : コンピュータ

プロフィール

dayan

Author:dayan
小職は、SE(システムエンジニア)を専門としておりますが、技術的な情報を中心に、それ以外に経済関連の日記、たわいもない日記も載せていきます。
[公式HPもよろしく!]

天気予報

-天気予報コム- -FC2-
リンク
ブロとも申請フォーム

この人とブロともになる

カテゴリー
最近の記事
ブログ内検索
最近のコメント
最近のトラックバック
RSSフィード
月別アーカイブ


上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。