Статьи

Google Fit для Android: API истории

Google Fit — это платформа, которая позволяет разработчикам создавать приложения, ориентированные на данные о пригодности пользователей. Одним из инструментов, предоставляемых Google, является Google Fit для Android, который доступен в виде пакета в Сервисах Google Play.

В предыдущем уроке мы изучали, как использовать Google Fit Recording API для хранения данных о фитнесе через Сервисы Google Play. В этом учебном пособии эта тема раскрывается путем изучения того, как получать доступ к данным, хранящимся в Google Fit, и обновлять их с помощью API History.

Что делает Google Fit мощным, так это то, что данные о фитнесе хранятся через облачные сервисы Google, что позволяет получать к ним доступ и анализировать их позднее. В то время как API датчиков и записи ориентированы на сбор данных о пригодности с устройства Android, API истории предназначен для того, чтобы предоставить разработчикам легкий доступ к этим сохраненным данным.

В этом руководстве вы познакомитесь с примером проекта, который демонстрирует, как работать с History API. Готовый продукт можно скачать с GitHub .

Чтобы использовать Google Fit, вам нужно создать идентификатор клиента OAuth 2.0 и зарегистрировать свое приложение через Консоль разработчика Google. Подробное объяснение того, как настроить проект в консоли разработчика Google, можно найти в моем руководстве по API Google Fit Sensors .

Как только ваше приложение будет настроено для аутентификации в консоли разработчика Google, используйте имя пакета, которое вы зарегистрировали, чтобы создать новое приложение Android с минимальным SDK 9 и пустым Activity .

После создания базового приложения для Android откройте build.gradle , включите Google Play Services в узле зависимостей и синхронизируйте свое приложение.

1
compile ‘com.google.android.gms:play-services-fitness:8.4.0’

Теперь вы сможете включать необходимые классы Google Play Services в свое приложение. Прежде чем мы углубимся в код Java для этого учебника, откройте файл activity_main.xml и изменим его так, чтобы он включал пять элементов Button , которые мы будем использовать для демонстрации некоторых функций API истории.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version=»1.0″ encoding=»utf-8″?>
<LinearLayout xmlns:android=»http://schemas.android.com/apk/res/android»
    android:layout_width=»match_parent»
    android:layout_height=»match_parent»
    android:paddingBottom=»@dimen/activity_vertical_margin»
    android:paddingLeft=»@dimen/activity_horizontal_margin»
    android:paddingRight=»@dimen/activity_horizontal_margin»
    android:paddingTop=»@dimen/activity_vertical_margin»
    android:orientation=»vertical»>
 
    <Button
        android:id=»@+id/btn_view_week»
        android:layout_width=»match_parent»
        android:layout_height=»wrap_content»
        android:text=»View this weeks steps» />
 
    <Button
        android:id=»@+id/btn_view_today»
        android:layout_width=»match_parent»
        android:layout_height=»wrap_content»
        android:text=»View today’s steps»/>
 
    <Button
        android:id=»@+id/btn_add_steps»
        android:layout_width=»match_parent»
        android:layout_height=»wrap_content»
        android:text=»Add step data for today» />
 
    <Button
        android:id=»@+id/btn_update_steps»
        android:layout_width=»match_parent»
        android:layout_height=»wrap_content»
        android:text=»Update step data for yesterday» />
 
    <Button
        android:id=»@+id/btn_delete_steps»
        android:layout_width=»match_parent»
        android:layout_height=»wrap_content»
        android:text=»Delete step data for yesterday» />
 
</LinearLayout>

Когда вы запускаете ваше приложение, пользовательский интерфейс должен выглядеть так:

Чтобы продолжить настройку проекта, откройте MainActivity.java и реализуйте следующие обратные вызовы и необходимые методы:

  • GoogleApiClient.ConnectionCallbacks
  • GoogleAPiClient.OnConnectionFailedListener
  • View.OnClickListener

Вам также необходимо добавить переменную-член для GoogleApiClient и набор ссылок на ваши объекты Button , как показано ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class MainActivity extends AppCompatActivity implements
    GoogleApiClient.ConnectionCallbacks,
    GoogleApiClient.OnConnectionFailedListener,
    View.OnClickListener {
  
    private Button mButtonViewWeek;
    private Button mButtonViewToday;
    private Button mButtonAddSteps;
    private Button mButtonUpdateSteps;
    private Button mButtonDeleteSteps;
 
    private GoogleApiClient mGoogleApiClient;
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
         
        mButtonViewWeek = (Button) findViewById(R.id.btn_view_week);
        mButtonViewToday = (Button) findViewById(R.id.btn_view_today);
        mButtonAddSteps = (Button) findViewById(R.id.btn_add_steps);
        mButtonUpdateSteps = (Button) findViewById(R.id.btn_update_steps);
        mButtonDeleteSteps = (Button) findViewById(R.id.btn_delete_steps);
 
        mButtonViewWeek.setOnClickListener(this);
        mButtonViewToday.setOnClickListener(this);
        mButtonAddSteps.setOnClickListener(this);
        mButtonUpdateSteps.setOnClickListener(this);
        mButtonDeleteSteps.setOnClickListener(this);
    }
  
    @Override
    public void onConnectionSuspended(int i) {
        Log.e(«HistoryAPI», «onConnectionSuspended»);
    }
 
    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
        Log.e(«HistoryAPI», «onConnectionFailed»);
    }
  
    public void onConnected(@Nullable Bundle bundle) {
        Log.e(«HistoryAPI», «onConnected»);
    }
 
  
    @Override
    public void onClick(View v) {
    }
}

Последнее, что вам нужно сделать, чтобы настроить пример проекта, это подключиться к сервисам Google Play. Это можно сделать в конце метода onCreate() .

1
2
3
4
5
6
mGoogleApiClient = new GoogleApiClient.Builder(this)
       .addApi(Fitness.HISTORY_API)
       .addScope(new Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
       .addConnectionCallbacks(this)
       .enableAutoManage(this, 0, this)
       .build();

С помощью приведенного выше кода мы подключаемся к сервисам Google Play и запрашиваем доступ к History API в Google Fit, а также разрешение на чтение и запись данных об активности. Добавляя свойство enableAutoManage , Службы Google Play управляют подключением и отключением правильно.

Используя History API, вы можете извлекать и объединять данные, вставлять значения, которые вы собрали в своем приложении, или удалять данные, которые могут быть сохранены. Важно отметить, что все операции по History API в этом руководстве должны выполняться вне основного потока. Другими словами, каждая операция выполняется в AsyncTask :

1
2
3
4
5
6
private class ViewWeekStepCountTask extends AsyncTask<Void, Void, Void> {
    protected Void doInBackground(Void… params) {
        displayLastWeeksData();
        return null;
    }
}

Одна из задач, которую вы, возможно, захотите выполнить, — использовать или отображать данные, которые были собраны с течением времени. Вы можете сделать это, создав новый DataReadRequest для определенного типа данных, который соответствует некоторым параметрам запроса.

Вы можете сгруппировать эти данные вместе в объектах Bucket по времени или сеансам. В этом примере вы запрашиваете агрегированные данные шага за последнюю неделю и собираете эти данные по дням.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
Calendar cal = Calendar.getInstance();
Date now = new Date();
cal.setTime(now);
long endTime = cal.getTimeInMillis();
cal.add(Calendar.WEEK_OF_YEAR, -1);
long startTime = cal.getTimeInMillis();
 
java.text.DateFormat dateFormat = DateFormat.getDateInstance();
Log.e(«History», «Range Start: » + dateFormat.format(startTime));
Log.e(«History», «Range End: » + dateFormat.format(endTime));
 
//Check how many steps were walked and recorded in the last 7 days
DataReadRequest readRequest = new DataReadRequest.Builder()
        .aggregate(DataType.TYPE_STEP_COUNT_DELTA, DataType.AGGREGATE_STEP_COUNT_DELTA)
        .bucketByTime(1, TimeUnit.DAYS)
        .setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
        .build();

GoogleApiClient DataReadRequest , вы можете запросить данные у GoogleApiClient с помощью команды Fitness.HistoryApi.readData . Вы также вызываете await на API-клиенте, так что поток ожидает до минуты, ожидая запроса. Если что-то идет не так, поток продолжается после этой минуты.

1
DataReadResult dataReadResult = Fitness.HistoryApi.readData(mGoogleApiClient, readRequest).await(1, TimeUnit.MINUTES);

Если вызов успешно завершен, вы можете получить доступ к данным о пригодности пользователя из объекта DataReadResult . Ваш запрос данных шага возвращается в объектах Bucket , хотя другие типы данных возвращаются в виде List объектов DataSet . В зависимости от типа возвращаемых данных вы можете получить к ним следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
//Used for aggregated data
if (dataReadResult.getBuckets().size() > 0) {
    Log.e(«History», «Number of buckets: » + dataReadResult.getBuckets().size());
    for (Bucket bucket : dataReadResult.getBuckets()) {
        List<DataSet> dataSets = bucket.getDataSets();
        for (DataSet dataSet : dataSets) {
            showDataSet(dataSet);
        }
    }
}
//Used for non-aggregated data
else if (dataReadResult.getDataSets().size() > 0) {
    Log.e(«History», «Number of returned DataSets: » + dataReadResult.getDataSets().size());
    for (DataSet dataSet : dataReadResult.getDataSets()) {
        showDataSet(dataSet);
    }
}

Затем вы можете извлечь каждый DataPoint из объектов DataSet , которые были возвращены.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private void showDataSet(DataSet dataSet) {
    Log.e(«History», «Data returned for Data type: » + dataSet.getDataType().getName());
    DateFormat dateFormat = DateFormat.getDateInstance();
    DateFormat timeFormat = DateFormat.getTimeInstance();
 
    for (DataPoint dp : dataSet.getDataPoints()) {
        Log.e(«History», «Data point:»);
        Log.e(«History», «\tType: » + dp.getDataType().getName());
        Log.e(«History», «\tStart: » + dateFormat.format(dp.getStartTime(TimeUnit.MILLISECONDS)) + » » + timeFormat.format(dp.getStartTime(TimeUnit.MILLISECONDS)));
        Log.e(«History», «\tEnd: » + dateFormat.format(dp.getEndTime(TimeUnit.MILLISECONDS)) + » » + timeFormat.format(dp.getStartTime(TimeUnit.MILLISECONDS)));
        for(Field field : dp.getDataType().getFields()) {
            Log.e(«History», «\tField: » + field.getName() +
                    » Value: » + dp.getValue(field));
        }
    }
}

Если приложение на устройстве пользователя включило API записи в течение последней недели, вы получите данные за каждый день, когда оно было активным. Журналы из приведенного выше кода должны выглядеть примерно так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
E/History: Range Start: Feb 17, 2016
E/History: Range End: Feb 24, 2016
E/History: Number of buckets: 7
E/History: Data returned for Data type: com.google.step_count.delta
E/History: Data point:
E/History: Type: com.google.step_count.delta
E/History: Start: Feb 17, 2016 11:01:46 PM
E/History: End: Feb 17, 2016 11:01:46 PM
E/History: Field: steps Value: 9360
E/History: Data returned for Data type: com.google.step_count.delta
E/History: Data returned for Data type: com.google.step_count.delta
E/History: Data returned for Data type: com.google.step_count.delta
E/History: Data point:
E/History: Type: com.google.step_count.delta
E/History: Start: Feb 21, 2016 9:58:03 AM
E/History: End: Feb 21, 2016 9:58:03 AM
E/History: Field: steps Value: 10041
E/History: Data returned for Data type: com.google.step_count.delta
E/History: Data point:
E/History: Type: com.google.step_count.delta
E/History: Start: Feb 21, 2016 11:22:53 PM
E/History: End: Feb 22, 2016 11:22:53 PM
E/History: Field: steps Value: 10786
E/History: Data returned for Data type: com.google.step_count.delta
E/History: Data point:
E/History: Type: com.google.step_count.delta
E/History: Start: Feb 22, 2016 11:06:31 PM
E/History: End: Feb 23, 2016 11:06:31 PM
E/History: Field: steps Value: 13099
E/History: Data returned for Data type: com.google.step_count.delta
E/History: Data point:
E/History: Type: com.google.step_count.delta
E/History: Start: Feb 23, 2016 11:02:31 PM
E/History: End: Feb 24, 2016 11:02:31 PM
E/History: Field: steps Value: 9765

Теперь вы знаете, как получить данные Google Fit на основе таймфрейма. Один из наиболее распространенных вариантов использования — это данные за текущий день. К счастью, Google сделал это проще, добавив вызов Fitness.HistoryApi.readDailyTotal , который создает объект DailyTotalResult содержащий данные, собранные за день.

1
2
3
4
private void displayStepDataForToday() {
    DailyTotalResult result = Fitness.HistoryApi.readDailyTotal( mGoogleApiClient, DataType.TYPE_STEP_COUNT_DELTA ).await(1, TimeUnit.MINUTES);
    showDataSet(result.getTotal());
}

Приведенный выше код записывает данные о шагах пользователя за день.

1
2
3
4
5
6
E/History: Data returned for Data type: com.google.step_count.delta
E/History: Data point:
E/History: Type: com.google.step_count.delta
E/History: Start: Feb 24, 2016 8:39:59 AM
E/History: End: Feb 24, 2016 8:39:59 AM
E/History: Field: steps Value: 9474

Хотя возможность считывания данных из Google Fit важна, в некоторых случаях вам может понадобиться добавить свои собранные данные в хранилище данных Google Fit. Для этого вы выбираете временной диапазон для данных и создаете объект DataSource который представляет информацию, которую вы помещаете в Google Fit. Затем вы создаете объект DataSet и помещаете в него новый DataPoint для операции вставки.

В этом примере мы предположим, что пользователь прошел один миллион шагов, или примерно 500 миль, и вставим эти необработанные данные о шагах в последний час активности пользователя.

Важно отметить, что если вы выберете временной диапазон, охватывающий несколько дней, количество вставляемых шагов будет равномерно распределено между этими днями.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
Calendar cal = Calendar.getInstance();
Date now = new Date();
cal.setTime(now);
long endTime = cal.getTimeInMillis();
cal.add(Calendar.HOUR_OF_DAY, -1);
long startTime = cal.getTimeInMillis();
 
DataSource dataSource = new DataSource.Builder()
    .setAppPackageName(this)
    .setDataType(DataType.TYPE_STEP_COUNT_DELTA)
    .setName(«Step Count»)
    .setType(DataSource.TYPE_RAW)
    .build();
     
int stepCountDelta = 1000000;
DataSet dataSet = DataSet.create(dataSource);
 
DataPoint point = dataSet.createDataPoint()
        .setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS);
point.getValue(Field.FIELD_STEPS).setInt(stepCountDelta);
dataSet.add(point);

После создания объекта DataSet его можно вставить в хранилище данных Google Fit с помощью вызова insertData API insertData .

1
Fitness.HistoryApi.insertData(mGoogleApiClient, dataSet).await(1, TimeUnit.MINUTES);

Если вы вставите шаги пользователя, а затем прочитаете журнал активности, вы должны увидеть что-то похожее на следующее:

1
2
3
4
5
6
E/History: Data returned for Data type: com.google.step_count.delta
E/History: Data point:
E/History: Type: com.google.step_count.delta
E/History: Start: Feb 27, 2016 10:05:18 PM
E/History: End: Feb 28, 2016 10:05:18 PM
E/History: Field: steps Value: 1008747

После того, как вы вставили данные в Google Fit, вы больше не сможете вставить данные такого типа, если они перекрываются с существующими данными. Это может вызвать проблемы, если пользователь прошел 500 миль, а затем прошел еще 500.

Чтобы решить эту проблему, вам нужно использовать вызов updateData . К счастью, обновление данных практически идентично вставке новых данных, за исключением того, что вместо этого вы создаете DataUpdateRequest .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Calendar cal = Calendar.getInstance();
Date now = new Date();
cal.setTime(now);
long endTime = cal.getTimeInMillis();
cal.add(Calendar.HOUR_OF_DAY, -1);
long startTime = cal.getTimeInMillis();
 
DataSource dataSource = new DataSource.Builder()
        .setAppPackageName(this)
        .setDataType(DataType.TYPE_STEP_COUNT_DELTA)
        .setName(«Step Count»)
        .setType(DataSource.TYPE_RAW)
        .build();
 
int stepCountDelta = 2000000;
DataSet dataSet = DataSet.create(dataSource);
 
DataPoint point = dataSet.createDataPoint()
        .setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS);
point.getValue(Field.FIELD_STEPS).setInt(stepCountDelta);
dataSet.add(point);
 
DataUpdateRequest updateRequest = new DataUpdateRequest.Builder().setDataSet(dataSet).setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS).build();
Fitness.HistoryApi.updateData(mGoogleApiClient, updateRequest).await(1, TimeUnit.MINUTES);

Если вы должны были выйти из системы на следующий день после выполнения вышеизложенного, вы должны увидеть более двух миллионов шагов.

1
2
3
4
5
6
E/History: Data returned for Data type: com.google.step_count.delta
E/History: Data point:
E/History: Type: com.google.step_count.delta
E/History: Start: Feb 27, 2016 10:05:18 PM
E/History: End: Feb 28, 2016 10:05:18 PM
E/History: Field: steps Value: 2008747

Последней операцией, доступной через API истории Google Fit, является возможность удаления данных, которые вы сохранили в Google Fit. Это можно сделать, выбрав диапазон дат, создав объект DataDeleteRequest и вызвав deleteData из History API. Хотя вы можете удалить свои собственные вставленные данные, данные, сохраненные в Google Fit, не удаляются.

01
02
03
04
05
06
07
08
09
10
11
12
13
Calendar cal = Calendar.getInstance();
Date now = new Date();
cal.setTime(now);
long endTime = cal.getTimeInMillis();
cal.add(Calendar.DAY_OF_YEAR, -1);
long startTime = cal.getTimeInMillis();
 
DataDeleteRequest request = new DataDeleteRequest.Builder()
        .setTimeInterval(startTime, endTime, TimeUnit.MILLISECONDS)
        .addDataType(DataType.TYPE_STEP_COUNT_DELTA)
        .build();
 
Fitness.HistoryApi.deleteData(mGoogleApiClient, request).await(1, TimeUnit.MINUTES);

Как вы уже видели, History API — это невероятно мощный инструмент для доступа к хранилищу данных Google Fit и управления им. В то время как другие API Google Fit предназначены для сбора данных, API истории позволяет поддерживать и получать доступ к данным для анализа. Теперь вы сможете создавать удивительные приложения, использующие эту функцию.