Добавление проверки лицензии на стороне клиента в ваше приложение

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

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

После того как вы настроили учетную запись издателя и среду разработки (см. Настройка лицензирования ), вы готовы добавить проверку лицензии в свое приложение с помощью библиотеки проверки лицензий (LVL).

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

  1. Добавление разрешения лицензирования в манифест вашего приложения.
  2. Реализация политики — вы можете выбрать одну из полных реализаций, представленных в LVL, или создать свою собственную.
  3. Внедрение обфускатора , если ваша Policy будет кэшировать любые данные ответа о лицензии.
  4. Добавление кода для проверки лицензии в основной активности вашего приложения.
  5. Реализация DeviceLimiter (необязательно и не рекомендуется для большинства приложений).

В разделах ниже описаны эти задачи. Когда вы закончите интеграцию, вы сможете успешно скомпилировать свое приложение и начать тестирование, как описано в разделе «Настройка тестовой среды» .

Обзор полного набора исходных файлов, включенных в LVL, см. в разделе «Сводка классов и интерфейсов LVL» .

Добавление разрешения на лицензирование

Чтобы использовать приложение Google Play для отправки проверки лицензии на сервер, ваше приложение должно запросить соответствующее разрешение com.android.vending.CHECK_LICENSE . Если ваше приложение не объявляет разрешение на лицензирование, но пытается инициировать проверку лицензии, LVL выдает исключение безопасности.

Чтобы запросить разрешение на лицензирование в вашем приложении, объявите элемент <uses-permission> как дочерний элемент <manifest> следующим образом:

<uses-permission android:name="com.android.vending.CHECK_LICENSE" />

Например, вот как пример приложения LVL объявляет разрешение:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ...">
    <!-- Devices >= 3 have version of Google Play that supports licensing. -->
    <uses-sdk android:minSdkVersion="3" />
    <!-- Required permission to check licensing. -->
    <uses-permission android:name="com.android.vending.CHECK_LICENSE" />
    ...
</manifest>

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

Реализация политики

Служба лицензирования Google Play сама не определяет, следует ли предоставить пользователю с определенной лицензией доступ к вашему приложению. Скорее, эта ответственность возлагается на реализацию Policy , которую вы предоставляете в своем приложении.

Политика — это интерфейс, объявленный LVL, который предназначен для хранения логики вашего приложения для разрешения или запрета доступа пользователей на основе результата проверки лицензии. Чтобы использовать LVL, ваше приложение должно обеспечить реализацию Policy .

Интерфейс Policy объявляет два метода: allowAccess() processServerResponse() , которые вызываются экземпляром LicenseChecker при обработке ответа от сервера лицензий. Он также объявляет перечисление под названием LicenseResponse , которое определяет значение ответа лицензии, передаваемое в вызовах processServerResponse() .

  • processServerResponse() позволяет предварительно обработать необработанные данные ответа, полученные от сервера лицензирования, прежде чем определить, следует ли предоставлять доступ.

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

    При локальном хранении данных ответа Policy должна обеспечивать запутывание данных (см. Реализация обфускатора ниже).

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

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

  • ServerManagedPolicy — гибкая Policy , которая использует предоставленные сервером настройки и кэшированные ответы для управления доступом в различных сетевых условиях, а также
  • StrictPolicy , который не кэширует данные ответа и разрешает доступ только в том случае, если сервер возвращает лицензированный ответ.

Для большинства приложений настоятельно рекомендуется использовать ServerManagedPolicy. ServerManagedPolicy является значением LVL по умолчанию и интегрировано с примером приложения LVL.

Рекомендации по пользовательским политикам

В реализации лицензирования вы можете использовать одну из полных политик, представленных в LVL (ServerManagedPolicy или StrictPolicy), или создать собственную политику. Для любого типа пользовательской политики существует несколько важных моментов проектирования, которые необходимо понимать и учитывать при реализации.

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

Если вы разрабатываете собственную политику, мы рекомендуем, чтобы Policy :

  1. Кэширует (и должным образом скрывает) самый последний успешный ответ на лицензию в локальном постоянном хранилище.
  2. Возвращает кэшированный ответ для всех проверок лицензий, пока кэшированный ответ действителен, вместо отправки запроса на сервер лицензирования. Настоятельно рекомендуется установить срок действия ответа в соответствии с дополнительными параметрами VT , предоставленными сервером. Дополнительные сведения см. в разделе «Дополнительные сведения об ответах сервера» .
  3. Использует экспоненциальный период задержки, если повторение каких-либо запросов приводит к ошибкам. Обратите внимание, что клиент Google Play автоматически повторяет неудачные запросы, поэтому в большинстве случаев вашей Policy не требуется повторять их.
  4. Предоставляет «льготный период», который позволяет пользователю получить доступ к вашему приложению в течение ограниченного времени или количества использований, пока проверка лицензии повторяется. Льготный период приносит пользу пользователю, предоставляя доступ до тех пор, пока следующая проверка лицензии не будет успешно завершена, и он приносит пользу вам, устанавливая жесткое ограничение на доступ к вашему приложению, когда нет действительного ответа на лицензию.

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

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

Серверуправляемая политика

LVL включает полную и рекомендуемую реализацию интерфейса Policy под названием ServerManagedPolicy. Реализация интегрирована с классами LVL и служит Policy по умолчанию в библиотеке.

ServerManagedPolicy обеспечивает всю обработку лицензий и ответов на повторные попытки. Он кэширует все данные ответа локально в файле SharedPreferences , запутывая их с помощью реализации Obfuscator приложения. Это гарантирует, что данные ответа на лицензию будут в безопасности и сохранятся при включении и выключении устройства. ServerManagedPolicy предоставляет конкретные реализации методов интерфейсаprocessServerResponse processServerResponse() allowAccess() , а также включает набор вспомогательных методов и типов для управления ответами на лицензии.

Важно отметить, что ключевой особенностью ServerManagedPolicy является использование предоставленных сервером настроек в качестве основы для управления лицензированием в течение периода возврата средств приложения, а также при изменении сети и условий ошибок. Когда приложение обращается к серверу Google Play для проверки лицензии, сервер добавляет несколько настроек в виде пар ключ-значение в поле дополнительных ответов определенных типов лицензий. Например, сервер предоставляет, среди прочего, рекомендуемые значения для срока действия лицензии приложения, льготного периода повторных попыток и максимально допустимого количества повторов. ServerManagedPolicy извлекает значения из ответа о лицензии в своем processServerResponse() и проверяет их в своем allowAccess() . Список предоставленных сервером параметров, используемых ServerManagedPolicy, см. в разделе Дополнительные сведения об ответах сервера .

Для удобства, максимальной производительности и преимуществ использования настроек лицензии с сервера Google Play настоятельно рекомендуется использовать ServerManagedPolicy в качестве Policy лицензирования .

Если вы обеспокоены безопасностью данных ответа о лицензии, которые хранятся локально в SharedPreferences , вы можете использовать более сильный алгоритм запутывания или разработать более строгую Policy , которая не хранит данные лицензии. LVL включает пример такой Policy — дополнительную информацию см. в разделе StrictPolicy .

Чтобы использовать ServerManagedPolicy, просто импортируйте его в свою Activity, создайте экземпляр и передайте ссылку на него при создании LicenseChecker . Дополнительные сведения см. в разделах Создание экземпляра LicenseChecker и LicenseCheckerCallback .

СтрогаяПолитика

LVL включает альтернативную полную реализацию интерфейса Policy под названием StrictPolicy. Реализация StrictPolicy обеспечивает более ограничительную политику, чем ServerManagedPolicy, поскольку она не позволяет пользователю получить доступ к приложению, пока во время доступа от сервера не будет получен ответ о лицензии, указывающий, что пользователь имеет лицензию.

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

  • Ответ о лицензии получен от сервера лицензирования, и
  • Ответ о лицензии указывает, что у пользователя есть лицензия на доступ к приложению.

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

В то же время эта Policy представляет собой проблему для обычных пользователей, поскольку это означает, что они не смогут получить доступ к приложению, когда отсутствует сетевое соединение (сотовое или Wi-Fi). Другим побочным эффектом является то, что ваше приложение будет отправлять на сервер больше запросов на проверку лицензии, поскольку использование кэшированного ответа невозможно.

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

Чтобы использовать StrictPolicy, просто импортируйте его в свою Activity, создайте экземпляр и передайте ссылку на него при создании LicenseChecker . Дополнительные сведения см. в разделах Создание экземпляра LicenseChecker и LicenseCheckerCallback .

Типичная реализация Policy требует сохранения данных ответа о лицензии для приложения в постоянном хранилище, чтобы они были доступны при вызовах приложений и циклах включения и выключения устройства. Например, Policy будет сохранять временную метку последней успешной проверки лицензии, количество повторов, период действия лицензии и аналогичную информацию в постоянном хранилище, а не сбрасывать значения каждый раз при запуске приложения. Policy по умолчанию, включенная в LVL, ServerManagedPolicy, хранит данные ответа о лицензии в экземпляре SharedPreferences , чтобы гарантировать постоянство данных.

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

LVL помогает приложению хранить данные ответа на запрос лицензии безопасным и постоянным образом. Во-первых, он предоставляет интерфейс Obfuscator , который позволяет вашему приложению использовать выбранный им алгоритм обфускации для хранимых данных. Опираясь на это, LVL предоставляет вспомогательный класс PreferenceObfuscator, который выполняет большую часть работы по вызову класса Obfuscator приложения, а также чтению и записи запутанных данных в экземпляре SharedPreferences .

LVL предоставляет полную реализацию Obfuscator под названием AESObfuscator, которая использует шифрование AES для сокрытия данных. Вы можете использовать AESObfuscator в своем приложении без изменений или адаптировать его под свои нужды. Если вы используете Policy (например, ServerManagedPolicy), которая кэширует данные ответа на лицензию, настоятельно рекомендуется использовать AESObfuscator в качестве основы для вашей реализации Obfuscator . Дополнительную информацию см. в следующем разделе.

AESОбфускатор

LVL включает полную и рекомендуемую реализацию интерфейса Obfuscator под названием AESObfuscator. Реализация интегрирована с примером приложения LVL и служит Obfuscator по умолчанию в библиотеке.

AESObfuscator обеспечивает безопасную обфускацию данных с помощью AES для шифрования и дешифрования данных при их записи или чтении из хранилища. Obfuscator инициализирует шифрование, используя три поля данных, предоставленные приложением:

  1. Соль — массив случайных байтов, используемый для каждой (не)обфускации.
  2. Строка идентификатора приложения, обычно имя пакета приложения.
  3. Строка идентификатора устройства, полученная из максимально возможного количества источников, специфичных для устройства, чтобы сделать ее максимально уникальной.

Чтобы использовать AESObfuscator, сначала импортируйте его в свою деятельность. Объявите частный статический конечный массив для хранения байтов соли и инициализируйте его 20 случайно сгенерированными байтами.

Котлин

// Generate 20 random bytes, and put them here.
private val SALT = byteArrayOf(
        -46, 65, 30, -128, -103, -57, 74, -64, 51, 88,
        -95, -45, 77, -117, -36, -113, -11, 32, -64, 89
)

Ява

...
    // Generate 20 random bytes, and put them here.
    private static final byte[] SALT = new byte[] {
     -46, 65, 30, -128, -103, -57, 74, -64, 51, 88, -95,
     -45, 77, -117, -36, -113, -11, 32, -64, 89
     };
    ...

Затем объявите переменную для хранения идентификатора устройства и сгенерируйте для нее значение любым необходимым способом. Например, пример приложения, включенный в LVL, запрашивает системные настройки для android.Settings.Secure.ANDROID_ID , который уникален для каждого устройства.

Обратите внимание: в зависимости от используемых вами API вашему приложению может потребоваться запросить дополнительные разрешения для получения информации об устройстве. Например, чтобы запросить у TelephonyManager получение IMEI устройства или связанных данных, приложению также потребуется запросить разрешение android.permission.READ_PHONE_STATE в своем манифесте.

Прежде чем запрашивать новые разрешения с единственной целью получения информации об устройстве для использования в вашем Obfuscator , подумайте, как это может повлиять на ваше приложение или его фильтрацию в Google Play (поскольку некоторые разрешения могут привести к тому, что инструменты сборки SDK добавят связанные <uses-feature> ).

Наконец, создайте экземпляр AESObfuscator, передав соль, идентификатор приложения и идентификатор устройства. Вы можете создать экземпляр напрямую при создании Policy и LicenseChecker . Например:

Котлин

    ...
    // Construct the LicenseChecker with a Policy.
    private val checker = LicenseChecker(
            this,
            ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)),
            BASE64_PUBLIC_KEY
    )
    ...

Ява

    ...
    // Construct the LicenseChecker with a Policy.
    checker = new LicenseChecker(
        this, new ServerManagedPolicy(this,
            new AESObfuscator(SALT, getPackageName(), deviceId)),
        BASE64_PUBLIC_KEY // Your public licensing key.
        );
    ...

Полный пример см. в разделе MainActivity в примере приложения LVL.

Проверка лицензии по активности

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

Чтобы добавить проверку лицензии и обработать ответ, необходимо:

  1. Добавить импорт
  2. Реализуйте LicenseCheckerCallback как закрытый внутренний класс.
  3. Создайте обработчик для публикации из LicenseCheckerCallback в поток пользовательского интерфейса.
  4. Создание экземпляра LicenseChecker и LicenseCheckerCallback
  5. Вызовите checkAccess() , чтобы инициировать проверку лицензии.
  6. Вставьте свой открытый ключ для лицензирования
  7. Вызовите метод onDestroy() вашего LicenseChecker, чтобы закрыть соединения IPC.

В разделах ниже описаны эти задачи.

Обзор проверки лицензии и ответа на нее

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

Проверка лицензии состоит из двух основных действий:

  • Вызов метода для инициации проверки лицензии — в LVL это вызов метода checkAccess() создаваемого вами объекта LicenseChecker .
  • Обратный вызов, возвращающий результат проверки лицензии. В LVL это интерфейс LicenseCheckerCallback , который вы реализуете. В интерфейсе объявлены два метода, allow() и dontAllow() , которые вызываются библиотекой в ​​зависимости от результата проверки лицензии. Вы реализуете эти два метода с любой необходимой вам логикой, чтобы разрешить или запретить пользователю доступ к вашему приложению. Обратите внимание, что эти методы не определяют, разрешать ли доступ — за это определение отвечает реализация вашей Policy . Скорее, эти методы просто обеспечивают поведение приложения, позволяющее разрешать и запрещать доступ (и обрабатывать ошибки приложения).

    Методыallow allow() и dontAllow() предоставляют «причину» своего ответа, которая может быть одним из значений Policy : LICENSED , NOT_LICENSED или RETRY . В частности, вам следует обработать случай, когда метод получает ответ RETRY для dontAllow() и предоставить пользователю кнопку «Повторить попытку», что могло произойти из-за того, что служба была недоступна во время запроса.

Рисунок 1. Обзор типичного взаимодействия при проверке лицензии.

На диаграмме выше показано, как происходит типичная проверка лицензии:

  1. Код в основном действии приложения создает экземпляры объектов LicenseCheckerCallback и LicenseChecker . При создании LicenseChecker код передает Context , используемую реализацию Policy и открытый ключ учетной записи издателя для лицензирования в качестве параметров.
  2. Затем код вызывает метод checkAccess() объекта LicenseChecker . Реализация метода вызывает Policy , чтобы определить, существует ли действительный ответ лицензии, кэшированный локально в SharedPreferences .
    • Если да, то реализация checkAccess() вызывает allow() .
    • В противном случае LicenseChecker инициирует запрос на проверку лицензии, который отправляется на сервер лицензирования.

    Примечание. Сервер лицензирования всегда возвращает LICENSED при проверке лицензии черновика приложения.

  3. При получении ответа LicenseChecker создает LicenseValidator, который проверяет подписанные данные лицензии и извлекает поля ответа, а затем передает их в вашу Policy для дальнейшей оценки.
    • Если лицензия действительна, Policy кэширует ответ в SharedPreferences и уведомляет об этом валидатор, который затем вызывает методallow allow() объекта LicenseCheckerCallback .
    • Если лицензия недействительна, Policy уведомляет об этом валидатор, который вызывает метод dontAllow() в LicenseCheckerCallback .
  4. В случае устранимой локальной или серверной ошибки, например, когда сеть недоступна для отправки запроса, LicenseChecker передает ответ RETRY методу processServerResponse() вашего объекта Policy .

    Кроме того, оба метода обратного вызоваallow allow() и dontAllow() получают аргумент reason . Причиной allow() обычно является Policy.LICENSED или Policy.RETRY , а причиной dontAllow() обычно является Policy.NOT_LICENSED или Policy.RETRY . Эти значения ответа полезны, поскольку вы можете показать соответствующий ответ для пользователя, например, предоставив кнопку «Повторить», когда dontAllow() отвечает Policy.RETRY , что могло произойти из-за того, что служба была недоступна.

  5. В случае ошибки приложения, например, когда приложение пытается проверить лицензию пакета с недопустимым именем, LicenseChecker передает ответ об ошибке методу applicationError() класса LicenseCheckerCallback.

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

Добавить импорт

Сначала откройте файл класса основного действия приложения и импортируйте LicenseChecker и LicenseCheckerCallback из пакета LVL.

Котлин

import com.google.android.vending.licensing.LicenseChecker
import com.google.android.vending.licensing.LicenseCheckerCallback

Ява

import com.google.android.vending.licensing.LicenseChecker;
import com.google.android.vending.licensing.LicenseCheckerCallback;

Если вы используете реализацию Policy по умолчанию, предоставляемую вместе с LVL, ServerManagedPolicy, также импортируйте ее вместе с AESObfuscator. Если вы используете собственную Policy или Obfuscator , импортируйте их.

Котлин

import com.google.android.vending.licensing.ServerManagedPolicy
import com.google.android.vending.licensing.AESObfuscator

Ява

import com.google.android.vending.licensing.ServerManagedPolicy;
import com.google.android.vending.licensing.AESObfuscator;

Реализуйте LicenseCheckerCallback как закрытый внутренний класс.

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

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

В большинстве случаев вам следует объявить реализацию LicenseCheckerCallback как частный класс внутри основного класса Activity вашего приложения.

При необходимости реализуйте методыallow allow() и dontAllow() . Для начала вы можете использовать в методах простые способы обработки результатов, например отображение результата лицензирования в диалоговом окне. Это поможет вам быстрее запустить приложение и может помочь в отладке. Позже, после того как вы определили, какое именно поведение вам нужно, вы можете добавить более сложную обработку.

Некоторые предложения по обработке нелицензионных ответов в dontAllow() включают:

  • Отображение диалогового окна «Попробуйте еще раз» для пользователя, включая кнопку для запуска новой проверки лицензии, если указанной reason является Policy.RETRY .
  • Отображение диалогового окна «Купить это приложение», включающего кнопку, которая обеспечивает глубокую ссылку пользователя на страницу сведений о приложении в Google Play, на которой пользователь может приобрести приложение. Дополнительную информацию о том, как настроить такие ссылки, см. в разделе «Ссылки на ваши продукты» .
  • Отобразите всплывающее уведомление, указывающее, что возможности приложения ограничены, поскольку оно не лицензировано.

В приведенном ниже примере показано, как пример приложения LVL реализует LicenseCheckerCallback с методами, которые отображают результат проверки лицензии в диалоговом окне.

Котлин

private inner class MyLicenseCheckerCallback : LicenseCheckerCallback {

    override fun allow(reason: Int) {
        if (isFinishing) {
            // Don't update UI if Activity is finishing.
            return
        }
        // Should allow user access.
        displayResult(getString(R.string.allow))
    }

    override fun dontAllow(reason: Int) {
        if (isFinishing) {
            // Don't update UI if Activity is finishing.
            return
        }
        displayResult(getString(R.string.dont_allow))

        if (reason == Policy.RETRY) {
            // If the reason received from the policy is RETRY, it was probably
            // due to a loss of connection with the service, so we should give the
            // user a chance to retry. So show a dialog to retry.
            showDialog(DIALOG_RETRY)
        } else {
            // Otherwise, the user isn't licensed to use this app.
            // Your response should always inform the user that the application
            // isn't licensed, but your behavior at that point can vary. You might
            // provide the user a limited access version of your app or you can
            // take them to Google Play to purchase the app.
            showDialog(DIALOG_GOTOMARKET)
        }
    }
}

Ява

private class MyLicenseCheckerCallback implements LicenseCheckerCallback {
    public void allow(int reason) {
        if (isFinishing()) {
            // Don't update UI if Activity is finishing.
            return;
        }
        // Should allow user access.
        displayResult(getString(R.string.allow));
    }

    public void dontAllow(int reason) {
        if (isFinishing()) {
            // Don't update UI if Activity is finishing.
            return;
        }
        displayResult(getString(R.string.dont_allow));

        if (reason == Policy.RETRY) {
            // If the reason received from the policy is RETRY, it was probably
            // due to a loss of connection with the service, so we should give the
            // user a chance to retry. So show a dialog to retry.
            showDialog(DIALOG_RETRY);
        } else {
            // Otherwise, the user isn't licensed to use this app.
            // Your response should always inform the user that the application
            // isn't licensed, but your behavior at that point can vary. You might
            // provide the user a limited access version of your app or you can
            // take them to Google Play to purchase the app.
            showDialog(DIALOG_GOTOMARKET);
        }
    }
}

Кроме того, вам следует реализовать метод applicationError() , который вызывается LVL, чтобы позволить вашему приложению обрабатывать ошибки, которые невозможно повторить. Список таких ошибок см. в разделе Коды ответов сервера в Справочнике по лицензированию . Вы можете реализовать метод любым необходимым способом. В большинстве случаев метод должен регистрировать код ошибки и вызывать dontAllow() .

Создайте обработчик для публикации из LicenseCheckerCallback в поток пользовательского интерфейса.

Во время проверки лицензии LVL передает запрос приложению Google Play, которое осуществляет связь с сервером лицензирования. LVL передает запрос через асинхронный IPC (с использованием Binder ), поэтому фактическая обработка и сетевое взаимодействие не происходят в потоке, управляемом вашим приложением. Аналогичным образом, когда приложение Google Play получает результат, оно вызывает метод обратного вызова через IPC, который, в свою очередь, выполняется в пуле потоков IPC в процессе вашего приложения.

Класс LicenseChecker управляет IPC-взаимодействием вашего приложения с приложением Google Play, включая вызов, отправляющий запрос, и обратный вызов, получающий ответ. LicenseChecker также отслеживает запросы открытых лицензий и управляет их временем ожидания.

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

Для вашего приложения это означает, что:

  1. Ваши методы LicenseCheckerCallback во многих случаях будут вызываться из фонового потока.
  2. Эти методы не смогут обновлять состояние или вызывать какую-либо обработку в потоке пользовательского интерфейса, если вы не создадите обработчик в потоке пользовательского интерфейса и не отправите свои методы обратного вызова в обработчик.

Если вы хотите, чтобы ваши методы LicenseCheckerCallback обновляли поток пользовательского интерфейса, создайте экземпляр Handler в методе onCreate() основного действия, как показано ниже. В этом примере методы LicenseCheckerCallback примера приложения LVL (см. выше) вызывают displayResult() для обновления потока пользовательского интерфейса с помощью метода post() обработчика.

Котлин

    private lateinit var handler: Handler

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        handler = Handler()
    }

Ява

    private Handler handler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        handler = new Handler();
    }

Затем в методах LicenseCheckerCallback вы можете использовать методы Handler для отправки объектов Runnable или Message в Handler. Вот как пример приложения, включенный в LVL, отправляет Runnable обработчику в потоке пользовательского интерфейса для отображения состояния лицензии.

Котлин

private fun displayResult(result: String) {
    handler.post {
        statusText.text = result
        setProgressBarIndeterminateVisibility(false)
        checkLicenseButton.isEnabled = true
    }
}

Ява

private void displayResult(final String result) {
        handler.post(new Runnable() {
            public void run() {
                statusText.setText(result);
                setProgressBarIndeterminateVisibility(false);
                checkLicenseButton.setEnabled(true);
            }
        });
    }

Создание экземпляра LicenseChecker и LicenseCheckerCallback

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

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

  • Context приложения
  • Ссылка на реализацию Policy , используемую для проверки лицензии. В большинстве случаев вы будете использовать реализацию Policy по умолчанию, предоставляемую LVL, ServerManagedPolicy.
  • Переменная String, содержащая открытый ключ вашей учетной записи издателя для лицензирования.

Если вы используете ServerManagedPolicy, вам не потребуется прямой доступ к классу, поэтому вы можете создать его экземпляр в конструкторе LicenseChecker , как показано в примере ниже. Обратите внимание, что вам необходимо передать ссылку на новый экземпляр Obfuscator при создании ServerManagedPolicy.

В приведенном ниже примере показано создание экземпляров LicenseChecker и LicenseCheckerCallback из метода onCreate() класса Activity.

Котлин

class MainActivity : AppCompatActivity() {
    ...
    private lateinit var licenseCheckerCallback: LicenseCheckerCallback
    private lateinit var checker: LicenseChecker

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Construct the LicenseCheckerCallback. The library calls this when done.
        licenseCheckerCallback = MyLicenseCheckerCallback()

        // Construct the LicenseChecker with a Policy.
        checker = LicenseChecker(
                this,
                ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)),
                BASE64_PUBLIC_KEY // Your public licensing key.
        )
        ...
    }
}

Ява

public class MainActivity extends Activity {
    ...
    private LicenseCheckerCallback licenseCheckerCallback;
    private LicenseChecker checker;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Construct the LicenseCheckerCallback. The library calls this when done.
        licenseCheckerCallback = new MyLicenseCheckerCallback();

        // Construct the LicenseChecker with a Policy.
        checker = new LicenseChecker(
            this, new ServerManagedPolicy(this,
                new AESObfuscator(SALT, getPackageName(), deviceId)),
            BASE64_PUBLIC_KEY // Your public licensing key.
            );
        ...
    }
}

Обратите внимание, что LicenseChecker вызывает методы LicenseCheckerCallback из потока пользовательского интерфейса только в том случае, если локально кэшируется действительный ответ лицензии. Если проверка лицензии отправляется на сервер, обратные вызовы всегда происходят из фонового потока, даже в случае сетевых ошибок.

Вызовите checkAccess(), чтобы инициировать проверку лицензии.

В своей основной деятельности добавьте вызов метода checkAccess() экземпляра LicenseChecker . В вызове передайте ссылку на экземпляр LicenseCheckerCallback в качестве параметра. Если вам нужно обработать какие-либо специальные эффекты пользовательского интерфейса или управление состоянием перед вызовом, вам может оказаться полезным вызвать checkAccess() из метода-оболочки. Например, пример приложения LVL вызывает checkAccess() из метода-оболочки doCheck() :

Котлин

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ...
        // Call a wrapper method that initiates the license check
        doCheck()
        ...
    }
    ...
    private fun doCheck() {
        checkLicenseButton.isEnabled = false
        setProgressBarIndeterminateVisibility(true)
        statusText.setText(R.string.checking_license)
        checker.checkAccess(licenseCheckerCallback)
    }

Ява

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        // Call a wrapper method that initiates the license check
        doCheck();
        ...
    }
    ...
    private void doCheck() {
        checkLicenseButton.setEnabled(false);
        setProgressBarIndeterminateVisibility(true);
        statusText.setText(R.string.checking_license);
        checker.checkAccess(licenseCheckerCallback);
    }

Вставьте свой открытый ключ для лицензирования

Для каждого приложения сервис Google Play автоматически генерирует 2048-битную пару открытого и закрытого ключей RSA, которая используется для лицензирования и выставления счетов в приложении. Пара ключей однозначно связана с приложением. Несмотря на то, что эта пара ключей связана с приложением, это не то же самое, что ключ, который вы используете для подписи своих приложений (или производный от него).

Консоль Google Play предоставляет открытый ключ для лицензирования любому разработчику, вошедшему в консоль Play, но сохраняет закрытый ключ скрытым от всех пользователей в безопасном месте. Когда приложение запрашивает проверку лицензии для приложения, опубликованного в вашей учетной записи, сервер лицензирования подписывает ответ о лицензии, используя закрытый ключ пары ключей вашего приложения. Когда LVL получает ответ, он использует открытый ключ, предоставленный приложением, для проверки подписи ответа лицензии.

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

  1. Перейдите в консоль Google Play и войдите в систему. Убедитесь, что вы входите в учетную запись, из которой опубликовано (или будет опубликовано) лицензируемое вами приложение.
  2. На странице сведений о приложении найдите ссылку «Службы и API» и щелкните ее.
  3. На странице «Службы и API» найдите раздел «Лицензирование и выставление счетов в приложениях» . Ваш открытый ключ для лицензирования указан в поле «Ваш лицензионный ключ для этого приложения» .

Чтобы добавить открытый ключ в ваше приложение, просто скопируйте/вставьте строку ключа из поля в ваше приложение как значение строковой переменной BASE64_PUBLIC_KEY . При копировании убедитесь, что вы выбрали всю ключевую строку, не пропуская ни одного символа.

Вот пример из примера приложения LVL:

Котлин

private const val BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... " //truncated for this example
class LicensingActivity : AppCompatActivity() {
    ...
}

Ява

public class MainActivity extends Activity {
    private static final String BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... "; //truncated for this example
    ...
}

Вызовите метод onDestroy() вашего LicenseChecker, чтобы закрыть соединения IPC.

Наконец, чтобы позволить LVL очиститься до изменения Context вашего приложения, добавьте вызов метода onDestroy() LicenseChecker из реализации onDestroy() вашего Activity. Этот вызов заставляет LicenseChecker правильно закрыть любое открытое соединение IPC с ILicensingService приложения Google Play и удалить все локальные ссылки на службу и обработчик.

Невызов метода onDestroy() LicenseChecker может привести к проблемам в течение жизненного цикла вашего приложения. Например, если пользователь меняет ориентацию экрана во время активной проверки лицензии, Context приложения уничтожается. Если ваше приложение не закрывает IPC-соединение LicenseChecker должным образом, ваше приложение выйдет из строя при получении ответа. Аналогичным образом, если пользователь выходит из вашего приложения, когда проверка лицензии выполняется, ваше приложение будет отключаться при получении ответа, если только оно не называется метод LicenseChecker 's onDestroy() для отключения от службы.

Вот пример из образца приложения, включенного в LVL, где mChecker является экземпляром LicenseChecker :

Котлин

    override fun onDestroy() {
        super.onDestroy()
        checker.onDestroy()
        ...
    }

Ява

    @Override
    protected void onDestroy() {
        super.onDestroy();
        checker.onDestroy();
        ...
    }

Если вы расширяете или изменяете LicenseChecker , вам также может потребоваться вызвать метод LicenseChecker от finishCheck() , чтобы очистить любые открытые соединения IPC.

Реализация DeviceLimiter

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

LVL поддерживает лицензирование для каждого устройства, предоставляя интерфейс DeviceLimiter , который объявляет один метод allowDeviceAccess() . Когда лицензиат Validator обрабатывает ответ с сервера лицензирования, он вызывает allowDeviceAccess() , передавая строку идентификатора пользователя, извлеченную из ответа.

Если вы не хотите поддерживать ограничение устройства, работа не требуется - класс LicenseChecker автоматически использует реализацию по умолчанию под названием Nulldevicelimiter. Как следует из названия, nulldevicelimiter-это класс «no-op», метод allowDeviceAccess() просто возвращает LICENSED ответ для всех пользователей и устройств.

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

  • Требуется, чтобы вы предоставили бэкэнд -сервер для управления отображением пользователей и устройств, и
  • Это может непреднамеренно привести к тому, что пользователю будет отказано в доступе к приложению, которое он законно приобрел на другом устройстве.

Запутывание вашего кода

Чтобы обеспечить безопасность вашего приложения, особенно для оплачиваемой приложения, которая использует лицензионные и/или пользовательские ограничения и защиту, очень важно запутать код вашего приложения. Правильно запутывание вашего кода затрудняет вредоносного пользователя, чтобы декомпилировать байт -код приложения, изменить его - например, путем удаления проверки лицензии - и затем перекомпилировать его.

Несколько программ в области опскатора доступны для приложений Android, включая Proguard , который также предлагает функции-оптимизации кода. Использование Proguard или аналогичной программы для запугивания вашего кода настоятельно рекомендуется для всех приложений, которые используют лицензирование Google Play.

Публикация лицензированного приложения

Когда вы закончите тестирование реализации лицензии, вы готовы опубликовать приложение в Google Play. Следуйте нормальным шагам, чтобы подготовить , подписать , а затем опубликовать приложение .

Где получить поддержку

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

Таблица 2. Ресурсы поддержки разработчиков для Google Play Licensing Service.

Тип поддержки Ресурс Диапазон тем
Проблемы разработки и тестирования Группы Google: Android-разработчики Скачать и интеграция LVL, библиотечные проекты, вопросы Policy , идеи пользователя, обработка ответов, Obfuscator , МПК, настройка среды тестирования
Переполнение стека: http://stackoverflow.com/questions/tagged/android
Счетчики, публикация и развертывание Google Play Help Forum Учетные записи издателей, лицензионные пары ключей, тестовые учетные записи, ответы на серверы, ответы тестирования, развертывание приложений и результаты
FAQ поддержки лицензирования рынка
LVL выпуска трекер Трекер выпуска проекта MarketCensing Ошибки и выпуск отчетов, связанных специально с классами исходного кода LVL и реализациям интерфейса

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

Дополнительные ресурсы

Пример приложения, включенная в LVL, дает полный пример того, как инициировать проверку лицензии и обработать результат, в классе MainActivity .