techium

このブログは何かに追われないと頑張れない人たちが週一更新をノルマに技術情報を発信するブログです。もし何か調査して欲しい内容がありましたら、@kobashinG or @muchiki0226 までいただけますと気が向いたら調査するかもしれません。

GAS事始め(その1):スプレッドシートを操作する

社内ツールとしてGoogle DocsGmailを使用している人も最近では多いと思いますが、しっかり使いこなせているでしょうか。 私は使いこなせていません。

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"を基に、メソッド名が生成されています。

f:id:uentseit:20160310224018p:plain:w200
図.実行結果

ちなみに、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の使い方

こちらもどうぞ

blog.techium.jp

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.

stackoverflow.com

内部で保持してるキャッシュが影響しているとのことなので、一度アプリケーションを再インストールすれば回避できた。

sample code

今回のサンプルコードはこちら。 Hugoを利用しているので@DebugLogなアノテーションが入ってます。

github.com

Electronのライフサイクル

Electronをアプリを作成する際にライフサイクルを知る必要があります。 どのタイミングでどのような挙動をすべきかの考える時にそれぞれのイベントのタイミングを把握しておきましょう。

platform依存のイベントとlogin系のイベントを除いたライフサイクルは次の様になります。

ライフサイクル

f:id:muchiki0226:20160306221816p:plain

イベントの詳細

各イベントの詳細は次の表のようになります。

イベント 詳細
will-finish-launching 基本的な起動が完了した時のイベント。WindowsLinuxは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が作成されたイベント。

上記のようなイベントを取得できるため、そのタイミングによって動作を考え実装を行う事になります。

サンプルコード

動きを確認するサンプル

github.com