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

Pocket

Mobileプログラム

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

Index

  • Functionalities

  • Files

  • Permissions

  • Explanations

機能

bioinfo mobile (スマートフォン)
  • スマートウォッチから送信されたデータを画面上に表示する。

  • 上記のデータを Internet上のサーバに送信する。

  • (このプログラムは画面がフォアグランドでなくても動作するが、×などで閉じられた場合には動作を停止する。)

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.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/wblogo"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    tools:context=".MainActivity">

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

        <TextView android:layout_height="wrap_content" android:layout_width="match_parent" android:textSize="24sp" android:text="Time" android:id="@+id/timeTextView"/>

        <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:textSize="24sp" android:text="TextView" android:id="@+id/xValue" android:background="@android:color/white" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/textView"/>

        <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:textSize="24sp" android:text="TextView" android:id="@+id/yValue" android:background="@android:color/white" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/xValue"/>

        <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:textSize="24sp" android:text="TextView" android:id="@+id/zValue" android:background="@android:color/white" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/yValue"/>

        <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:textSize="24sp" android:text="hrValue" android:id="@+id/hrValue" android:background="@android:color/white" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/yValue"/>

        <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:textSize="24sp" android:text="luxValue" android:id="@+id/luxValue" android:background="@android:color/white" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/yValue"/>

        <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:textSize="24sp" android:text="scValue" android:id="@+id/scValue" android:background="@android:color/white" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_below="@+id/yValue"/>

        <TextView android:layout_height="wrap_content" android:layout_width="match_parent" android:text="tvProcess" android:id="@+id/tvProcess"/>

        <TextView android:layout_height="wrap_content" android:layout_width="match_parent" android:text="tvResult" android:id="@+id/tvResult"/>

    </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.java

package net.wellbeing.bioinfo;

import androidx.appcompat.app.AppCompatActivity;

import android.app.ActionBar;
import android.app.Activity;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
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.wearable.MessageApi;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Wearable;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Calendar;

public class MainActivity extends Activity implements GoogleApiClient.ConnectionCallbacks, MessageApi.MessageListener {
    private static final String TAG = MainActivity.class.getName();
    private GoogleApiClient mGoogleApiClient;

    TextView timeTextView;
    TextView xTextView;
    TextView yTextView;
    TextView zTextView;
    TextView hrTextView;
    TextView luxTextView;
    TextView scTextView;
    int x, y, z;

    String[] names = new String[]{"x-value", "y-value", "z-value", "hr"};
    int[] colors = new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.CYAN};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        timeTextView = (TextView) findViewById(R.id.timeTextView);
        xTextView = (TextView) findViewById(R.id.xValue);
        yTextView = (TextView) findViewById(R.id.yValue);
        zTextView = (TextView) findViewById(R.id.zValue);
        hrTextView = (TextView) findViewById(R.id.hrValue);
        luxTextView = (TextView) findViewById(R.id.luxValue);
        scTextView = (TextView) findViewById(R.id.scValue);
        ActionBar ab = getActionBar();
        //ab.hide();
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                    @Override
                    public void onConnectionFailed(ConnectionResult connectionResult) {
                        Log.d(TAG, "onConnectionFailed:" + connectionResult.toString());
                    }
                })
                .addApi(Wearable.API)
                .build();

    }

    @Override
    protected void onStart() {
        super.onStart();
        mGoogleApiClient.connect();
    }

    @Override
    protected void onStop() {
        super.onStop();
        /*
        if (null != mGoogleApiClient &amp;&amp; mGoogleApiClient.isConnected()) {
            Wearable.MessageApi.removeListener(mGoogleApiClient, this);
            mGoogleApiClient.disconnect();
        }
        */
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        //getMenuInflater().inflate(R.menu.my, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        //if (id == R.id.action_settings) {
        //    return true;
        //}
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onConnected(Bundle bundle) {
        Log.d(TAG, "onConnected");
        Wearable.MessageApi.addListener(mGoogleApiClient, this);
    }

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

    }

    @Override
    public void onMessageReceived(MessageEvent messageEvent) {
        //xTextView.setText(messageEvent.getPath());
        String msg = messageEvent.getPath();
        String[] value = msg.split(",", 0);

        timeTextView.setText("time: "+String.valueOf(value[0]));
        xTextView.setText("acc x: "+String.valueOf(value[1]));
        yTextView.setText("acc y: "+String.valueOf(value[2]));
        zTextView.setText("acc z: "+String.valueOf(value[3]));
        hrTextView.setText("hb: "+String.valueOf(value[4]));
        luxTextView.setText("lux: "+String.valueOf(value[5]));
        scTextView.setText("sc: "+String.valueOf(value[6]));

        TextView tvProcess = findViewById(R.id.tvProcess);
        TextView tvResult = findViewById(R.id.tvResult);
        PostAccess access = new PostAccess(tvProcess, tvResult);
        access.execute("http://well-being.agnes.aoyama.ac.jp/HR/wp-content/plugins/well-being/post.php", msg,"");
    }


    private class PostAccess extends AsyncTask<String, String, String> {
        private static final String DEBUG_TAG = "PostAccess";
        private TextView _tvProcess;
        private TextView _tvResult;
        private boolean _success = false;

        public PostAccess(TextView tvProcess, TextView tvResult) {
            _tvProcess = tvProcess;
            _tvResult = tvResult;
        }

        @Override
        public String doInBackground(String... params) {
            String urlStr = params[0];
            String msg = params[1];
            String comment = params[2];

            //String postData = "name= " + name + "&amp;comment=" + comment;
            String postData = "";
            String[] value = msg.split(",", 0);
            String timestamp = "";
            for( int i=0; i<7; i++) {
                if( i==0 ) {
                    timestamp = value[i];
                    continue;
                }
                if( i==1 ) {
                    postData = "d0="+"0,0,"+timestamp+",acc x,"+value[i];
                }
                if( i==2 ) {
                    postData = postData+"&amp;d1="+"0,0,"+timestamp+",acc y,"+value[i];
                }
                if( i==3 ) {
                    postData = postData+"&amp;d2="+"0,0,"+timestamp+",acc z,"+value[i];
                }
                if( i==4 ) {
                    postData = postData+"&amp;d3="+"0,0,"+timestamp+",hr,"+value[i];
                }
                if( i==5 ) {
                    postData = postData+"&amp;d4="+"0,0,"+timestamp+",lux,"+value[i];
                }
                if( i==6 ) {
                    postData = postData+"&amp;d5="+"0,0,"+timestamp+",sc,"+value[i];
                }

            }

            HttpURLConnection con = null;
            InputStream is = null;
            String result = "";

            try {
                //publishProgress(getString(R.string.msg_send_before));
                //publishProgress("message send before");
                URL url = new URL(urlStr);
                con = (HttpURLConnection) url.openConnection();
                con.setRequestMethod("POST");
                con.setConnectTimeout(5000);
                con.setReadTimeout(5000);
                con.setDoOutput(true);
                OutputStream os = con.getOutputStream();
                os.write(postData.getBytes());
                os.flush();
                os.close();
                int status = con.getResponseCode();
                if (status != 200) {
                    throw new IOException("ステータスコード: " + status);
                }
                //publishProgress(getString(R.string.msg_send_after));
                //publishProgress("message send after");
                is = con.getInputStream();

                result = is2String(is);
                _success = true;
            }
            catch(SocketTimeoutException ex) {
                //publishProgress(getString(R.string.msg_err_timeout));
                publishProgress("message error timeout: "+ex.toString());
                Log.e(DEBUG_TAG, "タイムアウト", ex);
            }
            catch(MalformedURLException ex) {
                //publishProgress(getString(R.string.msg_err_send));
                publishProgress("message err url malformed: "+ex.toString());
                Log.e(DEBUG_TAG, "URL変換失敗", ex);
            }
            catch(IOException ex) {
                //publishProgress(getString(R.string.msg_err_send));
                publishProgress("message err send: "+ex.toString());
                Log.e(DEBUG_TAG, "通信失敗", ex);
            }
            finally {
                if (con != null) {
                    con.disconnect();
                }
                try {
                    if (is != null) {
                        is.close();
                    }
                }
                catch (IOException ex) {
                    //publishProgress(getString(R.string.msg_err_parse));
                    publishProgress("message err parse"+ex.toString());
                    Log.e(DEBUG_TAG, "InputStream解析失敗", ex);
                }
            }
            return result;
        }

        @Override
        public void onProgressUpdate(String... values) {
            super.onProgressUpdate(values);
            /*
            String message = _tvProcess.getText().toString();
            if (!message.equals("")) {
                message += "\n";
            }
            message += values[0];
            _tvProcess.setText(message);
            */
            _tvProcess.setText(values[0]);
        }

        @Override
        public void onPostExecute(String result) {
            if (_success) {
                String status = "";
                String message = "";
                //onProgressUpdate(getString(R.string.msg_parse_before));
                //onProgressUpdate("message parse before");
                try {
                    JSONObject rootJson = new JSONObject(result);
                    status = rootJson.getString("status");
                    message = rootJson.getString("message");
                }
                catch (JSONException ex) {
                    //onProgressUpdate(getString(R.string.msg_err_parse));
                    onProgressUpdate("message err parse:"+ex.toString());
                    Log.e(DEBUG_TAG, "JSON解析失敗", ex);
                }
                //onProgressUpdate(getString(R.string.msg_parse_after));
                onProgressUpdate("status: "+status.toString()+", message: "+message.toString());

                //String message = getString(R.string.dlg_msg_name) + name + "\n" + getString(R.string.dlg_msg_comment) + comment;
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
                message = "Sent "+sdf.format(Calendar.getInstance().getTime());
                String[] msg = _tvResult.getText().toString().split("\n",0);
                for(int i = 0; i<msg.length; i++) {
                    message = message + "\n"+ msg[i];
                    if ( i> 10 ) {
                        break;
                    }
                }

                _tvResult.setText(message);
            }
        }

        private String is2String(InputStream is) throws IOException {
            BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            StringBuffer sb = new StringBuffer();
            char[] b = new char[1024];
            int line;
            while(0 <= (line = reader.read(b))) {
                sb.append(b, 0, line);
            }
            return sb.toString();
        }
    }
}

Permissions

通信及び接続状態の利用

アプリからインターネット上の外部サーバにアクセスするためには、以下の2つの権限が必要になります。Wear側の生体情報センサはシステム設定画面上からの許可をする必要がありましたが、下記の2つは開発環境では特に何もしなくても権限が取得され、通信ができています。

  • uses-permission android:name=”android.permission.INTERNET” ・・・ インターネット接続を行う際に必要になります。

  • uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” ・・・ 接続状態を取得するために必要になります。

コード解説

端末間での通信: データを Wear から Mobile に送信する。

Wear側(送信側)のプログラム
   private class SensorWorker extends AsyncTask<JobParameters,Void,String> implements SensorEventListener {

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

        private GoogleApiClient mGoogleApiClient;
        private String mNode;

        @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();

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

        @Override
        protected void onPostExecute(String result) {

            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);
        }
    }

Wear→Mobileの通信は定型文ばかりで特に解説する部分もない。これはこういうものだと思って、必要な際にオプションを調べるようにする。

11行目: new GoogleApiClient.Builder()…build()

Google APIを通したクライアントの設定。コールバック(条件が満たされた場合に実行される手続き)の登録もできる。

21行目: mNode = nodes.getNodes().get(0).getId();

通信可能なノード(端末)が複数見つかる場合も想定されている。但し、本プログラムでは配列の最初のノードに決め打ちしている。これで今のところ、不都合がない。

41行目: mGoogleApiClient.connect()

設定を元に接続する。

53行目: Wearable.MessageApi.sendMessage()

データを送信している。


Mobile側(受信側)プログラム
public class MainActivity extends Activity implements GoogleApiClient.ConnectionCallbacks, MessageApi.MessageListener {
    private GoogleApiClient mGoogleApiClient;
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                    @Override
                    public void onConnectionFailed(ConnectionResult connectionResult) {
                        Log.d(TAG, "onConnectionFailed:" + connectionResult.toString());
                    }
                })
                .addApi(Wearable.API)
                .build();
    }
 
    @Override
    protected void onStart() {
        super.onStart();
        mGoogleApiClient.connect();
    }
 
    @Override
    protected void onStop() {
        super.onStop();
    }
  
    @Override
    public void onConnected(Bundle bundle) {
        Wearable.MessageApi.addListener(mGoogleApiClient, this);
    }
 
    @Override
    public void onConnectionSuspended(int i) {
    }
 
    @Override
    public void onMessageReceived(MessageEvent messageEvent) {
        //xTextView.setText(messageEvent.getPath());
        String msg = messageEvent.getPath();
        String[] value = msg.split(",", 0);
    }
 }

続いて受信側の処理です。こちらも定型文が多く、こういうものと思って実装するしかないでしょう。

1行目: implements GoogleApiClient.ConnectionCallbacks

GoogleApiClientを利用した通信の Callbacks(特定の状態になった際に呼ばれる関数)をプログラムするクラスに指定します。この宣言で実装可能な関数は「public void onConnected(Bundle bundle)」と「public void onConnectionSuspended(int i)」です。

尚、上記に加えて「GoogleApiClient.OnConnectionFailedListener」を implements として指定すると、「public void onConnectionFailed(ConnectionResult result)」を実装できるようになります。本プログラムでは利用していません。

8行目: new GoogleApiClient.Builder(this)…build();

諸々の設定を行って、Android間での通信を司るGoogle API Clientのインスタンスを作成しています。

  • .addConnectionCallbacks(this) ・・・ 「onConnected()」と「onConnectionSuspended()」が記述されているインスタンスを指定します。この場合、「this」ですので、このインスタンスのonConnected()」と「onConnectionSuspended()」が利用されます。

  • .addOnConnectionFailedListener() ・・・ 接続失敗時の処理をどのインスタンスで実行するかを指定します。ここでは無名インスタンスを生成しています。

  • .addApi(Wearable.API) ・・・ まじないみたいなものか。未調査。

23行目: mGoogleApiClient.connect()

onStart()内で接続を行っています。

33行目: Wearable.MessageApi.addListener(mGoogleApiClient, this)

伝言を受信した場合に処理を行う関数「onMessageReceived(MessageEvent messageEvent)」がどのインスタンスに存在するかを指定しています。この場合は、「this」としていますので、このインスタンスの「onMessageReceived()」が呼ばれます。

41行目: public void onMessageReceived(MessageEvent messageEvent)

Wearable.addListener()で指定され、伝言を受信した場合に呼び出されます。内容は、「messageEvent.getPath()」で取得できるようです。

データをインターネット上の外部サーバにポストする。

Mobile側(送信)プログラム
public class MainActivity extends Activity implements GoogleApiClient.ConnectionCallbacks, MessageApi.MessageListener {

    @Override
    public void onMessageReceived(MessageEvent messageEvent) {

        PostAccess access = new PostAccess(tvProcess, tvResult);
        access.execute("http://well-being.agnes.aoyama.ac.jp/HR/wp-content/plugins/well-being/post.php", msg,"");
    }
 
 
    private class PostAccess extends AsyncTask<String, String, String> {
        private static final String DEBUG_TAG = "PostAccess";
        private boolean _success = false;
 
        public PostAccess(TextView tvProcess, TextView tvResult) {
        }
 
        @Override
        public String doInBackground(String... params) {
            String urlStr = params[0];
            String msg = params[1];
            String comment = params[2];
 
            String postData = "";
            String[] value = msg.split(",", 0);
            String timestamp = "";
            for( int i=0; i<7; i++) {
                if( i==0 ) {
                    timestamp = value[i];
                    continue;
                } else if( i==1 ) {
                    postData = "d0="+"0,0,"+timestamp+",acc x,"+value[i];
                } else if( i==2 ) {
                    postData = postData+"&amp;d1="+"0,0,"+timestamp+",acc y,"+value[i];
                } else if( i==3 ) {
                    postData = postData+"&amp;d2="+"0,0,"+timestamp+",acc z,"+value[i];
                } else if( i==4 ) {
                    postData = postData+"&amp;d3="+"0,0,"+timestamp+",hr,"+value[i];
                } else if( i==5 ) {
                    postData = postData+"&amp;d4="+"0,0,"+timestamp+",lux,"+value[i];
                } else if( i==6 ) {
                    postData = postData+"&amp;d5="+"0,0,"+timestamp+",sc,"+value[i];
                }
            }
 
            HttpURLConnection con = null;
            InputStream is = null;
            String result = "";
 
            try {
                URL url = new URL(urlStr);
                con = (HttpURLConnection) url.openConnection();
                con.setRequestMethod("POST");
                con.setConnectTimeout(5000);
                con.setReadTimeout(5000);
                con.setDoOutput(true);
                OutputStream os = con.getOutputStream();
                os.write(postData.getBytes());
                os.flush();
                os.close();
                int status = con.getResponseCode();
                if (status != 200) {
                    throw new IOException("ステータスコード: " + status);
                }
                is = con.getInputStream();
 
                result = is2String(is);
                _success = true;
            }
            catch(SocketTimeoutException ex) {
            }
            catch(MalformedURLException ex) {
            }
            catch(IOException ex) {
            }
            finally {
                if (con != null) {
                    con.disconnect();
                }
                try {
                    if (is != null) {
                        is.close();
                    }
                }
                catch (IOException ex) {
                }
            }
            return result;
        }
  
        @Override
        public void onPostExecute(String result) {
            if (_success) {
                String status = "";
                String message = "";
                try {
                    JSONObject rootJson = new JSONObject(result);
                    status = rootJson.getString("status");
                    message = rootJson.getString("message");
                }
                catch (JSONException ex) {
                }
 
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
                message = "Sent "+sdf.format(Calendar.getInstance().getTime());
            }
        }
 
        private String is2String(InputStream is) throws IOException {
            BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
            StringBuffer sb = new StringBuffer();
            char[] b = new char[1024];
            int line;
            while(0 <= (line = reader.read(b))) {
                sb.append(b, 0, line);
            }
            return sb.toString();
        }
    }
}

今度はインターネット(HTTP POST)を介した通信ですが、同じく定型句が多くの部分を占めます。

6行目: private class PostAccess extends AsyncTask

インターネットを介した通信の場合、どうしても処理が遅延する可能性があります。その間、アプリケーションが止まってユーザからの入力を受け付けなくなると、利便性が損なわれます。そこで非同期処理を使って通信を行います。

AsyncTaskは前のページで説明した通りです。

Server側プログラム
<?php

require_once('bioinfo_classes.php');


if(
        ( isset($_SERVER["SHELL"]) || isset($_SERVER["HOMEPATH"]) )
                &amp;&amp; stristr($argv[0],'post.php') ){

                $_REQUEST["d0"]="0,0,".date("Y-m-d H:i:s").",0,0";
}

if( isset($_REQUEST["d0"]) ){
        $i = 0;
        $bioinfo_list = new BioInfo_List();
        while(true){
                $k = "d".$i;
                if( isset($_REQUEST[$k]) ){
                        $bioinfo_list->add_bioinfo_from_csv($_REQUEST[$k]);
                }else{
                        break;
                }
                $i++;
        }
        if( count($bioinfo_list->bioinfo_list) > 0 ){
                $bioinfo_list->insert_db();
        }
        //var_dump($bioinfo_list);
}

Class Bioinfo_List {

	public $bioinfo_list=array();

	public function add_bioinfo_from_csv($csv){
		$rec = new BioInfo();
		$rec->set_values_from_csv($csv);
		$this->bioinfo_list[] = $rec;
	}

	public function insert_db(){
		//echo "insert db<BR>\n";
		try {
			$pdo_opts = [
				PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
				PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
			];
			$pdo = new PDO ( 'mysql:host='.$GLOBALS["DB_HOST"].';dbname='.$GLOBALS["DB_NAME"].';charset=utf8', $GLOBALS["DB_USER"], $GLOBALS["DB_PW"], $pdo_opts );
			$stmt = $pdo->prepare( 'INSERT INTO `'."sensor_data".'`
				(`user_id`,`device_id`,`timestamp`,`sensor_id`,`value`)
				VALUES (:user_id,:device_id,:timestamp,:sensor_id,:value)
				ON DUPLICATE KEY UPDATE
				`value`=VALUES(`value`)
				;');

			foreach( $this->bioinfo_list as $i => $rec ){
				//var_dump($rec);
				$d = explode(' ', $rec->timestamp);
				if( count($d) == 3 ){
					array_pop($d);
					$rec->timestamp=join(' ',$d);
				}
				$stmt->bindValue(':user_id', $rec->user_id, PDO::PARAM_STR);
				$stmt->bindValue(':device_id', $rec->device_id, PDO::PARAM_STR);
				$stmt->bindValue(':timestamp', $rec->timestamp, PDO::PARAM_STR);
				$stmt->bindValue(':sensor_id', $rec->sensor_id, PDO::PARAM_STR);
				$stmt->bindValue(':value', $rec->value, PDO::PARAM_STR);
				$stmt->execute();
			}
		}catch(PDOException $PDO_ERROR){
			header('Content-Type: text/plain; charset=UTF-8', true, 500);
			exit('DataBase ERROR/'.$PDO_ERROR->getMessage()."\n");
		}
		return(count($this->bioinfo_list));
	}
}

Class BioInfo {
	public $user_id;
	public $device_id;
	public $timestamp;
	public $sensor_id;
	public $value;

	public function set_values_from_csv($csv){
		$d = explode(",",$csv);
		$this->user_id = $d[0];
		$this->device_id = $d[1];
		$this->timestamp = $d[2];
		$this->sensor_id = $d[3];
		$this->value = $d[4];
	}
	public function set_values_from_assoc($assoc){
		foreach ( $assoc as $k => $v ){
			$this->$k = $v;
		}
	}
}

?>

サーバ側のプログラムはPHPです。BioInfo というクラスにセンサのデータを対応させ、その配列に対する操作を BioInfo_List というクラスにまとめています。

次のページ

Android Wear センサ活用チュートリアル4 PHP編
Chart.js: グラフを表示する

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