Сведения об использовании пакета SDK для мобильных приложений Azure в клиенте Android.

В этом руководстве показано, как использовать пакет SDK для мобильных приложений для клиентов Android, чтобы реализовать распространенные сценарии, в том числе:

  • запрос данных (вставка, обновление и удаления);
  • Аутентификация.
  • обработка ошибок;
  • настройка клиента.

В этом руководстве описано использование клиентского пакета Android SDK. См. дополнительные сведения о серверных пакетах SDK для мобильных приложений, включая сведения о работе с серверными пакетами SDK для .NET и использовании серверного пакета SDK для Node.js.

Справочная документация

Справочник Javadocs по API клиентской библиотеки Android см. на сайте GitHub.

Поддерживаемые платформы

Пакет SDK для мобильных приложений Azure в клиенте Android поддерживает API уровней 19–24 (от KitKat до Nougat). Это применяется к телефонам и планшетам. В процессе проверки подлинности, в частности, применяется общий метод сбора учетных данных, используемый на веб-платформах. Серверный поток проверки подлинности не работает на устройствах малого форм-фактора, таких как часы.

Настройка и необходимые компоненты

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

Если вы решите не изучать краткий учебник, то выполните следующие задачи.

Обновление файла сборки Gradle

Измените оба файла build.gradle :

  1. добавьте следующий код в файл build. gradle уровня Project :

    buildscript {
        repositories {
            jcenter()
            google()
        }
    }
    
    allprojects {
        repositories {
            jcenter()
            google()
        }
    }
    
  2. Добавьте следующий код в файл build.gradle уровня Module app внутри тега dependencies.

    implementation 'com.microsoft.azure:azure-mobile-android:3.4.0@aar'
    

    Сейчас последней версией является 3.4.0. Поддерживаемые версии см. на сайте Bintray.

Включение разрешения INTERNET

Для доступа к Azure вам нужно включить для приложения разрешение INTERNET. Если оно не включено, добавьте следующую строку кода в файл AndroidManifest.xml :

<uses-permission android:name="android.permission.INTERNET" />

Создание подключения клиента

Служба мобильных приложений Azure предоставляет четыре функции для мобильных приложений:

  • доступ к данным и их синхронизация в автономном режиме со службой мобильных приложений Azure;
  • вызов настраиваемых API-интерфейсов, написанных с использованием пакета SDK для сервера мобильных приложений Azure;
  • проверка подлинности с помощью функции проверки подлинности и авторизации в службе приложений Azure;
  • регистрация push-уведомлений в Центрах уведомлений.

Чтобы использовать каждую из этих функций, сначала необходимо создать объект MobileServiceClient. В мобильном клиенте необходимо создать только один объект MobileServiceClient (то есть следует использовать шаблон с одним элементом). Чтобы создать объект MobileServiceClient, используйте следующий код:

MobileServiceClient mClient = new MobileServiceClient(
    "<MobileAppUrl>",       // Replace with the Site URL
    this);                  // Your application Context

Параметр <MobileAppUrl> — это строка или URL-адрес, указывающий на серверную часть мобильного приложения. Если серверная часть мобильного приложения расположена в службе приложений Azure, используйте безопасную версию https:// URL-адреса.

Клиенту требуется доступ к классу Activity или Context. В приведенном примере кода за это отвечает параметр this. Создание объекта MobileServiceClient должно начаться в методе onCreate() класса Activity, указанного в файле AndroidManifest.xml.

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

package com.example.appname.services;

import android.content.Context;
import com.microsoft.windowsazure.mobileservices.*;

public class AzureServiceAdapter {
    private String mMobileBackendUrl = "https://myappname.azurewebsites.net";
    private Context mContext;
    private MobileServiceClient mClient;
    private static AzureServiceAdapter mInstance = null;

    private AzureServiceAdapter(Context context) {
        mContext = context;
        mClient = new MobileServiceClient(mMobileBackendUrl, mContext);
    }

    public static void Initialize(Context context) {
        if (mInstance == null) {
            mInstance = new AzureServiceAdapter(context);
        } else {
            throw new IllegalStateException("AzureServiceAdapter is already initialized");
        }
    }

    public static AzureServiceAdapter getInstance() {
        if (mInstance == null) {
            throw new IllegalStateException("AzureServiceAdapter is not initialized");
        }
        return mInstance;
    }

    public MobileServiceClient getClient() {
        return mClient;
    }

    // Place any public methods that operate on mClient here.
}

Теперь вы можете вызвать AzureServiceAdapter.Initialize(this); в методе onCreate() основного действия. Другие методы, которым нужен доступ к клиенту, используют AzureServiceAdapter.getInstance();, чтобы получить ссылку на адаптер службы.

Операции с данными

Ядро пакета SDK для мобильных приложений Azure предоставляет доступ к данным, сохраненным в SQL Azure в серверной части мобильного приложения. Доступ к этим данным можно получить, используя классы со строгим типом (предпочтительный способ) или нетипизированные запросы (нерекомендуемый способ). Основная часть этого раздела посвящена использованию классов со строгим типом.

Определение клиентских классов данных

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

  • идентификатор
  • text
  • complete

Соответствующий типизированный клиентский объект находится в файле MyDataTable.java:

public class ToDoItem {
    private String id;
    private String text;
    private Boolean complete;
}

Включите методы Getter или Setter в каждое добавленное поле. Если таблица SQL Azure содержит больше столбцов, в этот класс необходимо добавить соответствующие поля. Например, если объект переноса данных (DTO) содержал целочисленный столбец Priority, то вы можете добавить это поле вместе с методами получения и задания.

private Integer priority;

/**
* Returns the item priority
*/
public Integer getPriority() {
    return mPriority;
}

/**
* Sets the item priority
*
* @param priority
*            priority to set
*/
public final void setPriority(Integer priority) {
    mPriority = priority;
}

См. дополнительные сведения о создании дополнительных таблиц в серверной части мобильных приложений, включая сведения об определении контроллера таблиц (серверная часть .NET) и определении таблиц с помощью динамической схемы (серверная часть Node.js).

В таблице серверной части мобильных приложений Azure определено пять специальных полей, четыре из которых доступны клиентам:

  • String id — глобальный уникальный идентификатор записи. Мы советуем сделать этот идентификатор представлением строки объекта UUID.
  • DateTimeOffset updatedAt — дата и время последнего обновления. Поле updatedAt устанавливается на сервере и никогда не должно задаваться в коде клиента.
  • DateTimeOffset createdAt — дата и время создания объекта. Поле createdAt устанавливается на сервере и никогда не должно задаваться в коде клиента.
  • byte[] version — обычно это поле представлено в виде строки. Версия также устанавливается на сервере.
  • boolean deleted — указывает, что запись была удалена, но еще не очищена. Не используйте deleted в качестве свойства класса.

Поле id является обязательным. Поля updatedAt и version используются при синхронизации в автономном режиме (для добавочной синхронизации и устранения конфликтов соответственно). createdAt — это эталонное поле. Оно не используется клиентом. Имена свойств можно передавать по сети. Они не изменяются. Но вы можете создать сопоставление между своим объектом и этими именами, используя библиотеку gson. Пример:

package com.example.zumoappname;

import com.microsoft.windowsazure.mobileservices.table.DateTimeOffset;

public class ToDoItem
{
    @com.google.gson.annotations.SerializedName("id")
    private String mId;
    public String getId() { return mId; }
    public final void setId(String id) { mId = id; }

    @com.google.gson.annotations.SerializedName("complete")
    private boolean mComplete;
    public boolean isComplete() { return mComplete; }
    public void setComplete(boolean complete) { mComplete = complete; }

    @com.google.gson.annotations.SerializedName("text")
    private String mText;
    public String getText() { return mText; }
    public final void setText(String text) { mText = text; }

    @com.google.gson.annotations.SerializedName("createdAt")
    private DateTimeOffset mCreatedAt;
    public DateTimeOffset getCreatedAt() { return mCreatedAt; }
    protected void setCreatedAt(DateTimeOffset createdAt) { mCreatedAt = createdAt; }

    @com.google.gson.annotations.SerializedName("updatedAt")
    private DateTimeOffset mUpdatedAt;
    public DateTimeOffset getUpdatedAt() { return mUpdatedAt; }
    protected void setUpdatedAt(DateTimeOffset updatedAt) { mUpdatedAt = updatedAt; }

    @com.google.gson.annotations.SerializedName("version")
    private String mVersion;
    public String getVersion() { return mVersion; }
    public final void setVersion(String version) { mVersion = version; }

    public ToDoItem() { }

    public ToDoItem(String id, String text) {
        this.setId(id);
        this.setText(text);
    }

    @Override
    public boolean equals(Object o) {
        return o instanceof ToDoItem && ((ToDoItem) o).mId == mId;
    }

    @Override
    public String toString() {
        return getText();
    }
}

Создание ссылки на таблицу

Для доступа к таблице сначала создайте объект MobileServiceTable, вызвав метод getTable объекта MobileServiceClient. У этого метода две перегрузки.

public class MobileServiceClient {
    public <E> MobileServiceTable<E> getTable(Class<E> clazz);
    public <E> MobileServiceTable<E> getTable(String name, Class<E> clazz);
}

В следующем коде mClient — это ссылка на объект MobileServiceClient. Первая перегрузка используется при быстром запуске, когда имя класса и имя таблицы одинаковы.

MobileServiceTable<ToDoItem> mToDoTable = mClient.getTable(ToDoItem.class);

Вторая перегрузка используется, когда имя таблицы отличается от имени типа (первый параметр — это имя таблицы).

MobileServiceTable<ToDoItem> mToDoTable = mClient.getTable("ToDoItemBackup", ToDoItem.class);

Запрос таблицы серверной части

Сначала получите ссылку на таблицу. Затем выполните запрос к ссылке на таблицу. Запрос сочетает в себе следующее:

Предложения должны располагаться в таком же порядке.

Фильтрация результатов

Запрос выглядит следующим образом:

List<MyDataTable> results = mDataTable
    // More filters here
    .execute()          // Returns a ListenableFuture<E>
    .get()              // Converts the async into a sync result

Приведенный выше пример возвращает все результаты (вплоть до максимального размера страницы, заданного на сервере). Метод .execute() выполняет запрос в серверной части. Перед передачей в серверную часть мобильных приложений этот запрос преобразовывается в запрос OData v3. После получения этот запрос преобразовывается в серверной части в инструкцию SQL перед выполнением его на экземпляре SQL Azure. Так как сетевая активность занимает некоторое время, метод .execute() возвращает ListenableFuture<E>.

Фильтрация возвращаемых данных

Следующий запрос при выполнении возвращает все элементы из таблицы ToDoItem, где для переменной complete задано значение false.

List<ToDoItem> result = mToDoTable
    .where()
    .field("complete").eq(false)
    .execute()
    .get();

mToDoTable — это ссылка на таблицу мобильной службы, созданную ранее.

Для определения фильтра вызовите метод where для ссылки на таблицу. После метода where вызывается метод field, а затем — метод, который определяет логический предикат. Возможные методы предиката: eq (равно), ne (не равно), gt (больше), ge (больше или равно), lt (меньше), le (меньше или равно). Эти методы позволяют сравнивать числовые и строковые поля с указанными значениями.

Фильтровать данные можно по датам. Следующие методы позволяют сравнить поле даты целиком или частично: year, month, day, hour, minute и second. В следующем примере добавляется фильтр для элементов, дата выполнения которых равна 2013.

List<ToDoItem> results = MToDoTable
    .where()
    .year("due").eq(2013)
    .execute()
    .get();

Следующие методы поддерживают применение сложных фильтров к строковым полям: startsWith, endsWith, concat, subString, indexOf, replace, toLower, toUpper, trim и length. В следующем примере отфильтровываются строки таблицы, в которых столбец text начинается с PRI0.

List<ToDoItem> results = mToDoTable
    .where()
    .startsWith("text", "PRI0")
    .execute()
    .get();

Следующие методы оператора поддерживают использование числовых полей: add, sub, mul, div, mod, floor, ceiling и round. В следующем примере отфильтровываются строки таблицы, в которых значение duration является четным числом.

List<ToDoItem> results = mToDoTable
    .where()
    .field("duration").mod(2).eq(0)
    .execute()
    .get();

Предикаты можно объединять с помощью логических методов and, or и not. В следующем примере объединяются два приведенных выше примера.

List<ToDoItem> results = mToDoTable
    .where()
    .year("due").eq(2013).and().startsWith("text", "PRI0")
    .execute()
    .get();

Группирование и вложение логических операторов.

List<ToDoItem> results = mToDoTable
    .where()
    .year("due").eq(2013)
    .and(
        startsWith("text", "PRI0")
        .or()
        .field("duration").gt(10)
    )
    .execute().get();

Более подробное описание и примеры фильтрации см. в статье Exploring the richness of the Mobile Services Android client query model (Исследование возможностей модели запросов клиента мобильных служб Android).

Сортировка возвращаемых данных

Следующий код возвращает все элементы из таблицы ToDoItem , упорядоченные по полю text . mToDoTable — это ссылка на таблицу серверной части, созданную ранее:

List<ToDoItem> results = mToDoTable
    .orderBy("text", QueryOrder.Ascending)
    .execute()
    .get();

Первый параметр метода orderBy — это строка, совпадающая с именем поля, по которому выполняется сортировка. Второй параметр использует перечисление QueryOrder , чтобы определить порядок сортировки (по возрастанию или убыванию). При фильтрации с помощью метода WHERE метод WHERE должен быть вызван до метода OrderBy .

Выбор определенных столбцов

В следующем фрагменте кода показано, как получить все элементы из таблицы ToDoItems, в которой отображаются только поля complete и text. mToDoTable — это ссылка на таблицу серверной части, созданную ранее:

List<ToDoItemNarrow> result = mToDoTable
    .select("complete", "text")
    .execute()
    .get();

Параметры функции select являются строковыми именами столбцов таблицы, которые требуется вернуть. Метод select должен следовать за такими методами, как where и orderBy. Затем можно вызывать такие методы разбивки на страницы, как skip и top.

Возврат данных на страницах

Данные всегда возвращаются на страницах. Максимальное число возвращаемых записей устанавливается на сервере. Если клиент запрашивает большее число записей, сервер возвращает только установленное максимальное количество записей. По умолчанию максимальный размер страницы на сервере составляет 50 записей.

Первый пример показывает, как выбрать пять верхних элементов из таблицы. Следующий запрос возвращает элементы из таблицы ToDoItem. mToDoTable — это ссылка на таблицу серверной части, созданную ранее:

List<ToDoItem> result = mToDoTable
    .top(5)
    .execute()
    .get();

Приведенный ниже запрос пропускает первые пять элементов, возвращая следующие пять элементов.

List<ToDoItem> result = mToDoTable
    .skip(5).top(5)
    .execute()
    .get();

Если вы хотите получить все записи в таблице, реализуйте код для выполнения итерации по всех страницах:

List<MyDataModel> results = new ArrayList<>();
int nResults;
do {
    int currentCount = results.size();
    List<MyDataModel> pagedResults = mDataTable
        .skip(currentCount).top(500)
        .execute().get();
    nResults = pagedResults.size();
    if (nResults > 0) {
        results.addAll(pagedResults);
    }
} while (nResults > 0);

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

Совет

Выбор правильного размера страницы — это соотношение между использованием памяти во время выполнения запроса, потреблением пропускной способности и задержкой получения всех данных. Значение по умолчанию (50 записей) подходит для всех устройств. Если вы работаете на устройствах с большим объемом памяти, увеличьте это значение до 500. Мы пришли к выводу, что при увеличении размера страницы до более 500 записей возникают недопустимые задержки и большие проблемы с памятью.

Практическое руководство. Объединение методов запросов

Методы, используемые в запросах таблиц серверной части, можно сцепить. Цепочка методов запроса позволяет, например, выбирать определенные столбцы отфильтрованных строк, которые сортируются и разбиваются на страницы. Можно создавать сложные логические фильтры. Каждый метод запроса возвращает объект Query. Чтобы завершить серию методов и фактически выполнить запрос, вызовите метод execute . Пример:

List<ToDoItem> results = mToDoTable
        .where()
        .year("due").eq(2013)
        .and(
            startsWith("text", "PRI0").or().field("duration").gt(10)
        )
        .orderBy(duration, QueryOrder.Ascending)
        .select("id", "complete", "text", "duration")
        .skip(200).top(100)
        .execute()
        .get();

Методы запроса в цепочке должны быть упорядочены следующим образом:

  1. методы фильтрации (где);
  2. методы сортировки (orderBy);
  3. методы выборки (выберите);
  4. методы разбиения по страницам (skip и top).

Привязка данных к пользовательскому интерфейсу

Привязка данных состоит из трех компонентов:

  • источник данных;
  • макет экрана;
  • адаптер, который связывает два эти компонента.

В нашем примере кода мы возвращаем данные из таблицы ToDoItem SQL Azure мобильных приложений в массив. Это действие типично для приложений для обработки данных. Запросы к базе данных часто возвращают набор строк, которые клиент получает в виде списка или массива. В этом примере массив является источником данных. Код указывает макет экрана, который определяет представление данных, отображаемых на устройстве. Два компонента соединяются адаптером, который в этом коде является расширением класса ArrayAdapter<ToDoItem>.

Определение макета

Макет определяется несколькими фрагментами XML-кода. С учетом существующего макета приведенный ниже код представляет объект ListView , который требуется заполнить данными с сервера.

    <ListView
        android:id="@+id/listViewToDo"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        tools:listitem="@layout/row_list_to_do" >
    </ListView>

В приведенном выше коде атрибут listitem указывает идентификатор макета для отдельной строки в списке. Этот код задает флажок и связанный с ним текст, при этом экземпляры создаются для каждого элемента в списке. В этом макете поле id не отображается. В более сложном макете указываются дополнительные поля на экране. Этот код находится в файле row_list_to_do.xml.

<?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:orientation="horizontal">
    <CheckBox
        android:id="@+id/checkToDoItem"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/checkbox_text" />
</LinearLayout>

Определение адаптера

Источник данных нашего представления — это массив объектов ToDoItem, поэтому мы создадим адаптер как подкласс ArrayAdapter<ToDoItem>. Этот подкласс создаст представление для каждого объекта ToDoItem с помощью макета row_list_to_do. В коде мы определили следующий класс, который представляет собой расширение класса ArrayAdapter<E>:

public class ToDoItemAdapter extends ArrayAdapter<ToDoItem> {
}

Переопределите метод getView адаптера. Пример:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;

        final ToDoItem currentItem = getItem(position);

        if (row == null) {
            LayoutInflater inflater = ((Activity) mContext).getLayoutInflater();
            row = inflater.inflate(R.layout.row_list_to_do, parent, false);
        }
        row.setTag(currentItem);

        final CheckBox checkBox = (CheckBox) row.findViewById(R.id.checkToDoItem);
        checkBox.setText(currentItem.getText());
        checkBox.setChecked(false);
        checkBox.setEnabled(true);

        checkBox.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                if (checkBox.isChecked()) {
                    checkBox.setEnabled(false);
                    if (mContext instanceof ToDoActivity) {
                        ToDoActivity activity = (ToDoActivity) mContext;
                        activity.checkItem(currentItem);
                    }
                }
            }
        });
        return row;
    }

Мы создаем экземпляр этого класса в действии следующим образом:

    ToDoItemAdapter mAdapter;
    mAdapter = new ToDoItemAdapter(this, R.layout.row_list_to_do);

Второй параметр конструктора ToDoItemAdapter — это ссылка на макет. Теперь можно создать экземпляр ListView и назначить адаптер в ListView.

    ListView listViewToDo = (ListView) findViewById(R.id.listViewToDo);
    listViewToDo.setAdapter(mAdapter);

Привязка данных к пользовательскому интерфейсу с помощью адаптера

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

    public void showAll(View view) {
        AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>(){
            @Override
            protected Void doInBackground(Void... params) {
                try {
                    final List<ToDoItem> results = mToDoTable.execute().get();
                    runOnUiThread(new Runnable() {

                        @Override
                        public void run() {
                            mAdapter.clear();
                            for (ToDoItem item : results) {
                                mAdapter.add(item);
                            }
                        }
                    });
                } catch (Exception exception) {
                    createAndShowDialog(exception, "Error");
                }
                return null;
            }
        };
        runAsyncTask(task);
    }

Адаптер вызывается при каждом изменении таблицы ToDoItem . Так как изменения выполняются на уровне отдельных записей, обрабатываться будут отдельные строки, а не коллекция. При вставке элемента вызывается метод add адаптера, а при удалении — метод remove.

Полный пример можно найти в проекте быстрого запуска для Android.

Вставка данных в серверную часть

Создайте экземпляр класса ToDoItem и задайте его свойства.

ToDoItem item = new ToDoItem();
item.text = "Test Program";
item.complete = false;

Затем с помощью insert() вставьте объект.

ToDoItem entity = mToDoTable
    .insert(item)       // Returns a ListenableFuture<ToDoItem>
    .get();

Возвращаемая сущность соответствует данным, вставленным в таблицу серверной части, в том числе идентификатору и другим значениям (например, поля createdAt, updatedAt и version), заданным в серверной части.

Для таблиц мобильных приложений необходим первичный ключевой столбец с именем ID. Этот столбец должен быть строкой. По умолчанию столбец идентификаторов содержит значения GUID. Можно указать другие уникальные значения, в том числе электронные адреса или имена пользователей. Если строковое значение идентификатора для вставленной записи не предусмотрено, то серверная часть создает новое значение GUID.

Использование строковых значений идентификаторов связано с такими преимуществами.

  • Идентификаторы можно создавать без обмена данными с базой данных.
  • Можно легко объединять записи из разных таблиц или баз данных.
  • Значения идентификаторов удобнее интегрировать с логикой приложения.

Строковые значения идентификатора НЕОБХОДИМЫ для поддержки автономной синхронизации. После сохранения идентификатора в серверной базе данных его изменить невозможно.

Обновление данных в мобильном приложении

Чтобы обновить данные в таблице, передайте новый объект в метод update() .

mToDoTable
    .update(item)   // Returns a ListenableFuture<ToDoItem>
    .get();

В этом примере item — это ссылка на строку в таблице ToDoItem, в которую были внесены изменения. Строка с таким же значением id обновится.

Удаление данных в мобильном приложении

В следующем коде показано, как удалить данные из таблицы, указав объект данных.

mToDoTable
    .delete(item);

Удалить элемент также можно, указав поле id удаляемой строки.

String myRowId = "2FA404AB-E458-44CD-BC1B-3BC847EF0902";
mToDoTable
    .delete(myRowId);

Поиск определенного элемента по идентификатору

Найдите элемент с определенным значением в поле id с помощью метода lookUp().

ToDoItem result = mToDoTable
    .lookUp("0380BAFB-BCFF-443C-B7D5-30199F730335")
    .get();

Практическое руководство. Работа с нетипизированными данными

Нетипизированная модель программирования предоставляет точный контроль над сериализацией JSON. Существует несколько распространенных сценариев, в которых может потребоваться использовать нетипизированную модель программирования. Например, если таблица серверной части содержит много столбцов, и вам нужно указать ссылку только на их подмножество. Для использования типизированной модели необходимо определить все столбцы, заданные в серверной части мобильных приложений в классе данных. Большая часть вызовов интерфейса API для доступа к данным аналогична вызовам в типизированной модели. Основное различие заключается в том, что в нетипизированной модели методы вызываются для объекта MobileServiceJsonTable, а не MobileServiceTable.

Создание экземпляра нетипизированной таблицы

Аналогично типизированной модели сначала вы получаете ссылку на таблицу, но в данном случае это объект MobileServicesJsonTable . Чтобы получить ссылку, вызовите метод getTable для экземпляра клиента.

private MobileServiceJsonTable mJsonToDoTable;
//...
mJsonToDoTable = mClient.getTable("ToDoItem");

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

Вставка данных в нетипизированную таблицу

В следующем коде показано, как вставить данные. Первый шаг — создание объекта JsonObject, который является частью библиотеки gson.

JsonObject jsonItem = new JsonObject();
jsonItem.addProperty("text", "Wake up");
jsonItem.addProperty("complete", false);

Затем с помощью insert() вставьте нетипизированный объект в таблицу.

JsonObject insertedItem = mJsonToDoTable
    .insert(jsonItem)
    .get();

Если необходимо получить идентификатор вставленного объекта, используйте метод getAsJsonPrimitive() .

String id = insertedItem.getAsJsonPrimitive("id").getAsString();

Удаление данных из нетипизированной таблицы

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

mToDoTable
    .delete(insertedItem);

Вы также можете удалить экземпляр напрямую с помощью идентификатора:

mToDoTable.delete(ID);

Получение всех строк из нетипизированной таблицы

В следующем коде показано, как получить всю таблицу. Поскольку вы используете таблицы Json, можно выборочно вывести лишь некоторые столбцы таблицы.

public void showAllUntyped(View view) {
    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground(Void... params) {
            try {
                final JsonElement result = mJsonToDoTable.execute().get();
                final JsonArray results = result.getAsJsonArray();
                runOnUiThread(new Runnable() {

                    @Override
                    public void run() {
                        mAdapter.clear();
                        for (JsonElement item : results) {
                            String ID = item.getAsJsonObject().getAsJsonPrimitive("id").getAsString();
                            String mText = item.getAsJsonObject().getAsJsonPrimitive("text").getAsString();
                            Boolean mComplete = item.getAsJsonObject().getAsJsonPrimitive("complete").getAsBoolean();
                            ToDoItem mToDoItem = new ToDoItem();
                            mToDoItem.setId(ID);
                            mToDoItem.setText(mText);
                            mToDoItem.setComplete(mComplete);
                            mAdapter.add(mToDoItem);
                        }
                    }
                });
            } catch (Exception exception) {
                createAndShowDialog(exception, "Error");
            }
            return null;
        }
    }.execute();
}

Для нетипизированной модели доступен тот же набор средств фильтрации, то есть методы фильтрации и разбиения по страницам, что и для типизированной модели.

Реализация синхронизации в автономном режиме

Пакет SDK для клиента мобильных приложений Azure также реализует синхронизацию данных в автономном режиме. Это стало возможным благодаря хранению копии данных сервера локально в базе данных SQLite. Операции в автономной таблице выполняются без подключения к мобильному приложению. Синхронизация данных в автономном режиме повышает устойчивость и производительность за счет более сложной логики устранения конфликтов. Пакет SDK для клиента мобильных приложений Azure реализует следующие возможности:

  • Добавочная синхронизация. Загружаются только обновленные и новые записи. Это позволяет уменьшить потребление пропускной способности и памяти.
  • Оптимистичный параллелизм. Операции рассматриваются как успешные. Устранение конфликтов откладывается до выполнения обновлений на сервере.
  • Устранение конфликтов. Пакет SDK обнаруживает время внесения конфликтного изменения на сервере и предоставляет обработчики для отправки уведомлений пользователю.
  • Обратимое удаление. Удаленные записи помечаются как удаленные, что позволяет другим устройствам обновить их автономный кэш.

Инициализация синхронизации в автономном режиме

Перед использованием каждую автономную таблицу необходимо определить в автономном кэше. Как правило, определение таблицы выполняется сразу после создания клиента:

AsyncTask<Void, Void, Void> initializeStore(MobileServiceClient mClient)
    throws MobileServiceLocalStoreException, ExecutionException, InterruptedException
{
    AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
        @Override
        protected void doInBackground(Void... params) {
            try {
                MobileServiceSyncContext syncContext = mClient.getSyncContext();
                if (syncContext.isInitialized()) {
                    return null;
                }
                SQLiteLocalStore localStore = new SQLiteLocalStore(mClient.getContext(), "offlineStore", null, 1);

                // Create a table definition.  As a best practice, store this with the model definition and return it via
                // a static method
                Map<String, ColumnDataType> toDoItemDefinition = new HashMap<String, ColumnDataType>();
                toDoItemDefinition.put("id", ColumnDataType.String);
                toDoItemDefinition.put("complete", ColumnDataType.Boolean);
                toDoItemDefinition.put("text", ColumnDataType.String);
                toDoItemDefinition.put("version", ColumnDataType.String);
                toDoItemDefinition.put("updatedAt", ColumnDataType.DateTimeOffset);

                // Now define the table in the local store
                localStore.defineTable("ToDoItem", toDoItemDefinition);

                // Specify a sync handler for conflict resolution
                SimpleSyncHandler handler = new SimpleSyncHandler();

                // Initialize the local store
                syncContext.initialize(localStore, handler).get();
            } catch (final Exception e) {
                createAndShowDialogFromTask(e, "Error");
            }
            return null;
        }
    };
    return runAsyncTask(task);
}

Получение ссылки на автономную таблицу кэша

Для таблицы в Интернете используйте .getTable(), а для автономной таблицы — .getSyncTable():

MobileServiceSyncTable<ToDoItem> mToDoTable = mClient.getSyncTable("ToDoItem", ToDoItem.class);

Все методы, доступные для таблиц в Интернете (в том числе методы фильтрации, разбиения по страницам, сортировки, вставки, обновления и удаления данных), работают одинаково хорошо и с автономными таблицами.

Синхронизация локального автономного кэша

Синхронизация выполняется под управлением приложения. Ниже приведен пример метода синхронизации.

private AsyncTask<Void, Void, Void> sync(MobileServiceClient mClient) {
    AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>(){
        @Override
        protected Void doInBackground(Void... params) {
            try {
                MobileServiceSyncContext syncContext = mClient.getSyncContext();
                syncContext.push().get();
                mToDoTable.pull(null, "todoitem").get();
            } catch (final Exception e) {
                createAndShowDialogFromTask(e, "Error");
            }
            return null;
        }
    };
    return runAsyncTask(task);
}

Если в метод .pull(query, queryname) добавить имя запроса, тогда выполняется добавочная синхронизация и возвращаются только записи, созданные или измененные с момента последнего успешного запроса.

Обработка конфликтов во время синхронизации в автономном режиме

Если конфликт происходит во время выполнения операции .push(), возникает исключение MobileServiceConflictException. В это исключение внедрен выданный сервером элемент, который можно извлечь, добавив в исключение метод .getItem(). Настройте принудительную отправку, добавив в объект MobileServiceSyncContext следующие элементы:

  • .cancelAndDiscardItem()
  • .cancelAndUpdateItem()
  • .updateOperationAndItem()

Когда все конфликты будут помечены должным образом, вызовите метод .push(), чтобы устранить их.

Вызов настраиваемого интерфейса API

Настраиваемый интерфейс API позволяет определить пользовательские конечные точки, которые предоставляют функциональные возможности сервера, не сопоставляемые с операциями вставки, обновления, удаления или чтения. При использовании настраиваемого интерфейса API вы получаете больше возможностей для управления сообщениями, в том числе для чтения и установки заголовков HTTP-сообщений, а также определения форматов текста сообщений, отличных от JSON.

Вызовите из клиента Android метод invokeApi для вызова конечной точки настраиваемого API. В следующем примере показано, как вызвать конечную точку API с именем completeAll, которая возвращает класс коллекции с именем MarkAllResult.

public void completeItem(View view) {
    ListenableFuture<MarkAllResult> result = mClient.invokeApi("completeAll", MarkAllResult.class);
    Futures.addCallback(result, new FutureCallback<MarkAllResult>() {
        @Override
        public void onFailure(Throwable exc) {
            createAndShowDialog((Exception) exc, "Error");
        }

        @Override
        public void onSuccess(MarkAllResult result) {
            createAndShowDialog(result.getCount() + " item(s) marked as complete.", "Completed Items");
            refreshItemsFromTable();
        }
    });
}

Метод invokeApi вызывается на стороне клиента, что приводит к отправке запроса POST новому настраиваемому API. Результат, возвращаемый настраиваемым интерфейсом API, отображается в диалоговом окне сообщения, как и любые ошибки. Другие версии метода invokeApi позволяют при необходимости отправить объект в тексте запроса, указать метод HTTP и отправить параметры запроса вместе с запросом. Кроме того, доступны нетипизированные версии invokeApi .

Добавление проверки подлинности в приложение

Процесс добавления этих компонентов подробно описан в руководствах.

Служба приложений поддерживает проверку подлинности пользователей приложения с помощью разных внешних поставщиков удостоверений: Facebook, Google, учетной записи Майкрософт, Twitter и Azure Active Directory. Можно задать разрешения таблиц, чтобы предоставить доступ к определенным операциям только пользователям, прошедшим проверку подлинности. Удостоверения пользователей, прошедших проверку подлинности, также можно применять для реализации правил авторизации в серверной части.

Поддерживаются два потока проверки подлинности: Серверный поток и клиентский поток. Серверный поток обеспечивает самый простой способ аутентификации, так как он использует веб-интерфейс поставщика удостоверений. Для реализации аутентификации серверного потока не нужны дополнительные пакеты SDK. Аутентификация серверного потока не обеспечивает тесную интеграцию с мобильным устройством и рекомендуется только для подтверждения концепции.

Клиентский поток обеспечивает более тесную интеграцию с возможностями устройства, такими как единый вход, так как использует пакеты SDK, предоставляемые конкретным поставщиком удостоверений. Например, можно интегрировать пакет SDK для Facebook в мобильное приложение. Мобильный клиент переключается на приложение Facebook и подтверждает ваш вход, прежде чем переключиться обратно на мобильное приложение.

Чтобы включить аутентификацию в приложении, необходимо выполнить четыре действия:

  • Регистрация приложения для аутентификации в поставщике удостоверений.
  • Настройка серверной части службы приложений.
  • Ограничение разрешений таблицы только аутентифицированными пользователями.
  • Добавление кода проверки подлинности в приложение.

Можно задать разрешения таблиц, чтобы предоставить доступ к определенным операциям только пользователям, прошедшим проверку подлинности. Также можно использовать идентификатор безопасности пользователя, прошедшего проверку, чтобы изменять запросы. Дополнительную информацию см. в разделе Приступая к работе с проверкой подлинности и справочной документации по пакету SDK для сервера.

Серверный поток проверки подлинности

Следующий код запускает процесс входа серверного потока с помощью поставщика Google. Из-за требований к безопасности поставщика Google необходимо выполнить дополнительную конфигурацию:

MobileServiceUser user = mClient.login(MobileServiceAuthenticationProvider.Google, "{url_scheme_of_your_app}", GOOGLE_LOGIN_REQUEST_CODE);

Кроме того, добавьте в основной класс Activity следующий метод:

// You can choose any unique number here to differentiate auth providers from each other. Note this is the same code at login() and onActivityResult().
public static final int GOOGLE_LOGIN_REQUEST_CODE = 1;

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // When request completes
    if (resultCode == RESULT_OK) {
        // Check the request code matches the one we send in the login request
        if (requestCode == GOOGLE_LOGIN_REQUEST_CODE) {
            MobileServiceActivityResult result = mClient.onActivityResult(data);
            if (result.isLoggedIn()) {
                // login succeeded
                createAndShowDialog(String.format("You are now logged in - %1$2s", mClient.getCurrentUser().getUserId()), "Success");
                createTable();
            } else {
                // login failed, check the error message
                String errorMessage = result.getErrorMessage();
                createAndShowDialog(errorMessage, "Error");
            }
        }
    }
}

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

Кроме того, необходимо также настроить проект настраиваемых вкладок. Сначала укажите URL-адрес перенаправления. Добавьте в файл AndroidManifest.xml следующий фрагмент кода:

<activity android:name="com.microsoft.windowsazure.mobileservices.authentication.RedirectUrlActivity">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="{url_scheme_of_your_app}" android:host="easyauth.callback"/>
    </intent-filter>
</activity>

Добавьте параметр redirectUriScheme в файл build.gradle приложения:

android {
    buildTypes {
        release {
            // … …
            manifestPlaceholders = ['redirectUriScheme': '{url_scheme_of_your_app}://easyauth.callback']
        }
        debug {
            // … …
            manifestPlaceholders = ['redirectUriScheme': '{url_scheme_of_your_app}://easyauth.callback']
        }
    }
}

Наконец, добавьте com.android.support:customtabs:28.0.0 в список зависимостей в файле build.gradle:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.google.code.gson:gson:2.3'
    implementation 'com.google.guava:guava:18.0'
    implementation 'com.android.support:customtabs:28.0.0'
    implementation 'com.squareup.okhttp:okhttp:2.5.0'
    implementation 'com.microsoft.azure:azure-mobile-android:3.4.0@aar'
    implementation 'com.microsoft.azure:azure-notifications-handler:1.0.1@jar'
}

Получите идентификатор вошедшего в систему пользователя из MobileServiceUser с помощью метода getUserId. Пример использования Futures для вызова API асинхронного входа см. в статье Приступая к работе с проверкой подлинности.

Предупреждение

В упомянутой схеме URL-адресов учитывается регистр. Убедитесь, что во всех вхождениях {url_scheme_of_you_app} используется один и тот же регистр.

Кэширование маркеров аутентификации

Для этого необходимо сохранить идентификатор пользователя и маркер аутентификации локально на устройстве. При следующем запуске приложения проверьте кэш — если эти значения существуют, то можно пропустить процедуру входа и повторно заполнить клиент этими данными. Однако это конфиденциальные данные и они должны храниться в зашифрованном виде, чтобы обеспечить безопасность в случае кражи телефона. См. полный пример кэширования маркеров аутентификации.

При попытке использовать маркер с истекшим сроком действия вы получите ответ 401 не санкционировано . Ошибки аутентификации можно обрабатывать с помощью фильтров. Фильтры перехватывают запросы к серверной части службы приложений. Код фильтра проверяет наличие ответа 401, инициирует вход в систему и возобновляет запрос, породивший ответ 401.

Использование токенов обновления

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

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

  • Для приложения Azure Active Directory настройте секрет клиента. Укажите секрет клиента в службе приложений Azure при настройке проверки подлинности Azure Active Directory. При вызове метода .login() передайте response_type=code id_token как параметр:

    HashMap<String, String> parameters = new HashMap<String, String>();
    parameters.put("response_type", "code id_token");
    MobileServiceUser user = mClient.login
        MobileServiceAuthenticationProvider.AzureActiveDirectory,
        "{url_scheme_of_your_app}",
        AAD_LOGIN_REQUEST_CODE,
        parameters);
    
  • Для Google передайте access_type=offline как параметр:

    HashMap<String, String> parameters = new HashMap<String, String>();
    parameters.put("access_type", "offline");
    MobileServiceUser user = mClient.login
        MobileServiceAuthenticationProvider.Google,
        "{url_scheme_of_your_app}",
        GOOGLE_LOGIN_REQUEST_CODE,
        parameters);
    
  • Для учетной записи Майкрософт выберите область wl.offline_access.

Чтобы обновить токен, вызовите метод .refreshUser():

MobileServiceUser user = mClient
    .refreshUser()  // async - returns a ListenableFuture<MobileServiceUser>
    .get();

Мы советуем создать фильтр, который определяет ответ 401 от сервера и пытается обновить токен пользователя.

Вход в систему с использованием клиентского потока проверки подлинности

Общая процедура входа в систему с использованием клиентского потока проверки подлинности состоит из следующих этапов:

  • Настройка функции проверки подлинности и авторизации в службе приложений Azure, как и при серверном потоке проверки подлинности.

  • Интеграция пакета SDK поставщика проверки подлинности для создания маркера доступа.

  • Вызовите метода .login() следующим образом (result должен быть AuthenticationResult):

    JSONObject payload = new JSONObject();
    payload.put("access_token", result.getAccessToken());
    ListenableFuture<MobileServiceUser> mLogin = mClient.login("{provider}", payload.toString());
    Futures.addCallback(mLogin, new FutureCallback<MobileServiceUser>() {
        @Override
        public void onFailure(Throwable exc) {
            exc.printStackTrace();
        }
        @Override
        public void onSuccess(MobileServiceUser user) {
            Log.d(TAG, "Login Complete");
        }
    });
    

См. полный пример кода в следующем разделе.

Замените метод onSuccess() на любой код, который следует использовать для успешного входа. В строке {provider} необходимо указать допустимое значение поставщика, например aad (Azure Active Directory), facebook, google, microsoftaccount или twitter. При реализации пользовательской проверки подлинности вы также можете использовать тег поставщика проверки подлинности.

Проверка подлинности пользователей с помощью библиотеки проверки подлинности Active Directory (ADAL)

Библиотеку проверки подлинности Active Directory (ADAL) можно использовать для входа пользователей в приложение с помощью Azure Active Directory. Использование входа посредством клиентского потока является более предпочтительным, чем использование методов loginAsync() , так как он обеспечивает более естественный интерфейс входа для пользователя и позволяет выполнять дополнительную настройку.

  1. Настройте серверную часть мобильного приложения для входа с помощью AAD, следуя указаниям в учебнике Настройка приложения службы приложений для использования службы входа Azure Active Directory . Обязательно выполните дополнительный этап регистрации собственного клиентского приложения.

  2. Установите ADAL, добавив в файл build.gradle следующие определения.

    repositories {
        mavenCentral()
        flatDir {
            dirs 'libs'
        }
        maven {
            url "YourLocalMavenRepoPath\\.m2\\repository"
        }
    }
    packagingOptions {
        exclude 'META-INF/MSFTSIG.RSA'
        exclude 'META-INF/MSFTSIG.SF'
    }
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation('com.microsoft.aad:adal:1.16.1') {
            exclude group: 'com.android.support'
        } // Recent version is 1.16.1
        implementation 'com.android.support:support-v4:28.0.0'
    }
    
  3. Добавьте указанный ниже код в приложение, выполнив следующие замены.

    • Замените строку INSERT-AUTHORITY-HERE именем клиента, в котором подготовлено приложение. Его необходимо указать в формате https://login.microsoftonline.com/contoso.onmicrosoft.com.
    • Замените текст INSERT-RESOURCE-ID-HERE идентификатором клиента для серверной части мобильного приложения. Идентификатор клиента можно скопировать на портале в разделе Настройки Azure Active Directory на вкладке Дополнительно.
    • Замените текст INSERT-CLIENT-ID-HERE идентификатором клиента, скопированным из собственного клиентского приложения.
    • Замените текст INSERT-REDIRECT-URI-HERE конечной точкой сайта /.auth/login/done , используя схему HTTPS. Это значение должно быть аналогично https://contoso.azurewebsites.net/.auth/login/done.
private AuthenticationContext mContext;

private void authenticate() {
    String authority = "INSERT-AUTHORITY-HERE";
    String resourceId = "INSERT-RESOURCE-ID-HERE";
    String clientId = "INSERT-CLIENT-ID-HERE";
    String redirectUri = "INSERT-REDIRECT-URI-HERE";
    try {
        mContext = new AuthenticationContext(this, authority, true);
        mContext.acquireToken(this, resourceId, clientId, redirectUri, PromptBehavior.Auto, "", callback);
    } catch (Exception exc) {
        exc.printStackTrace();
    }
}

private AuthenticationCallback<AuthenticationResult> callback = new AuthenticationCallback<AuthenticationResult>() {
    @Override
    public void onError(Exception exc) {
        if (exc instanceof AuthenticationException) {
            Log.d(TAG, "Cancelled");
        } else {
            Log.d(TAG, "Authentication error:" + exc.getMessage());
        }
    }

    @Override
    public void onSuccess(AuthenticationResult result) {
        if (result == null || result.getAccessToken() == null
                || result.getAccessToken().isEmpty()) {
            Log.d(TAG, "Token is empty");
        } else {
            try {
                JSONObject payload = new JSONObject();
                payload.put("access_token", result.getAccessToken());
                ListenableFuture<MobileServiceUser> mLogin = mClient.login("aad", payload.toString());
                Futures.addCallback(mLogin, new FutureCallback<MobileServiceUser>() {
                    @Override
                    public void onFailure(Throwable exc) {
                        exc.printStackTrace();
                    }
                    @Override
                    public void onSuccess(MobileServiceUser user) {
                        Log.d(TAG, "Login Complete");
                    }
                });
            }
            catch (Exception exc){
                Log.d(TAG, "Authentication error:" + exc.getMessage());
            }
        }
    }
};

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (mContext != null) {
        mContext.onActivityResult(requestCode, resultCode, data);
    }
}

Настройка связи между клиентом и сервером

Как правило, клиент использует HTTP-подключение на основе базовой библиотеки HTTP, входящей в пакет SDK для Android. Используя другой тип подключения, вы сможете:

  • использовать альтернативную библиотеку HTTP для настройки времени ожидания;
  • предоставить индикатор выполнения;
  • добавить пользовательский заголовок для поддержки функции управления API;
  • настроить перехват неудачных ответов, что позволит повторно выполнить проверку подлинности;
  • записывать серверные запросы в службу аналитики.

Использование альтернативной библиотеки HTTP

После создания ссылки на клиент вызовите метод .setAndroidHttpClientFactory(). Это, например, позволит установить время ожидания подключения в 60 секунд (по умолчанию 10 секунд).

mClient = new MobileServiceClient("https://myappname.azurewebsites.net");
mClient.setAndroidHttpClientFactory(new OkHttpClientFactory() {
    @Override
    public OkHttpClient createOkHttpClient() {
        OkHttpClient client = new OkHttpClient();
        client.setReadTimeout(60, TimeUnit.SECONDS);
        client.setWriteTimeout(60, TimeUnit.SECONDS);
        return client;
    }
});

Реализация фильтра хода выполнения

Реализовав атрибут ServiceFilter, вы сможете настроить перехват каждого запроса. Например, следующий код позволяет обновить стандартный индикатор выполнения:

private class ProgressFilter implements ServiceFilter {
    @Override
    public ListenableFuture<ServiceFilterResponse> handleRequest(ServiceFilterRequest request, NextServiceFilterCallback next) {
        final SettableFuture<ServiceFilterResponse> resultFuture = SettableFuture.create();
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (mProgressBar != null) mProgressBar.setVisibility(ProgressBar.VISIBLE);
            }
        });

        ListenableFuture<ServiceFilterResponse> future = next.onNext(request);
        Futures.addCallback(future, new FutureCallback<ServiceFilterResponse>() {
            @Override
            public void onFailure(Throwable e) {
                resultFuture.setException(e);
            }
            @Override
            public void onSuccess(ServiceFilterResponse response) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (mProgressBar != null)
                            mProgressBar.setVisibility(ProgressBar.GONE);
                    }
                });
                resultFuture.set(response);
            }
        });
        return resultFuture;
    }
}

Этот фильтр можно присоединить к клиенту следующим образом:

mClient = new MobileServiceClient(applicationUrl).withFilter(new ProgressFilter());

Настройка заголовков запросов

Используйте атрибут ServiceFilter и присоедините фильтр точно так же, как и атрибут ProgressFilter:

private class CustomHeaderFilter implements ServiceFilter {
    @Override
    public ListenableFuture<ServiceFilterResponse> handleRequest(ServiceFilterRequest request, NextServiceFilterCallback next) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                request.addHeader("X-APIM-Router", "mobileBackend");
            }
        });
        SettableFuture<ServiceFilterResponse> result = SettableFuture.create();
        try {
            ServiceFilterResponse response = next.onNext(request).get();
            result.set(response);
        } catch (Exception exc) {
            result.setException(exc);
        }
    }
}

Настройка автоматической сериализации

Можно указать стратегию преобразования, которая применяется к каждому столбцу, с помощью API из библиотеки gson. Клиентская библиотека Android использует gson для сериализации объектов Java в данные JSON перед отправкой данных в службу приложений Azure. В следующем коде используется метод setFieldNamingStrategy() для задания стратегии. Этот пример удалит начальный знак ("m"), а затем преобразует следующий знак в нижний регистр для каждого имени поля. Например, он будет включать "mId" в "ID". Реализуйте стратегию преобразования, чтобы уменьшить потребность SerializedName() в заметках для большинства полей.

FieldNamingStrategy namingStrategy = new FieldNamingStrategy() {
    public String translateName(File field) {
        String name = field.getName();
        return Character.toLowerCase(name.charAt(1)) + name.substring(2);
    }
}

client.setGsonBuilder(
    MobileServiceClient
        .createMobileServiceGsonBuilder()
        .setFieldNamingStrategy(namingStrategy)
);

Этот код необходимо выполнить перед созданием ссылки на мобильный клиент с помощью MobileServiceClient.