• 検索結果がありません。

Android実践プログラミング 現場で生まれた設計パターン

N/A
N/A
Protected

Academic year: 2021

シェア "Android実践プログラミング 現場で生まれた設計パターン"

Copied!
154
0
0

読み込み中.... (全文を見る)

全文

(1)

生まれた設計パターン

TechBooster

 著

(2)

Android

実践プログラミング ∼現場で生まれた設計パターン∼を手に取っていただき、あ りがとうございます。本書は

Android

開発における設計上の課題をいかに解決してきたか、 という現場の知恵と工夫をあつめています。仕様を検討し、設計し、コードで実践するまでの 考え方が詰まった一冊です。

Android

における設計パターンの一例として、参考になれば幸い です。

(

発行代表

@mhidaka)

本書の内容について

アプリ開発で出会った困ったコード

15

設計ガイドラインの思想と効果的な実装

• Gradle

プロジェクト設定の逆引きリファレンス

気になる周辺技術

Docker

の今と未来を解説

TechBooster

とは

TechBooster

Android

をはじめとしたモバイルのための技術サークル*1です。オープン ソースへの貢献や社会還元を目的にサイトでモバイル技術を解説しています。

お問い合わせ先

本書に関するお問い合わせ

https://plus.google.com/+TechboosterOrg/

(3)

はじめに

1

本書の内容について

. . . .

1

TechBooster

とは

. . . .

1

お問い合わせ先

. . . .

1

1

駄目コードとその対策

6

1.1

Activity

間のデータのやりとりが

static

変数で行われている

. . . .

6

1.2

無名クラスの

OnClickListener

がそこら中で生成されている

. . . .

9

1.3

Handler

がそこら中で生成されている

. . . .

13

1.4

ポーリングでしか状態を知ることができないスレッドがある

. . . .

16

1.5

try-catch

が乱暴に

Exception

で受けている

. . . .

21

1.6

Thread

が回収できなくなって仕方なく

killProcess

している

. . . .

22

1.7

static

フィールドに状態をもつオブジェクトが入っている

. . . .

25

1.8

内部の処理が微妙に異なる

AsyncTask

が無数にある

. . . .

28

1.9

Preference

への書き込みに個々で

edit

メソッドを呼んでいる

. . . .

33

1.10

Application

Service

の実体を突っ込んでいる

. . . .

38

1.11

OutOfMemoryError

避けが悪影響だけ出している

. . . .

42

1.12

カスタム

Application

が本来あるべき振る舞いをしない

. . . .

50

1.13

xml

を使わずにコードで

View

を延々と生成している

. . . .

51

1.14

synchronized

がそこら中にある

. . . .

52

1.15

new

を嫌ってローカル変数をフィールドに追いやってる

. . . .

52

1.16

おわりに

. . . .

53

(4)

2

Android100

機種サポートしたいけど

10

機種しか試験するお金がありませ んでした

54

2.1

はじめに

. . . .

54

2.2

ケーススタディ

. . . .

55

3

Google

に従わない方が幸せになるかもしれない

7

つのケース

60

3.1

縦向き(

portrait

)固定でいい

. . . .

61

3.2

minSdkVersion

はなるべく高くしよう

. . . .

61

3.3

DialogFragment

は素人は諦めよう

. . . .

64

3.4

PreferenceActivity

は古い使い方でいい

. . . .

65

3.5

ぼっちに

ContentProvider

は要らない

. . . .

68

3.6

「タブレット向け」ではなく「大きな画面向け」で

. . . .

69

3.7

下タブはタブじゃないと思いこもう

. . . .

70

3.8

そうは言っても、原則には従おう

. . . .

73

4

UCD

に沿ったアプリ設計の実践 ∼

PlayStore

で☆

1

をつけられないために∼

74

4.1

序論

. . . .

74

4.2

UCD

という考え方

. . . .

75

4.3

UCD

の実践

. . . .

76

4.4

スマートフォンアプリ設計での実践

. . . .

79

4.5

結論

. . . .

81

5

Android Gradle

プロジェクト逆引きリファレンス

84

5.1

はじめに

. . . .

84

5.2

applicationId

を変更したい

. . . .

86

5.3

minSdkVersion

build.gradle

から指定したい

. . . .

87

5.4

targetSdkVersion

build.gradle

から指定したい

. . . .

88

5.5

versionCode

を算出するロジックを組み込みたい

. . . .

88

5.6

一部の機能の異なる

apk

を作りたい

. . . .

89

5.7

Product Flavor

を追加したい

. . . .

91

5.8

Build Type

を追加したい

. . . .

92

(5)

5.10

メソッド数上限を回避したい

. . . .

95

5.11

ProGuard

を有効化したい

. . . .

97

5.12

使用していないリソースを削除して

apk

を小さくしたい(

1

. . . .

98

5.13

使用していないリソースを削除して

apk

を小さくしたい(

2

. . . .

99

5.14

RenderScript Support Library

を使いたい

. . . 101

5.15

ABI

ごとに別の

apk

を作りたい

. . . 102

5.16

density

ごとに別の

apk

を作りたい

. . . 104

5.17

ビルドタイプ

/

フレーバー固有の依存ライブラリを追加したい

. . . 106

5.18

ビルドバリアント固有の依存ライブラリを追加したい

. . . 106

5.19

推移的依存関係を管理したい(依存ライブラリの除外)

. . . 107

5.20

ライブラリプロジェクトをプロジェクトルートの外に置きたい

. . . 108

5.21

ライブラリプロジェクトをデバッグビルド連動させたい

. . . 109

5.22

Build Type

ごとに異なる値を

AndroidManifest

に書きたい

. . . 111

5.23

BuildConfig

に定数フィールドを追加したい

. . . 113

5.24

リリース証明書の設定を外出ししたい

. . . 115

5.25

デバッグ証明書をリポジトリに入れて共有したい

. . . 117

5.26

Android Wear

アプリを追加したい

. . . 117

5.27

すべてのビルドバリアントに対して処理を書きたい

. . . 119

5.28

lint

の設定を記述したい

. . . 119

5.29

assets

内の特定のファイルを除外したい

. . . 121

5.30

コンパイル済みの

jni

ライブラリ(

.so

)を取り込みたい

. . . 122

5.31

NDK

*.c

*.cpp

ファイルをコンパイルしたい

. . . 122

5.32

依存ライブラリの重複ファイルを除外

/

一つだけ取り込みたい

. . . 124

5.33

apk

ファイルのファイル名を変えたい

. . . 125

5.34

IDE

からのビルドを判別したい

. . . 126

5.35

ビルドサーバでのビルドを高速化したい

. . . 126

5.36

Gradle

ラッパーのバージョンを更新したい

. . . 127

5.37

ProGuard

のマッピングファイルのパスを取得したい

. . . 129

5.38

おわりに

. . . 130

6

Docker

が切り開くソフトウェア開発の未来

131

6.1

仮想化

. . . 131

(6)

6.2

VM

とコンテナに基づくアプローチの違い

. . . 132

6.3

コンテナのパフォーマンス

. . . 134

6.4

Docker

が採用されるのはなぜか

. . . 134

6.5

Docker

コンテナを用いた一例

. . . 135

6.6

VM

Docker

コンテナの住み分け

. . . 136

6.7

Docker

を活かすソフトウェア設計

. . . 137

6.8

マイクロサービスを阻む技術上の課題

. . . 140

6.9

マイクロサービスを阻む管理上の課題

. . . 141

6.10

コンテナ管理サービスの夜明け

. . . 142

6.11

クラウド事業者のコンテナ戦略

. . . 144

6.12

Docker

プラットフォームの未来

. . . 146

6.13

Docker

後の世界

. . . 149

6.14

最後に

. . . 151

著者紹介

152

(7)

駄目コードとその対策

Android

アプリケーション開発ではいろいろな要素を取り扱います。

View

のイベントの扱 い方やマルチスレッド、

Handler

では少々小難しいコードの書き方が必要になります。それ らのコードの書き方を押さえず、そのまま

Android

アプリケーションとして実装するとコー ドが読み難くなったり解りづらくなったりします。ここではそれらのコードの書き方で、どの ような問題が出て、どうすれば改善するのかを紹介します。 なお、この章で挙げている駄目な例はフィクションではありません。

1.1

Activity

間のデータのやりとりが

static

変数で行われて

いる

1つの画面で完結するアプリケーションは少なく、通常、アプリケーションは複数の画面を 持ちます。1つの画面から別の画面へ遷移するとき、いくつかのパラメーターを引渡します。 パラメーターの中身は入力内容であったり、ユーザーの情報であったり、アプリ固有の状態で あったり多岐にわたります。

Android

では1つの画面は

Activity

によって構成され、パラメー タは

Intent

で与えるのが慣習です。 駄目なコードの例として、画面間のパラメーターを

static

変数で渡している例を見てみま しょう。以降の例ではパラメータにリスト

1.1

のデータクラスを使用します。 リスト1.1: UserModel.java

public class UserModel implements Serializable { public enum Role {

PROGRAMMER, DESIGNNER, MANAGER }

private long id;

private String username; private Role role;

(8)

/* GetterとSetterは省略 */ }

このデータクラスをリスト

1.2

FirstDameActivity

からリスト

1.3

SecondDameAc-tivity

への遷移する場合を見てみましょう。

リスト1.2: 悪い例FirstDameActivity.java

public class FirstDameActivity extends Activity implements View.OnClickListener {

private UserModel mUserModel; @Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button1).setOnClickListener(this); // mUserModelを作る /* 省略 */ } @Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

// パラメータを設定

SecondDameActivity.sUserModel = mUserModel;

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

} } }

リスト1.3: 悪い例SecondDameActivity.java

public class SecondDameActivity extends Activity { public static UserModel sUserModel;

private UserModel mUserModel; @Override

(9)

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // static 変数からパラメータを受け取る mUserModel = sUserModel; } } リスト

1.2

FirstDameActivity#onClick

メソッド内でパラメータとして

mUserModel

SecondDameActivity.sUserModel

に代入しています。一見このコードは動作するように見 えますが、実際動作します。しかしいくつかの問題の原因を抱えています。

Android

のプロセスはメモリ不足などの理由で破棄されるため、破棄されたタイミングで

SecondDameActivity.sUserModel

がクリアされ、再度起動した際に

sUserModel

から値が 取れないという現象を起こします。また

SecondDameActivity

を2回目に起動するときに

sUserModel

の設定を忘れると古い

sUserModel

がそのまま使われるという挙動も起こり ます。 これらの他にも問題があるので、正しく

Android

の手法に書き換えましょう。書き換えた コードがリスト

1.4

とリスト

1.5

になります。 リスト1.4: 良い例FirstRefineActivity.java

public class FirstRefineActivity extends Activity implements View.OnClickListener {

private UserModel mUserModel; @Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); findViewById(R.id.button1).setOnClickListener(this); // mUserModelを作る /* 省略 */ } @Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

Intent intent = new Intent(this, SecondRefineActivity.class); // パラメータを設定

(10)

intent.putExtra(SecondRefineActivity.EXTRA_USER_MODEL, mUserModel); startActivity(intent); } } } リスト1.5: 良い例SecondRefineActivity.java

public class SecondRefineActivity extends Activity {

public static final String EXTRA_USER_MODEL = "user_model"; private UserModel mUserModel;

@Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

// Intent からパラメータを受け取る

mUserModel = (UserModel) getIntent()

.getSerializableExtra(EXTRA_USER_MODEL); } } リスト

1.4

Intent#putExtra

メソッドで

Intent

の追加情報としてパラメータを渡すよう にしています。そしてリスト

1.5

でその追加情報を取り出しています。

Intent#putExtra

メソッドで

Intent

の追加情報としてパラメータを渡す方法は面倒に見える こともあります。しかしその面倒なことの代わりに、不意なプロセスの破棄に対応したり、再 起動時の対応をフレームワークが行なってくれるようになります。特にこのケースは開発で

Activity

を作る際に一番最初に触るところでありながら、トラブルが発生するのは開発の終盤 であるため、最初からフレームワークに合わせた形で実装するようにしましょう。

1.2

無名クラスの

OnClickListener

がそこら中で生成されて

いる

ボ タ ン の よ う な

View

が ク リ ッ ク さ れ た と き の イ ベ ン ト を 扱 う に は 、皆 さ ん お 馴 染 み の

OnClickListener

を 使 用 し ま す 。

OnClickListener

の イ ン ス タ ン ス を

(11)

View#setOnClickListener

メソッドで設定していると思いますが、

OnClickListener

のイ ンスタンス自体はどのようにして生成しているでしょうか。

リスト

1.6

Activity#onCreate

内に

button1

button2

について、それらの押されたと きの処理をすべて書き込んだ場合のコードです。

リスト1.6: 悪い例ManyOnClickListenerDameActivity.java

public class ManyOnClickListenerDameActivity extends Activity { private EditText mEditText1;

private EditText mEditText2; @Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_many_handlers); // button1の押されたときの処理を設定する findViewById(R.id.button1).setOnClickListener( new View.OnClickListener() { @Override

public void onClick(View v) {

mEditText1.setText("button1 clicked!"); } }); // button2の押されたときの処理を設定する findViewById(R.id.button2).setOnClickListener( new View.OnClickListener() { @Override

public void onClick(View v) {

mEditText2.setText("button2 clicked!"); }

});

mEditText1 = (EditText) findViewById(R.id.editText1); mEditText2 = (EditText) findViewById(R.id.editText2); }

}

リスト

1.6

Activity#onCreate

内で行われていることを整理してみましょう。

1. OnClickListener

button1

button2

に設定する

2. OnClickListener

のインスタンスを生成する

(12)

リスト

1.6

の中では、これら3つのことを

Activity#onCreate

メソッドの中ですべて行なっ ています。しかし

Activity#onCreate

メソッドは本来

Activity

の初期化や生成についての処 理を行なうものであり、これら3つがすべて入っているため読みにくくなっていないでしょう か。特に

3.

の処理の定義は行数が増えやすく、

onCreate

内に書くとメソッドの記述が長く なり、コードが読みにくくなります。

リスト

1.7

では、

OnClickListener

Acitivity

implements

することで、

2.

のインスタ ンスの生成を不要にしています。また

3.

の処理の定義を

Activity#onClick

メソッドに分離さ せています。

リスト1.7: 良い例ManyOnClickListenerRefine1Activity.java

public class ManyOnClickListenerRefine1Activity extends Activity implements View.OnClickListener {

private EditText mEditText1; private EditText mEditText2; @Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

setContentView(R.layout.activity_many_handlers); // OnClickListenerの設定

findViewById(R.id.button1).setOnClickListener(this); findViewById(R.id.button2).setOnClickListener(this); mEditText1 = (EditText) findViewById(R.id.editText1); mEditText2 = (EditText) findViewById(R.id.editText2); }

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

mEditText1.setText("button1 clicked!"); } else if (v.getId() == R.id.button1) {

mEditText2.setText("button2 clicked!"); } }} リスト

1.7

のようにボタンが押されたときの処理を分離させることで、

Activity#onCreate

をがスッキリしました。

Activity#onCreate

内では画面の

View

要素の初期化をすることが多 く、画面の

View

が増えればその分だけ大きくなることが多いです。このため

View

が押され

(13)

たときの処理を記載していくと、どんどん大きくなってしまい、可読性が低下します。これを 避けるにはインスタンスの生成、設定、処理の定義などやることが複数ある場合は、それらの コードを可能な限り別の場所にまとめて書くようにし、メソッドが肥大化しないようにしま しょう。

インスタンスの生成について

OnClickListener

Activity

implements

するのが嫌な場 合、メンバ変数にしてしまう手がありますのでこちらも紹介しておきます(リスト

1.8

)。

リスト1.8: 良い例ManyOnClickListenerRefine2Activity.java

public class ManyOnClickListenerRefine2Activity extends Activity { private EditText mEditText1;

private EditText mEditText2; @Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

setContentView(R.layout.activity_many_handlers);

findViewById(R.id.button1).setOnClickListener(mOnClickListener); findViewById(R.id.button2).setOnClickListener(mOnClickListener); mEditText1 = (EditText) findViewById(R.id.editText1);

mEditText2 = (EditText) findViewById(R.id.editText2); }

private View.OnClickListener mOnClickListener = new View.OnClickListener() {

@Override

public void onClick(View v) {

if (v.getId() == R.id.button1) {

mEditText1.setText("button1 clicked!"); } else if (v.getId() == R.id.button1) {

mEditText2.setText("button2 clicked!"); }

} }; }

リスト

1.8

OnClickListener

Activity

implements

する代わりに、メンバ変数とし て

OnClickListener

を生成して保持しています。もし

Activity

の規模が大きくなり

Activity

(14)

ける手があります。 ところで

onClick

メソッドの中に直接処理を書くことについてはどうでしょう。厳密にい えば、あくまで

onClick

メソッドはイベントのハンドリングだけにすべきかもしれません。し かし数行で終わる簡単な処理の場合は直接

onClick

メソッド内に書いたほうがわかりやすい かも知れませんね。この辺りは処理の長さに合わせてケースバイケースで判断するようにし ましょう。

1.3

Handler

がそこら中で生成されている

Handler

クラスは

Android

のスレッド間の処理のやりとりの肝となるクラスです。別ス レッドから処理や結果を引き渡すときや、一定時間後に処理を実行したいときなどに使用しま す。便利なクラスではあるのですが、

Handler

を闇雲に生成すると、コードが煩雑になり可読 性が低下します。 ボタンが押されたとき、一定時間後に処理を行なう処理を例に説明します。リスト

1.9

button1

button2

がクリックされると、

5000ms

後にそれぞれ

editText1

editText2

に文 字列を出力する処理の実装です。

リスト1.9: 悪い例ManyHandlersDameActivity.java

public class ManyHandlersDameActivity extends Activity implements View.OnClickListener {

private EditText editText1; private EditText editText2; @Override

protected void onCreate(Bundle savedInstanceState) { /* 省略 */

}

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

// その場でHandlerを生成して5000ms後に処理を実行させる new Handler().postDelayed(new Runnable() {

@Override

public void run() {

editText1.setText("button1 clicked!"); }

}, 5000);

(15)

// その場でHandlerを生成して5000ms後に処理を実行させる new Handler().postDelayed(new Runnable() {

@Override

public void run() {

editText1.setText("button2 clicked!"); } }, 5000); } } } 見てのとおり、その場で

Handler

を生成し、

postDelayed

5000ms

後に処理が実行すべ き処理である

Runnable

を渡しています。これは乱暴ですね。

Handler

は丁寧に扱うように しましょう。 リスト

1.10

では

Handler

の生成回数を抑えるため、クラス変数(

static

変数)として生成 しています。 リスト1.10: 良い例 ManyHandlersRefine1Activity.java

public class ManyHandlersRefine1Activity extends Activity implements View.OnClickListener {

private EditText editText1; private EditText editText2;

private static Handler sHandler = new Handler(); @Override

protected void onCreate(Bundle savedInstanceState) { /* 省略 */

}

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

sHandler.postDelayed(new Runnable() { @Override

public void run() {

editText1.setText("button1 clicked!"); }

}, 5000);

} else if (v.getId() == R.id.button2) { sHandler.postDelayed(new Runnable() {

(16)

public void run() { editText1.setText("button2 clicked!"); } }, 5000); } } } 少しマシになりました。このままでも特に問題は無いのですが、

Runnable

のインスタンス をボタンを押すたびに生成しているのが気になるかも知れません。これはメッセージを送る ようにすることで、リスト

1.11

のような書き方にすることができます。 リスト1.11: 良い例 ManyHandlersRefine2Activity.java

public class ManyHandlersRefine2Activity extends Activity implements View.OnClickListener {

private static final int EVENT_BUTTON1_CLICKED = 1; private static final int EVENT_BUTTON2_CLICKED = 2; private static Handler sHandler = new Handler() {

@Override

public void handleMessage(Message msg) { super.handleMessage(msg);

ManyHandlersRefine2Activity activity

= (ManyHandlersRefine2Activity) msg.obj; if (msg.what == EVENT_BUTTON1_CLICKED) {

activity.editText1.setText("button1 clicked!"); } else if (msg.what == EVENT_BUTTON2_CLICKED) {

activity.editText2.setText("button2 clicked!"); }

} };

private EditText editText1; private EditText editText2; @Override

protected void onCreate(Bundle savedInstanceState) { /* 省略 */

}

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

(17)

Message msg =

sHandler.obtainMessage(EVENT_BUTTON1_CLICKED, this); sHandler.sendMessageDelayed(msg, 5000);

} else if (v.getId() == R.id.button2) {

// メッセージを作成してsHandlerに渡し、処理自体はsHandlerに任せる Message msg = sHandler.obtainMessage(EVENT_BUTTON2_CLICKED, this); sHandler.sendMessageDelayed(msg, 5000); } } } リスト

1.11

ではリスト

1.10

と異なり、

sHandler

へはメッセージを送るだけで、

editText1

editText2

への処理自体は

sHandler

に記述しています。メッセージを送るだけなので毎回

Runnable

を生成する必要がなくなっています。

Handler

クラスは便利な反面、

UI

スレッドと強い関係を持つため、迂闊に生成するとメモ リリークの原因になるなど、危険な側面もあります。メモリリークは開発中には気づきにく く、リリース間際になって発覚して大変なことになりやすいので注意が必要です。闇雲に生成 したりせず、コードの書き方をあらかじめ整理して開発を進めるようにしましょう。

1.4

ポーリングでしか状態を知ることができないスレッドが

ある

長い処理は

UI

スレッドで行うべきではないのはよく知られています。しかし別スレッドで 処理を行うとき、処理が終わったことをどのようにして

UI

スレッドに通知しているでしょ うか。 たとえば

button1

を押したとき、長い処理の結果を

editText1

に表示する処理を考えてみま しょう。リスト

1.12

では、別に生成した

Thread

で処理を行い、処理が終わると

taskFinished

に終わったことを書き込み、

taskResult

には処理の結果を書き込んでいます。そして

UI

ス レッドは

1000ms

ごとに

taskFinished

true

になることを確認し、

true

になると結果を

editText

に表示しています。

(18)

public class PollingDameActivity extends Activity implements View.OnClickListener {

private int taskResult; // 値の受け渡しのための変数

private boolean taskFinished; // 処理が終わったか判定するための変数 private EditText editText1;

private EditText editText2;

private static Handler sHandler = new Handler(); @Override

protected void onCreate(Bundle savedInstanceState) { /* 省略 */

}

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

taskFinished = false;

// 別スレッドで行なう処理の本体 new Thread() {

@Override

public void run() { super.run();

int result = 0;

{ // 長い処理のつもり

for (int i = 0; i <= 100; i++) { result += i; } } // 処理が終わったことを変数に書き込む taskResult = result; taskFinished = true; } }.start();

Runnable runnable = new Runnable() { @Override

public void run() {

// 処理が終わっているか確認、まだなら1秒後に再確認する if (taskFinished) { editText1.setText(String.valueOf(taskResult)); } else { sHandler.postDelayed(this, 1000); } }

(19)

}; sHandler.postDelayed(runnable, 1000); } } } このコードの駄目なところを整理してみましょう。

1.

別スレッドの処理の完了と結果を保持するためだけに

taskFinished

taskResult

と いうメンバ変数が追加されている

2. 1000ms

ごとに

UI

スレッドが処理の完了を確認しているが早く終わっても必ず

1000ms

待ってしまう

3.

仮に待ち時間を

100ms

10ms

すると、今度は確認回数が増えるため、

CPU

使用率 が増加してしまう これらの他にも

Thread

new

で生成して実行しているため、後からハンドリングできな くなっている問題もあります。このコードの駄目なところを単純に修正するとリスト

1.13

よ うなコードになります。 リスト1.13: 良い例 PollingRefine1Activity.java

public class PollingRefine1Activity extends Activity implements View.OnClickListener {

private EditText editText1; private EditText editText2;

/** UIスレッドに処理を移管させるためのHandler */ private static Handler sHandler = new Handler(); @Override

protected void onCreate(Bundle savedInstanceState) { /* 省略 */

}

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

// 別スレッドで行なう処理の本体 new Thread() {

(20)

public void run() { super.run();

int result = 0;

{ // 長い処理のつもり

for (int i = 0; i <= 100; i++) { result += i;

} }

// UIスレッドで行なうべき処理を作る final int finalResult = result;

Runnable runnable = new Runnable() { @Override

public void run() {

// 結果をeditText1に出力する editText1.setText(String.valueOf(finalResult)); } }; sHandler .post(runnable); // sHandler経由でUIスレッドで実行 // runOnUiThread(runnable); // 代わりにこのメソッドも使える } }.start(); } } } この処理では

editText1

への出力処理を

sHandler

を経由して

UI

スレッドに実行させてい ます。ここでは

sHandler.post

としていますが、

Activity#runOnUiThread

メソッドを使うこ とでも

UI

スレッドに処理を実行させることができます。これにより先に挙げた駄目なところ が改善されました。 実はこの処理、

Android

の標準ライブラリである

AsyncTask

クラスを使用することで、 もっと読みやすくすることができます。

AsyncTask

クラスを使用するように置き換えたもの がリスト

1.14

になります。 リスト1.14: 良い例 PollingRefine2Activity.java

public class PollingRefine2Activity extends Activity implements View.OnClickListener {

private EditText editText1; private EditText editText2;

(21)

@Override

protected void onCreate(Bundle savedInstanceState) { /* 省略 */

}

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

AsyncTask<Void,Void,Integer> task

= new AsyncTask<Void, Void, Integer>() { @Override

protected Integer doInBackground(Void... params) { // バックグラウンドで行われる

int result = 0;

{ // 長い処理のつもり

for (int i = 0; i <= 100; i++) { result += i; } } return result; } @Override

protected void onPostExecute(Integer result) { // UIスレッドで行われる editText1.setText(String.valueOf(result)); } }; task.execute(); } } }

AsyncTask

doInBackground

メソッドは別スレッドで実行されますが、

onPostExecute

UI

スレッド上で実行される仕様になっています。このため

doInBackground

に時間の掛 かる処理を書き、

onPostExecute

UI

スレッドで行なうべき結果の出力処理を書くことで、 どれが別スレッドでどれが

UI

スレッドで実行されるかを明確にすることができます。 リスト

1.12

のように、

taskFinished

taskResult

といった変数を用いる場合、別スレッ ドで行なうべき処理が増えた際にこれらの変数も一緒に増えてしまい、ソースの可読性の低下 や誤って別の変数をみるなどしてバグの原因となってしまいます。 別スレッドの処理結果を

UI

スレッドに引き渡さなければならないときは、

AsyncTask

(22)

使うようにしましょう。

1.5

try-catch

が乱暴に

Exception

で受けている

Java

では例外の処理を取り扱うのに

Exception

クラスを使用します。たとえば

Inte-ger#parseInt

メソッドで

String

型の値を

int

型へ変換するとき、

String

が数値として認識で きない場合、

NumberFormatException

がスローされます。また通信処理の多くでは、通信 の失敗時に

IOException

がスローされます。

リスト

1.15

の例は

EditText

から受け取った文字列を数値に変換する場合の駄目なパター ンです。

リスト1.15: 悪い例 ExceptionDameActivity.java

EditText editText1 = (EditText) findViewById(R.id.editText1); int intValue = -1; try { intValue = Integer.parseInt(editText1.getText().toString()); } catch (Exception e) { } if (intValue == -1) {

Log.d("Hoge", "Bad input."); // ここでエラーの場合の処理を行なう } else { // ここで正常な場合の処理を行なう } この書き方の駄目な箇所を整理しましょう。

1. try-catch

Exception

で受けている

2. -1

が入力された場合、エラーとして扱われる このコードの書き方では、

1.

のとおり

Exception

で受けているため、

editText1

が存在せず

null

になるような場合でもエラーとして扱われてします。また、ユーザーが

"-1"

を入力したと き、変数

intValue

-1

か否かでエラーを判定しているため、エラーとして判定されてしまい ます。 これらの問題を改善した実装がリスト

1.16

になります。

editText1

が存在せず

null

になる 場合は正しく

NullPointerException

になりますし、ユーザーが

"-1"

を入力したときも誤って

(23)

エラーと判定されることもありません。前述の問題が解決した上に、コード量が少なくなって いることがわかります。

リスト1.16: 良い例 ExceptionRefineActivity.java

EditText editText1 = (EditText) findViewById(R.id.editText1); try {

int intValue = Integer.parseInt(editText1.getText().toString()); // ここで正常な場合の処理を行なう

} catch (NumberFormatException e) { Log.d("Hoge", "Bad input.");

// ここでエラーの場合の処理を行なう } ここで紹介した例はどちらかというと

Android

というより

Java

のお作法の問題になりま す。

Android

の標準フレームワークも

Java

の作法に則って作られているため、例外処理につ いても同じように作法を守って実装することでフレームワークがよいようにしてくれます。 特に例外処理は乱暴に

Exception

でキャッチしたりすると他のバグと混ざって解決が困難な ことになるので、日頃から作法を守ったコードを書くようにしましょう。

1.6

Thread

が回収できなくなって仕方なく

killProcess

して

いる

時間の掛かる処理は

UI

スレッドで実行するのは好ましくないため、

AsyncTask

Thread

で別スレッドにします。また、ものによってはバックグラウンドで動き続けることを前提に するものもあると思います。アプリの動作中にバックグラウンドで動き続けるものがあると き、どうやってそれを止めているでしょうか。動き続けるからいいやと止めることを考えてな かったり、プロセスを

kill

することで強引に止めていないでしょうか。 たとえばサービスが一つのスレッドを持っている場合を考えてみましょう。リスト

1.17

は バックグラウンドで動き続けるスレッドを一つ持っています。

Service#onCreate

メソッド 内で直接

new

で生成し、その場で

start

メソッドを読んで実行しています。 リスト1.17: 悪い例 KillProcessDameService.java

(24)

public class KillProcessDameService extends Service { @Override

public IBinder onBind(Intent intent) { /* 省略 */

}

@Override

public void onCreate() { super.onCreate();

new Thread() { @Override

public void run() { super.run(); while(true) { // do some thing } } }.start(); } @Override

public void onDestroy() { super.onDestroy(); Process.killProcess(Process.myPid()); } } こう なる と プロ グラ ム から 止め る こと は 最 早 でき ませ ん 。唯一 残 さ れ た手 段は

Ser-vice#onDestroy

に書かれている

Process#killProcess

です。サービスの終了時にこのメ ソッドによってプロセスを

kill

することでスレッドを停止させています。どのみちサービスを 終了するのはアプリケーションの終了と同じだから問題が無いように思われるかも知れませ んが、次のような副作用があります。

1.

サービスの終了がプロセスが

kill

を意味するため、アプリケーションの設計上サービス の停止と再開ができなくなる

2. JUnit

の実行時に

Service#onDestroy

が呼ばれると、その場で

JUnit

が停止してしま うため、事実上

JUnit

が使いものにならなくなる

3. Process.killProcess

を呼ばないようにしてサービスを再起動すると、古いスレッドが 残ったままになる

(25)

このようなことにならないように、この手のスレッドはサービスの生存期間に内包するべき です。このためスレッドを作成する際は、何がスレッドの開始と停止の責務を負い、どの期間 に動作するかを意識するようにしましょう。この例ではサービスが開始と停止の責務を負い、 サービスの生存期間中に動作するようにすべきです。

リスト

1.18

では

Service#onCreate

内で

Thread

を作成していますが、

Thread#interrupt()

を呼ぶと停止するようになっています。

リスト1.18: 良い例 KillProcessRefine1Service.java

public class KillProcessRefine1Service extends Service { private Thread mThread;

@Override

public IBinder onBind(Intent intent) { /* 省略 */

}

@Override

public void onCreate() { super.onCreate();

mThread = new Thread() { @Override

public void run() { super.run(); while(!Thread.currentThread().isInterrupted()) { // do some thing } } }; mThread.start(); } @Override

public void onDestroy() { super.onDestroy(); mThread.interrupt(); } } この例では停止するために

Thread#interrupt

メソッドと

Thread#isInterrupted

メソッド を使用していますが、

volatile

boolean

の共有変数を使うなど、他にも方法はいくつかあり ます。 停止については1つ注意点があります。リスト

1.18

Service#onDestroy

内ではスレッ

(26)

ドに停止を促しているだけで実際の停止までは待っていません。このため短時間でサービス が停止して再起動した際に、瞬間的にスレッドが2個できることが起こりえます。もしスレッ ドが瞬間的にでも2個あるとアプリケーションとして好ましく無い場合はリスト

1.19

のよう に、

Thread#join

メソッドを使って停止を待つようにしましょう。

リスト1.19: 良い例 KillProcessRefine2Service.java

public class KillProcessRefine2Service extends Service { private Thread mThread;

/* 省略 */ @Override

public void onDestroy() { super.onDestroy(); mThread.interrupt(); // 必要なら止まるのを確認するまでonDestroy を抜けないようにする try { mThread.join(); } catch (InterruptedException e) { // 必要ならハンドリングする } } } 別スレッドで処理を行なうことは非常に便利で、特に

Bluetooth

ADK

で外部デバイスを 制御する場合は必須の要素です。しかし便利故に取り扱いを誤ると逃げ出して捕まえられな くなります。そうならないようにどのタイミングで開始し、停止するか設計レベルで明確にし て扱うようにしましょう。

1.7

static

フィールドに状態をもつオブジェクトが入っている

ハードウェアや特殊なサーバーと通信するとき、それらを管理するオブジェクトをどのよう に持っているのでしょうか。恐らく直接的には

Activity

Service

がそれらを管理している と思いますが、それらの関係をうまく管理できているでしょうか。もしそれらを

static

フィー ルドに格納するときは

Activity

Service

のライフサイクルと巧くやらないと思わぬ不具合 に行き当たります。 リスト

1.20

の例では

Service

の開始時に専用の通信スレッドである

HogeThread

を作り、

(27)

それを

static

フィールドである

sThread

に保持させています。

リスト1.20: 悪い例 StaticFieldDameService.java

public class StaticFieldDameService extends Service { private static Thread sThread;

static class HogeThread extends Thread { @Override

public void run() { super.run(); while(!isInterrupted()) { // 何か通信する処理 } } } @Override

public IBinder onBind(Intent intent) { /* 省略 */

}

@Override

public void onCreate() { super.onCreate();

// 専用の通信スレッドをここで生成する sThread = new HogeThread();

sThread.start(); } }

static

フィールドであるため、

Service

が破棄された後も明示的に破棄しなければ残り続け ます。このため

Service

が不意に再起動すると

HogeThread

を新たに作成され、元々あった

HogeThread

は終了されること無く、

HogeThread

が2個生成された状態に陥ります。 これらの問題を回避するには、まず

onCreate

で生成したものは

onDestroy

内で廃棄するよ うにします。そうすると最早

static

フィールドである必要が無くなりますので、

HogeThread

を保持する変数を通常のフィールドに変更します。修正したコードはリスト

1.21

になり ます。 リスト1.21: 良い例 StaticFieldRefineService.java

(28)

public class StaticFieldRefineService extends Service { private Thread mThread;

static class HogeThread extends Thread { @Override

public void run() { super.run(); while(!isInterrupted()) { // 何か通信する処理 } } } @Override

public IBinder onBind(Intent intent) { /* 省略 */

}

@Override

public void onCreate() { super.onCreate();

// 専用の通信スレッドをここで生成する mThread = new HogeThread();

mThread.start(); }

@Override

public void onDestroy() { super.onDestroy(); // ここで停止して廃棄する mThread.interrupt(); mThread = null; } } ハードウェアや特殊なサーバーと通信するとしても、それらの通信が

Service

のライフサ イクルより長くなる必要はまずありません。いくら長い時間の管理が必要でもサービスのラ イフサイクル内に収めるべきです。そうすれば厳重な管理が必要な

static

フィールドを使う こと無く、見通しのよいコードにすることができます。

static

フィールドに状態をもつオブジェクトを保持させなければならなくなった場合は一度

(29)

クル内に収めるようにしましょう。

1.8

内部の処理が微妙に異なる

AsyncTask

が無数にある

UI

スレッドでは行なうべきではない時間がかかる処理や通信処理を作るとき、別タスクで 実行させるために

AsyncTask

を使用します。

AsyncTask

は便利な反面いろいろなコードの 書き方ができるため、可読性が低下しないよう注意が必要です。 リスト

1.22

では、時間の掛かる処理のつもりとして、

button1

が押されたときに

0

100

まで足すタスクを実行して結果を

mEditText1

に出力する処理と、

button2

が押されたときに

101

200

まで足すタスクを実行して結果を

mEditText2

に出力する処理の2つを定義してい ます。処理自体は軽い処理ですがここには時間がかかる処理が入っていると思ってください。 リスト

1.22

は冗長な例です。 リスト1.22: 悪い例 ManyAsyncTaskDameActivity.java

public class ManyAsyncTaskDameActivity extends Activity implements View.OnClickListener {

private EditText mEditText1; private EditText mEditText2; @Override

protected void onCreate(Bundle savedInstanceState) { /* 省略 */

}

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

AsyncTask<Void,Void,Integer> task

= new AsyncTask<Void, Void, Integer>() { @Override

protected Integer doInBackground(Void... params) { int result = 0;

{ // 時間の掛かる処理のつもり

for (int i = 0; i <= 100; i++) { result += i; } } return result; } @Override

(30)

protected void onPostExecute(Integer integer) { super.onPostExecute(integer); mEditText1.setText(String.valueOf(integer)); } }; task.execute();

} else if (v.getId() == R.id.button2) { AsyncTask<Void,Void,Integer> task

= new AsyncTask<Void, Void, Integer>() { @Override

protected Integer doInBackground(Void... params) { int result = 0;

{ // 時間の掛かる処理のつもり

for (int i = 101; i <= 200; i++) { result += i; } } return result; } @Override

protected void onPostExecute(Integer integer) { super.onPostExecute(integer); mEditText2.setText(String.valueOf(integer)); } }; task.execute(); } } } リスト

1.22

の2つの

AsyncTask

について、まず

AsyncTask#doInBackground

メソッド 中を整理して見ましょう。次のような箇所に問題があります。

1.

2つの

AsyncTask#doInBackground

の動きの違いは繰り返しの回数だけである。

2.

2つの

AsyncTask#onPostExecute

の動きの違いは出力先の

EditText

が異なるだけで ある。

3.

おまけ:長い処理を行なうときはユーザーに処理中であることを見せるべき。 まず

1.

AsyncTask#doInBackground

メソッドの中身を共通化してみましょう。リスト

1.23

SumAsyncTask

のように書き換えることができます。それぞれ

0-100

101-200

(31)

AsyncTask#onPostExecute

の引数で渡すようになっています。

リスト1.23: 良い例 ManyAsyncTaskRefine1Activity.java

public class ManyAsyncTaskRefine1Activity extends Activity implements View.OnClickListener {

private EditText mEditText1; private EditText mEditText2;

static class SumAsyncTask extends AsyncTask<Integer, Void, Integer> { @Override

protected Integer doInBackground(Integer... params) { int result = 0;

{ // 時間の掛かる処理のつもり

for (int i = params[0]; i <= params[1]; i++) { result += i; } } return result; } } @Override

protected void onCreate(Bundle savedInstanceState) { /* 省略 */

}

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

SumAsyncTask task = new SumAsyncTask() { @Override

protected void onPostExecute(Integer integer) { super.onPostExecute(integer);

mEditText1.setText(String.valueOf(integer)); }

};

task.execute(0, 100);

} else if (v.getId() == R.id.button2) { SumAsyncTask task = new SumAsyncTask() {

@Override

protected void onPostExecute(Integer integer) { super.onPostExecute(integer);

mEditText2.setText(String.valueOf(integer)); }

(32)

task.execute(101, 200); } } } 次に

2.

3.

を改善します。

AsyncTask

が少々長めになるため

SumAsyncTask

をリスト

1.24

として分離します。この

SumAsyncTask

の特徴としては、

ISumAsyncTaskListener

インターフェースと、

mUserData

メンバ変数があることです。

mUserData

メンバ変数は

Generics

での指定になっており、好きな型を指定できるようになっています。 リスト1.24: SumAsyncTask.java

public class SumAsyncTask<T> extends AsyncTask<Integer, Void, Integer> { public interface ISumAsyncTaskListener<T> {

public void onPreExecuteSumAsyncTask(T userData); public void onCancelSumAsyncTask(T userData);

public void onPostExecuteSumAsyncTask(int result, T userData); }

private ISumAsyncTaskListener<T> mListener; private T mUserData;

public SumAsyncTask(ISumAsyncTaskListener listener, T userData) { this.mListener = listener;

this.mUserData = userData; }

@Override

protected void onPreExecute() { super.onPreExecute();

mListener.onPreExecuteSumAsyncTask(mUserData); }

@Override

protected Integer doInBackground(Integer... params) { int result = 0;

for (int i=params[0];i<=params[1];i++) { result += i;

}

return result; }

@Override

(33)

super.onCancelled();

mListener.onCancelSumAsyncTask(mUserData); }

@Override

protected void onPostExecute(Integer result) {

mListener.onPostExecuteSumAsyncTask(result, mUserData); }

}

SumAsyncTask

を使ったものがリスト

1.25

となります。だいぶスッキリした感じになり ました。

SumAsyncTask

Generics

には

EditText

型を指定しています。

リスト1.25: 良い例 ManyAsyncTaskRefine2Activity.java

public class ManyAsyncTaskRefine2Activity extends Activity implements View.OnClickListener,

SumAsyncTask.ISumAsyncTaskListener<EditText> { private EditText mEditText1;

private EditText mEditText2; SumAsyncTask<EditText> mTask1; SumAsyncTask<EditText> mTask2;

private ProgressDialog mProgressDialog; @Override

protected void onCreate(Bundle savedInstanceState) { /* 省略 */

}

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

mTask1 = new SumAsyncTask<EditText>(this, mEditText1); mTask1.execute(0, 100);

} else if (v.getId() == R.id.button2) {

mTask2 = new SumAsyncTask<EditText>(this, mEditText2); mTask2.execute(101, 200);

} }

@Override

public void onPreExecuteSumAsyncTask(EditText userData) { mProgressDialog = new ProgressDialog(this);

mProgressDialog.show(); }

(34)

@Override

public void onCancelSumAsyncTask(EditText userData) { mProgressDialog.dismiss();

mProgressDialog = null; }

@Override

public void onPostExecuteSumAsyncTask(int result, EditText userData) { userData.setText(String.valueOf(result)); mProgressDialog.dismiss(); mProgressDialog = null; } } リスト

1.25

とリスト

1.23

の大きな違いは2つあります。 1つめは

AsyncTask

を匿名クラスで拡張し、

AsyncTask#onPostExecute

メソッドで実行 していたのを、

onPostExecuteSumAsyncTask

メソッド内で行なっていることです。このメ ソッドの第二引数は

Generics

により出力先の

EditText

が渡されるようになっています。こ れにより前の例ではそれぞれ個別に定義していた処理を1つにまとめることができています。 2 つ め は

onPostExecuteSumAsyncTask

と 同 じ よ う に 定 義 さ れ た

onPreExecute-SumAsyncTask

onCancelSumAsyncTask

です。これらのメソッドは

SumAsyncTask

内から呼ばれるように作られており、ここでプログレスダイアログを出す処理を完結させてい ます。 リスト

1.22

とリスト

1.25

を見比べてみてどちらのコードがスッキリしているでしょう か。別タスクを使用する処理では、別タスクの処理と

UI

スレッドの処理を分離させるため、 その継ぎ目となる処理が煩雑となりがちです。

AsyncTask

やそのリスナーとなるメソッドを

Interface

で上手に切りだすと、1箇所に複数の目的のコードが混在することを避けることが できます。もし書いているコードが煩雑になっている場合は

Interface

やメソッドの分割を行 い、読みやすくなるようにしましょう。

1.9

Preference

への書き込みに個々で

edit

メソッドを呼んで

いる

設定値や小さな値を保存するのに

SharedPreference

を使います。

SharedPreference

String

型以外のデータも

Key-Value

で値を保持できるのでちょっとした値を保存するのに便

(35)

利です。しかし便利ゆえにいくつかの弱点があります。

1. String

型でキーを指定しなければならず、しばしばキーを間違えてバグの原因になる

2.

誤って違うデータ型の値を入れると、取り出し時に

ClassCastException

がスローさ れる これらの弱点は非常に厄介です。どのように厄介かというと、間違えてもコンパイルエラー にならず、実行時の例外やおかしな挙動を引き起こすからです。 リスト

1.26

を例に見てみましょう。このコードではボタンが2個あり、押されるとそのボ タン名を

SharedPreferences

に保存します。そして

Activity#onCreate

の中で

editText1

に そのボタン名を設定する処理になっています。動作には問題はありませんが前述の問題があ り、開発を進めていく上での不安となる点が残っています。

リスト1.26: 悪い例 SharedPreferencesDameActivity.java

public class SharedPreferencesDameActivity extends Activity implements View.OnClickListener {

private SharedPreferences mPref; private EditText editText1;

@Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

setContentView(R.layout.activity_many_handlers); findViewById(R.id.button1).setOnClickListener(this); findViewById(R.id.button2).setOnClickListener(this); editText1 = (EditText) findViewById(R.id.editText1);

mPref = PreferenceManager.getDefaultSharedPreferences(this); String lastButton = mPref.getString("lastButton", "");

editText1.setText(lastButton); }

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

mPref.edit()

.putString("lastButton", "button1") .apply();

} else if (v.getId() == R.id.button2) { mPref.edit()

(36)

.putString("lastButton", "button2") .apply(); } } }

SharedPreferences

に保存する際のキーは

"lastButton"

となっていますので、これを定数 化してみましょう。定数化したものはリスト

1.27

となります。 リスト1.27: 良い例 SharedPreferencesRefine1Activity.java

public class SharedPreferencesRefine1Activity extends Activity implements View.OnClickListener {

private SharedPreferences mPref; private EditText editText1;

private String KEY_LAST_BUTTON = "lastButton"; @Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

setContentView(R.layout.activity_many_handlers); findViewById(R.id.button1).setOnClickListener(this); findViewById(R.id.button2).setOnClickListener(this); editText1 = (EditText) findViewById(R.id.editText1);

mPref = PreferenceManager.getDefaultSharedPreferences(this); String lastButton = mPref.getString(KEY_LAST_BUTTON, ""); editText1.setText(lastButton);

}

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

mPref.edit()

.putString(KEY_LAST_BUTTON, "button1") .apply();

} else if (v.getId() == R.id.button2) { mPref.edit()

.putString(KEY_LAST_BUTTON, "button2") .apply();

} }

(37)

} 見やすくなったでしょうか。そして前述の問題点は解決できているでしょうか。キー の

"lastButton"

が定数化されていますが、実は解決できていません。なぜならキーが複数ある 場合は、別のキーと取り違える可能性がまだ残っているからです。 こ の よ う な 場 合 、こ れ ら の 問 題 を 回 避 す る に は リ ス ト

1.28

の よ う な

SharedPref-erences

の ラ ッ パ ー ク ラ ス を 作 る と 回 避 で き ま す 。こ の ラ ッ パ ー ク ラ ス で は キ ー を

KEY_LAST_BUTTON

の定数にしていますが

private

になっているので、メソッドの呼 び元はキーを意識しなくてよくなっており、キーを取り違える心配もありません。またアクセ スする処理を

getLastButton

メソッドと

putLastButton

メソッドにまとめているので、誤っ て別のデータ型を入れることはありません。すなわちラッパークラスに前述の問題点を閉じ 込めているのです。 リスト1.28: MySharedPreferences.java

public class MySharedPreferences {

private static final String KEY_LAST_BUTTON = "lastButton"; private SharedPreferences mPref;

private SharedPreferences.Editor mEditor;

public MySharedPreferences(SharedPreferences mPref) { this.mPref = mPref;

}

public MySharedPreferences edit() { mEditor = mPref.edit();

return this; }

public void apply() { mEditor.apply(); mEditor = null; }

public String getLastButton() {

return mPref.getString(KEY_LAST_BUTTON, ""); }

public MySharedPreferences putLastButton(String value) { mEditor.putString(KEY_LAST_BUTTON, value);

(38)

} }

このラッパークラスを扱うように直したものがリスト

1.29

となります。

MySharedPrefer-ences

を使うようにしたことで、前述の問題がいずれも解決していることがわかります。

リスト1.29: 良い例 SharedPreferencesRefine2Activity.java

public class SharedPreferencesRefine2Activity extends Activity implements View.OnClickListener {

private MySharedPreferences mPref; private EditText editText1;

@Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

setContentView(R.layout.activity_many_handlers); findViewById(R.id.button1).setOnClickListener(this); findViewById(R.id.button2).setOnClickListener(this); editText1 = (EditText) findViewById(R.id.editText1); mPref = new MySharedPreferences(

PreferenceManager.getDefaultSharedPreferences(this)); String lastButton = mPref.getLastButton();

editText1.setText(lastButton); }

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

mPref.edit()

.putLastButton("button1") .apply();

} else if (v.getId() == R.id.button2) { mPref.edit() .putLastButton("button2") .apply(); } } }

(39)

動けばいいという意味ではラッパークラス作らず、そのまま書いてしまっても違いはありま せん。しかしコード後から見たときや修正するときに、キーやデータ型のように手で合わせな いといけないものがそのままになっていると、誤って記述した際に不具合の原因となってしま います。特にこれらのキーやデータ型は誤って記述してしまうとコンパイルエラーにもなら ないため非常に厄介です。これら避けるため、ラッパークラスを作成して問題点の責務をラッ パークラスに持たせ、扱う側が無駄に悩まなくていいようにしましょう。ラッパークラスは単 にラッピングしているのではなく、そういった責務も封じ込めているのです。

1.10

Application

Service

の実体を突っ込んでいる

Android

アプリケーションではバックグラウンドで処理を行いたいときに

Service

を使用 します。

Service

の生存期間は

Activity

に比べて長めで、ものによってはプロセスの開始と同 時に起動してずっと動作するものもあります。ずっと動作するからと

Application

に保持させ たりすると、

Service

のライフサイクルから逸脱し、面倒な不具合に繋がることがあります。

リスト

1.30

onCreate

メソッド内で

Application

に自身を保持させ、

onDestroy

メソッ ド内で解除するような乱暴な実装の例です。この

Service

hoge

メソッドという文字列返 すだけのメソッドが実装されています。

リスト1.30: 悪い例 ResidentDameService.java

public class ResidentDameService extends Service { private IResidentDameService.Stub mBinder =

new IResidentDameService.Stub() { };

@Override

public IBinder onBind(Intent intent) { return mBinder;

}

@Override

public void onCreate() { super.onCreate();

DameApplication app = (DameApplication) getApplication(); app.setDameService(this);

(40)

@Override

public void onDestroy() { super.onDestroy();

DameApplication app = (DameApplication) getApplication(); app.setDameService(null);

}

public String hoge() { return "hogehoge"; } } この

Service

hoge

メソッドから文字列を受け取り、画面に表示するだけの乱暴な実装 の

Activity

がリスト

1.31

になります。 リスト1.31: 悪い例 ResidentDameActivity.java

public class ResidentDameActivity extends Activity implements View.OnClickListener {

private EditText editText1; @Override

protected void onCreate(Bundle savedInstanceState) { /* 省略 */

}

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) {

DameApplication app = (DameApplication) getApplication(); ResidentDameService service = app.getDameService();

if (service != null) { editText1.setText(app.getDameService().hoge()); } } } } こ の

Activity

ResidentDameService#onCreate

が 呼 び 出 さ れ た 後 な ら ば 動 く こ と は 動 き ま す 。し か し

Service

が 起 動 す る 前 や 再 起 動 し た 場 合 は

DameApplica-tion.getDameService()

null

を返すため正常に動作しません。また

Activity

側で対応し ようとしても、起動や再起動が発生したか否かのイベントが通知されないため、対応が行

(41)

えません。これを回避するには

Application

に保持させたりせず、標準の手順にあるとおり

ServiceConnection

を使います。

まず

onCreate

メソッド内で

Application

に自身を保持させたり、

onDestroy

メソッド内で 解除したりするような乱暴な実装を削除します。

リスト1.32: 良い例 ResidentRefineService.java

public class ResidentRefineService extends Service {

public class LocalBinder extends IResidentRefineService.Stub { public ResidentRefineService getService() {

return ResidentRefineService.this; }

}

private LocalBinder mBinder = new LocalBinder(); @Override

public IBinder onBind(Intent intent) { return mBinder;

}

public String hoge() { return "hogehoge"; } }

Activity

側もリスト

1.33

のように、

ServiceConnection

を作成し、標準の手続きにした がって

Service

bind

するようにします。 リスト1.33: 良い例 ResidentRefineActivity.java

public class ResidentRefineActivity extends Activity implements View.OnClickListener {

private EditText editText1;

private ResidentRefineService mService;

ServiceConnection mServiceConnection = new ServiceConnection() { @Override

public void onServiceConnected

(ComponentName name, IBinder service) {

mService = ((LocalBinder) service).getService(); }

(42)

public void onServiceDisconnected(ComponentName name) { mService = null;

} };

@Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

findViewById(R.id.button1).setOnClickListener(this); editText1 = (EditText) findViewById(R.id.editText1); }

@Override

protected void onStart() { super.onStart();

Intent intent = new Intent(this, ResidentRefineService.class); bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); }

@Override

protected void onPause() { super.onPause();

unbindService(mServiceConnection); }

@Override

public void onClick(View v) { if (v.getId() == R.id.button1) { if (mService != null) { editText1.setText(mService.hoge()); } } } } 一見面倒には見えますが、不安定な状態をハンドリングできるようにするために必要な手続 きです。これは

Activity

Service

にはライフサイクルの違いこそあれ、2つは対等な関係 であるからです。対等な2つを接続するということはそれなりの手順が必要になるというこ とです。

(43)

ServiceCon-nection

のリークに繋がります。この例では

Service

bind/unbind

するタイミングは

on-Start()/onStop()

を使用していますが、

onCreate()/onDestroy()

onPause()/onResume()

など、

Activity

のライフサイクル上で必ず通る組み合わせを使用するとリークを回避すること ができます。

1.11

OutOfMemoryError

避けが悪影響だけ出している

OutOfMemoryError

、通称

OOM

が発生したときどのような対策をしているでしょうか。 まさか原因の特定もせずにそこらじゅうの変数に手当たりしだい

null

を代入したりしていな いでしょうか。

Android

のアプリケーション開発では

Memory Analyzer(

以下

MAT)

と呼ば れるツールが使用できます。このプラグインでは実行中のアプリケーションのメモリをダン プし、どのクラスのインスタンスがどれだけメモリを専有し、それが何故解放されないのかを 調べることができます。

MAT

Eclipse

のプラグインとして動作しますが、スタンドアロン 版もあります。

(44)

Eclipse

の場合、このプラグインをインストールすると、

DDMS

パースペクティブの

Dump

HPROF file

ボタンを押すとダンプしたメモリを解析できるようになります。

図1.2 Dump HPROF fileボタン

たとえば次のような無駄に大きな

byte

配列をもつ

Activity

の解析をしてみましょう。メン バ変数として

16MB

byte

配列を保持しています。

リスト1.34: LargeBytesActivity.java

public class LargeBytesActivity extends ActionBarActivity { private byte[] mLargeBytes = new byte[16 * 1024 * 1024]; @Override

protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } この

16MB

byte

配列をメモリリークの原因ということにして、

MAT

を使って追ってみ ましょう。

まず最初に

Dump HPROF file

ボタンを押します。すると図

1.3

のような画面が表示され ます。この画面ではおおよそメモリを大きく使用しているクラスが何であるのかが円グラフ で表示されます。

(45)

図1.3 Memory Analyzerの初期表示

今回はメモリを大きく保持しているクラスが何であるかを確認したいので、ヒストグラムを 表示します。ヒストグラムを表示するには図

1.4

のヒストグラムのボタンを押します。

(46)

図1.4 Create a histogramボタン

すると図

1.5

のように、各クラスのインスタンスの数と、どれだけメモリを占有している かが表示されます。今回の場合では

byte

配列が一番大きくメモリを占有しているのがわかり ます。

図 1.1 Memory Analyzer http://www.eclipse.org/mat/
図 1.2 Dump HPROF file ボタン
図 1.3 Memory Analyzer の初期表示
図 1.4 Create a histogram ボタン
+7

参照

関連したドキュメント

Toyotsu Rare Earths India Private Limited、Toyota Tsusho Gas E&amp;P Trefoil Pty Ltd、. Toyota Tsusho

機器表に以下の追加必要事項を記載している。 ・性能値(機器効率) ・試験方法等に関する規格 ・型番 ・製造者名

A carnet is an international, unified Customs document under an international system based on “Customs Conventions on the Temporary Importation of Private Road Vehicles”

具体音出現パターン パターン パターンからみた パターン からみた からみた音声置換 からみた 音声置換 音声置換の 音声置換 の の考察

パターン1 外部環境の「支援的要因(O)」を生 かしたもの パターン2 内部環境の「強み(S)」を生かした もの

今回、子ども劇場千葉県センターさんにも組織診断を 受けていただきました。県内の子ども NPO

Q7 建設工事の場合は、都内の各工事現場の実績をまとめて 1

現状では、3次元CAD等を利用して機器配置設計・配 管設計を行い、床面のコンクリート打設時期までにファ