スポンサーサイト

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

Android開発においての設計・開発の注意点

AndroidアプリケーションはJavaで記述します。
Javaは非常に柔軟なプログラミングが可能な言語で、登場した当初はガベージコレクションの際に一時停止する遅い言語というイメージでしたが、今やHotSpotを利用するとC/C++よりも速くなる場合があるという、登場当初からは想像もつかなかった言語に成長しています。

前振りはさておき、そんなJavaという言語を利用してAndroidではアプリケーションを開発しますが、AndroidのJavaは他のプラットフォームにおけるJavaとは大きく異なる点があります。


01. static変数はキャッシュ以外の目的で利用してはいけない
static変数はアプリケーションが起動してから終了するまで、
ずっと保持され続ける値というイメージがあるかと思います。
これを利用して、例えば予め重い画像処理においてカラーテーブルを作成して、
それをstatic変数に保持しておくといった手法はよく使われます。
そのため、Androidにおいてもこのような処理をついやってしまいがちな方が多いのですが、
実はこれが大きな落とし穴なのです。
Androidにおいては、端末のメモリが少なくなるとアプリで利用されているstatic変数がすべてクリアされてしまいます。

特にこれが危険なのが、Object型変数をstatic変数にしていた場合です。
Object型のstatic変数がクリアされた場合、その値は null となります。
今までのJavaの常識から考えると、一度値を設定したstatic変数の値はアプリが終了するまでずっと変わらないため、nullチェックを行わずにアクセスするようにプログラミングしてしまいがちですが、
これが元で、なんだかよくわからないけどNullPointerExceptionが発生するAndroidアプリケーションが出来上がってしまうのです。

この問題に対する対策としては、static変数をキャッシュ以外の目的で利用しないことです。
つまり、static変数はいつクリアされてもおかしくないという前提でプログラミングする必要があります。
もし従来のstatic変数と同様に、ずっと値を保持し続けておきたいのであれば、
プリファレンス(SharedPreferencesなど)を使いましょう。

また、手っ取り早くこの現象を再現したいのであれば
Task Killerアプリを使うことです。
端末によって出来たり出来なかったりしますが、以下の手順で再現できます。


  1. アプリ起動中にホームボタンを押下する
  2. Task Killerアプリを起動し、1で起動していたアプリだけをKillする
  3. ホームボタンを長押しして、起動中アプリ一覧を表示すると、1のアプリが表示されているのでこれを選択する
  4. static変数がクリアされた状態で、1のアプリが再開する


Android利用者には比較的Task Killerアプリがインストールしている方が多いため、
static変数の利用を前提とした設計で作られたアプリケーションがTask Killされてしまうと
何度もエラーで落ちてしまう可能性が高くなります。

このような現象を発生させないためにも
static変数を利用する際は、キャッシュ以外の目的で利用しないようにしましょう。


02. Activityのメンバ変数は突然クリアされる場合がある
実は端末のメモリが少なくなると、static変数の他にActivityのメンバ変数までAndroidはクリアしてしまいます。
端末のメモリが少なくなると、現在のアプリケーションの一部領域を残してすべてクリアしてしまうという話は、
もしかしたらみなさんも一度は聞いたことがあるかもしれません。
ただ、このActivityのメンバ変数が突然クリアされてしまうということを意識してプログラミングをしている人がどれほどいるのかというと、私の周りには殆どいませんでした。
これを意識してプログラミングをしないと、01でも述べたように「なんだかよくわからないけどNullPointerExceptionが発生するアプリ」を作りだしてしまうのです。

特にこの現象が発生しやすいのが、アプリ内からインテントでカメラアプリを起動して戻ってきた場合です。
カメラアプリはメモリを非常に多く使うため、カメラアプリで端末の殆どのメモリを利用してしまう傾向があります。
このとき、カメラアプリ起動元アプリへ処理が戻ったときに、ActivityにObject型メンバ変数が定義されているものなら、クリアされたメンバ変数へアクセスしてNullPointerExceptionが発生してしまうわけです。
カメラアプリを使わない場合でも、裏でどのようなアプリが動いているかは端末を利用しているユーザですらわかりませんので、いつメモリが不足してActivityのメンバ変数がクリアされてしまってもいいように作るしかないわけです。


では、どうすればこれを回避できるのかというと、
ActivityにあるonSaveInstanceStateメソッドとonRestoreInstanceStateメソッドをオーバーライドして利用することで回避できます。

端末がメモリ不足になると、Androidはメモリ内にあるアプリケーションの各アクティビティに対して
onSaveInstanceStateメソッドをコールします。
この onSaveInstanceState メソッドは Bundle 型の引数をもっており、
このBundle型の変数に保存された値は、Activityのメンバ変数やstatic変数がクリアされた場合でも
ずっとメモリに保持され続けるという特徴があります。
そして、アプリケーションが再度アクティブになったときに、onRestoreInstanceStateメソッドがコールされます。
この onRestoreInstanceState メソッドも Bundle 型の引数をもっており、
onSaveInstanceStateで保存された情報がそのまま与えられますので、
このメソッド内で、クリアされたActivityのメンバ変数情報を復元することが可能になるわけです。

以下に例を示します。



/**
* テスト用アクティビティ。
*
* @author Kou
*
*/
public class TestActivity extends Activity {


/**
* テスト日付データ
*/
private Date testDate;


@Override
protected void onActivityResult(
final int requestCode,
final int resultCode,
final Intent data
) {

long time = testDate.getTime(); // ←メモリ不足になった場合に、testDateが null になってしまう場合がある

}

}




/**
* テスト用アクティビティ。
*
* @author Kou
*
*/
public class TestActivity extends Activity {


/**
* テスト日付データ
*/
private Date testDate;



/**
* メンバ変数の保存処理を行う。
*
* @param outState 保存するメンバ変数の保存先 Bundle データ
*/
@Override
protected void onSaveInstanceState(
final Bundle outState
) {

// 日付を保存する
outState.putLong("testDate", testDate.getTime());

}


/**
* メンバ変数の復帰処理を行う。
*
* {@link #onSaveInstanceState(Bundle)} メソッドで保存されたメンバ変数の
* 復帰処理を行う。
*
* @param savedInstanceState 保存されたメンバ変数を格納した Bundle データ
*/
@Override
protected void onRestoreInstanceState(
final Bundle savedInstanceState
) {

// 日付を復元する
testDate = new Date(savedInstanceState.getLong("testDate"));

}


@Override
protected void onActivityResult(
final int requestCode,
final int resultCode,
final Intent data
) {

long time = testDate.getTime(); // ←メモリ不足になった場合でも、testDateが null にならない!

}

}



このように、Androidにおいては突然メモリがクリアされることを想定して
プログラミングをしなければならないのですが、
「Javaを知っていればなんとかなる」と前提知識を持たないまま参入する方が多かったり、先駆者たちもこの問題に対してはそれほど興味を持っていないのか各Webサイトでも取り上げなかったり、そもそもGoogle自体がこれほど通常のJavaとは異なるポイントを大々的に説明していないという点が重なったりと、結果として、不具合の多いアプリケーションがマーケットにたくさん溢れてしまう結果となっています。


とはいえ、「理屈はわかったが実際に組むとなると非常に大変」という局面も抱えています。次回は、どのような変数がクリアされ、またどのような変数がクリアされないのかについて詳しく説明していきたいと思います。
スポンサーサイト
プロフィール

Kou

Author:Kou
モバイル関連の開発ばかりやってる人のブログです。たまにWebもやります。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

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