スポンサーサイト

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

[Android] Eclipse で外部 jar ファイルをプロジェクトから参照する方法

他社の作成した .jar ファイルを、自分のjavaプロジェクトから参照するための設定。
いつも忘れてしまうので、ここにメモしておく。

1) 自プロジェクトのプロパティを開く。
2) プロパティ画面左側ペインで、「Java Build Path」を選択する。
3) 右側ペインの上部のタブ(?)で「Libraries」を選択する。
4) 右側の「Add JARs」ボタンを押して、.jar ファイルを指定する。

これでOK。
スポンサーサイト

[Android] オブジェクトの粒度について

最近、同僚の書いた Android アプリケーションのコードを見て、こちらの記事を書いた。

もっと書きたくなってしまったので、本来は備忘録だったはずのこのブログに、さらに書いておくことにしてみる。



彼自信は自身のコードにご満悦の様子なのだが、そのコードを読んでみたら、ついついダメ出しをしたくなってしまった次第だ。

メインの Activity のコードが 2000行を超えており、その他の .java が10本ほど。
その他の .java は、アダプタ派生クラスなど、ちょっとしたもので構成されており、それぞれ10行程度から多くても100行程度。
という構成だった。

たしかに、彼の作ったアプリケーションは、メインActivityでタブを切り替えて使うタイプのもので、メインActivityに処理が集中しがちなのは認める。

だが、集中したその処理を全てメインActivityの中だけで処理して、満足げな顔をしていてはダメだ。

全然ダメだ。





例えば、それなりに多い情報を ListView で表示しているのだが、単にアダプタの派生クラスを作ってそれでおしまいにしている。

当然ながら、アダプタでやっていることは、本来アダプタでやることだけだ。
つまり、独自データ構造から、ListView行用のViewに配置された各GUIパーツにデータを渡すという作業だ。

それは全然いいのだが。

1行あたり、結構な情報量のリストなわけなのに、1行あたりのデータの管理を、全部メインActivityでやっている。

これが、ダメだ。

もっと小さいアプリケーションなら、問題ないと思う。
だが、メイン2000行 に対して、そのアダプタが10から100行程度ではいけない。

ここはひとつ、そのListViewが司るデータ全般を、ListView自身にまかせるべきだろう。

幸いなことに、というか、極めて当然なことに、Android Java では標準のListViewから独自クラスを派生させて、それを、.xml に記述できる。






つまりこういうことだ。
***** MyHogeListView.jaba *****

public MyHogeListView extends ListView {
public void initializeListView(Activity activity, ArrayList<重要データ> hoge) {
MyHogeAdapter adaper = new MyHogeAdapter(activity);
...アダプタへのデータ追加...
}
public void hogeHogeHoge() {
...メインActivityから、何らかの状況で呼ばれて、何らかの処理をする...
}
public void fugaFugaFuga() {
...メインActivityから、何らかの状況で呼ばれて、何らかの処理をする...
}
}
class MyHogeAdapter extends ListAdapter<重要データ> {
...一般的なアダプタのあれこれをする...
}
// ひょっとすると、Javaでは非staticな内部クラスとして MyHogeAdapter を定義できたかもしれないが、ここではそれは論じない



Adapter だけではできなかった(又は、できたとしても、美しくできなかった)諸々のことを、ListView 派生クラスを作ることによって、全てカプセル化するのだ。

.xmlにも、このカスタムクラスを直接記述できる。
たとえば、

<jp.co.hoge.hogeapplication.MyHogeListView attrib="hoge">
</jp.co.hoge.hogeapplication.MyHogeListView>

といった具合に。



カプセル化って何?とか言う人もいるかもしれないが、オブジェクト指向の重要な要素のひとつ(のハズ)だ。

Androidプログラムの規模だと、この程度のオブジェクトの粒度がちょうど良いという気がしている。(将来は分からないが)




さらに言うと、上記のようなリストがいくつかあって、それぞれ同じ ArrayList<重要データ> を扱いながらも、結構違った表現でエンドユーザーに表示する、といったパターンもあるだろう。

その場合は、迷わず共通基底クラスを用意することを考えるべきだ。

つまりこういうことだ。

public MyHogeListBaseView extends ListView {
...各派生クラスでの共通変数を持つ...
...各派生クラスでの共通メソッドを持つ...
}


public MyHogeListBaseViewSuper extends MyHogeListBaseView {
...このクラス独自で行うメソッドを持つ...
...なんだったら、このクラス独自で持っておきたい変数を持つ...
public void onCreate(...) {
このクラス用のListViewに対してArrayList<重要データ>を使って初期化する。
}
}


public MyHogeListBaseViewUltra extends MyHogeListBaseView {
...このクラス独自で行うメソッドを持つ...
...なんだったら、このクラス独自で持っておきたい変数を持つ...
public void onCreate(...) {
このクラス用のListViewに対してArrayList<重要データ>を使って初期化する。
}
}






ListView 派生クラスに限ったことではなく、Activity でもそうだ。

例えば、とあるアプリケーションで、全ての画面に「終了」ボタンがあるとしたら、迷わず共通基底クラスを作るべきだ。


public MyBaseActivity extends Activity {
@Override
public void onCreate() {
super.onCreate();
...終了ボタンのクリックリスナー登録...
}
public void クリックリスナー() {
...アプリケーション終了処理...
}
}


public MyOtherHogeActivity extends MyBaseActivity {
...このActivity固有の処理...(終了ボタンの処理はいらない)
}


public MyOtherFugaActivity extends MyBaseActivity {
...このActivity固有の処理...(終了ボタンの処理はいらない)
}





このことも、オブジェクトの粒度を適切に保つために有効な手段である。

[Android] 変数名の付け方

初めて、自分用のメモではないことを書こうと思った。


Android Java の変数名の付け方についてだ。
特に、クラスのメンバ変数。

最近、同僚の書いたAndroidのコードを見たのだが、メンバ変数は
  m_hogeHoge
などとなっていた。

また、ネットで見かける(特にブログで見かける)コード片では、単に
  hogeHoge
だったり、
  hogeHoge_
だったり、
  hoge_hoge
する。

この人たちはGoogleのコードを見たことがないのだろうか?

いや、そんなことはあるまい。
少なくともSDKのサンプルぐらいは見ているはずだ。

それにも関わらず、我流を通そうという気持ちなのだろう。

私の同僚氏は、C++/MFC での開発ばかりをやっていたため、
  m_hogeHoge
と、やりたいのだろうし、ブログの人たちはまた別の過去の経験からそうしているのだろう。
ひょっとすると、そもそもまだ自分のスタイルすらできていないのかもしれない。

だが、新しいジャンルのコードを書こうと思った時に、まずは先人のスタイルにそって書いてみようと、何故思わないのだろうか?

(まあ、中には、おかしなリーダーのせいで、おかしなコーディング規約を押し付けられ続けてきた反発のためにそうなってしまっている人もいるのかもしれないが)


MFCならMFCの、Android Java なら Android Java の、iPhone Objective-C なら iPhone Objective-C の書き方がそれなりにあるわけなので、まずは素直にそれに倣ってみたらよかろうに。

読む側の気持ちとか考えてないんだろうなあ。

新しいジャンルを自分で開拓する立場にあって初めて、そこでやってみたら良いと思う。

相当レベルまで極めてみて、それでもやはり先人のスタイルより自分のスタイルの方が良いと思えれば、敢えて別の書き方をするのも良いだろうが、私の経験上、この種のことで我流を通そうとする人のコードに限って、構造上極めて汚いものが多い。







話はちょっと変わるが、ハンガリー表記法についても、ついでに語っておこう。

私は実はハンガリー表記法は好きだ。
(と言っても、C/C++やVBに限った話で、Android Java では Google スタイルにしているのだ)


ハンガリー表記法を激しく嫌っている人たちが大昔からいるのは知っている。

彼らの反論には、実は積極的には耳を傾けてこなかったのだが、それでも、一番良く耳にしたのが、
  変数の型が変わった時に、変数名まで変えなければいけないから、大変なのだ。
というものだった。

強い型付けがされているコンパイル言語である C/C++ でそんなことを言うのはどうかと思う。

型が変わったら、どのみち全ての参照箇所を変える必要があるだろうに。
まさかキャストしたりとかバカなことをしているのだろうか。
(キャストは禁止。どうしてもやむを得ない場合のみキャストをするようにすべし)

嬉しいことに、型が変わったらコンパイラがエラー(又は警告)で教えてくれるのだから、全て直すのはたやすいことだと思うのだ。

私の経験上、広範囲に渡って参照されるような型が変わるようなことはほとんどないのだが、そもそも、システム全体に関わるような重要な変数なり定数なりの型が変わるような状況自体を深刻にとらえる必要があるわけで、変数名を書き換えるとか瑣末なこともって、ハンガリー表記法を攻撃する理由にするのはおかしいと思う。


若干説得力のあった理由としては、自然言語上の問題だ。

例えば変数を日本語で書けるC言語があったとして、ハンガリー表記法っぽく書くと、
  int 数(値)合計金額 = 1234;
とか、
  char* 文(字列)氏名 = "ほげ山ほげ夫";
などということになる。

これが非常に気持ちが悪い、という意見を耳にしたことがある。
その意見は、英語を母語とする人の意見だったので、上記の例は英語に置き換えて考えなければならない。

たしかに英語が母語だと気持ちが悪いかもしれないなあ、とは思う。

特に、プログラミング言語の予約語のほとんどが英語だから、上記の例を全て日本語にすると、
  数値 数(値)合計金額 = 1234;
とか、
  文字列* 文(字列)氏名 = "ほげ山ほげ夫";
という感じに見えるのだろう。


ただ、十分に英語が堪能だとは言えない人にとっては、プレフィックスも本来の変数名の部分も単なる記号にすぎないと思うのだ。
(本来の自然言語自体も記号に過ぎないわけだが、英語の人にとっては、あまりにも不自然に見えるわけだろう)


ASCII文字が少なすぎるからいけないのかもしれない。

変数の型を示すのに、特別な文字コードを割りあえてて、例えば、
int ω合計金額 = 1234;
とか、
  char* ν氏名 = "ほげ山ほげ夫";
としたら、ひょっとすると、英語を母語とする人にも違和感がないのではないか?
などと思うのだが。
(ちなみに、ここで使っているωとかνは、今までにない、プログラム専用の文字だと想像してほしい)

もっと言えば、どうせプログラミング言語の予約語など、大してないのだから、
ω ω合計金額 = 1234;
とか、
  ν ν氏名 = "ほげ山ほげ夫";
でもいいぐらいだ。
それが十分に広まってくれさえすれば、何でもいい。



まあ、そんなことにはならないだろうし、私も別にハンガリー表記法をゴリ押しするつもりは毛頭ないのだ。

ただ、変数名(シンボル全般)について言えば、先ほどのAndroid Javaと同様、広く知れ渡った既存のコードから学ぶことなく我流を通そうという人に対してイラっとしたので、ついつい書いてしまった。



[Android] アクティビティの状態遷移の実験 その2

Android アクティビティの状態遷移について、再度実験を行った。
前回と同様の実験なのだが、コンストラクタがどのように呼ばれるのか、についての調査を加えてある。
さらに、呼び出し元のActivityのステイタス変化についても調べた。

Android の Activity には、以下の7つの状態が存在し、状態の変化が起こった場合にはそれぞれ対応するメソッドが呼ばれる。

1) onCreate
2) onRestart
3) onStart
4) onResume
5) onPause
6) onStop
7) onDestroy

以下のようなコードを書いて、実際のところ、どのような状況でどのようにステイタスが変化するのかを調べる実験を行った。

package jp.android.TestActivityStatus;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;

public class DoTestActivity extends Activity {

static final String TAG = "DoTestActivity";
String mParam;

public DoTestActivity() {
super();
Log.i(TAG, "*** constructed ***");
}

@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.do_test);

// 呼び出し元から渡されたパラメータを取り出す
Bundle extras = getIntent().getExtras();
if (extras != null) {
mParam = (String)extras.getSerializable("Param");
if (mParam != null) {
Log.d(TAG, "Param を取得できた");
} else {
Log.d(TAG, "ScheduleSetting が空だ!");
}
} else {
Log.d(TAG, "getExtras()がnullを返した!");
}
}

@Override
public void onStart() {
Log.i(TAG, "onStart");
super.onStart();
}

@Override
public void onResume() {
Log.i(TAG, "onResume");
super.onResume();
}

@Override
public void onPause() {
Log.i(TAG, "onPause");
super.onPause();
}

@Override
public void onStop() {
Log.i(TAG, "onStop");
super.onStop();
}

@Override
public void onDestroy() {
Log.i(TAG, "onDestroy");
super.onDestroy();
}

@Override
public void onRestart() {
Log.i(TAG, "onRestart");
super.onRestart();
}
}


このインテントを起動(?)する側は、以下のように、putExtra() によってパラメータ(?)を与えるようにしている。
呼び出された方のアクティビティで、そのパラメータがきちんと取得できるかどうか、という点も、実験によって明らかにしたかったのだ。

Intent intent = new Intent(getApplicationContext(), ActivityStatusActivity.class);
intent.putExtra("Param", "ほげほげ");
startActivityForResult(intent, 1);




■実験結果

実験結果は以下のとおりだった。


1) 最初にアクティビティが表示された時

INFO/DoTestActivity(1309): *** constructed ***
INFO/DoTestActivity(1309): onCreate
DEBUG/DoTestActivity(1309): Param を取得できた
INFO/DoTestActivity(1309): onStart
INFO/DoTestActivity(1309): onResume


2) その状態で、携帯端末を90度回転させた時

INFO/DoTestActivity(1309): onPause
INFO/DoTestActivity(1309): onStop
INFO/DoTestActivity(1309): onDestroy
INFO/DoTestActivity(1309): *** constructed ***
INFO/DoTestActivity(1309): onCreate
DEBUG/DoTestActivity(1309): Param を取得できた
INFO/DoTestActivity(1309): onStart
INFO/DoTestActivity(1309): onResume

このように、回転させるとなんと、一旦アクティビティは Destroy されることが分かった!!
また、Destroyされているにもかかわらず、呼び出し側がセットしていたパラメータが再度取得可能な状態であった点には驚いた。


3) アクティビティ表示状態から、[戻る]ボタン(ハードウェアのボタン)を押した時

INFO/DoTestActivity(1309): onPause
INFO/呼び出し元のActivity(1309): onDestroy
INFO/呼び出し元のActivity(1309): *** constructed ***
INFO/呼び出し元のActivity(1309): onCreate
INFO/呼び出し元のActivity(1309): onStart
INFO/呼び出し元のActivity(1309): onResume
INFO/DoTestActivity(1309): onStop
INFO/DoTestActivity(1309): onDestroy

このように、[戻る]ボタンを押した場合は、Activity#onDestroy() が呼ばれることが見て取れる。
呼び出し元のActivityが、一旦Destroyされた後で再構築される点が意外だ。


4) アクティビティ表示状態から、[ホーム]ボタン(ハードウェアのボタン)を押した時

INFO/DoTestActivity(1309): onPause
INFO/DoTestActivity(1309): onStop

このように、[ホーム]ボタンを押した場合は、Destroyされないことが見て取れる。


5) 上記 4) の状態から、再度アプリを起動した時

この場合は2つのパターンが見られた。

a. パターンその1
INFO/DoTestActivity(1309): onDestroy
INFO/DoTestActivity(1309): *** constructed ***
INFO/DoTestActivity(1309): onCreate
DEBUG/DoTestActivity(1309): Param を取得できた
INFO/DoTestActivity(1309): onStart
INFO/DoTestActivity(1309): onResume

b.パターンその2
INFO/DoTestActivity(1376): onRestart
INFO/DoTestActivity(1376): onStart
INFO/DoTestActivity(1376): onResume

パターンその1ではActivityが再構築されているが、パターンその2では単にリスタートされている。
システムのメモリ使用状況等によって変わるのかもしれない。


6) 自動消灯

一定時間放置しておいて、スクリーンが自動消灯した場合にどうなるかを見てみた。

INFO/DoTestActivity(1309): onPause


7) 自動消灯した状態から、点灯

上記 6) の状態から、電源ボタンを押した場合、つまり、消灯状態から復帰した場合にどうなるかを見てみた。

INFO/DoTestActivity(1309): onResume


8) 点灯後に、ロック解除

上記 7) の操作を行うと、ロック解除画面になるが(ロック設定している場合)、ロック解除を行うとどうなるかを調べた。
この場合、特にステイタスに変化はなかった。
つまり、上記 7) の操作で onResume が呼ばれた後で、変化はなかった。



[Android] 他のアプリケーションの Preferences にアクセスする方法

Androidで他のアプリケーションの Preferences にアクセスする方法。

以下のようにして、エミュレータに標準で入っていたアプリの Preferences を読み込んでみた。



package jp.android;

import java.util.Map;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.util.Log;

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

PrefInfo[] prefInfoArray = new PrefInfo[] {
new PrefInfo("com.android.browser", "com.android.browser_preferences"),
new PrefInfo("com.android.contacts", "dialtacts"),
new PrefInfo("com.android.mms", "com.android.mms_preferences"),
new PrefInfo("com.android.phone", "_has_set_default_values"),
new PrefInfo("com.android.providers.contacts", "com.android.providers.contacts_preferences"),
new PrefInfo("com.android.providers.telephony", "preferred-apn"),
new PrefInfo("com.android.settings", "ManageAppsInfo.prefs"),
};

for (PrefInfo prefInfo : prefInfoArray) {
peepAnotherPreferences(prefInfo);
}
}

private boolean peepAnotherPreferences(PrefInfo prefInfo) {
try {
Context context = createPackageContext(prefInfo.getPackageName(), CONTEXT_IGNORE_SECURITY);
SharedPreferences pref = context.getSharedPreferences(prefInfo.getPrefName(), MODE_WORLD_READABLE);
Map<String, ?> map = pref.getAll();
for (String key : map.keySet()) {
Object value = map.get(key);
if (value != null) {
Log.d("hoge", String.format("[%s]%s=%s", prefInfo.getPackageName(), key, value.toString()));
} else {
Log.d("hoge", String.format("[%s]%s=null", prefInfo.getPackageName(), key));
}
}
if (0 == map.size()) {
Log.d("hoge", String.format("[%s] --- empty", prefInfo.getPackageName()));
}
} catch (NameNotFoundException e) {
e.printStackTrace();
return false;
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}

}

class PrefInfo {

String mPackageName;
String mPrefName;

public PrefInfo(String packageName, String prefName) {
mPackageName = packageName;
mPrefName = prefName;
}

public String getPackageName() {
return mPackageName;
}
public String getPrefName() {
return mPrefName;
}
}



ただ、どのアプリも、外部からのアクセスを許可していないため、全て読み込み不能であった。
残念。

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