С 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 комментарий:
Спасибо, очень полезная статья
Отправить комментарий