GAS事始め(その1):スプレッドシートを操作する
社内ツールとしてGoogle DocsやGmailを使用している人も最近では多いと思いますが、しっかり使いこなせているでしょうか。 私は使いこなせていません。
GoogleのサービスはGAS(Google Apps Script)と連携することにより、アイディア次第でサービス間を横断した非常に強力な ツールを構築することができます。この記事では最も利用頻度が高いと思われるスプレッドシートをGASから制御する 基本的な操作方法を紹介します。
続きを読むData Bindingを使ってみる その1
DroidKaigiの土産話に感化された私は、今更ながらData-Bindingを使ってみました。
まずは公式を見ながら基本的なところからやってみます。
【環境】
Android Studio 2.0 beta 6
1.準備
まずはData Bindingを利用するため、appモジュールにあるbuild.gradleに設定を追記します。
[build.gradle]
android {
(略)
dataBinding{
enabled = true
}
}
Android Studio 1.3 Developer Previewの頃はdependenciesに追記したりプラグインの記述が必要だったりしたようですが、
Android Studio 2.0現在はこれだけでオッケーです。
dataBindingのenabledオプションはAndroid Plugin DSLの1.5から追加されていますので、Android Plugin for Gradle の1.5.0以降を利用します。
buildscript { (略) dependencies { //classpath 'com.android.tools.build:gradle:2.0.0-beta6' classpath 'com.android.tools.build:gradle:1.5.0' } }
2. TextViewに文字列をバインドしてみる
次に、実際にData Bindingを使ってみます。
まずはData Bindingを使用していない以下のようなレイアウトを用意しました。
[activity_main.xml]
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> (・・・略・・・) <TextView android:id="@+id/lbl_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="名前:" android:layout_below="@id/txt_hello"> </TextView> <TextView android:id="@+id/txt_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/txt_hello" android:layout_toRightOf="@id/lbl_name"/> <TextView android:id="@+id/lbl_gender" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="性別:" android:layout_below="@id/lbl_name"> </TextView> <TextView android:id="@+id/txt_gender" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/lbl_name" android:layout_toRightOf="@id/lbl_gender"/> </RelativeLayout>
[MainActivity.java]
public class MainActivity extends AppCompatActivity { private TextView tv_name; private TextView tv_gender; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main_no_binding); tv_name = (TextView)findViewById(R.id.txt_name); tv_gender = (TextView)findViewById(R.id.txt_gender); tv_name.setText("ハンタくん"); tv_gender.setText("MAN"); } }
これを、まずは以下のようにData Bindingに対応してみます。
[activity_main.xml 改]
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="hanter" type="monhaan.example.com.monhaan.Hanter"/> </data> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> (・・・略・・・) <TextView android:id="@+id/lbl_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/txt_hello" android:text="名前:"> </TextView> <TextView android:id="@+id/txt_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/txt_hello" android:layout_toRightOf="@id/lbl_name" android:text="@{hanter.name}"/> <TextView android:id="@+id/lbl_gender" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/lbl_name" android:text="性別:"> </TextView> <TextView android:id="@+id/txt_gender" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/lbl_name" android:layout_toRightOf="@id/lbl_gender" android:text="@{hanter.gender}" /> </RelativeLayout> </layout>
Data Bindingを利用するレイアウトファイルは、layoutタグをrootとする必要があります。
layoutタグの中にDataタグを用意し、さらにその中に'hanter'プロパティをvariableタグで定義します。
この'hanter'は、typeに指定したHanterクラス(後述)のオブジェクトで、activity_main.xml内で利用することができるバインド変数となります。
26行目と41行目で、'hanter'が持つnameプロパティとgenderプロパティの値を、それぞれTextViewの文字列としてセットするようにします。
バインドする側のソースコードは以下のようにします。
[MainActivity.java 改]
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binder = DataBindingUtil.setContentView(this, R.layout.activity_main); Hanter hanter = new Hanter("ハンタくん", "man"); binder.setHanter(hanter); } }
7行目で、レイアウトファイル(activity_main.xml)に変数をバインドするためのActivityMainBindingクラスのインスタンスを生成しています。
ActivityMainBindingクラスは"activity_main.xml 改"を作成すると自動生成されるクラスで、"activity_main.xml"のファイル名を基に、"Binding"をクラス名として付与した名称で生成されます。
8行目でHanterクラスのインスタンスを生成(ここでは"名前"と"性別"にそれぞれ"hantakun"と"MAN"を設定している)し、9行目でレイアウトファイルにバインドしています。
ActivityMainBinding#setHanterメソッドも自動生成されており、"activity_main.xml 改"の4行目でname属性に指定した"hanter"を基に、メソッド名が生成されています。
図.実行結果
ちなみに、Hanterクラスは以下のようになっています。
[Hanter.java]
public class Hanter { private String name; private String gender; public Hanter(String name, String gender){ this.name = name; this.gender = gender; } public String getName() { return this.name; } public String getGender() { return this.gender; } }
何の変哲もありませんが、次回以降でこちらにも手を入れていきます。
さて、長くなりそうなので、続きは次回。
サンプルコード
github.com
JobSchedulerの使い方
こちらもどうぞ
JobSchedulerとは?
JobSchedulerはJob登録を行い、端末が指定した状態になったときにSystemに実行してもらう機能を持つAPIです。 ネットワークの状態や、給電状態、DeviceのIdle状態などを契機に設定が行え、再起動時にもよしなにしてくれる機能を持つ、 高機能なv21以降で利用できるAPIです。 support libraryには入ってないのでバックポートは無いが、AlarmManagerを利用するよりは定期処理が組みやすいのでオススメです。
使い方
ManifestへJobServiceの登録
JobSchedulerを利用する場合は、JobServiceを継承したJobの実行者を実装する必要があります。 まずはこのJobServiceをManifestに登録しましょう。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.kobashin.sample.jobschedulersample"> <!-- setPersistedを利用する場合はBOOT_COMPLETEDの宣言が必要--> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <application ...> <activity android:name=".MainActivity"> <!-- 省略 --> </activity> <!-- Required android.permission.BIND_JOB_SERVICE --> <service android:name=".MyJobService" android:permission="android.permission.BIND_JOB_SERVICE" android:exported="true"></service> </application> </manifest>
ここでは、JobServiceを継承したクラスとしてMyJobServiceを作成しました。
ポイントは、MyJobServiceにandroid.permission.BIND_JOB_SERVICE
を宣言している点です。
これが無いと、java.lang.IllegalArgumentException: Scheduled service does not require android.permission.BIND_JOB_SERVICE permission
というエラーが発生します。
また、後述するsetPersistedを利用した再起動後にもScheduleを継続させる機能を利用する場合には、BOOT_COMPLETEDの宣言が必要です。
JobServiceの実装
JobServiceに実装すべきは、JobService.onStartJob()とJobService.onStopJob()メソッドです。
メソッド名 | 概要 |
---|---|
onStartJob() | ScheduleしたJobInfoの条件になった時に呼び出される。Jobの実処理を実装する。メインスレッドで呼び出されるので、Jobの実態は別スレッドで行うこと。 |
onStopJob() | Jobの実行中にJobInfoの条件が崩れた場合に呼び出される。このメソッドが呼び出されるときはJobを止める時である。OSはjobFinished)()を呼び出すことを期待している。 |
サンプルでは、Jobとして別スレッドで10秒sleepする処理を入れています。
public class MyJobService extends JobService { /* 省略 */ // ---- scheduled job interface @Override @DebugLog public boolean onStartJob(JobParameters params) { // 登録したJob実行タイミングで呼び出される // Jobの実処理を書く。ただし、メインスレッドで呼び出されるので、 // 重たい処理は別Threadで行うこと。 mParams = params; new Thread(new Runnable() { @Override @DebugLog public void run() { try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } if (mParams != null) { // jobFinished(JobParameters params, boolean needsReschedule) // params: Job開始時にonStartJob()の引数で受け取ったparamsを指定 // needsReschedule: // true JobInfo.Builder.setBackoffCriteria()で指定したback-off criteriaに従って // JobをRescheduleする // false Resucheduleしない jobFinished(mParams, false); } } }).start(); // 処理が継続している場合はtrueを返す。trueを返した場合は、処理終了時にjobFinished()をコールすること // 特に何もしていない場合はfalseを返す。 return true; } @Override @DebugLog public boolean onStopJob(JobParameters params) { // 要求したJobの実行中に条件を満たさなくなった場合に呼び出される // これが呼び出された場合は jobFinished() を呼び出すべきである jobFinished(params, false); // trueの場合、jobをback-off設定値に合わせてrescheduleする // returnした後の処理実行は保証されない return false; } }
これで、JobがScheduleされた時に実行する部分を実装したことになります。
JobServiceの利用
JobをScheduleするとき、Cancelするときには、Scheduler.schedule() / Scheduler.cancel() / Scheduler.cancelAll()を利用します。 これらはメソッド名のままなので、詳細な説明は必要ないでしょう。
ScheduleするJobはJobInfoの形式で作成します。 JobInfoはBuilderを持っており、このBuilderを通じてJobの実行タイミングを制御できます。 各メソッドでどういったことができるかはコメントを厚めに入れているので、そこを参照してください。
サンプルでは、MyJobServiceのpublic staticなメソッドとして、schedule() / cancel()を作成しました。
public class MyJobService extends JobService { public MyJobService() { } private final static ComponentName JOB_SERVICE_NAME = new ComponentName("com.kobashin.sample.jobschedulersample", "com.kobashin.sample.jobschedulersample.MyJobService"); private final static int JOB_ID = 0x01; public static void cancelJobs(Context context){ JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); scheduler.cancel(JOB_ID); //scheduler.cancelAll(); } @DebugLog public static void schedule(Context context) { JobScheduler scheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, JOB_SERVICE_NAME); // setBackOffCriteria(long initialBackoffMillis, int backoffPolicy) // initialbackoffMillis: バックオフ時間算出の基準値 // backoffPolicy: BACKOFF_POLICY_LINEARかBACKOFF_POLICY_EXPONENTIALを指定 // LINEARの時は current+initial * fail_count // EXPONENTIALの時は current + initial * 2 ^ (fail_count -1) // 後にリトライされる。defaultは30sec, EXPONENTIAL。最長バックオフは5hr builder.setBackoffCriteria(10000, JobInfo.BACKOFF_POLICY_LINEAR); // setExtras(PersistableBundle) // PersistableBundleを利用して、onStartJob時に取り出すbundleを用意できる PersistableBundle bundle = new PersistableBundle(); builder.setExtras(bundle); // setMinimumLatency() // 実行可能になってからの最低遅延時間を設定する // 定期実行Jobには必要ないため、build()時にエラー扱いとなる // builder.setMinimumLatency(5000); // setOverrideDeadline() // 実行可能になってからの最大遅延時間を設定する // 定期実行Jobには必要ないため、build()時にエラー扱いとなる // builder.setOverrideDeadline(20000); // setPeriodic() // 定期実行を設定する。前のJobが終わってからの経過時間(Millis)を指定する // Priodic指定した場合は、状態が変更されても継続実行される // builder.setPeriodic(10000); // setPersisted() // 再起動時にJobを実行継続させるかどうか。 // trueを設定した場合は、BOOT_COMPLETEDが無いとエラー扱いとなる builder.setPersisted(true); // setRequiredNetworkType() // Jobの実行に必要なネットワーク形態を設定する // NETWORK_TYPE_NONE: 指定なし(ありでもなしでも) // NETWORK_TYPE_ANY: なんらかのネットワーク // NETWORK_TYPE_UNMETERD: 従量制でないネットワーク builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); // Deviceがidle maintenance windowの時に実行するかどうか // idolかつbackoffCriteriaを設定するとexceptionを拾う // builder.setRequiresDeviceIdle(true); // Deviceが給電状態かどうかを設定する builder.setRequiresCharging(true); scheduler.schedule(builder.build()); } /* 省略 */ }
はまりポイント
setPersisted()を利用する際に、BOOT_COMPLETEDのパーミッションを入れてるのに下記エラーが発生する問題が起きた。
java.lang.IllegalArgumentException: Error: requested job be persisted without holding RECEIVE_BOOT_COMPLETED permission.
内部で保持してるキャッシュが影響しているとのことなので、一度アプリケーションを再インストールすれば回避できた。
sample code
今回のサンプルコードはこちら。 Hugoを利用しているので@DebugLogなアノテーションが入ってます。
Electronのライフサイクル
Electronをアプリを作成する際にライフサイクルを知る必要があります。 どのタイミングでどのような挙動をすべきかの考える時にそれぞれのイベントのタイミングを把握しておきましょう。
platform依存のイベントとlogin系のイベントを除いたライフサイクルは次の様になります。
ライフサイクル
イベントの詳細
各イベントの詳細は次の表のようになります。
イベント | 詳細 |
---|---|
will-finish-launching | 基本的な起動が完了した時のイベント。WindowsとLinuxはreadyイベントと同じ意味を持ちますが、OS Xの場合はapplicationWillFinishLaunchingの通知と同じ意味を持ちます。自動アップデータやクラッシュレポートなどを実施するタイミング。 |
ready | 初期化完了イベント。BrowserWindowのインスタンスを生成し、ウィンドウを表示したりなどを実施する。 |
window-all-closed | すべてのウィンドウを閉じたイベント。全ウィンドウを閉じた時にアプリの終了を促したりを実施する。 |
before-quit | ウィンドウを閉じている最中で、アプリが終了を始めたイベント |
will-quit | ウィンドウが閉じ、アプリ終了前のイベント。 |
quit | アプリ終了中イベント。 |
browser-window-blur | BrowserWindowからフォーカスが外れたイベント。 |
browser-window-focus | BrowserWindowにフォーカスがあたったイベント。 |
browser-window-created | BrowserWindowが作成されたイベント。 |
上記のようなイベントを取得できるため、そのタイミングによって動作を考え実装を行う事になります。
サンプルコード
動きを確認するサンプル