Android-биллинг в приложении - restoreTransactionInformation

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

Мой код для обновления работает нормально.

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

При запуске приложения я проверяю флаг, установленный после первого запуска. Если этот флаг отсутствует, пользователю показывается диалоговое окно, предупреждающее его о том, что приложение проверит предыдущие покупки, и когда они нажимают кнопку ОК, вызывается метод restoreTransactionInformation. Это вызывает принудительное закрытие приложения.

Поскольку биллинг внутри приложения не работает при отладке или на эмуляторе, мне приходится публиковать подписанную версию приложения каждый раз, когда я хочу попробовать код. У меня нет никакого способа узнать, почему приложение закрывается, когда я пытаюсь сделать запрос restoreTransactionInformation. Кто-нибудь знает, как я могу его диагностировать или что может привести к смерти моего приложения? Или рабочий пример того, как использовать метод restoreTransactionInformation?

РЕДАКТИРОВАТЬ: Таким образом, похоже, что запрос RESTORE_TRANSACTIONS получает правильный ответ и возвращает детали моей тестовой покупки. К сожалению, прежде чем он сможет что-либо сделать с ним, приложение принудительно закрывается. Вот логкат (без запутанного кода) того, что происходит сразу после того, как рынок ответит на запрос RESTORE_TRANSACTIONS:

I/BillingService( 6484): confirmTransaction()
D/Finsky  ( 1884): [7] MarketBillingService.getPreferredAccount: com.hippypkg: Account from first account.
I/BillingService( 6484): current request is:**********
I/BillingService( 6484): RESTORE_TRANSACTIONS Sync Response code: RESULT_OK
D/WindowManagerImpl( 6484): finishRemoveViewLocked, mViews[0]: com.android.internal.policy.impl.PhoneWindow$DecorView@**********
W/InputManagerService( 1381): [unbindCurrentClientLocked] Disable input method client.
W/InputManagerService( 1381): [startInputLocked] Enable input method client.
D/NativeCrypto( 1884): returned from sslSelect() with result 1, error code 2
D/Finsky  ( 1884): [1] MarketBillingService.sendResponseCode: Sending response RESULT_OK for request ********** to com.hippypkg.
I/BillingService( 6484): Received action: com.android.vending.billing.PURCHASE_STATE_CHANGED
I/BillingService( 6484): purchaseStateChanged got signedData: {"nonce":**********,"orders":[{"orderId":"**********","packageName":"com.hippypkg","productId":"hippy_upgrade_free_to_full","purchaseTime":1331476540000,"purchaseState":0}]}
I/BillingService( 6484): purchaseStateChanged got signature: **********==
I/BillingService( 6484): signedData: {"nonce":**********,"orders":[{"orderId":"**********","packageName":"com.hippypkg","productId":"hippy_upgrade_free_to_full","purchaseTime":1331476540000,"purchaseState":0}]}
I/BillingService( 6484): signature: **********==
I/BillingService( 6484): confirmTransaction()
I/BillingService( 6484): makerequestbundle success
I/BillingService( 6484): putstringarray success
D/Finsky  ( 1884): [24] MarketBillingService.getPreferredAccount: com.hippypkg: Account from first account.
D/AndroidRuntime( 6484): Shutting down VM
W/dalvikvm( 6484): threadid=1: thread exiting with uncaught exception (group=0x4001d5a0)
E/AndroidRuntime( 6484): FATAL EXCEPTION: main
E/AndroidRuntime( 6484): java.lang.RuntimeException: Unable to start receiver com.hippypkg.BillingReceiver: java.lang.NullPointerException
E/AndroidRuntime( 6484):    at android.app.ActivityThread.handleReceiver(ActivityThread.java:2144)
E/AndroidRuntime( 6484):    at android.app.ActivityThread.access$2400(ActivityThread.java:135)
E/AndroidRuntime( 6484):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1114)
E/AndroidRuntime( 6484):    at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime( 6484):    at android.os.Looper.loop(Looper.java:150)
E/AndroidRuntime( 6484):    at android.app.ActivityThread.main(ActivityThread.java:4385)
E/AndroidRuntime( 6484):    at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime( 6484):    at java.lang.reflect.Method.invoke(Method.java:507)
E/AndroidRuntime( 6484):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:849)
E/AndroidRuntime( 6484):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:607)
E/AndroidRuntime( 6484):    at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime( 6484): Caused by: java.lang.NullPointerException
E/AndroidRuntime( 6484):    at android.os.Parcel.readException(Parcel.java:1328)
E/AndroidRuntime( 6484):    at android.os.Parcel.readException(Parcel.java:1276)
E/AndroidRuntime( 6484):    at com.android.vending.billing.IMarketBillingService$Stub$Proxy.sendBillingRequest(IMarketBillingService.java:100)
E/AndroidRuntime( 6484):    at com.hippypkg.BillingHelper.confirmTransaction(BillingHelper.java:152)
E/AndroidRuntime( 6484):    at com.hippypkg.BillingHelper.verifyPurchase(BillingHelper.java:250)
E/AndroidRuntime( 6484):    at com.hippypkg.BillingReceiver.purchaseStateChanged(BillingReceiver.java:41)
E/AndroidRuntime( 6484):    at com.hippypkg.BillingReceiver.onReceive(BillingReceiver.java:23)
E/AndroidRuntime( 6484):    at android.app.ActivityThread.handleReceiver(ActivityThread.java:2103)
E/AndroidRuntime( 6484):    ... 10 more
W/ActivityManager( 1381):   Force finishing activity com.hippypkg/.Hippy

3 ответа

Решение

Так что мне наконец удалось это выяснить.

Если вы посмотрите на Google Docs для обзора биллинга в приложении, в нем говорится:

Тип запроса RESTORE_TRANSACTIONS также инициирует широковещательное намерение PURCHASE_STATE_CHANGED, которое содержит информацию о транзакции того же типа, которая отправляется во время запроса на покупку, хотя вам не нужно отвечать на это намерение сообщением CONFIRM_NOTIFICATIONS.

В обычном цикле покупки-подтверждения транзакции, когда вы запрашиваете покупку продукта In App Billing, Google отправляет обратно JSON с несколькими полями. Одним из этих полей является "tification_id ". Когда Google отправляет намерение PURCHASE_STATE_CHANGED, оно ожидает ответа CONFIRM_NOTIFICATIONS от приложения с пакетом, содержащим набор информации, в том числе Notification_id. Все хорошо здесь.

Проблема начинается, когда вы получаете PURCHASE_STATE_CHANGED от Google для запроса RESTORE_TRANSACTIONS из приложения. Этот JSON не содержит полей notificaion_id. Библиотека по-прежнему отвечает сообщением CONFIRM_NOTIFICATIONS, добавляя массив package_id в пакет, который в данном случае равен нулю. Вот что вызывает исключение NullPointerException.

Решение: я изменил класс BillingHelper.java, добавив логическое значение для отслеживания того, когда пользователь совершает обычную покупку и когда он хочет восстановить транзакции. Если это запрос restoreTransactions, я отправляю сообщение обратно обработчику и пропускаю шаг Подтверждения.

РЕДАКТИРОВАТЬ: код для вышеупомянутого исправления находится в BillingHelper.java. Я использую логический флаг, чтобы отслеживать, сделал ли пользователь вызов RESTORE_TRANSACTIONS (isRestoreTransactions).

В методе verifyPurchase BillingHelper.java я изменил код следующим образом:

protected static void verifyPurchase(String signedData, String signature) {
        ArrayList<VerifiedPurchase> purchases = BillingSecurity.verifyPurchase(signedData, signature);

        if(isRestoreTransaction)
        {
            /*
            *
            *Add some logic to retrieve the restored purchase product ID's from the 'purchases' array
            *
            */

            //Set the boolean to false
            isRestoreTranscation = false;

            //Send a message to the handler, informing it that purchases were restored
            if(mCompletedHandler != null){
                mCompletedHandler.sendEmptyMessage(0);
            } else {
                Log.e(TAG, "verifyPurchase error. Handler not instantiated. Have you called setCompletedHandler()?");
            }
        }
        else
        {
            /*
            *......
            *......
            *......
            *Original method body here
            *......
            *......
            *......
            */
        }
    }

Вы не можете сделать различие между "переустановкой" и "очищенными данными приложения". По сути, они одинаковы: общие настройки пусты. И вам не нужно.

Что касается диагностики проблемы, поместите кнопку "Восстановить транзакции" и просто нажмите ее в разных состояниях (только что установлены, установленные флажки и т. Д.). Тогда смотрите logcat.

Кстати, может быть, лучше сначала придерживаться исходного кода Google, в этом случае вы получите больше помощи. В Google Code также есть несколько проектов, которые обертывают код IAB, чтобы его было немного проще интегрировать.

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

Просто взгляните на метод restoreDatabase() в образце Dungeons, он делает то, что вам нужно, проверяет, используя SharedPreferences, если это первый запуск и вызывает ли он метод restoreTransactions.

Для отладки просто подключите ваше устройство к затмению и проверьте logcat, просто не забудьте установить для константы Debug (в Consts.java) значение true, а в манифесте также установите для тега debuggable значение true.

Чтобы лучше понять пример кода, я добавил намного больше отладки, и теперь он работает.

Другие вопросы по тегам