AndroidアプリテストのためのJenkins設定 3

前回の続きです。今回は最後のステップ「3. プロジェクト毎の設定」の内容を記述します。

1. 実行環境の設定
Jenkinsを実行させるためのマシン環境の設定です。

2. Jenkins共通設定
Jenkinsの実行全般に関わる設定です。

3. プロジェクト毎の設定 (←今回)
Jenkinsに登録した各プロジェクトの固有の設定です。

3. プロジェクト毎の設定

Jenkinsのトップにアクセスします。左側メニューの「新規ジョブ作成」をクリックします。「ジョブ名」には任意のジョブ名を入力し、「マルチ構成プロジェクトのビルド」を選択します。「OK」をクリックすると、ジョブの設定画面に遷移します。

ソースコード管理
このエリアでは、テストプロジェクトとテスト対象プロジェクトの各々について設定します。
まずはテスト対象プロジェクトから。
「Multiple SCMs」を選択し、「Add SCM」をクリックして「Git」を選択します。

「Repositories」にリポジトリURLを入力します。(リポジトリURLはBitbucketにアクセスすると記載されています。)

Repository URL: git@bitbucket.org:Kyakujin/repo_sampleproject.git

「Branches to build」に以下を入力します。

Branch Specifier (blank for default): **

下の方にある「高度な設定」ボタンをクリックし、以下を設定します。

「Local subdirectory for repo(optional)」:repo_sampleproject
「Unique SCM name(optional)」: repo_sampleproject


次はテストプロジェクトについて設定します。
「Multiple SCMs」を選択し、「Add SCM」をクリックして「Git」を選択します。

「Repositories」にリポジトリURLを入力します。(リポジトリURLはBitbucketにアクセスすると記載されています。)

Repository URL: git@bitbucket.org:Kyakujin/repo_sampleproject_testproj.git

「Branches to build」に以下を入力します。

Branch Specifier (blank for default): **

下の方にある「高度な設定」ボタンをクリックし、以下を設定します。

「Local subdirectory for repo(optional)」:repo_sampleproject_testproj
「Unique SCM name(optional)」: repo_sampleproject_testproj

ビルド・トリガの設定
定期的に実行させる場合はここで設定を行います。「SCMをポーリング」にチェックし、例えば以下のように設定します。

スケジュール: 0-59/30 * * * *

マトリックスの設定
Androidのバージョンや画面解像度等の組み合わせテストをここで設定することができます。今回の例では、OSバージョン2.3.3と4.2の元で、解像度としてWVGAとHVGAの組み合わせテストを実行します。

「軸の追加」ボタンをクリックします。「ユーザ定義」を選択して以下を入力。
(以下、「軸の追加」ボタンクリック毎に設定します。)

名前:OS
値:2.3.3 4.2

名前:display
値:WVGA HVGA

名前:density
値:mdpi

名前:abi
値:armeabi armeabi-v7a

なお、abiについてはOSバージョンに応じたarmeabiを指定する必要があります。各OSに対応したarmeabiは以下のコマンドで調べることができます。

cd /var/lib/jenkins/android-sdk-linux/tools
./android list targets

「組み合わせフィルター」をチェックし、フィルターに以下を入力します。その下の「各設定を順次起動」にチェックを入れます。ここでは、OSが「2.3.3」でありabiが「armeabi」の構成、またはOSが「4.2」でabiが「armeabi-v7a」の構成での実行を指定しています。

(OS=="2.3.3" && abi=="armeabi") || (OS=="4.2" && abi=="armeabi-v7a")

ビルド環境
「Run an Android emulator during build」にチェックを入れ、以下を設定します。

Android OS version:${OS}
Screen density:${density}
Screen resolution:${display}
Device locale:ja_JP
SD card size:16M
Target ABI:${abi}

「Common emulator options」配下の項目をすべてチェックを外します。特に、「Show emulator window」にチェックが入っているとエミュレータの起動に失敗するようで、ここはハマりポイントの1つのようです。

ビルド
「ビルド手順の追加」をクリックし、シェルの実行を選択します。そして「シェルスクリプト」の欄に以下を入力します。

cd ${WORKSPACE}
export PATH=$PATH:/var/lib/jenkins/android-sdk-linux/tools:/usr/bin

echo UPDATE PROJECT
android update project -p ./repo_sampleproject
echo UPDATE TEST PROJECT
android update test-project -m ../repo_sampleproject -p ./repo_sampleproject_testproj

「ビルド手順の追加」をクリックします。「Antの呼び出し」を選択し、以下を設定します。

使用するAnt: Ant
ターゲット: clean debug install

「高度な設定」をクリックし、以下を設定します。

ビルドファイル:./repo_autoeco/build.xml
プロパティ:sdk.dir=/var/lib/jenkins/android-sdk-linux

もう一つ「ビルド手順の追加」をクリックします。「Antの呼び出し」を選択し、以下を設定します。

使用するAnt: Ant
ターゲット: clean debug install test

「高度な設定」をクリックし、以下を設定します。

ビルドファイル:./repo_autoeco_testproj/build.xml
プロパティ:sdk.dir=/var/lib/jenkins/android-sdk-linux

「ビルド後の処理の追加」をクリックします。「成果物を保存」を選択し、「保存するファイル」に以下を入力します。

**/bin/*.apk

「ビルド後の処理の追加」をクリックします。「E-mail通知」を選択し、メールアドレスを入力。ビルド失敗時などにメールが送信されます。「不安定ビルドも逐一メールを送信」にもチェックを入れます。


以上まで設定出来れば、JenkinsによるAndroidのテストが実行出来ます。Jenkinsのトップ画面からジョブ(AndroidSampleProject)を選択し、画面遷移後の左側メニューにある「ビルド実行」をクリックします。

ビルドが成功すれば、以下の様な青い丸印が組み合わせマトリクスに表示されます。(失敗すれば赤丸になります)。

また、コンソール出力を見れば、以下のようにテストが成功したことが確認できます。

【注意】
64bitのLinux環境でAndroidエミュレータを実行させる場合、64bitに対応させるためのライブラリを別途インストールする必要があります。もしそれをしないままエミュレータを起動しようとすると、以下のようなメッセージが出ます。(メッセージ内容は環境によって異なるかもしれません。)
「/lib/ld-linux.so.2: bad ELF interpreter: No such file or directory」

実際に次のようなコマンドでエミュレータを作り、それが起動できるか試してみると具体的なエラーが分かります。

./android create avd -n avd4.1.2 -t 23 --abi armeabi-v7a
./emulator -avd avd4.1.2 -scale 0.5

だいたいにおいて、エラーメッセージとして「◯◯ライブラリが無い」という意味のメッセージが出るので、その都度◯◯に該当するライブラリをインストールしていくのが手っ取り早いと思います。私の環境の場合は、以下のようなライブラリをインストールすることにで問題解決しました。

yum install ld-linux.so.2
yum install libstdc++.so.6
yum install libz.so.1

上記エラーを含め、Jenkins実行時に何らかのエラーが出る場合は、およそ以下のことが原因のようです。

  • 64bit対応のためのライブラリが未インストール
  • Android SDKのアップデートが不完全
  • jenkinsユーザが然るべきフォルダにアクセスできない(アクセス権がない)

一番目の原因の対応については先ほど述べました。
二番目のSDKアップデート不完全については、再度「./android update sdk --no-ui」を実行します。
最後の問題については、「chown -R jenkins:jenkins [ディレクトリ] 」でjenkinsユーザにアクセス権を与えます。


以上で、AndroidアプリテストのためのJenkins設定は完了です。この他、FindBugsやその他諸々の解析ツールを組み込むこともできますので、テストの目的に応じて各々のツールを追加インストールしてもらえればと思います。

お疲れ様でした。

AndroidアプリテストのためのJenkins設定 2

前回の続きです。今回は以下のステップのうち「2. Jenkins共通設定」の内容を記述します。

1. 実行環境の設定
Jenkinsを実行させるためのマシン環境の設定です。

2. Jenkins共通設定 (←今回)
Jenkinsの実行全般に関わる設定です。

3. プロジェクト毎の設定
Jenkinsに登録した各プロジェクトの固有の設定です。

2. Jenkins共通設定

2.1 プラグインのインストール
Jenkinsへアクセスします。
http://localhost:8080
(または、https://:8080/)

トップ画面の左端にあるメニュー「Jenkinsの管理」から、「プラグインの管理」→「利用可能」タブと辿り、下記のプラグインをイントールします。

  • Git Plugin
  • Android Emulator Plugin
  • Jenkins Multiple SCMs plugin

「インストール完了後、ジョブがなければJenkinsを再起動する」にチェックを入れ、再起動させます。
(ちなみに、ブラウザのアドレスバーに「http://localhost:8080/safeRestart」と打ち込んで実行することでも、Jenkinsの再起動ができます。)

2.2 ユーザ登録と権限設定
トップ画面の「Jenkinsの管理」から、「グローバルセキュリティの設定」→「セキュリティを有効化」にチェック→「アクセス制御」-「ユーザー情報」へと辿ります。「Jenkinsのユーザーデータベース」を選択して、「ユーザーにサインアップを許可」のチェックをはずします。

「管理者権限」→「行列による権限設定(プロジェクト単位) 」を選択します。

最後に「CSRF対策」にチェックを入れ、「Default Crumb Issuer」を選択します。

適用した後、Jenkinsのトップ画面へ行くとサインアップ画面が表示されるので、ユーザー情報としてすべての項目を入力します。

2.3 環境変数等の項目設定

グローバル プロパティ
トップ画面の「Jenkinsの管理」から、「システムの設定」→「グローバル プロパティ」と辿り、「環境変数」にチェックを入れます。
追加ボタンをクリックし、「キーと値のリスト」に以下を設定します。

キー: ANDROID_HOME
値: /var/lib/jenkins/android-sdk-linux

Android
Android」の設定項目「Android SDK root」に以下を入力します。

Android SDK root: ${ANDROID_HOME}

JDK
「インストール済みJDK」のエリアの「JDK追加」ボタンをクリックし、「自動インストール」のチェックをはずして以下を設定します。

名前: JDK
JAVA_HOME: /usr/java/default

Git
以下の項目を設定します。なお、gitそのものがマシンへインストールされていない場合は、インストールを済ませておきます。(CentOSへのgitのインストール手順は検索すれば沢山出てきますので、その手順については割愛させて頂きます。)

Name: Default
Path to Git executable: /usr/bin/git

Ant
「インストール済みAnt」のエリアの「Ant追加」ボタンをクリックし、「自動インストール」のチェックをはずして以下を設定します。

名前: Ant
ANT_HOME: /etc/alternatives/anthome

Git plugin
以下を設定します。

Global Config user.name Value: jenkins
Global Config user.email Value: jenkins@jenkins-server


2.4 sshの設定
jenkinsユーザがBitbucketにアクセス出来るように設定します。そのためには、以下のコマンドにて鍵を生成し、Bitbucketに公開鍵の内容を登録します。(何か聞いてきたらメッセージ内容を確認して、Enterを押して進めます)

sudo -u jenkins ssh-keygen

鍵(id_rsaとid_rsa.pub)が生成されていることを確認します。

ls -la /var/lib/jenkins/.ssh

bitbucket.orgにアクセスし、アカウント管理から「SSHキー」を選択し、「鍵を追加」をクリックします。ラベルには任意の名前を設定し、「キー」の欄にid_rsa.pubの内容を貼り付けます。(catコマンドなどで表示してコピー・ペースト)

cat /var/lib/jenkins/.ssh/id_rsa.pub

この時点で、公開鍵を作成した作業環境から一旦bitbucket.orgにアセクスします。それによりサイト認証(.ssh/known_hostファイル生成)をすることができます。途中何か聞いてきたらメッセージを確認して「yes」で進めます。

sudo -u jenkins git clone git@bitbucket.org:Kyakujin/repo_sampleproject.git /var/lib/jenkins/tmp

(「git@bitbucket.org:Kyakujin/repo_sampleproject.git」の部分は、各自で登録したリポジトリURLを指定します。)


以上で、「2. Jenkins共通設定」の内容は終わりです。次回は最後のステップ「3. プロジェクト毎の設定」の内容を記述します。

AndroidアプリテストのためのJenkins設定 1

今更ながらですが、個人的に作成しているAndroidアプリの作成プロセスにJenkinsを導入してみました。

個人で行う小規模のアプリ作成であれば、とりあえずビルドしてエミュレータや実機でテストして、そして、ある程度の品質が確認できれば公開することは簡単ですが、それでも作成したアプリ数が増えるにつれ、バグ修正や機能追加などが大きな負担になってきます。テストケースが飛躍的に増えるからです。

また、Androidの場合はSDKバージョンが変わると、従来正常に動作していた機能が想定外の挙動をするケースも有るため、Androidアプリ作成においては、リグレッションテストをいかに効率的に行うか(≒自動化させるか)が肝となってきます。

そこで、Jenkinsです。JenkinsはCI(継続的インテグレーション)を実行するためのツールで、特にAndroidのような様々なハード/OSバージョン環境下でのテストが求められるプラットフォームでは非常に有用だと判断しました。

しかし、Jenkinsを使用するには種々の設定が必要となり、その設定は慣れていないと非常に手間取ってしまいます。そこで、Androidアプリ作成におけるJenkinsの設定内容を、記録としてここに残したいと思います。ただ、私もJenkinsについてはそれほど深く関知はしておりませんので、もしかしたら無駄な設定があるかもしれませんが、とりあえずテスト自動化が実現できた設定内容を掲載します。

参考にさせていただいた主な資料は以下の2点です。

基本的には、上記書籍で述べられている設定をベースにしました。ただし、書籍の方ではバージョン管理としてSubversionを採用していたので、Gitを使いたかった私としては上記サイトの記事をGit設定周りを中心に適宜参照させてもらいました。

なお、今回の実行環境(OS)は次の通りで、仮想環境に構築したCentOSの方にAndroid SDKやJenkinsをインストールしました。

VMware Player: 5.0.2
ホストOS: Windows 7 (64bit)
ゲストOS: CentOS 6.4 (64bit)

Jenkinsには多くのプラグインがあり、テストの目的に応じてそれらを使用することができますが、今回やりたかったことは、「リポジトリからソースを取得してビルドし、いくつかのハード環境(エミュレータ)のもとに、テストプロジェクトを走らせる」ということ。ですので、Jenkinsでモジュールを走らせる最低限の設定になります。

設定は大きく3つのステップに分かれます。

1. 実行環境の設定
Jenkinsを実行させるためのマシン環境の設定です。

2. Jenkins共通設定
Jenkinsの実行全般に関わる設定です。

3. プロジェクト毎の設定
Jenkinsに登録した各プロジェクトの固有の設定です。

以降、この3つのステップ順に具体的に記述していきます。

なお、Jenkinsで実行させるプロジェクトのソースは、あらかじめリポジトリに登録しておく必要があります。今回のケースではBitbucketを利用しました。(Bitbucketにリポジトリを作成する方法は、ネットで検索すれば沢山出てくると思いますので、ここでは割愛させて頂きます。)
また、今回の設定で使ったソースコードは、テスト対象プロジェクトとして「SampleProject」、テストプロジェクトを「SampleProjectTest」として作成しました。Jenkinsの設定そのものとはあまり関係ありませんが、一応ソースをGitHubに置いておきます。

1. 実行環境の設定
まずはJenkins実行に必要な関連モジュール群をインストールします。

1.1 JDKのインストール

(1) JDKダウンロードページへアクセス
Java SE Development Kit 7 Downloads」のページへアクセスします。下記をクリックするとダウンロードページへ移行します。
Java SE Development Kit 7 - Downloads | Oracle Technology Network | Oracle

(2) rpmファイルのダウンロード
黄色の矢印で示した部分(jdk-7u25-linux-x64.rpm)をクリックし、rpmファイルをダウンロードします。

(3) rpmのインストール
ダウンロードしたrpmをインストールします。以降、rootユーザで実行します。

rpm -ivh jdk-7u25-linux-x64.rpm

(4) 環境変数の設定。
profileを編集します。

vi /etc/profile

下記内容を追加します。

export JAVA_HOME=/usr/java/default
export PATH=$PATH:$JAVA_HOME/bin
export CLASSPATH=.:$JAVA_HOME/jre/lib:$JAVA_HOME/lib:$JAVA_HOME/lib/tools.jar

編集したprofileを反映させます。

source /etc/profile

(5) JDKのバージョン確認
念の為、JDKバージョンがインストールしたものであるかを確認します。

java -version 

JDKバージョンがインストールしたものでなければ、下記コマンドで表示されるメニューでOralce JDKを使うように設定します。

update-alternatives --config java 

1.2 Jenkinsのインストール

Jenkinsをダウンロードしインストールを実行します。途中でなにか聞いてきたら、メッセージを確認して「y」で先に進めます。

wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
install jenkins

Jenkinsサービスを起動します。

service jenkins start

ブラウザから以下のアドレスにアクセスし、Jenkinsが起動したことを確認します。

http://localhost:8080/


(備考: Jenkinsのインストールが完了すると、システムにjenkinsユーザが新たに追加されます。)

1.3 Antのインストール

Antをダウンロードします。

wget http://ftp.riken.jp/net/apache/ant/binaries/apache-ant-1.9.2-bin.tar.gz

tarボールを展開し、展開されたディレクトリを/user/libに移動します。

tar zxvf apache-ant-1.9.2-bin.tar.gz
mv apache-ant-1.9.2 /usr/lib

リンクを張ります。

ln -s /usr/lib/apache-ant-1.9.2/bin/ant /etc/alternatives/ant
ln -s /usr/lib/apache-ant-1.9.2/ /etc/alternatives/anthome
ln -s /etc/alternatives/ant /usr/bin/ant

profileを編集します。

vi /etc/profile

下記内容を追加します。

export ANT_HOME=/etc/alternatives/anthome

編集したprofileを反映させます。

source /etc/profile

1.4 Android SDKのインストール

Android SDKをダウンロードします。(ここではr22.0.5をダウンロードしていますが、最新のSDKを取得したい場合はDownload Android Studio and SDK toolsを確認して下さい。)

wget http://dl.google.com/android/android-sdk_r22.0.5-linux.tgz

tarボールを展開し、展開されたディレクトリをJenkinsのディレクトリに移動します。

tar xvf android-sdk_r22.0.5-linux.tgz
mv android-sdk-linux/ /var/lib/jenkins/

所有者をjenkinsユーザに変更します。

cd /var/lib/jenkins
chown -R jenkins:jenkins android-sdk-linux

(ちなみに自分の経験では、Jenkinsの実行にて何らかのエラーが発生する場合は、SDKなどjenkinsユーザがアクセスすべきディレクトリ/ファイルに、jenkinsユーザのアクセス権が設定されていなかったケースが多々ありました。)

SDKをアップデートします。途中でライセンスの確認等を聞かれたら、文面を確認して「y」で進めます。

cd android-sdk-linux/tools
./android update sdk --no-ui

以上で、「1. 実行環境の設定」の内容は終わりです。次回は次のステップ「2. Jenkins共通設定」を記載します。

Androidアプリのコード中にアサーションを使いたい

JavaでもC++でも、コードの中に成り立つべき条件があり、それを検証する場合はアサーションを活用することができます。

例えばJavaならこのように書けます。

public testCompare(){
    // 中略
    int a = 0;
    int b = 1;
    assert isEqualNum(a, b); 
}

private boolean isEqualNum(int a, int b) {
    return a == b ? true : false;
}

このassert機能は、Eclipseの設定でassertを有効化すれば使用可能になるのですが、Androidのプロジェクトを実行ターゲットにした場合、なぜかそれができませんでした。Javaアプリケーションがターゲットであれば引数タブに「-ea」をつければ良いのですが、Androidの場合は引数タブそのものが無いんです。

しかたないので、擬似的にassert機能を書きました。

public testCompareForAndroid(){
    // 中略
    int a = 0;
    int b = 1;
    if (!isEqualNum(a, b)) throw new AssertionError("Assertionエラー!");
}

// isEqualNum()は最初の例と同じ

C言語の関数ポインタのような実装をすればもっとスッキリ書けそうなんですが、当面はこれでしのぎます。

TimePickerDialogのonTimeSet()が2回連続でコールされてしまう件の回避例

時間設定の便利なコンポーネントとしてTimePickerDialogがありますが、このダイアログはSDKバージョン4.1.x以降から想定外の動作をするケースがあります。今回はそれに対する回避策の例をメモ書きします。

まず、TimePickerDialogというのはどういうものかというと、以下のようなUIとして画面上に表示されます。

 

このダイアログで設定(OK)ボタンをタップすると、onTimeSet()というイベントリスナー関数が呼ばれ、この中で設定時刻が関数パラメータ経由で渡されてきます。そして、任意の処理が実行されるわけです。任意の処理というのは、例えば設定時刻をDBに登録したりとか、それはアプリの機能仕様に従ってお好きなように記述します。

ここで問題なのですが、SDKバージョン4.1.x(Jelly Bean)以降から、このダイアログの設定ボタンをタップすると、なぜかonTimeSet()が二回連続で実行されるようになりました。もしDBへのデータ登録処理を書いていれば、データ登録が二重に実行されてしまいます。APIのバグらしき現象。

この問題が修正されるまで待っているわけにもいかないと思いますので、とりあえず回避策を考えてみました。もちろん、ここで書く回避策は単なる一つの例ですので、他にもやりようによっては様々な方法が考えられるでしょう。

私の場合、回避策として以下の方針を考えました。TimePickerDialogを継承したクラスを作成し、そこで設定時刻取得の仕組みを新たに作るという考え方です。

  1. onTimeSet()の中では何もせず、代わりに設定ボタンのリスナーを再定義して、その中で実行したい処理を行う。
  2. 設定時刻を取得するために、TimePickerDialogを継承したクラスにて、時刻選択するたびに時刻をメンバ変数に保持する。
  3. 設定ボタンをタップした時、つまり1.のリスナーの中で、2.で保持されている時刻を取得する。
  4. 3.で時刻を取得した後に、DBへデータを書き込むなり任意の処理を行う。

この方針をソースコードに反映すると、以下のようになります。

// 設定時刻を格納するための変数(TimeDataクラスの定義は後述)
private TimeData mTimeData; 

//onCreateViewの中などでインスタンスを取得
 mTimeData = TimeData.getInstance();
// アプリの中で時刻設定を実行するときに呼ばれるメソッド
private void setTime() {
    
    TimePickerDialog.OnTimeSetListener listener = new TimePickerDialog.OnTimeSetListener() {
        // 問題のコールバック関数。ここではあえて何もしない。
        public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
            /** DO NOTHING */
        }
    };

    // TimePickerDialogを継承したカスタムクラスのインスタンスを取得
    if (timePickerDialog == null) {
        timePickerDialog = new CustomTimePickerDialog(getActivity(), listener, 0, 0, true);
        // TimeDataのインスタンスを渡し、時刻選択される毎に時刻データが格納されることを期待
        timePickerDialog.setTimeData(mTimeData);
    }

    // ボタンの定義
    timePickerDialog.setButton(
            // onTimeSet()はBUTTON_POSITIVEのタップでコールされる
            DialogInterface.BUTTON_POSITIVE,
            "設定",
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    // ボタンがクリックされた時の動作
                    if (mTimeData != null) {
                        // 時刻を取得する
                        int hour = mTimeData.getHour();
                        int minute = mTimeData.getMinute();
                        //
                        // ここで任意の処理を記述する
                        //
                    }
                }
            }
            );

    timePickerDialog.show();
}
// TimePickerDialogを継承したカスタムクラス
// (TimeSettingは自前のインターフェスで後述にて定義)
public class CustomTimePickerDialog extends TimePickerDialog implements TimeSetting {

    private TimeData mTimeData;

    public CustomTimePickerDialog(Context context, OnTimeSetListener callBack,
            int hourOfDay, int minute, boolean is24HourView) {
        super(context, callBack, hourOfDay, minute, is24HourView);
    }

    // 時刻が選択される度にコールされるリスナー関数
    @Override
    public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
        super.onTimeChanged(view, hourOfDay, minute);
        if(mTimeData != null) {
            // 現在選択している時刻を格納
            mTimeData.setHour(hourOfDay);
            mTimeData.setMinute(minute);
        }
    }

    @Override
    public TimeData getTimeData() {
        return mTimeData;
    }

    @Override
    public void setTimeData(TimeData data) {
        mTimeData = data;
    }
}
// 設定時刻を格納するためのクラス
public class TimeData {
    private int mHour = 0;
    private int mMinute = 0;

    private static TimeData instance;

    private TimeData() {
    };

    public static synchronized TimeData getInstance() {
        if (instance == null) {
            instance = new TimeData();
        }
        return instance;
    }

    public int getMinute() {
        return mMinute;
    }

    public void setMinute(int minute) {
        this.mMinute = minute;
    }

    public int getHour() {
        return mHour;
    }

    public void setHour(int hour) {
        this.mHour = hour;
    }
}
public interface TimeSetting {
    public abstract TimeData getTimeData();
    public abstract void setTimeData(TimeData data);
}

以上のコードで、一応は今回の問題を回避できました。ちなみに、上記コードでsetButtonでボタン定義するとonTimeSet()は一度コールされ、設定しなければ二度コールされてしまうという謎挙動です。

initLoader()とrestartLoader()のどちらを使うか

場合によってはrestartLoader()のみを使っても良さそう、というお話です。

Androidアプリにおいて、例えばデータベースからデータを非同期で読み込み、ListViewなどへ読み込んだデータを反映するときなどは、ローダーという機構を使用することができます。

そのローダーを使用するときの手順は、ざっくりと書けば下記のようになります。

  1. getLoaderManager()を使ってLoaderManagerのインスタンスを取得し、
  2. 取得したLoaderManagerのinitLoader()メソッドでローダーを初期化する。

この手順をLoaderCallbacks()インターフェースを実装したクラスに書けば、データ取得の実行段階に応じて、下記の各々のメソッドが自動的に呼ばれます。

  • onCreateLoader()…ローダーを最初に使うときに呼ばれる。
  • onLoadFinished()…データの読み込みが完了したときに呼ばれる。
  • onLoaderReset()…ローダーがリセットされたときに呼ばれる。

ところで、先にも書いたとおりローダーの初期化にはinitLoader()を実行するのですが、似たようなメソッドにrestartLoader()というのもあります。どちらもローダー開始機能を有しているのは同じですが、両者の違いは既存のローダー(同じIDのローダー)が存在していた場合に現れます。既存のローダーを「再利用する」のか、「破棄してローダーを再作成する」のかという違いです。

  • initLoader()…再利用する
  • restartLoader()…破棄してローダーを再作成する

この両者の機能の違いを勘案して、適宜initLoader()とrestartLoader()を使い分ければいいとは思うのですが、最初からrestartLoader()だけを使ってもよいのではないかという思いが私の頭によぎりました。ただ、そのとき懸念したことは、initLoader()とrestartLoader()の間に何らかの依存関係があり、それ故initLoader()のコールが必須なのかもしれないということです。

そこで、実際にinitLoader()とrestartLoader()の内部処理が記述されているソースコードを読み込んでみました。SDKに含まれているLoaderManager.javaというソースファイルに記述されています。実際にローダーを作成して返却している箇所を抜き出してみると…

initLoader()

info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
// 中略
return (Loader<D>)info.mLoader;

restartLoader()

info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
return (Loader<D>)info.mLoader;

ということで、両者とも結局はLoaderをクリエイトして返却していることは同じであり、各々の引数に設定するデータもreturnに至るまでの処理を見たところ、ローダーの新規作成時については同じようです。違いは、restartLoader()の方で分岐条件にてローダーを破棄する処理が記述されていること、initLoader()の方でローダーがあればそれをreturnしていることで、まあ仕様通りといえばその通りですが、ここではinitLoader()とrestartLoader()に直接の依存関係がない事が分かりましたので、その点だけでもソースを調べた甲斐がありました。

よって、ローダー再利用の必要がなければ、restartLoader()のみでローダー処理を記述しても問題ないように見えます。逆に言えば、例えば何らかのアクションで変更されたデータをListViewへ瞬時にリアルタイムに反映したい場合など、またローダーが再利用されたら困るような機能を実現したい場合などは、最初からrestartLoader()のみを使用する方が簡便かもしれません。

論より証拠ということで、最初からrestartLoader()のみを使用してデータベースから取得したデータをListViewに反映させてみたところ、想定通りに動作させることができました。

VMware PlayerのゲストOSからネット接続をするための設定

このところ、仮想マシンにWebサーバを入れて色々と実験しておりまして、やはりというか数々の設定に苦慮することが多々あります。そこで、解決したことを覚書として記録に残しておきたいと思います。

今回は、VMware Player上のゲストOSに固定IPを割り当て、そこからインターネット接続するための設定です。

主な登場人物は次の通りで、ホストOSであるWindows 7は自宅の無線LANルーター経由でネット接続が出来る環境です。

今回やりたいことは以下の2点。

1. 「ホストOS」から、固定IPを割り当てた「ゲストOS」上のWebサーバー(Apache)へ接続する。
2. 「ゲストOS」から、インターネット接続する。

1点目は大した問題もなく出来ましたが、2点目のゲストOSからのネット接続に意外と四苦八苦したので、今後の為にも2点目を実現させるためのヒントとして設定内容を残したいと思います。以下、【ホストOSでの設定】と【ゲストOSでの設定】に分けて設定内容を記載します。

【ホストOSでの設定】

(1) 無線LANアダプタの設定内容です。「VMware Bridge Protocol」にチェックが入っていることを確認します。ちなみに、この設定画面は次をたどることで出てきます。「Windowsスタートメニュー」→「コントロールパネル」→「ネットワークとインターネット」→「ネットワークと共有センター」→「アダプターの設定の変更」→「ワイヤレスネットワーク接続(※)」→「プロパティ」。以下、ネットワークアダプタの種類ごとに(※)を適宜読み替えてください。

(2) VMwareネットワークアダプタ(VMnet1)の設定内容です。「VMware Bridge Protocol」のチェックを外します。

インターネットプロトコルバージョン 4」をダブルクリック。ここの例では、IPアドレスに192.168.11.12を割り当てます。

(3) VMwareネットワークアダプタ(VMnet8)の設定内容です。「VMware Bridge Protocol」のチェックを外します。他はいじりません。

(4) VMware Playerの仮想マシン設定(今回対象のゲストOSの設定)のうち、「ネットワークアダプタ」の設定内容です。右側の「ネットワーク接続」のところでブリッジを選択します。


【ゲストOSでの設定】

(5) 次はゲストOS(Fedora 18)を立ち上げての設定です。ネットワーク設定画面を開き、IPv4セッティングタブ内にて方式を「手動」に変え、ここで固定IPアドレスとして例えば192.168.11.11を割り当てます。そして、ゲートウェイとして無線LANルーターIPアドレスである192.168.11.1を設定します。

以上の設定でネットワークの再起動もしくはマシンそのものを再起動することにより、「ホストOS」から「ゲストOS(192.168.11.11)」へ接続でき、且つ「ゲストOS」からインターネット接続ができました。

・ホストOSからゲストOSへ接続

・ゲストOSからインターネット接続

もし、上記の設定でも接続できない場合は、ゲストOSでSELinuxファイアウォールを無効にして試してみるといいかもしれません。あと、/etc/hostsのホスト定義の見直しなど。

今回は冒頭で述べたやりたい事項が一応達成はできたのですが、ネットワーク構築のプロフェッショナルな方から見たら、もしかしたら無駄な設定やおかしな設定があるかもしれません。その辺については今後分かり次第、適宜加筆修正して行きたいと思います。