Android Wear アプリ開発 センサ活用チュートリアル2 Android Studio 開発 Wearプログラム編

Pocket

Wear プログラム

本ページは、Android Wear 開発チュートリアルの Wear(スマートウォッチ)プログラム編です。開発環境の構築や、Permission関連の設定、Wear↔Mobile間通信については、こちらの Index からご参考ください。

Index

  • Functionalities

  • Files

  • Permissions

  • Explanations

Functionalities

プログラム名挙動記事
bioinfo wear (スマートウォッチ)
  • スマートウォッチ上で開始ボタンと中止ボタンを表示する。

  • ユーザが開始ボタンを押した場合、定期的にセンサの値を取得する。

  • 取得したセンサの値をスマートフォン側のプログラムに送信する。

  • スマートウォッチ上の画面が消された場合でも定期実行を継続し、ユーザが停止ボタンを押した場合のみ、実行を停止する。

Files

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.wellbeing.bioinfo">

    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.BODY_SENSORS"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-feature android:name="android.hardware.type.watch" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/wblogo"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@android:style/Theme.DeviceDefault">
        <uses-library
            android:name="com.google.android.wearable"
            android:required="true" />

        <!--
               Set to true if your app is Standalone, that is, it does not require the handheld
               app to run.
        -->
        <meta-data
            android:name="com.google.android.wearable.standalone"
            android:value="true" />

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name=".SensorJobService" android:exported="true" android:permission="android.permission.BIND_JOB_SERVICE"/>
    </application>

</manifest>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.wear.widget.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/dark_grey"
    android:padding="@dimen/box_inset_layout_padding"
    tools:context=".MainActivity"
    tools:deviceIds="wear">

    <LinearLayout android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical">

        <TextView
            android:id="@+id/text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/hello_world"
            app:layout_box="all" />

        <Button
            android:id="@+id/start_btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Start" />

        <Button
            android:id="@+id/stop_btn"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Stop" />

    </LinearLayout>

    <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/clock" app:layout_box="all" android:textColor="@android:color/white" android:layout_gravity="bottom|start"/>

</androidx.wear.widget.BoxInsetLayout>

MainActivity.java

package net.wellbeing.bioinfo;

import android.app.Activity;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.TextView;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class MainActivity extends Activity {
    private TextView mTextView;
    public ComponentName mServiceName;
    private final String TAG = MainActivity.class.getName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        mTextView = findViewById(R.id.text);
        mTextView.setTextSize(14.0f);

        CtrlFile ctrlfile = new CtrlFile();
        if( ctrlfile.checkfile() ) {
            mTextView.setText("Running.");
        }else{
            mTextView.setText("Stopped.");
        }

        mServiceName = new ComponentName(this, SensorJobService.class);

        findViewById(R.id.start_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                CtrlFile file = new CtrlFile();
                if( file.checkfile() ) {
                    mTextView.setText("Already running");
                    return;
                }

                JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
                for (int i = 0; i < 1; i++) {
                    JobInfo jobInfo = new JobInfo.Builder(i, mServiceName)
                            .setMinimumLatency(1000)
                            .setOverrideDeadline(60000)
                            .setPersisted(true)
                            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                            .build();
                    scheduler.schedule(jobInfo);
                }

                file.createfile();
                mTextView.setText("Running");

            }
        });

        findViewById(R.id.stop_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                CtrlFile file = new CtrlFile();
                file.deletefile();
                mTextView.setText("Stopped");
                JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
                scheduler.cancelAll();
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

}

class SensorJobService extends JobService {
    private final String TAG = MainActivity.class.getName();

    @Override
    public boolean onStartJob(JobParameters params) {
        new ToastTask().execute(params);
        new SensorWorker().execute(params);

        CtrlFile file = new CtrlFile();
        if( !file.checkfile() ) {
            return true;
        }
        ComponentName mServiceName = new ComponentName(this, SensorJobService.class);

        JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        for (int i = 0; i < 1; i++) {
            JobInfo jobInfo = new JobInfo.Builder(i, mServiceName)
                    .setMinimumLatency(300*1000)
                    .setOverrideDeadline(180*1000)
                    .setPersisted(true)
                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                    .build();
            scheduler.schedule(jobInfo);
        }
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }

    private class ToastTask extends AsyncTask<JobParameters, Void, String> {

        protected JobParameters mJobParam;

        @Override
        protected void onPreExecute() {

        }

        @Override
        protected String doInBackground(JobParameters... params) {
            mJobParam = params[0];
            return String.valueOf(mJobParam.getJobId());
        }

        @Override
        protected void onPostExecute(String result) {
            jobFinished(mJobParam, false);
        }
    }

    private class SensorWorker extends AsyncTask<JobParameters,Void,String> implements SensorEventListener {

        private final String TAG = MainActivity.class.getName();
        protected JobParameters mJobParam;

        private GoogleApiClient mGoogleApiClient;
        private SensorManager mSensorManager;
        private String mNode;

        private float x = 0, y = 0, z = 0;
        private int hr = 10;
        private float gyx = 0, gyy = 0, gyz = 0;
        private float lux = 100.0f;
        private int sc = 0;
        private int kcal = 0;
        int count = 0;

        @Override
        protected void onPreExecute() {

        }

        @Override
        protected String doInBackground(JobParameters... params) {
            mGoogleApiClient = new GoogleApiClient.Builder(getApplicationContext())
                    .addApi(Wearable.API)
                    .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                        @Override
                        public void onConnected(Bundle bundle) {
                            Log.d(TAG, "onConnected");

                            Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
                                @Override
                                public void onResult(NodeApi.GetConnectedNodesResult nodes) {
                                    if (nodes.getNodes().size() > 0) {
                                        mNode = nodes.getNodes().get(0).getId();

                                    }
                                }
                            });
                        }

                        @Override
                        public void onConnectionSuspended(int i) {
                            Log.d(TAG, "onConnectionSuspended");

                        }
                    })
                    .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                        @Override
                        public void onConnectionFailed(ConnectionResult connectionResult) {
                            Log.d(TAG, "onConnectionFailed : " + connectionResult.toString());
                        }
                    })
                    .build();

            mGoogleApiClient.connect();

            mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);

            Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
            mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL*5);

            Sensor gySensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
            mSensorManager.registerListener(this, gySensor, SensorManager.SENSOR_DELAY_NORMAL*5);

            try {
                Thread.sleep(3*1000);
                //wait();
            }catch(Exception e) {

            }
            mSensorManager.unregisterListener(this);

            Sensor hrSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_HEART_RATE);
            mSensorManager.registerListener(this, hrSensor, SensorManager.SENSOR_DELAY_NORMAL);

            //Sensor hbSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_HEART_BEAT);
            //mSensorManager.registerListener(this, hbSensor, SensorManager.SENSOR_DELAY_NORMAL);

            Sensor scSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
            mSensorManager.registerListener(this, scSensor, SensorManager.SENSOR_DELAY_NORMAL);

            Sensor luxSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
            mSensorManager.registerListener(this, luxSensor, SensorManager.SENSOR_DELAY_NORMAL);

            Sensor kcalSensor = mSensorManager.getDefaultSensor(65548);
            mSensorManager.registerListener(this, kcalSensor, SensorManager.SENSOR_DELAY_NORMAL);

            int i=0;
            while( i < 3 ) {
                if( hr > 10 &amp;&amp; lux != 0 ){
                    break;
                }
                try {
                    Thread.sleep(10*1000);
                    //wait();
                }catch(Exception e) {

                }
                i++;

            }

            mJobParam = params[0];
            return String.valueOf(mJobParam.getJobId());
        }

        @Override
        protected void onPostExecute(String result) {
            mSensorManager.unregisterListener(this);

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");

            String SEND_DATA = sdf.format(Calendar.getInstance().getTime()) + "," + x + "," + y + "," + z + "," + hr + "," + lux + "," + sc;
            if (mNode != null) {
                Wearable.MessageApi.sendMessage(mGoogleApiClient, mNode, SEND_DATA, null).setResultCallback(new ResultCallback<MessageApi.SendMessageResult>() {
                    @Override
                    public void onResult(MessageApi.SendMessageResult result) {
                        if (!result.getStatus().isSuccess()) {
                            Log.d(TAG, "ERROR : failed to send Message" + result.getStatus());
                        }
                    }
                });
            }

            jobFinished(mJobParam, false);
        }

        @Override
        public void onSensorChanged(SensorEvent event) {

            if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
                x = event.values[0];
                y = event.values[1];
                z = event.values[2];
            }

            else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
                gyx = event.values[0];
                gyy = event.values[1];
                gyz = event.values[2];
            }

            else if (event.sensor.getType() == Sensor.TYPE_LIGHT) {
                lux = event.values[0];
            }

            else if (event.sensor.getType() == Sensor.TYPE_HEART_RATE) {
                int h = hr;
                hr =(int)event.values[0];
                if( hr == 255 ){
                    hr = h;
                }
            }

            else if (event.sensor.getType() == Sensor.TYPE_STEP_COUNTER) {
                sc = (int) event.values[0];
            }

            else if (event.sensor.getType() == 65548) {
                kcal = (int) event.values[0];
            }

        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        }

    }
}

class CtrlFile {
    private String filename = "/data/data/net.wellbeing.bioinfo/log.txt";

    public boolean checkfile() {
        File file = new File(filename);
        return( file.exists() );
    }

    public boolean createfile() {
        FileOutputStream fos;
        try {
            //fos = openFileOutput(fileName, Context.MODE_PRIVATE);
            fos = new FileOutputStream(new File(filename));
            fos.write(new Date().toString().getBytes());
        } catch (IOException e) {
            String path = new File(".").getAbsoluteFile().getParent();
            e.printStackTrace();
            return(false);
        }
        return(true);
    }
    public boolean deletefile() {
        File file = new File(filename);
        return(file.delete());
    }
}

Permissions

ここでは本プログラムで必要になるアプリの権限設定について説明します。アプリの権限は AndroidManifest.xmlで設定します。本プログラムで必要となる権限は以下の通りです。

生体情報センサを利用する

  • uses-permission android:name=”android.permission.BODY_SENSORS” ・・・ 心拍等の生体情報のセンサデータを取得する際に必要になります。AndroidManifest.xmlに加えて、プログラム導入後にスマートウォッチの画面からの設定も必要になります。下記、ご参考ください。

定期処理を実行する

  • uses-permission android:name=”android.permission.RECEIVE_BOOT_COMPLETED” ・・・ 定期処理を実行する際に、ハードウェアの再起動後も処理を継続する場合に設定します。設定しない場合、ハードウェアの再起動後に再び定期処理を登録する必要があります。

  • service android:name=”.SensorJobService” android:exported=”true” android:permission=”android.permission.BIND_JOB_SERVICE” ・・・ 定期実行のための処理を登録する際に必要になります。

生体情報センサを利用する(スマートウォッチ画面から)

  • 前述の通り、心拍など生体情報センサを使う為には、プログラムを開発した後、スマートウォッチの画面から権限を設定する必要があります。「設定」→「アプリと通知」→「アプリの権限」→当該プログラム→「センサー」のチェックを入れる必要があります。

Explanations

センサの値を取得する

private class SensorWorker extends AsyncTask<JobParameters,Void,String> implements SensorEventListener {
        @Override
        public void onSensorChanged(SensorEvent event) {
        }
        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        }
}

1行目「implements SensorEventListener」

センサの値を取得するためには、あるクラスに対して、「implements SensorEventListener」を宣言します。この宣言を行うと、Android Studioが自動で「@Override public void onSensorChanged(SensorEvent event) {}」「@Override public void onAccuracyChanged(Sensor sensor, int accuracy) {}」というメソッドを用意してくれます。

3行目「onSensorChanged() 」

センサの値に変更があった際に呼ばれます。

6行目「onAccuracyChanged()」

センサの精度に変更があった際に呼ばれるものと思いますが、確認できていません。

            mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);

            Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
            mSensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL*5);

            try {
                Thread.sleep(3*1000);
                //wait();
            }catch(Exception e) {

            }
            mSensorManager.unregisterListener(this);

            Sensor hrSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_HEART_RATE);
            mSensorManager.registerListener(this, hrSensor, SensorManager.SENSOR_DELAY_NORMAL);

            int i=0;
            while( i < 3 ) {
                if( hr > 10 &amp;&amp; lux != 0 ){
                    break;
                }
                try {
                    Thread.sleep(10*1000);
                    //wait();
                }catch(Exception e) {

                }
                i++;

            }

1行目「getSystemService(SENSOR_SERVICE)」

まず「センサマネージャ」(センサ管理のクラス)のインスタンスを取得します。

3行目「getDefaultSensor()」

上記の「センサマネージャ」の「getDefaultSensor()」でセンサ制御のインスタンスを取得します。

4行目「registerListener()」

イベントリスナ、センサの種類、精度を指定します。当然、イベントリスナに指定するクラスにはには先程の「implements SensorEventListener」が必要になります。センサの種類を指定する「Sensor.TYPE_*」は定数なので、同じ要領でハードウェアベンダ独自のセンサを呼び出そうとしましたが、うまく行きませんでいた。分かる人教えてください。

7行目「Thread.sleep()」

当該処理のスレッドは一旦停止して、この間に「onSensorChanged(SensorEvent event)」が呼び出されることを期待しています。但し、想定したセンサのデータが必ず取得できるとは限りません。1分程度待っても、期待するセンサのデータが取得できないこともあります。

分かりずらいところですが、センサには極めて頻繁に値が更新されるものと、値の更新が極めて緩慢なものがあります。例えば、加速度やジャイロは値の更新が頻繁ですので、上記では3秒 Thread.sleep() を実行し、その間に「onSensorChanged()」が呼び出され、値が取得できるよう期待しています。頻繁に更新されるセンサの値を取得することにより、極めて速く電池残量が消費されるようです。例えば5分に1回30秒の加速度を取得した場合、私のスマートウォッチではほぼ半日で電池がなくなります。プログラムを動作させた結果として電池の消費が早すぎるようであれば、加速度センサ、ジャイロセンサ等、電池を消費するセンサの値を取りすぎているかもしれません。3秒に根拠はありません(汗)。実際には2秒でも1秒でも良いかもしれません。

23行目「Thread.sleep()」

上記に対して、心拍数や照度は極めて緩慢なセンサのようです。ここでは30秒待つことにしていますが、この秒数では「onSensorChanged()」が呼び出されず、値が取得できないことがあります。値が取得できるまで待つという処理も論理的には可能ですが、値が長時間取れなかった場合、実行中のプログラムが残り続けることになりますので、これを避けるために10秒×3回にしています。(もっとエレガントなやり方があれば教えてください。)

        @Override
        public void onSensorChanged(SensorEvent event) {

            if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
                x = event.values[0];
                y = event.values[1];
                z = event.values[2];
            }

            if (event.sensor.getType() == Sensor.TYPE_HEART_RATE) {
                int h = hr;
                hr =(int)event.values[0];
                if( hr == 255 ){
                    hr = h;
                }
            }

        }

ハードウェアでセンサーの値に変更があると「onSensorChanged(SensorEvent event)」が呼ばれます。頻繁に何度も実行される可能性のあるメソッドですので、この中で重たい処理を行ったり、メモリを大量に消費したりする操作は避けた方が無難でしょう。

5行目「x = event.values[0];」

更新されたセンサの値は、「SensorEvent」インスタンスの「values」という配列に格納されています。値が一つであれば「values[0]」の値を使えば良いですし、加速度などX,Y,Zの3つの値が同時に取得されるものは、「values[0],values[1],values[2]」のように使うことができます。

13行目

開発に利用したスマートウォッチでは、時々、心拍数として 255を返してくることがあり、この異常値を取り除いています。心拍のように異常値が明確であれば良いですが(心拍が255だとウサギです)、他の多くのセンサは異常値かどうか判別はつきません。

前述の通り、センサを使う為には、AndroidManifest.xmlへの記載と、スマートウォッチの画面上でアプリの権限を設定することも必要になります。


定期実行を行う

Androidの定期実行の仕組みは、かなり複雑です。電池を可能に消費するアプリケーションへの対応、ユーザから見えないバックグラウンド処理に対する規制などが原因のようです。


  1. スタートボタンとストップボタン、簡単なメッセージを表示する「MainActivity」クラスの画面を表示します。スタートボタンが押され、CtrlFileがなければ、時期を指定してJobSchedulerに処理を登録します。

    ファイルを使ってシステムの動作状態、停止状態を判断しているのは、JobSchedulerに登録された SensorJobServiceクラスのスレッド内から本システム全体の動作を判定するためです。(これで動作していますが、もっと洗練された方法があるかもしれません。) JobSchedulerから起動されたスレッドの場合、主画面の「MainActivity」が存在しているとは限らず、何らかの形でシステムの動作状態を判断する必要があるためです。

  2. JobSchedulerは指定された時機に、SensorJobServiceのインスタンスを生成し「onStartJob()」を呼び出します。(因みにスケジュールされたジョブがキャンセルされた場合は「onStopJob()」を呼び出します。)

  3. 上記の「onStartJob()」の中から、SensorWorkerクラスを非同期スレッドとして実行します。非同期スレッドですので、非同期処理の終了を待たずに「onStartJob()」のスレッドは処理を進めます

  4. 自分自身(SensorJobService)をJobSchedulerに登録します。現在、15分より短い間間隔での周期実行はシステム上許可されていないという情報があり、これを回避するためです。周期実行ができないので、1回1回登録しなおしています。システムの制約を意図的に回避しているので、いつかは塞がれてしまう方法と思いますが、今現在はこの方法で15分より短い間隔での実行ができています。

  5. ③で開始操作を行った非同期処理が実行されます。プロセス管理システムは「extends AsyncTask 」を指定したクラスの中から、「onPreExecute()」「doInBackground()」「onPostExecute()」の順に呼び出します。


public class MainActivity extends Activity  {
   ComponentName mServiceName;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        CtrlFile ctrlfile = new CtrlFile();
        if( ctrlfile.checkfile() ) {
            mTextView.setText("Running.");
        }else{
            mTextView.setText("Stopped.");
        }

        mServiceName = new ComponentName(this, SensorJobService.class);

        findViewById(R.id.start_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                CtrlFile file = new CtrlFile();
                if( file.checkfile() ) {
                    mTextView.setText("Already running");
                    return;
                }

                JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
                for (int i = 0; i < 1; i++) {
                    JobInfo jobInfo = new JobInfo.Builder(i, mServiceName)
                            .setMinimumLatency(1000)
                            .setOverrideDeadline(60000)
                            .setPersisted(true)
                            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                            .build();
                    scheduler.schedule(jobInfo);
                }
                file.createfile();

            }
        });

        findViewById(R.id.stop_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                CtrlFile file = new CtrlFile();
                file.deletefile();

                JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
                scheduler.cancelAll();
            }
        });
    }
}


14行目: new ComponentName(this, SensorJobService.class)

このクラスのコンポーネント名を取得します。この行とこの後の3項目、「new ComponentName()」「getSystemService()」「new JobInfo.Builder().build()」「scheduler.schedule()」がJobSchedulerの登録処理となります。ここで登録するクラスは「extends JobService」が必須となります。

26行目: getSystemService(Context.JOB_SCHEDULER_SERVICE)

システムのJobSchedulerのインスタンスを取得しています。

28行目: new JobInfo.Builder(i, mServiceName).build();

登録するジョブの設定を行います。

  • .setMinimumLatency(1000) ・・・ 最低時間間隔。JobSchedulerの場合、処理の起動条件もありますので、いつ実行されるかは一概に分かりません。取り敢えず、最低何ミリ秒はあけてから処理を起動するという値になります。ここでは最初の実行なので1秒で実行されることにしています。

  • .setOverrideDeadline(60000) ・・・ いつまでに実行されるかを設定します。

  • .setPersisted(true) ・・・ 登録され実行されていなかった処理をハードウェアの再起動後に登録しなおすかという設定です。

  • .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) ・・・ ネットワーク接続が必要かという設定です。「NETWORK_TYPE_ANY」を設定していますので、Bluetooth、WaveLAN、その他、ネットワークの種類は問わずです。

  • .build() ・・・ 最後に必ず必要なようです。

29行目: scheduler.schedule(jobInfo)

設定した JobInfo のインスタンスをスケジューラに登録します。

48行目: scheduler.cancelAll()

スケジューラに登録されている処理を中止する場合に使うようです。未確認です。

class SensorJobService extends JobService {

    @Override
    public boolean onStartJob(JobParameters params) {
        new SensorWorker().execute(params);

        CtrlFile file = new CtrlFile();
        if( !file.checkfile() ) {
            return true;
        }

        ComponentName mServiceName = new ComponentName(this, SensorJobService.class);
        JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);

        for (int i = 0; i < 1; i++) {
            JobInfo jobInfo = new JobInfo.Builder(i, mServiceName)
                    .setMinimumLatency(300*1000)
                    .setOverrideDeadline(180*1000)
                    .setPersisted(true)
                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                    .build();
            scheduler.schedule(jobInfo);
        }
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
}

1行目: extends JobService

JobSchedulerに登録するクラスにはこの宣言が必要になります。

4行目: public boolean onStartJob(JobParameters params) {}

JobSchedulerは、設定された時機と条件が満たされると登録されたクラスの onStartJob() を呼び出します。

5行目: new SensorWorker().execute(params);

AsyncTask(非同期処理)を実行します。

7行目:

特定のファイルの存在確認をしています。MainActivityの画面から「Stop」ボタンが押されるとこの特定ファイルが消されます。

28行目: public boolean onStopJob(JobParameters params) {}

JobSchedulerに登録された処理が取り消された場合に呼び出されるようです。未確認。


    private class SensorWorker extends AsyncTask<JobParameters,Void,String> implements SensorEventListener {

        protected JobParameters mJobParam;

        @Override
        protected void onPreExecute() {
        }

        @Override
        protected String doInBackground(JobParameters... params) {
            mJobParam = params[0];
            return String.valueOf(mJobParam.getJobId());
        }

        @Override
        protected void onPostExecute(String result) {
            mSensorManager.unregisterListener(this);
            jobFinished(mJobParam, false);
        }

        @Override
        public void onSensorChanged(SensorEvent event) {
        }

        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {
        }
    }

次のページ

Android Wear センサ活用チュートリアル3 Android Studio 開発 Mobileプログラム編
端末間での通信: データを Wear から Mobile に送信する /
データをインターネット上の外部サーバにポストする

Android Wear アプリ開発 センサ活用チュートリアル 1 Android Studio 準備編
Android Wear アプリ開発 センサ活用チュートリアル3 Android Studio 開発 Mobileプログラム編