С free и premium разобрались, теперь усложним задачу. Пусть наше приложение распространяется на нескольких площадках (Google Play, Samsung Apps и Amazon) и в нём есть встроенные покупки. Хорошо бы также сделать, чтобы библиотеки подключались только для нужной сборки, то есть чтобы версия для Google Play не тянула с собой логику самсунга. Ну и конечно, для каждой площадки мы должны собирать и free, и premium.
Страшно?
А всего-то что надо сделать:
- Включение/отключение библиотек
- Добавление в разные сборки специфической логики
- Как-то сочетать несколько flavors
Кому лень читать буквы, пожалуйте сразу в код.
Включение зависимостей в разные вариации
Для начала добавим в билд-скрипт несколько вариаций:
app/build.gradle
productFlavors { premium { applicationId "com.demos.productflavors.premium" } free { applicationId "com.demos.productflavors" }google { } samsung { } amazon { }}
Версии для Google Play дополнительных библиотек не требуется. Для самсунговского биллинга нужно подключить дополнительный проект iap3Helper
, а для амазоновского — добавить jar-библиотеку. Делается это так:
app/build.gradle
dependencies { compile fileTree(dir: 'libs/common', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.0' compile 'com.android.support:design:23.1.0'amazonCompile fileTree(dir: 'libs/amazon', include: ['*.jar']) samsungCompile project(':iap3Helper')}
Таким образом, специфические либы попадут только в те вариации, в которых нужны. Подключение библиотек из репозиториев работает точно так же. Также стоит обратить внимание на организацию папки libs
: в ней есть отдельная директория common
, где лежат либы для всех вариаций (в моей демке, правда, там пусто, но вдруг понадобится), и amazon
, где всё только для амазона.
Добавление в разные сборки специфической логики
Организация проекта получается примерно такой (названия пакетов для краткости опущены):
/src /main /java MainActivity.java PurchaseFlow.java IabListener.java /res AndroidManifest.xml /amazon /java AmazonPurchaseFlow.java PurchaseFlowFactory.java /res AndroidManifest.xml /google /java GooglePurchaseFlow.java PurchaseFlowFactory.java /res AndroidManifest.xml /samsung /java SamsungPurchaseFlow.java PurchaseFlowFactory.java /res AndroidManifest.xml
В main
мы определяем базовый класс PurchaseFlow
с методом purchase
. В каждую вариацию добавляем по наследнику этого класса и реализуем там логика покупки. Также во всех вариациях присутствует по отдельному классу PurchaseFlowFactory
с примерно таким содержанием:
amazon/PurchaseFlowFactory.java
public class PurchaseFlowFactory { public static PurchaseFlow createPurchaseFlow(Activity activity, IabListener iabListener) { return new AmazonPurchaseFlow(activity, iabListener); } }
(В main
такой класс не нужен. Во-первых, компилятор выдаст ошибку Duplicate class, а во-вторых и писать там особенно нечего)
Работают ин-аппы по-разному, у всех у них свой жизненный цикл, инициализация и коллбэки. Это нужно унифицировать, поэтому в PurchaseFlow
появляются всякие onActivityResult
и onActivityCreate
. Также нужен и универсальный коллбэк, для этого в main
определён IabListener
. Не буду вдаваться в подробности архитектуры, она у каждого своя. Главное, что мы должным образом организовали код, и теперь можем в MainActivity
написать примерно следующее:
MainActivity.java
public class MainActivity extends AppCompatActivity { private PurchaseFlow mPurchaseFlow; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPurchaseFlow = PurchaseFlowFactory.createPurchaseFlow(this, mIabListener); mPurchaseFlow.onActivityCreate(this); … } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (mPurchaseFlow != null) { mPurchaseFlow.onActivityResult(requestCode, resultCode, data); } } public void startPurchaseFlow(View view) { mPurchaseFlow.purchase(getString(R.string.sku)); } private IabListener mIabListener = new IabListener() { @Override public void onServiceUnavailable() { DPLog.w("InApp billing service unavailable"); } @Override public void onPurchaseFinished() { DPLog.d("Purchase finished"); Toast.makeText(MainActivity.this, "Purchase finished", Toast.LENGTH_SHORT).show(); } @Override public void onPurchaseFailed() { DPLog.e("Purchase failed"); Toast.makeText(MainActivity.this, "Purchase failed", Toast.LENGTH_SHORT).show(); } }; }
При сборке вариации подхватится определённый именно для неё PurchaseFlowFactory
, так что при исполнении кода отработает нужная логика ин-аппа.
Дополнение манифеста
Для корректной работы встроенной покупки зачастую надо добавить приложению то разрешение, то какой-то компонент из библиотеки (активность, ресивер или сервис). Полностью дублировать манифест в каждой вариации не нужно, достаточно описать недостающие части. При сборке манифесты смержатся, и получится то, что надо. Например, версии для Google Play не нужно ничего, кроме дополнительного разрешения, и её манифест будет выглядеть всего лишь так:
google/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="com.android.vending.BILLING" /> </manifest>
Сочетания вариаций
А ведь кроме google/amazon/samsung у нас есть вариации free и premium. При нынешней конфигурации билд-скрипта мы можем собрать 10 сборок (дебажная и релизная для каждой из пяти вариаций), а надо бы 12 (googleFreeDebug, amazonPremiumRelease и т.д.)
На этот случай в билд-скрипте предусмотрена штука под названием flavor dimensions. По смыслу это что-то вроде «типа вариации». Мы должны определить их список, а потом указать для каждой вариации её тип. Получается примерно так:
app/build.gradle
android { … flavorDimensions "version", "inapp" productFlavors { premium { applicationId "com.demos.productflavors.premium" dimension "version" } free { applicationId "com.demos.productflavors" dimension "version" } google { dimension "inapp" } samsung { dimension "inapp" } amazon { dimension "inapp" } } }
Теперь билд-скрипт способен собрать нужные нам 12 сборок.
Application Id для сочетания вариаций
При нынешней конфигурации билда у всех free-версий айдишник будет com.demos.productflavors
, а у всех premium — com.demos.productflavors.premium
. Бывает, что этого недостаточно, и хочется сделать разные applicationId ещё и для каждого стора. Стандартных средств для этого нет, но можно написать скрипт:
app/build.gradle
def baseAppId = "com.demos.productflavors" android { … flavorDimensions "version", "inapp" productFlavors { … }applicationVariants.all { variant -> def mergedFlavor = variant.mergedFlavor def flavors = variant.productFlavors def appId = baseAppId for (int i = 0; i < flavors.size(); i++) { appId += "." appId += flavors[i].name } mergedFlavor.setApplicationId(appId); }}
Как несложно догадаться, этот скрипт просто добавляет в application id названия вариаций через точку. В итоге у нас получатся айдишники вида com.demos.productflavors.free.samsung
. Порядок следования названий вариаций зависит от порядка их типов в flavorDimensions
: если его поменять, будет com.demos.productflavors.samsung.free
.
Можно написать скрипт по-другому:
applicationVariants.all { variant -> def mergedFlavor = variant.mergedFlavor switch (variant.flavorName) { case "amazonFree": mergedFlavor.setApplicationId("com.demos.productflavors.amazon.free") break case "amazonPremium": mergedFlavor.setApplicationId("com.demos.productflavors.amazon.premium") break … } }
Так будет подлиннее, но зато можно более гибко настраивать айдишники, если есть такое желание.
А как вообще собирать?
Можно просто в IDE, выбрав нужный Build Variant.
А можно из командной строки примерно так:
./gradlew assembleSamsungPremiumDebug
Очевидно, в результате получится дебажная премиальная сборка для Samsung.
Можно собирать одной командой сразу несколько apk-шек
./gradlew assembleSamsungPremiumDebug assembleSamsungPremiumRelease
А можно собрать сразу все возможные варианты сборок:
./gradlew assemble
Всё
На этом пока всё. Есть ещё интересная штука Manifest Placeholders
, я обязательно о них напишу, как только придумаю, зачем они могут быть нужны.
Исходники к обеим частям. Просьба не рассматривать их, как исчерпывающую реализацию In-App billing. Это статья про варианты сборок, а не про применение конкретных библиотек. (Честно говоря, код для покупок написан левой ногой, лишь бы запускалось что-то похожее на правду)
1 комментарий:
Спасибо, очень полезная статья
Отправить комментарий