Die neue Dokumentation und der Hilfscode für Android Billing v3 verwenden startIntentSenderForResult()
, wenn Sie einen Kaufablauf starten. Ich möchte einen Kaufablauf starten (und das Ergebnis erhalten) von einer Fragment
.
Zum Beispiel schlägt die Dokumentation den Aufruf vor
startIntentSenderForResult(pendingIntent.getIntentSender(),
1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0));
und der Hilfscode Anrufe
mHelper.launchPurchaseFlow(this, SKU_GAS, 10001,
mPurchaseFinishedListener, "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
was startIntentSenderForResult()
aufruft.
Das Problem ist, dass der Aufruf von startIntentSenderForResult()
bewirkt, dass onActivityResult()
für die übergeordnete Activity
und nicht für die Fragment
aufgerufen wird, von der sie aufgerufen wurde (wo sich die IabHelper
befindet).
Ich könnte onActivityResult()
in der übergeordneten Activity
empfangen und dann manuell onActivityResult()
für die Fragment
aufrufen, aber gibt es eine Möglichkeit, startIntentSenderForResult()
von einer Fragment
aus aufzurufen, die das Ergebnis direkt an onActivityResult()
der Fragment
zurückgibt?
Ich schlage zwei Lösungen vor:
1.) Setzen Sie den IabHelper mHelper auf die Aktivität und rufen Sie den IabHelper aus dem Fragment auf.
So etwas wie:
Um diese Lösung zu verwenden, deklarieren Sie IabHelper in der Aktivität als öffentlich und verwenden Sie eine Methode, um den Launcher vom Fragment aus aufzurufen.
public class MyActivity extends Activity{
public IabHelper mHelper
public purchaseLauncher(){
mHelper.launchPurchaseFlow(this, SKU_GAS, 10001,
mPurchaseFinishedListener, "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");
}
/*The finished, query and consume listeners should also be implemented in here*/
}
public class FragmentActivity extends Fragment{
MyActivity myAct = (MyActivity) getActivity();
myAct.purchaseLauncher();
}
2.) Rufen Sie in onActivityResult das entsprechende Fragment auf, das das IabHelper-Objekt enthält. Ein geeignetes Fragment kann eine Zugriffsmethode auf das Hilfsobjekt haben.
protected void onActivityResult(int requestCode, int resultCode,Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag("YourTag");
if (fragment != null)
{
((MyFragmentWithIabHelper)fragment).onActivityResult(requestCode, resultCode,data);
}
}
1) Sie sollten Ihren resultCode (RC_REQUEST) ändern, um ihn mit einem Fragmentindex zu versehen.
int rc_reqest = RC_REQUEST + ((getActivity().getSupportFragmentManager().getFragments().indexOf(this)+1)<<16) ;
mHelper.launchPurchaseFlow(getActivity(), sku, rc_reqest ,mPurchaseFinishedListener, payload);
2) in IabHelper.launchPurchaseFlow (...)
change mRequestCode = requestCode
zu
mRequestCode = requestCode&0xffff;
In Bezug auf die sehr hilfreiche zweite Lösung von LEO:
Wenn Google das Problem mit startIntentSenderForResult behebt und den onActivityResult-Aufruf jetzt korrekt an das Fragment weiterleitet, sollte diese Lösung zukunftssicher sein, damit Das onActivityResult des Fragments nicht zweimal aufgerufen wird.
Ich möchte die folgende von LEO vorgeschlagene modifizierte Lösung vorschlagen.
In der übergeordneten Activity-Implementierung des Fragments:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
boolean handled = false;
// The following is a hack to ensure that the InAppPurchasesFragment receives
// its onActivityResult call.
//
// For more information on this issue, read here:
//
// http://stackoverflow.com/questions/14131171/calling-startintentsenderforresult-from-fragment-Android-billing-v3
//
// Note: If Google ever fixes the issue with startIntentSenderForResult() and
// starts forwarding on the onActivityResult to the fragment automatically, we
// should future-proof this code so it will still work.
//
// If we don't do anything and always call super.onActivityResult, we risk
// having the billing fragment's onActivityResult called more than once for
// the same result.
//
// To accomplish this, we create a method called checkIabHelperHandleActivityResult
// in the billing fragment that returns a boolean indicating whether the result was
// handled or not. We would just call Fragment's onActivityResult method, except
// its return value is void.
//
// Then call this new method in the billing fragment here and only call
// super.onActivityResult if the billing fragment didn't handle it.
if (inAppPurchasesFragment != null)
{
handled = inAppPurchasesFragment.checkIabHelperHandleActivityResult(requestCode, resultCode, data);
}
if (!handled)
{
super.onActivityResult(requestCode, resultCode, data);
}
}
Dann in der Implementierung Ihres IAB-Fragments:
/**
* Allow the IabHelper to process an onActivityResult if it can
*
* @param requestCode The request code
* @param resultCode The result code
* @param data The data
*
* @return true if the IABHelper handled the result, else false
*/
public boolean checkIabHelperHandleActivityResult(int requestCode, int resultCode, Intent data)
{
return (iabHelper != null) && iabHelper.handleActivityResult(requestCode, resultCode, data);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (!checkIabHelperHandleActivityResult(requestCode, resultCode, data))
{
super.onActivityResult(requestCode, resultCode, data);
}
}
Sie müssen Fragment und Daten an die übergeordnete Aktivität übergeben und dann das Fragment onActivityResult aus der übergeordneten Aktivität aufrufen.
so was
in fragment:
HomeActivity activity = (HomeActivity) getActivity();
activity.purchaseLauncher(this, mHelper, productDTO.getSku(), RC_REQUEST, mPurchaseFinishedListener, PAYLOAD);
in übergeordneter Aktivität:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (storeFragment != null) {
storeFragment.onActivityResult(requestCode, resultCode, data);
}
}
public void purchaseLauncher(StoreFragment storeFragment, IabHelper mHelper, String sku, int requestCode, IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener, String payload) {
this.storeFragment = storeFragment;
mHelper.launchPurchaseFlow(this, sku, requestCode, mPurchaseFinishedListener, payload);
}
Ab SDK 24 und höher ist auch eine startIntentSenderForResult - Methode in support Fragment verfügbar, die wie beabsichtigt funktioniert. Beachten Sie, dass es einen zusätzlichen Bundle-Parameter gibt, der als Null übergeben werden kann. Der endgültige Code lautet also:
startIntentSenderForResult(pendingIntent.getIntentSender(),
1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
Integer.valueOf(0), null);
Natürlich müssen für API 23 und darunter die in den anderen Antworten beschriebenen Tricks verwendet werden.
Ich empfehle, eine generische Behandlung dieses Problems in Ihrer Basisaktivitätsklasse zu erstellen, wenn Sie darauf Zugriff haben.
Zum Beispiel:
public abstract class BaseActivity extends Activity {
private List<ActivityResultHandler> mResultHandlers
= new ArrayList<ActivityResultHandler>();
public void registerActivityResultHandler(ActivityResultHandler resultHandler) {
mResultHandlers.add(resultHandler);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
for (ActivityResultHandler resultHandler : mResultHandlers) {
resultHandler.handle();
}
}
}
Natürlich müssen Sie die ActivityResultHandler-Schnittstelle durch Ihre Fragmente implementieren und beim Aktivitätsstart registrieren.
Edit: Android.support.v4.app.Fragment
enthält jetzt eine abwärtskompatible Version von startIntentSenderForResult()
, daher ist diese Antwort veraltet.
Alte Antwort:
Ab der Unterstützungsbibliothek 23.2.0 funktioniert das Ändern von requestCode
nicht mehr: FragmentActivity
verfolgt jetzt die Anforderungen, die von den Fragmenten stammen. Ich habe diese Methode der FragmentActivity
hinzugefügt, die die Fragment
hostete (Code basierend auf FragmentActivity.startActivityFromFragment(Fragment, Intent, int, Bundle)
):
public void startIntentSenderFromFragment(Fragment fragment, IntentSender intent, int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException {
if (requestCode == -1) {
startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, flagsValues, extraFlags);
return;
}
if ((requestCode & 0xffff0000) != 0) {
throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
}
try {
Method method = FragmentActivity.class.getDeclaredMethod("allocateRequestIndex", Fragment.class);
method.setAccessible(true);
int requestIndex = (int) method.invoke(this, fragment);
startIntentSenderForResult(intent, ((requestIndex + 1) << 16) + (requestCode & 0xffff), fillInIntent, flagsMask, flagsValues, extraFlags);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Beim Aufruf erhält nur die übergebene Fragment
den Aufruf von onActivityResult()
.
if (requestCode == RC_REQUEST)
{
Intent intent = new Intent(ContainerAvtivity.this,ContainerAvtivity.class);
startActivity(intent);
finish();
}
RC_REQUEST
ist derselbe wie beim Starten des Einkaufsablaufs
Fügen Sie dies in der Variable onActivityResult
Ihrer Aktivität hinzu. Der Inventarlistenersteller wird das gewünschte Ergebnis für Sie produzieren. (Ich weiß, dass es ein temporärer Fix ist, aber für mich gearbeitet hat)).
wenn Sie einen Rückruf für Ihr Fragment erhalten möchten, rufen Sie super.onActivityResult()
aus Ihrer Aktivität auf.
Dies wird Ihre Fragmente onActivityResult()
nennen.
Vergessen Sie nicht, startIntentSenderForResult
aus Ihrem Fragment-Kontext aufzurufen.
Verwenden Sie keinen Aktivitätskontext getActivity().startIntentSenderForResult
Sie müssen anrufen
super.onActivityResult(requestCode, resultCode, data);
onActivityResult zu Beginn Ihrer Aktivität und Ihres Fragments, um die Ergebnisse mit den Fragmenten zu kaskadieren.
In meiner FragmentActivity liest sich dies als
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// No action here, call super to delegate to Fragments
super.onActivityResult(requestCode, resultCode, data);
}
In meinem Fall habe ich onActivityResult in Activity gemacht:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
}
else {
Log.d(TAG, "onActivityResult handled by IABUtil.");
}
}
und das gleiche in fragment und es macht in app abrechnung funktioniert
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
}
else {
Log.d(ITEM_SKU, "onActivityResult handled by IABUtil.");
}
}