Предупреждение. Если приложение выполняет проверку лицензии на стороне клиента, злоумышленникам проще изменить или удалить логику, связанную с этим процессом.
Поэтому мы настоятельно рекомендуем проверять лицензии на стороне сервера.
После того как вы настроите аккаунт издателя и среду разработки (см. раздел Настройка лицензирования), вы сможете добавить в приложение проверку лицензии с помощью библиотеки LVL.
Чтобы добавить проверку лицензии с помощью LVL, выполните следующие действия:
- Добавление разрешения на проверку лицензии в манифест приложения.
- Реализация правил. Вы можете выбрать один из вариантов, предлагаемых LVL, или создать собственный.
- Реализация обфускатора, если
Policyбудет кешировать данные ответов на запросы лицензии. - Добавление кода для проверки лицензии в основной объект Activity приложения.
- Реализация 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 Tools не объединит его с манифестами зависимых приложений. Вместо этого необходимо объявить разрешение в манифесте каждого зависимого приложения.
Реализация политики
Сервис лицензирования Google Play не определяет, должен ли пользователь с определенной лицензией получить доступ к вашему приложению.
Это делается с помощью реализации Policy, которую вы добавляете в приложение.
Policy – это интерфейс, объявленный LVL и предназначенный для хранения логики приложения, которая разрешает или запрещает доступ пользователя на основе результатов проверки лицензии. Чтобы использовать LVL, ваше приложение должно предоставлять реализацию Policy.
Интерфейс Policy объявляет два метода, allowAccess() и processServerResponse(), которые вызываются экземпляром LicenseChecker при обработке ответа от сервера лицензирования. В нем также объявляется перечисление LicenseResponse, которое указывает значение ответа лицензии, передаваемое при вызовах processServerResponse().
processServerResponse()позволяет предварительно обработать необработанные данные ответа, полученные от сервера лицензирования, прежде чем определить, следует ли предоставлять доступ.Обычно из ответа сервера извлекаются некоторые или все поля, а затем данные сохраняются локально в постоянном хранилище, например в хранилище
SharedPreferences. Это позволяет обеспечить доступ к данным при вызовах приложения и перезагрузках устройства. Например,Policyможет сохранять в постоянном хранилище временную метку последней успешной проверки лицензии, количество повторных попыток, период действия лицензии и другую подобную информацию, а не сбрасывать значения при каждом запуске приложения.При локальном хранении данных ответа
Policyдолжен обеспечить их обфускацию (см. раздел Реализация обфускатора ниже).allowAccess()определяет, следует ли предоставить пользователю доступ к вашему приложению, на основе любых доступных данных ответа лицензии (с сервера лицензирования или из кеша) или другой информации, относящейся к приложению. Например, ваша реализацияallowAccess()может учитывать дополнительные критерии, такие как использование или другие данные, полученные с бэкенд-сервера. Во всех случаях реализацияallowAccess()должна возвращатьtrue, только если у пользователя есть лицензия на приложение, подтвержденная сервером лицензирования, или если проверку лицензии не удалось выполнить из-за временной проблемы с сетью или системой. В таких случаях ваше приложение может подсчитывать количество ответов с кодом RETRY и временно разрешать доступ до завершения следующей проверки лицензии.
Чтобы упростить добавление лицензирования в приложение и показать, как должен быть устроен Policy, в LVL включены две полные реализации Policy, которые можно использовать без изменений или адаптировать под свои нужды:
- ServerManagedPolicy – гибкий класс
Policy, который использует настройки, предоставленные сервером, и кешированные ответы, чтобы управлять доступом в различных условиях сети. - StrictPolicy – не кеширует данные ответа и разрешает доступ, только если сервер возвращает ответ о наличии лицензии.
Для большинства приложений настоятельно рекомендуется использовать ServerManagedPolicy. ServerManagedPolicy – это стандартная политика LVL, которая интегрирована с примером приложения LVL.
Рекомендации по созданию специальных правил
В своей реализации лицензирования вы можете использовать одно из полных правил, предоставляемых LVL (ServerManagedPolicy или StrictPolicy), или создать собственное правило. При реализации любого типа пользовательских правил необходимо учитывать несколько важных моментов.
Сервер лицензирования применяет общие ограничения на запросы, чтобы предотвратить чрезмерное использование ресурсов, которое может привести к отказу в обслуживании. Если приложение превышает лимит запросов, сервер лицензирования возвращает ответ 503, который передается в приложение как общая ошибка сервера. Это означает, что ответ на запрос лицензии не будет доступен пользователю до тех пор, пока лимит не будет сброшен, что может повлиять на пользователя в течение неопределенного периода времени.
Если вы создаете собственное правило, рекомендуем сделать следующее: Policy
- Кеширует (и надлежащим образом обфусцирует) последний успешный ответ лицензии в локальном постоянном хранилище.
- Возвращает кешированный ответ для всех проверок лицензии, пока он действителен, вместо того чтобы отправлять запрос на сервер лицензирования.
Настоятельно рекомендуем задавать срок действия ответа в соответствии с дополнительным параметром
VT, предоставляемым сервером. Подробнее о дополнительных данных в ответе сервера… - Использует экспоненциальную выдержку при повторной отправке запросов, которые привели к ошибкам. Обратите внимание, что клиент Google Play автоматически повторяет неудачные запросы, поэтому в большинстве случаев вашему
Policyне нужно повторять их. - Предоставляет "льготный период", в течение которого пользователь может получить доступ к вашему приложению на ограниченное время или ограниченное количество раз, пока выполняется повторная проверка лицензии. Льготный период выгоден пользователю, поскольку он может работать с приложением до следующей проверки лицензии, и вам, поскольку вы можете ограничить доступ к приложению, если не удается получить ответ о наличии действительной лицензии.
При разработке Policy важно следовать приведенным выше рекомендациям. Это позволит обеспечить максимальное удобство для пользователей и эффективный контроль над приложением даже в случае ошибок.
Обратите внимание, что любое Policy может использовать настройки, предоставленные сервером лицензирования, чтобы управлять сроком действия и кешированием, льготным периодом повторных попыток и т. д. Извлечь настройки, предоставленные сервером, несложно, и мы настоятельно рекомендуем это сделать. Пример извлечения и использования дополнительных данных можно найти в реализации ServerManagedPolicy. Список настроек сервера и информация о том, как их использовать, приведены в статье Дополнительная информация о запросе.
ServerManagedPolicy
В LVL входит полная и рекомендуемая реализация интерфейса Policy, которая называется ServerManagedPolicy. Реализация интегрирована с классами LVL и служит в библиотеке значением Policy по умолчанию.
ServerManagedPolicy обрабатывает все ответы, связанные с лицензиями и повторными попытками. Она кеширует все данные ответа локально в файле SharedPreferences, обфусцируя их с помощью реализации Obfuscator приложения. Это гарантирует, что данные ответа лицензии будут защищены и сохранятся при перезагрузке устройства. ServerManagedPolicy предоставляет конкретные реализации методов интерфейса processServerResponse() и allowAccess(), а также включает набор вспомогательных методов и типов для управления ответами на запросы лицензии.
Важная особенность ServerManagedPolicy заключается в том, что он использует настройки, предоставляемые сервером, в качестве основы для управления лицензированием в течение периода возврата средств за приложение и в различных условиях сети и ошибок.
Когда приложение обращается к серверу Google Play для проверки лицензии, сервер добавляет несколько настроек в виде пар "ключ-значение" в поле extras определенных типов ответов. Например, сервер предоставляет рекомендуемые значения для периода действия лицензии приложения, льготного периода повторной попытки, максимально допустимого количества повторных попыток и т. д. ServerManagedPolicy извлекает значения из ответа лицензии в методе processServerResponse() и проверяет их в методе allowAccess(). Список настроек, предоставляемых сервером и используемых ServerManagedPolicy, приведен в разделе Дополнительная информация о запросе.
Для удобства, оптимальной производительности и использования настроек лицензии с сервера Google Play настоятельно рекомендуется Policyиспользовать ServerManagedPolicy.
Если вы беспокоитесь о безопасности данных, которые хранятся локально в SharedPreferences, используйте более надежный алгоритм обфускации или создайте более строгий Policy, который не хранит данные лицензии. Пример такой Policy можно найти в LVL. Подробнее о StrictPolicy…
Чтобы использовать ServerManagedPolicy, импортируйте его в Activity, создайте экземпляр и передайте ссылку на него при создании LicenseChecker. Подробнее о том, как создать экземпляр LicenseChecker и LicenseCheckerCallback…
StrictPolicy
В LVL есть альтернативная полная реализация интерфейса Policy, которая называется StrictPolicy. Реализация StrictPolicy обеспечивает более строгие правила, чем ServerManagedPolicy, поскольку не позволяет пользователю получить доступ к приложению, если в момент доступа от сервера не получен ответ, подтверждающий наличие лицензии.
Основная особенность StrictPolicy заключается в том, что он не хранит никакие данные ответа лицензии локально в постоянном хранилище. Поскольку данные не сохраняются, повторные запросы не отслеживаются, а кешированные ответы не могут использоваться для проверки лицензии. Policy разрешает доступ, только если:
- Ответ лицензии получен от сервера лицензирования.
- Ответ на запрос лицензии указывает, что у пользователя есть лицензия на доступ к приложению.
Используйте StrictPolicy, если для вас важно, чтобы ни один пользователь не мог получить доступ к приложению, если у него нет лицензии на момент использования. Кроме того, Policy обеспечивает немного более высокий уровень безопасности, чем 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.
Подробная информация приведена в следующем разделе.
AESObfuscator
В LVL входит полная и рекомендуемая реализация интерфейса Obfuscator, которая называется AESObfuscator. Она интегрирована с примером приложения LVL и используется в библиотеке как Obfuscator по умолчанию.
AESObfuscator обеспечивает надежную обфускацию данных, используя AES для шифрования и расшифровки данных при их записи в хранилище или чтении из него.
Obfuscator использует для шифрования три поля данных, предоставляемые приложением:
- Соль – массив случайных байтов, используемый для каждой обфускации и деобфускации.
- Строка идентификатора приложения, обычно название пакета приложения.
- Строка идентификатора устройства, полученная из максимально возможного количества источников, связанных с устройством, чтобы сделать ее уникальной.
Чтобы использовать AESObfuscator, импортируйте его в Activity. Объявите закрытый статический конечный массив для хранения байтов соли и инициализируйте его до 20 случайно сгенерированных байтов.
Kotlin
// 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 )
Java
... // 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. Пример:
Kotlin
... // Construct the LicenseChecker with a Policy. private val checker = LicenseChecker( this, ServerManagedPolicy(this, AESObfuscator(SALT, packageName, deviceId)), BASE64_PUBLIC_KEY ) ...
Java
... // 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.
Проверка лицензии из Activity
После того как вы реализовали Policy для управления доступом к приложению, следующим шагом будет добавление в приложение проверки лицензии, которая при необходимости инициирует запрос к серверу лицензирования и управляет доступом к приложению на основе ответа лицензии. Все действия по добавлению проверки лицензии и обработке ответа выполняются в основном исходном файле Activity.
Чтобы добавить проверку лицензии и обработать ответ, выполните следующие действия:
- Добавление импорта
- Реализуйте LicenseCheckerCallback как закрытый внутренний класс
- Создайте обработчик для отправки данных из LicenseCheckerCallback в поток UI.
- Создайте экземпляр LicenseChecker и LicenseCheckerCallback.
- Вызовите checkAccess(), чтобы запустить проверку лицензии.
- Встройте открытый ключ для лицензирования.
- Вызовите метод onDestroy() объекта LicenseChecker, чтобы закрыть подключения IPC.
Ниже описано, как это сделать.
Общие сведения о проверке лицензии и ответе
В большинстве случаев проверку лицензии следует добавлять в основной класс Activity приложения, в метод onCreate(). Это гарантирует, что при запуске приложения пользователем проверка лицензии будет выполнена немедленно. В некоторых случаях проверку лицензии можно добавить и в другие места. Например, если в вашем приложении есть несколько компонентов Activity, которые другие приложения могут запускать с помощью Intent, вы можете добавить в них проверку лицензии.
Проверка лицензии состоит из двух основных действий:
- Вызов метода для инициации проверки лицензии. В LVL это вызов метода
checkAccess()объектаLicenseChecker, который вы создаете. - Обратный вызов, который возвращает результат проверки лицензии. В LVL это интерфейс
LicenseCheckerCallback, который вы реализуете. Интерфейс объявляет два метода,allow()иdontAllow(), которые вызываются библиотекой в зависимости от результата проверки лицензии. Вы реализуете эти два метода с любой логикой, которая вам нужна, чтобы разрешить или запретить пользователю доступ к вашему приложению. Обратите внимание, что эти методы не определяют, следует ли разрешать доступ. Это решение принимается в вашей реализацииPolicy. Эти методы просто определяют поведение приложения, то есть как разрешать и запрещать доступ (и обрабатывать ошибки приложения).Методы
allow()иdontAllow()указывают причину ответа, которая может быть одним из значенийPolicy,LICENSED,NOT_LICENSEDилиRETRY. В частности, необходимо обработать случай, когда метод получает ответRETRYдляdontAllow(), и предоставить пользователю кнопку "Повторить". Это может произойти, если сервис был недоступен во время запроса.
На диаграмме выше показано, как обычно выполняется проверка лицензии.
- Код в основном объекте Activity приложения создает экземпляры объектов
LicenseCheckerCallbackиLicenseChecker. При созданииLicenseCheckerкод передаетContext, реализациюPolicy, которую нужно использовать, и открытый ключ аккаунта издателя для лицензирования в качестве параметров. - Затем код вызывает метод
checkAccess()для объектаLicenseChecker. Реализация метода вызываетPolicy, чтобы определить, есть ли в локальном кешеSharedPreferencesдействительный ответ на запрос лицензии.- Если да, то реализация
checkAccess()вызываетallow(). - В противном случае
LicenseCheckerинициирует запрос на проверку лицензии, который отправляется на сервер лицензирования.
Примечание. При проверке лицензии для черновика приложения сервер лицензирования всегда возвращает значение
LICENSED. - Если да, то реализация
- Получив ответ,
LicenseCheckerсоздает объект LicenseValidator, который проверяет подписанные данные лицензии, извлекает поля ответа и передает их объектуPolicyдля дальнейшей оценки.- Если лицензия действительна,
Policyкеширует ответ вSharedPreferencesи уведомляет валидатор, который затем вызывает методallow()для объектаLicenseCheckerCallback. - Если лицензия недействительна,
Policyуведомляет валидатор, который вызывает методdontAllow()дляLicenseCheckerCallback.
- Если лицензия действительна,
- Если возникает устранимая локальная ошибка или ошибка сервера (например, когда сеть недоступна для отправки запроса),
LicenseCheckerпередает ответRETRYметодуprocessServerResponse()объектаPolicy.Кроме того, методы обратного вызова
allow()иdontAllow()получают аргументreason. Для методаallow()обычно указывается причинаPolicy.LICENSEDилиPolicy.RETRY, а для методаdontAllow()–Policy.NOT_LICENSEDилиPolicy.RETRY. Эти значения ответов полезны, чтобы вы могли показать пользователю соответствующий ответ, например, предоставив кнопку "Повторить", когдаdontAllow()отвечаетPolicy.RETRY, что могло быть связано с недоступностью сервиса. - В случае ошибки приложения, например когда оно пытается проверить лицензию пакета с недопустимым названием,
LicenseCheckerпередает ответ об ошибке методуapplicationError()объекта LicenseCheckerCallback.
Обратите внимание, что помимо запуска проверки лицензии и обработки ее результатов, которые описаны в разделах ниже, в приложении также должна быть реализована политика. Если Policy хранит данные ответа (например, ServerManagedPolicy), то также должна быть реализована обфускация.
Добавить импорты
Сначала откройте файл класса основного Activity приложения и импортируйте LicenseChecker и LicenseCheckerCallback из пакета LVL.
Kotlin
import com.google.android.vending.licensing.LicenseChecker import com.google.android.vending.licensing.LicenseCheckerCallback
Java
import com.google.android.vending.licensing.LicenseChecker; import com.google.android.vending.licensing.LicenseCheckerCallback;
Если вы используете стандартную реализацию Policy, которая поставляется вместе с LVL, ServerManagedPolicy, импортируйте ее вместе с AESObfuscator. Если вы используете собственные классы Policy или Obfuscator, импортируйте их.
Kotlin
import com.google.android.vending.licensing.ServerManagedPolicy import com.google.android.vending.licensing.AESObfuscator
Java
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() и dontAllow(). Для начала вы можете использовать простые методы обработки результатов, например отображение результата проверки лицензии в диалоговом окне. Это поможет вам быстрее запустить приложение и устранить неполадки. Позже, когда вы определите, какое поведение вам нужно, вы сможете добавить более сложную обработку.
Вот несколько советов по работе с ответами без лицензии в dontAllow():
- Показать пользователю диалоговое окно "Повторить попытку" с кнопкой для запуска новой проверки лицензии, если
reasonимеет значениеPolicy.RETRY. - Показать диалоговое окно "Купить это приложение" с кнопкой, которая перенаправляет пользователя на страницу приложения в Google Play, где его можно купить. Подробнее о том, как настроить такие ссылки…
- Показать всплывающее уведомление о том, что функции приложения ограничены, поскольку оно не лицензировано.
В примере ниже показано, как пример приложения LVL реализует LicenseCheckerCallback с методами, которые отображают результат проверки лицензии в диалоговом окне.
Kotlin
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) } } }
Java
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 в поток UI.
При проверке лицензии LVL передает запрос приложению Google Play, которое обрабатывает обмен данными с сервером лицензирования. LVL передает запрос через асинхронный IPC (с помощью Binder), поэтому фактическая обработка и сетевое взаимодействие не происходят в потоке, управляемом вашим приложением. Аналогично, когда приложение Google Play получает результат, оно вызывает метод обратного вызова через IPC, который, в свою очередь, выполняется в пуле потоков IPC в процессе вашего приложения.
Класс LicenseChecker управляет межпроцессным взаимодействием вашего приложения с Google Play, включая вызов, который отправляет запрос, и обратный вызов, который получает ответ. LicenseChecker также отслеживает открытые запросы лицензий и управляет их тайм-аутами.
Чтобы правильно обрабатывать тайм-ауты и входящие ответы, не влияя на поток UI приложения, LicenseChecker создает фоновый поток при создании экземпляра. В потоке выполняется вся обработка результатов проверки лицензии, независимо от того, является ли результат ответом, полученным от сервера, или ошибкой тайм-аута. По завершении обработки LVL вызывает методы LicenseCheckerCallback из фонового потока.
Для вашего приложения это означает следующее:
- Методы
LicenseCheckerCallbackво многих случаях будут вызываться из фонового потока. - Эти методы не смогут обновлять состояние или вызывать какую-либо обработку в потоке UI, если вы не создадите обработчик в потоке UI и не настроите методы обратного вызова для отправки в обработчик.
Если вы хотите, чтобы методы LicenseCheckerCallback обновляли поток UI, создайте экземпляр Handler в методе onCreate() основного действия, как показано ниже. В этом примере методы LicenseCheckerCallback примера приложения LVL (см. выше) вызывают displayResult(), чтобы обновить поток UI с помощью метода post() обработчика.
Kotlin
private lateinit var handler: Handler override fun onCreate(savedInstanceState: Bundle?) { ... handler = Handler() }
Java
private Handler handler; @Override public void onCreate(Bundle savedInstanceState) { ... handler = new Handler(); }
Затем в методах LicenseCheckerCallback можно использовать методы Handler, чтобы отправлять объекты Runnable или Message в Handler. Ниже показано, как пример приложения, включенный в LVL, отправляет Runnable в Handler в потоке UI, чтобы показать статус лицензии.
Kotlin
private fun displayResult(result: String) { handler.post { statusText.text = result setProgressBarIndeterminateVisibility(false) checkLicenseButton.isEnabled = true } }
Java
private void displayResult(final String result) { handler.post(new Runnable() { public void run() { statusText.setText(result); setProgressBarIndeterminateVisibility(false); checkLicenseButton.setEnabled(true); } }); }
Создайте экземпляры LicenseChecker и LicenseCheckerCallback
В методе onCreate() основного класса Activity создайте закрытые экземпляры LicenseCheckerCallback и LicenseChecker. Сначала необходимо создать экземпляр LicenseCheckerCallback, поскольку при вызове конструктора для LicenseChecker нужно передать ссылку на этот экземпляр.
При создании экземпляра LicenseChecker необходимо передать следующие параметры:
- Приложение
Context - Ссылка на реализацию
Policy, которая будет использоваться для проверки лицензии. В большинстве случаев вы будете использовать реализациюPolicyпо умолчанию, предоставляемую LVL, – ServerManagedPolicy. - Строковая переменная, содержащая открытый ключ вашего аккаунта издателя для лицензирования.
Если вы используете ServerManagedPolicy, вам не нужно обращаться к классу напрямую, поэтому вы можете создать его экземпляр в конструкторе LicenseChecker, как показано в примере ниже. Обратите внимание, что при создании ServerManagedPolicy необходимо передать ссылку на новый экземпляр Obfuscator.
В примере ниже показано создание экземпляров LicenseChecker и LicenseCheckerCallback из метода onCreate() класса Activity.
Kotlin
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. ) ... } }
Java
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 из потока UI, только если в локальном кеше есть действительный ответ о лицензии. Если запрос на проверку лицензии отправляется на сервер, обратные вызовы всегда поступают из фонового потока, даже в случае ошибок сети.
Вызовите checkAccess(), чтобы запустить проверку лицензии.
В основной объект Activity добавьте вызов метода checkAccess() экземпляра LicenseChecker. В вызове передайте ссылку на экземпляр LicenseCheckerCallback в качестве параметра. Если перед вызовом вам нужно обработать какие-либо специальные эффекты пользовательского интерфейса или управление состоянием, вы можете вызвать checkAccess() из метода-оболочки. Например, пример приложения LVL вызывает checkAccess() из метода-оболочки doCheck():
Kotlin
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) }
Java
@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 Console предоставляет открытый ключ для лицензирования любому разработчику, вошедшему в Play Console, но скрывает закрытый ключ от всех пользователей в безопасном месте. Когда приложение запрашивает проверку лицензии для приложения, опубликованного в вашем аккаунте, сервер лицензирования подписывает ответ на запрос, используя закрытый ключ из пары ключей приложения. Получив ответ, LVL использует открытый ключ, предоставленный приложением, чтобы проверить подпись ответа.
Чтобы добавить лицензирование в приложение, необходимо получить открытый ключ лицензирования и скопировать его в приложение. Чтобы найти открытый ключ приложения для лицензирования, выполните следующие действия:
- Войдите в Google Play Console. Убедитесь, что вы вошли в аккаунт, в котором опубликовано (или будет опубликовано) приложение, для которого вы настраиваете лицензирование.
- На странице приложения найдите ссылку Services & APIs (Сервисы и API) и нажмите на нее.
- На странице Сервисы и API найдите раздел Лицензирование и оплата контента. Открытый ключ для лицензирования указан в поле Лицензионный ключ для этого приложения.
Чтобы добавить открытый ключ в приложение, скопируйте строку ключа из поля и вставьте ее в приложение в качестве значения строковой переменной BASE64_PUBLIC_KEY. При копировании убедитесь, что вы выбрали всю строку ключа, не пропустив ни одного символа.
Вот пример из приложения LVL:
Kotlin
private const val BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... " //truncated for this example class LicensingActivity : AppCompatActivity() { ... }
Java
public class MainActivity extends Activity { private static final String BASE64_PUBLIC_KEY = "MIIBIjANBgkqhkiG ... "; //truncated for this example ... }
Вызовите метод onDestroy() объекта LicenseChecker, чтобы закрыть подключения IPC.
Наконец, чтобы библиотека LVL могла выполнить очистку перед изменением состояния приложения, добавьте вызов метода LicenseChecker объекта Context из реализации onDestroy() в Activity.onDestroy() В результате вызова LicenseChecker правильно закрывает все открытые IPC-подключения к ILicensingService приложения Google Play и удаляет все локальные ссылки на сервис и обработчик.
Если не вызвать метод onDestroy() объекта LicenseChecker, могут возникнуть проблемы с жизненным циклом приложения. Например, если пользователь изменит ориентацию экрана во время проверки лицензии, приложение Context будет закрыто. Если ваше приложение не закроет должным образом IPC-подключение LicenseChecker, то при получении ответа произойдет сбой. Аналогично, если пользователь выйдет из приложения во время проверки лицензии, оно аварийно завершит работу при получении ответа, если только не был вызван метод onDestroy() объекта LicenseChecker для отключения от сервиса.
Ниже приведен пример из приложения, включенного в LVL, где mChecker – это экземпляр LicenseChecker:
Kotlin
override fun onDestroy() { super.onDestroy() checker.onDestroy() ... }
Java
@Override protected void onDestroy() { super.onDestroy(); checker.onDestroy(); ... }
Если вы расширяете или изменяете LicenseChecker, вам также может потребоваться вызвать метод finishCheck() класса LicenseChecker, чтобы закрыть все открытые IPC-подключения.
Как реализовать DeviceLimiter
В некоторых случаях вам может потребоваться, чтобы Policy ограничивал количество устройств, на которых разрешено использовать одну лицензию. Это не позволит пользователю перенести лицензированное приложение на несколько устройств и использовать его на них под одним и тем же идентификатором аккаунта. Это также не позволит пользователю "поделиться" приложением, предоставив другим лицам информацию об аккаунте, связанном с лицензией. В противном случае они могли бы войти в этот аккаунт на своих устройствах и получить доступ к лицензии на приложение.
LVL поддерживает лицензирование для отдельных устройств, предоставляя интерфейс DeviceLimiter, который объявляет один метод – allowDeviceAccess(). Когда LicenseValidator обрабатывает ответ от сервера лицензирования, он вызывает allowDeviceAccess(), передавая строку идентификатора пользователя, извлеченную из ответа.
Если вы не хотите поддерживать ограничение устройств, никаких действий не требуется – класс LicenseChecker автоматически использует реализацию по умолчанию под названием NullDeviceLimiter. Как следует из названия, NullDeviceLimiter – это класс, который не выполняет никаких операций. Его метод allowDeviceAccess() просто возвращает ответ LICENSED для всех пользователей и устройств.
Внимание! Лицензирование на устройство не рекомендуется для большинства приложений по следующим причинам:
- Для этого вам потребуется предоставить бэкенд-сервер, который будет управлять сопоставлением пользователей и устройств, а также
- Это может привести к тому, что пользователь не сможет получить доступ к приложению, которое он законно приобрел на другом устройстве.
Обфускация кода
Чтобы обеспечить безопасность приложения, особенно платного, в котором используется лицензирование и/или специальные ограничения и средства защиты, очень важно обфусцировать код приложения. Правильная обфускация кода усложняет декомпиляцию байт-кода приложения, его изменение (например, удаление проверки лицензии) и последующую перекомпиляцию.
Для приложений Android доступно несколько программ-обфускаторов, в том числе ProGuard, которая также предлагает функции оптимизации кода. Для всех приложений, использующих лицензирование Google Play, настоятельно рекомендуется использовать ProGuard или аналогичную программу для обфускации кода.
Публикация лицензированного приложения
После завершения тестирования вы можете опубликовать приложение в Google Play. Выполните стандартные действия, чтобы подготовить, подписать и опубликовать приложение.
Где получить поддержку
Если у вас возникли вопросы или проблемы при реализации или развертывании публикации в приложениях, воспользуйтесь ресурсами поддержки, перечисленными в таблице ниже. Если вы зададите вопрос на правильном форуме, то быстрее получите нужную помощь.
Таблица 2. Ресурсы службы поддержки разработчиков для сервиса лицензирования Google Play.
| Тип поддержки | Ресурс | Разнообразие тем |
|---|---|---|
| Вопросы, связанные с разработкой и тестированием | Группы Google: android-developers | Скачивание и интеграция LVL, проекты библиотек, Policy
вопросы, идеи по улучшению пользовательского опыта, обработка ответов, Obfuscator, IPC, настройка тестовой среды |
| Stack Overflow: http://stackoverflow.com/questions/tagged/android | ||
| Проблемы с аккаунтами, публикацией и развертыванием | Справочный форум Google Play | Аккаунты издателей, пара лицензионных ключей, тестовые аккаунты, ответы сервера, тестовые ответы, развертывание приложения и результаты |
| Часто задаваемые вопросы о поддержке лицензирования в Google Play | ||
| Система отслеживания ошибок LVL | Система отслеживания ошибок в проекте Marketlicensing | сообщения об ошибках и проблемах, связанных с классами исходного кода LVL и реализацией интерфейса; |
Общую информацию о том, как публиковать записи в перечисленных выше группах, можно найти в разделе Ресурсы сообщества на странице "Ресурсы службы поддержки для разработчиков".
Дополнительные ресурсы
Пример приложения, включенный в LVL, содержит полный пример того, как инициировать проверку лицензии и обработать результат в классе MainActivity.