TitaniumでAndroidのバックグラウンド処理


Titanium Advent Calendar 2014の6日目です。
岩手でTitaniumを推進しているチイキットの伊藤です。Titanium界隈の巨人達がいる中で大変恐縮なのですが、初歩的なAndroidのバッググラウンド処理について書いていきます。

## やりたいこと
とてもシンプルで何秒、何分ごとに起動し、
条件をチェックして、条件が満たされている場合処理する、しない場合処理しない感じにします。
例えば、ニュースサイトからバックグラウンドで自動でデータを取得し、バックグラウンドに1時間立っていたら、ニュースを取得し、更新された記事を保存するような感じで使えると思います。
※今回はデータの保存まではやりません。。。

 * Androidでバックグラウンド処理
 * 時間＋αな条件での起動
 * ボタンを押したらバックグラウンド処理が停止・再開

##こんな感じの作ります
![screenshot_2014-12-06-21-31-48_1024.png](https://qiita-image-store.s3.amazonaws.com/0/19316/ff2264d4-f6df-d59c-ff26-aef839c17b02.png "screenshot_2014-12-06-21-31-48_1024.png")


### ログがこんな感じ
![スクリーンショット 2014-12-06 21.10.52.png](https://qiita-image-store.s3.amazonaws.com/0/19316/079e9f51-4ab3-848f-d1f5-9be35393b1f7.png "スクリーンショット 2014-12-06 21.10.52.png")

### ソースはこちらになります
環境はTitanium SDK 3.4.1.GAでAlloyつかってます。
> https://github.com/ganezasan/AlloyAndroidBackground.git

## バックグラウンド処理するには
serviceを使います。詳しいことを書くとボロがでるので、
こちらの公式ドキュメントを御覧ください。
> http://docs.appcelerator.com/titanium/3.0/#!/api/Titanium.Android.Service

こちらの順番で
 * tiapp.xmlにserviceを使うことを追記
 * service.jsにバックグラウンドで処理することを記載
 * index.jsにサービスの起動・バックグラウンドの検知を書く

## tiapp.xmlにserviceを使用する設定を追加する
service.jsというファイルをServiceとして実行し、指定された間隔（interval）で起動するように設定します。
service.jsファイルはapp/assets配下に置く設定になります。

``` tiapp.xml
	<android xmlns:android="http://schemas.android.com/apk/res/android">
	  <services>
	      <service type="interval" url="service.js"/>
	  </services>
	</android>
```
  
## service.jsにバックグラウンドで処理することを記載

今回はAlloy.Globalsに以下の変数を用意し、
「サービスが処理中じゃない」 かつ 「バックグラウンドに入っている」 かつ 「以前処理した時間より10秒たっている」場合に処理をするように記載しました。

``` alloy.js
Alloy.Globals.updateNow = false;
Alloy.Globals.activeFlg = true;
Alloy.Globals.updateOften = 10000;
```

処理中はAlloy.Globals.updateNowの変数をtrueにして処理が重複しないようにしています。
今回処理は記載していませんが、処理が終わったタイミングで、終了時刻を登録し、
Alloy.Globals.updateNowもfalseに変更しています。

``` service.js
Ti.API.info("service is running");
Ti.API.info(to_hhmi(Alloy.Globals.lastClosed));
Ti.API.info("service autoUpdate:" + Alloy.Globals.autoUpdate);
Ti.API.info("service activeFlg :" + Alloy.Globals.activeFlg);

// 現在時刻
var now = new Date();

if (!Alloy.Globals.updateNow
  && !Alloy.Globals.activeFlg 
  && (now.getTime() - Alloy.Globals.lastClosed.getTime() > Alloy.Globals.updateOften)) {
  Ti.API.info("============================");
  Ti.API.info("service Start!!!!");
  Ti.API.info("============================");
  Alloy.Globals.updateNow = true;


  //データ取得等の処理を記載する


  //処理終了の時刻を登録
  Alloy.Globals.lastClosed = now;   // データ取得時刻を保存
  //処理中に再度サービスの処理が起動されないようにするため
  Alloy.Globals.updateNow = false;
}
```


## index.jsにサービスの起動・バックグラウンドの検知を書く
アプリが起動したタイミングでserviceを起動し、
activityにアプリがバックグラウンドにいるのか、起動している状態なのかを検知するイベントをしこんでいきます。

``` index.js
//Activityのライフサイクルに応じてイベントを設定
$.index.addEventListener("open", function () {
  Ti.API.info("open");
  Alloy.Globals.activity = $.index.activity;
  Alloy.Globals.page = "index";
  Alloy.Globals.activeFlg = true;

  Alloy.Globals.activity.addEventListener("resume", function(d) {
    Ti.API.info("************************ Activity was resumed（再開したよ） ************************");
    Ti.API.info(Alloy.Globals.page);

    Alloy.Globals.activeFlg = true;
    Ti.API.info("activeFlg: true");
    Ti.API.info(Alloy.Globals.activeFlg);
  });
  Alloy.Globals.activity.addEventListener("pause", function(d) {
    Ti.API.info("************************ Activity was paused（バックグラウンドにはいったよ） ************************");
    Ti.API.info(Alloy.Globals.page);
    Ti.API.info(Alloy.Globals.activeFlg);

    if(Alloy.Globals.page == "index" && Alloy.Globals.activeFlg ){
      Alloy.Globals.activeFlg = false;
      // アプリが閉じた時の時間を保存
      Alloy.Globals.lastClosed = new Date();
      Ti.API.info("lastClosed: " + to_hhmi(Alloy.Globals.lastClosed));
    }
  });
  Alloy.Globals.activity.addEventListener("destroy", function() {
    Ti.API.info("Activity was destroied");
    Ti.API.info(Alloy.Globals.service);
    Ti.API.info(Alloy.Globals.serviceIntent);
  });
  // サービスを起動(Androidのみ)
  if (OS_ANDROID && Alloy.Globals.service == null) {
    Ti.API.info("************************ Service Start ************************");
    var intent = Titanium.Android.createServiceIntent({url: "service.js"});
    intent.putExtra("interval", Alloy.Globals.serviceOften);
    var service = Titanium.Android.createService(intent);
    service.start();
    Alloy.Globals.serviceStatus = true;
    Alloy.Globals.service = service;
    Alloy.Globals.serviceIntent = intent;
    $.index.addEventListener('android:back', function(){
      var intent = Ti.Android.createIntent({
        action: Ti.Android.ACTION_MAIN
      });
      intent.addCategory(Ti.Android.CATEGORY_HOME);
      Ti.Android.currentActivity.startActivity(intent);
    });
  }
});

```

注意する事として、Android特有のbackボタンを押したときにserviceがdestroyされてしまうため、backボタンを押したときにホームボタンと同じ動作をするよう変更しています。

こちらの記事を参考にさせて頂きました。
> http://selfkleptomaniac.org/archives/2128

もう１点たしか画面遷移したタイミングで、activityがpauseされるため、
バックグラウンドにいるのか、画面遷移したのかわからない状態になります。
その場合は、Alloy.Globals.pageで画面遷移する前にpageを変更するのを前回思いつきましたが、もっと良いやり方があるかと思いますので探してみてください。

## 完成

![ログGif](https://gist.github.com/ganezasan/0a3b16e12ec6d2582804/raw/0b6e5a3be2609642cb4c8f4e777325998aeaeb39/AndroidBackgroundLog.gif)


## まとめ
バックグラウンド処理はたまに使用することがあるので、
いままでやったことをまとめることができてよかったです。

ほんとは、[yagi](http://qiita.com/yagi_) さんの言っていたfirebaseで何かやってみたかったのですが、、

## 次は
明日は同じくチイキットの [@s4shiki](http://qiita.com/s4shiki) さんです。いやー岩手熱いですね〜！
よろしくお願いします！