메뉴 건너뛰기

모바일앱


심박수를 측정할 수 있는 센서가 부착된 안드로이드 스마트폰에서 심박수를 측정하는 코드 예제 입니다.


1. 가장 먼저 Google Developer Console 에서 Fitness API 사용을 활성화 합니다.

- 기존에 만든 프로젝트가 없다면 프로젝트를 생성합니다.

- 생성한 프로젝트에서 좌측의 "라이브러리"를 클릭합니다.

- Fitness API 를 검색합니다. 그리고 '사용설정'을 클릭합니다.


2. Google Developer Console 의 촤측에서 '사용자 인증정보'를 클릭합니다.

3. '사용자 인증정보 만들기'를 클릭하여 "OAuth 클라이언트 ID"를 선택합니다.

4. 안드로이드를 선택하면 서명 인증서 지문과 패키지이름을 입력하는 칸이 나타납니다.

- 아래와 같이 '디버그용 키스토어'의 인증서 지문을 얻어 옵니다. 

- 만약 릴리즈 바이너리용으로 제작하고 싶으시다면 아래의 'debug.keystore' 파일 대신 릴리즈용 키스토어 파일의 이름을 입력합니다

- 아래 내용은 Mac의 콘솔기준입니다.

keytool -keystore ~/.android/debug.keystore -list -v

- 패스워드를 물어보면 그냥 엔터를 입력합니다.

- 그리고 'SHA1' 이후의 값들을 긁어서 Google Developer Console 에 보이는 '서명 인증서 지문'란에 입력합니다.

- 패키지 이름을 입력한 후 '생성' 버튼을 클릭합니다.


5. 안드로이드의 'Androidmanifest.xml' 파일에 아래의 필요 권한들을 입력합니다.

<uses-permission android:name="android.permission.BODY_SENSORS" />
<uses-permission android:name="android.permission.FITNESS_BODY_READ" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

6. Logcat으로 3초마다 심작박동 수를 출력하는 Activity 코드입니다.
package org.airpage.heartbeat;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Build;
import android.os.PowerManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.Scopes;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.common.api.Scope;
import com.google.android.gms.common.api.Status;
import com.google.android.gms.fitness.Fitness;
import com.google.android.gms.fitness.data.DataPoint;
import com.google.android.gms.fitness.data.DataSource;
import com.google.android.gms.fitness.data.DataType;
import com.google.android.gms.fitness.data.Field;
import com.google.android.gms.fitness.data.Value;
import com.google.android.gms.fitness.request.DataSourcesRequest;
import com.google.android.gms.fitness.request.OnDataPointListener;
import com.google.android.gms.fitness.request.SensorRequest;
import com.google.android.gms.fitness.result.DataSourcesResult;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity {

    private String TAG = MainActivity.class.getName();
    private GoogleApiClient googleApiClient;
    private boolean authInProgress = false;
    private OnDataPointListener onDataPointListener;
    private static final int AUTH_REQUEST = 1;

    private static final String[] REQUIRED_PERMISSION_LIST = new String[]{
            Manifest.permission.BODY_SENSORS
    };

    private static final int REQUEST_PERMISSION_CODE = 12345;
    private List<String> missingPermission = new ArrayList<>();

    private boolean bCheckStarted = false;
    private boolean bGoogleConnected = false;

    private Button btnStart;
    private ProgressBar spinner;
    private PowerManager powerManager;
    private PowerManager.WakeLock wakeLock;
    private TextView textMon;



    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, "WAKELOCK");
        initUI();
        checkAndRequestPermissions();
    }

    private void initUI() {

        initGoogleApiClient();

        textMon = findViewById(R.id.textMon);
        spinner = findViewById(R.id.progressBar1);
        spinner.setVisibility(View.INVISIBLE);
        btnStart = findViewById(R.id.btnStart);
        btnStart.setText("Wait please ...");
        btnStart.setEnabled(false);
        btnStart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (bCheckStarted) {
                    //btnStart.setText(R.string.msg_start);
                    btnStart.setText("Start");
                    bCheckStarted = false;

                    unregisterFitnessDataListener();

                    spinner.setVisibility(View.INVISIBLE);

                    wakeLock.release();
                }
                else {
                    if (bGoogleConnected == true) {
                        findDataSources();
                        registerDataSourceListener(DataType.TYPE_HEART_RATE_BPM);
                        btnStart.setText("Stop");
                        //btnStart.setText(R.string.msg_stop);
                        bCheckStarted = true;

                        spinner.setVisibility(View.VISIBLE);

                        wakeLock.acquire();

                    }
                    else {
                        if (MainActivity.this.googleApiClient != null)
                            MainActivity.this.googleApiClient.connect();
                    }
                }
            }
        });
    }

    private void initGoogleApiClient() {
        this.googleApiClient = new GoogleApiClient.Builder(this)
                .addApi(Fitness.SENSORS_API)
                .addScope(new Scope(Scopes.FITNESS_BODY_READ))
                //.addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ))
                .addConnectionCallbacks(
                        new GoogleApiClient.ConnectionCallbacks() {

                            @Override
                            public void onConnected(Bundle bundle) {
                                Log.d(TAG, "initGoogleApiClient() onConnected good...");

                                bGoogleConnected = true;
                                btnStart.setText("Start");
                                btnStart.setEnabled(true);
                            }

                            @Override
                            public void onConnectionSuspended(int i) {

                                if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_NETWORK_LOST) {
                                    Log.d(TAG, "onConnectionSuspended() network_lost bad...");
                                } else if (i == GoogleApiClient.ConnectionCallbacks.CAUSE_SERVICE_DISCONNECTED) {
                                    Log.d(TAG, "onConnectionSuspended() service_disconnected bad...");

                                }
                            }
                        }
                )
                .addOnConnectionFailedListener(
                        new GoogleApiClient.OnConnectionFailedListener() {

                            @Override
                            public void onConnectionFailed(ConnectionResult result) {
                                Log.d(TAG, "Connection failed. Cause: " + result.toString());

                                if (!result.hasResolution()) {
                                    MainActivity.this.finish();
                                    return;
                                }

                                if (!authInProgress) {
                                    try {
                                        Log.d(TAG, "Attempting to resolve failed connection");
                                        authInProgress = true;
                                        result.startResolutionForResult(MainActivity.this,
                                                AUTH_REQUEST);
                                    } catch (IntentSender.SendIntentException e) {
                                        Log.e(TAG,
                                                "Exception while starting resolution activity", e);
                                        MainActivity.this.finish();
                                    }
                                }
                                else {
                                    MainActivity.this.finish();
                                }
                            }
                        }
                )
                .build();
    }

    /**
     * Checks if there is any missing permissions, and
     * requests runtime permission if needed.
     */
    private void checkAndRequestPermissions() {
        // Check for permissions
        for (String eachPermission : REQUIRED_PERMISSION_LIST) {
            if (ContextCompat.checkSelfPermission(this, eachPermission) != PackageManager.PERMISSION_GRANTED) {
                missingPermission.add(eachPermission);
            }
        }
        // Request for missing permissions
        if (missingPermission.isEmpty()) {
            if (MainActivity.this.googleApiClient != null)
                MainActivity.this.googleApiClient.connect();
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            ActivityCompat.requestPermissions(this,
                    missingPermission.toArray(new String[missingPermission.size()]),
                    REQUEST_PERMISSION_CODE);
        } else {
            if (MainActivity.this.googleApiClient != null)
                MainActivity.this.googleApiClient.connect();
        }

    }

    /**
     * Result of runtime permission request
     */
    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String[] permissions,
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        // Check for granted permission and remove from missing list
        if (requestCode == REQUEST_PERMISSION_CODE) {
            for (int i = grantResults.length - 1; i >= 0; i--) {
                if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
                    missingPermission.remove(permissions[i]);
                }
            }
        }
        // If there is enough permission, we will start the registration
        if (missingPermission.isEmpty()) {
            initGoogleApiClient();
            if (MainActivity.this.googleApiClient != null)
                MainActivity.this.googleApiClient.connect();
        } else {
            Toast.makeText(getApplicationContext(), "Failed get permissions", Toast.LENGTH_LONG).show();
            finish();
        }
    }

    private void findDataSources() {
        Fitness.SensorsApi.findDataSources(googleApiClient, new DataSourcesRequest.Builder()
                .setDataTypes(DataType.TYPE_HEART_RATE_BPM)
                // .setDataTypes(DataType.TYPE_SPEED)
                // .setDataTypes(DataType.TYPE_STEP_COUNT_CUMULATIVE)
                .setDataSourceTypes(DataSource.TYPE_RAW)
                .build())
                .setResultCallback(new ResultCallback<DataSourcesResult>() {
                    @Override
                    public void onResult(DataSourcesResult dataSourcesResult) {

                        for (DataSource dataSource : dataSourcesResult.getDataSources()) {

                            if (dataSource.getDataType().equals(DataType.TYPE_HEART_RATE_BPM)
                                    && onDataPointListener == null) {
                                Log.d(TAG, "findDataSources onResult() registering dataSource=" + dataSource);
                                registerDataSourceListener(DataType.TYPE_HEART_RATE_BPM);

                            }
                        }
                    }
                });

    }


    private void registerDataSourceListener(DataType dataType) {
        onDataPointListener = new OnDataPointListener() {
            @Override
            public void onDataPoint(DataPoint dataPoint) {
                for (Field field : dataPoint.getDataType().getFields()) {
                    Value aValue = dataPoint.getValue(field);
                    //Log.d(TAG, "Detected DataPoint field: " + field.getName());
                    //Log.d(TAG, "Detected DataPoint value: " + aValue);

                    //addContentToView("dataPoint=" + field.getName() + " " + aValue + "\n");
                    addContentToView(aValue.asFloat());
                }
            }
        };

        Fitness.SensorsApi.add(
                googleApiClient,
                new SensorRequest.Builder()
                        .setDataType(dataType)
                        .setSamplingRate(2, TimeUnit.SECONDS)
                        .setAccuracyMode(SensorRequest.ACCURACY_MODE_DEFAULT)
                        .build(),
                onDataPointListener)
                .setResultCallback(new ResultCallback<Status>() {
                    @Override
                    public void onResult(Status status) {
                        if (status.isSuccess()) {
                            Log.d(TAG, "onDataPointListener  registered good");
                        } else {
                            Log.d(TAG, "onDataPointListener failed to register bad");
                        }
                    }
                });

    }

    private void unregisterFitnessDataListener() {
        if (this.onDataPointListener == null) {
            return;
        }

        if (this.googleApiClient == null) {
            return;
        }

        if (this.googleApiClient.isConnected() == false) {
            return;
        }

        Fitness.SensorsApi.remove(
                this.googleApiClient,
                this.onDataPointListener)
                .setResultCallback(new ResultCallback<Status>() {
                    @Override
                    public void onResult(Status status) {
                        if (status.isSuccess()) {
                            Log.d(TAG, "Listener was removed!");
                        } else {
                            Log.d(TAG, "Listener was not removed.");
                        }
                    }
                });
        // [END unregister_data_listener]
    }


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

        Log.d(TAG, "onStart connect attempted");
    }

    @Override
    protected void onStop() {
        super.onStop();
        unregisterFitnessDataListener();

        if (this.googleApiClient != null && this.googleApiClient.isConnected()) {
            this.googleApiClient.disconnect();
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == AUTH_REQUEST) {
            authInProgress = false;

            if (resultCode == RESULT_OK) {

                if (!this.googleApiClient.isConnecting() && !this.googleApiClient.isConnected()) {
                    this.googleApiClient.connect();
                    Log.d(TAG, "onActivityResult googleApiClient.connect() attempted in background");

                }
            }
        }
    }

    private synchronized void addContentToView(final float value) {


        runOnUiThread(new Runnable() {

            @Override
            public void run() {

                if (spinner.getVisibility() == View.VISIBLE)
                    spinner.setVisibility(View.INVISIBLE);

                Log.d(TAG,"Heart Beat Rate Value : " + value);
                textMon.setText("Heart Beat Rate Value : " + value);
            }
        });
    }
}



7. Layout 파일입니다.

<!--
Copyright 2014 Google, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/main_activity_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingLeft="10dp"
    android:paddingTop="10dp"
    android:paddingRight="10dp"
    android:paddingBottom="10dp"
    android:background="#fff"
    android:weightSum="5"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="2"
        android:orientation="vertical">

        <ProgressBar
            android:layout_gravity="center_horizontal"
            android:id="@+id/progressBar1"
            style="?android:attr/progressBarStyleLarge"
            android:layout_width="30dp"
            android:layout_weight="1"
            android:layout_height="30dp"
            android:layout_centerHorizontal="true" />

        <TextView
            android:id="@+id/textMon"
            android:textAlignment="center"
            android:layout_weight="1"
            android:layout_width="match_parent"
            android:layout_height="30dp" />

        <Button
            android:id="@+id/btnStart"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_margin="10dp"
            android:layout_weight="1"
            android:padding="3dp"
            android:text="START"
            android:textAppearance="?android:attr/textAppearanceSmall"
            android:textColor="#333"
            android:typeface="normal" />
    </LinearLayout>
</LinearLayout>

건투를 빕니다^^





profile
제목 날짜
[TIP] 죽지 않는 안드로이드 서비스 만들기 (Unstoppable service) 6 2015.06.26
[TIP] 안드로이드 앱 빌드시 "Error:Execution failed for task ':app:compileDebugJavaWithJavac'" 오류가 발생할 경우 2017.09.13
[TIP] 안드로이드 앱 삭제 방지 기능 구현 2015.06.03
[TIP] 안드로이드에서 심박수 측정하는 코드 2019.01.02
[TIP] Google Cloud API 사용시 안드로이드의 Assets 폴더에 있는 Crendential 파일 사용하기 2018.05.29
[TIP] 안드로이드 스튜디오에서 Error:android-apt plugin is incompatible with the Android Gradle plugin. Please use 'annotationProcessor' configuration instead 오류날때 2018.02.02
[TIP] 안드로이드 앱 빌드시 "Error:Execution failed for task ':app:compileDebugJavaWithJavac'" 오류가 발생할 경우 2017.09.13
[TIP] Error:java.lang.OutOfMemoryError: GC overhead limit exceeded 2016.11.03
[TIP] 안드로이드 앱이 처음 설치될때 Referrer 정보 받아 오기 2016.08.30
[TIP] 안드로이드에서 구글 스프레드시트에 데이터 쓰기 2016.05.04
[TIP] 키보드가 나타날때 레이아웃이 위로 움직인다면 2016.03.22
[TIP] 안드로이드 - 설치된 앱 목록 얻기 2016.03.22
[TIP] 안드로이드의 최상단에 띄워놓은 Floating Window가 키보드를 인지하는 방법 2016.01.07
[TIP] 안드로이드의 Floating Window가 Back키를 인지 하는 방법 2 2016.01.07
[TIP] 죽지 않는 안드로이드 서비스 만들기 (Unstoppable service) 6 2015.06.26
[TIP] 내 안드로이드 앱에 위젯을 띄우는 방법 2015.06.03
[TIP] 안드로이드 앱 삭제 방지 기능 구현 2015.06.03
[TIP] Jquery Mobile로 iOS앱 개발시 Status바 처리 2014.08.27
[TIP] iOS 앱 개발시 HTTP POST로 데이터 전송 방법 2014.06.07
[TIP] 안드로이드에서 대용량 이미지 읽기 2013.04.15
[TIP] 안드로이드 카메라 영상에 그림 그린후 저장하기 2013.01.02
[펌] 안드로이드 에뮬레이터 속도 개선방법 2012.07.19
Android 기반의 재미있는 개발제품을 파는 사이트 2012.04.26
태그 목록
위로