Несмотря на то, что эмулятор, поставляемый в составе инструментальной среды Marmalade достаточно удобен и позволяет выполнить отладку всех мыслимых и немыслимых ситуаций, существует ряд задач с которыми он справиться не может. К таким задачам относится, например, отладка покупки продуктов из разрабатываемого приложения (через App Store или Android Market). Тестирование самой покупки обязательно должно выполняться на устройстве, но, часто бывает, что с покупкой связана логика приложения, которую было бы неплохо отладить под эмулятором. О том, как это сделать, ниже:
В качестве примера, рассмотрим приложение s3e/s3eIOSAppStoreBilling, поставляемое в составе examples к Marmalade. Если его запустить, то все что нам удастся увидеть — это модальное окно, с сообщением об ошибке «This example only works on iPhone with a test store set up for in-app purchase». Это конечно правильно, но нам то хочется отладить приложение до того, как мы его разместим на IPhone. Сделать это совсем не сложно. Мы разработаем заглушку, имитирующую (в отладочной сборке) поведение S3E iOS App Store Billing. Начнем с добавления наших файлов в mkb-файл:
#!/usr/bin/env mkb
files
{
s3eIOSAppStoreBilling.cpp
AppStoreStub.h
AppStoreStub.cpp
}
...
Сам код заглушки примитивен. В h-файле для каждой функции App Store Billing мы доопределяем заглушку с суффиксом «Stub» и в случае если сборка отладочная, подменяем вызовы директивой #define:
#ifndef _APPSTORESTUB_H_
#define _APPSTORESTUB_H_
#include <string.h>
#include "s3eTypes.h"
#include "s3eIOSAppStoreBilling.h"
#if defined IW_DEBUG
#define s3eIOSAppStoreBillingAvailable
s3eIOSAppStoreBillingAvailableStub
#define s3eIOSAppStoreBillingGetInt
s3eIOSAppStoreBillingGetIntStub
#define s3eIOSAppStoreBillingInit
s3eIOSAppStoreBillingInitStub
#define s3eIOSAppStoreBillingStart
s3eIOSAppStoreBillingInitStub
#define s3eIOSAppStoreBillingTerminate
s3eIOSAppStoreBillingTerminateStub
#define s3eIOSAppStoreBillingStop
s3eIOSAppStoreBillingTerminateStub
#define s3eIOSAppStoreBillingRequestProductInformation
s3eIOSAppStoreBillingRequestProductInformationStub
#define s3eIOSAppStoreBillingCancelProductInformationRequests
s3eIOSAppStoreBillingCancelProductInformationRequestsStub
#define s3eIOSAppStoreBillingRequestPayment
s3eIOSAppStoreBillingRequestPaymentStub
#define s3eIOSAppStoreBillingCompleteTransaction
s3eIOSAppStoreBillingCompleteTransactionStub
#define s3eIOSAppStoreBillingRestoreCompletedTransactions
s3eIOSAppStoreBillingRestoreCompletedTransactionsStub
#endif
s3eBool s3eIOSAppStoreBillingAvailableStub();
int32 s3eIOSAppStoreBillingGetIntStub(s3eIOSAppStoreBillingProperty property);
s3eResult s3eIOSAppStoreBillingInitStub(s3eProductInformationCallbackFn infoCallback,
s3ePaymentTransactionUpdateCallbackFn updateCallback,
void* userData);
void s3eIOSAppStoreBillingTerminateStub();
s3eResult s3eIOSAppStoreBillingRequestProductInformationStub(const char** productIdentifiers,
uint32 numProductIdentifiers);
void s3eIOSAppStoreBillingCancelProductInformationRequestsStub();
s3eResult s3eIOSAppStoreBillingRequestPaymentStub(s3ePaymentRequest* paymentRequest);
s3eResult s3eIOSAppStoreBillingCompleteTransactionStub(s3ePaymentTransaction* transaction,
s3eBool finalise);
s3eResult s3eIOSAppStoreBillingRestoreCompletedTransactionsStub();
void appStoreStubUpdate();
#endif // _APPSTORESTUB_H_
Реализация этих функций также не содержит ничего сверхестественного:
#include "AppStoreStub.h"
#define PRODUCT_INFO_REQUESTED 0x0001
#define TRANSACTION_REQUESTED 0x0002
s3eProductInformationCallbackFn productInfoCallback = NULL;
s3ePaymentTransactionUpdateCallbackFn transactionCallback = NULL;
void* billingData = NULL;
s3eProductInformation productInformation;
s3ePaymentTransaction transaction;
s3eTransactionReceipt receipt;
int eventMask = 0;
s3eBool s3eIOSAppStoreBillingAvailableStub() {
return S3E_TRUE;
}
int32 s3eIOSAppStoreBillingGetIntStub(s3eIOSAppStoreBillingProperty property) {
switch (property) {
case S3E_IOSAPPSTOREBILLING_CAN_MAKE_PAYMENTS:
return 1;
default:
return 0;
}
}
s3eResult s3eIOSAppStoreBillingInitStub(s3eProductInformationCallbackFn infoCallback,
s3ePaymentTransactionUpdateCallbackFn updateCallback,
void* userData) {
productInfoCallback = infoCallback;
transactionCallback = updateCallback;
billingData = userData;
return S3E_RESULT_SUCCESS;
}
void s3eIOSAppStoreBillingTerminateStub() {}
s3eResult s3eIOSAppStoreBillingRequestProductInformationStub(const char** productIdentifiers,
uint32 numProductIdentifiers) {
if (numProductIdentifiers != 1) return S3E_RESULT_ERROR;
memset(&productInformation, 0, sizeof(productInformation));
strcpy(productInformation.m_ProductID, *productIdentifiers);
strcpy(productInformation.m_LocalisedTitle, *productIdentifiers);
strcpy(productInformation.m_LocalisedDescription, *productIdentifiers);
strcpy(productInformation.m_FormattedPrice, "0.00");
strcpy(productInformation.m_PriceLocale, "0.00");
productInformation.m_Price = 0;
productInformation.m_ProductStoreStatus = S3E_PRODUCT_STORE_STATUS_VALID;
eventMask |= PRODUCT_INFO_REQUESTED;
return S3E_RESULT_SUCCESS;
}
void s3eIOSAppStoreBillingCancelProductInformationRequestsStub() {}
s3eResult s3eIOSAppStoreBillingRequestPaymentStub(s3ePaymentRequest* paymentRequest) {
memset(&transaction, 0, sizeof(transaction));
transaction.m_TransactionStatus = S3E_PAYMENT_STATUS_PURCHASED;
transaction.m_Request = paymentRequest;
transaction.m_Retain = S3E_FALSE;
transaction.m_TransactionReceipt = &receipt;
strcpy(transaction.m_TransactionID, "1");
strcpy(transaction.m_OriginalTransactionID, "1");
memset(&receipt, 0, sizeof(receipt));
receipt.m_ReceiptSize = 0;
eventMask |= TRANSACTION_REQUESTED;
return S3E_RESULT_ERROR; // ???
}
s3eResult s3eIOSAppStoreBillingCompleteTransactionStub(s3ePaymentTransaction* transaction,
s3eBool finalise) {
return S3E_RESULT_SUCCESS;
}
s3eResult s3eIOSAppStoreBillingRestoreCompletedTransactionsStub() {
return S3E_RESULT_SUCCESS;
}
void appStoreStubUpdate() {
#if defined IW_DEBUG
if (eventMask != 0) {
if (eventMask & PRODUCT_INFO_REQUESTED) {
productInfoCallback(&productInformation, billingData);
}
if (eventMask & TRANSACTION_REQUESTED) {
transactionCallback(&transaction, billingData);
}
eventMask = 0;
}
#endif
}
Здесь следует упомянуть о функции appStoreStubUpdate. Дело в том, что App Store Billing API использует асинхронные вызовы для работы с магазином. Это означает, что вызовы callback функций должны выполняться вне контекста вызова функций запроса информации о продукте s3eIOSAppStoreBillingRequestProductInformation и совершения покупки s3eIOSAppStoreBillingRequestPayment. Эти вызовы мы будем выполнять в appStoreStubUpdate, вызывая последнюю из update-функции Maramalade-приложения.
Также, может вызывать вопросы код возврата s3eIOSAppStoreBillingRequestPaymentStub. Честно говоря, мне самому непонятен этот момент. По логике вещей, при успешном выполнении она должна возвращать S3E_RESULT_SUCCESS (равное 0), но в примере используется следующая проверка корректности формирования запроса:
if (s3eIOSAppStoreBillingRequestPayment(&g_PaymentRequest))
SetStatus("Purchasing %s...", g_ProductID2);
else
SetStatus("Purchasing %s FAILED", g_ProductID2);
То есть, ожидается ненулевое значение. По всей видимости, разработчики примера допустили ошибку, но на этот момент стоит обратить внимание, при отладке приложения на iPhone.
Последнее, о чем следует упомянуть, это чек. В s3eIOSAppStoreBillingRequestPaymentStub мы формируем пустой чек:
...
memset(&receipt, 0, sizeof(receipt));
receipt.m_ReceiptSize = 0;
...
Но это, возможно, не вполне корректно, если приложение должно верифицировать чек или использовать какую-то информацию, содержащуюся в нем (например дату покупки). О том как строится корректный чек можно прочитать здесь. Здесь описано как выполнить верификацию чека.
Осталось совсем немного. В код примера мы добавляем #include AppStoreStub.h (он обязательно должен идти последним) и вызов appStoreStubUpdate, для эмуляции асинхронных вызовов:
bool ExampleUpdate()
{
appStoreStubUpdate();
...
Скомпилировав пример и запустив его под отладчиком, мы можем убедиться, что теперь мы можем запрашивать информацию о продукте и совершать покупки, совершенно не беспокоя при этом App Store.
Автор: GlukKazan