从片段调用startIntentSenderForResult(Android计费v3)

从片段调用startIntentSenderForResult(Android计费v3),android,android-fragments,android-activity,android-billing,Android,Android Fragments,Android Activity,Android Billing,新的Android Billing v3文档和帮助程序代码在启动购买流时使用startinentsenderforresult()。我想从片段启动一个购买流(并接收结果) 例如,建议打电话 startIntentSenderForResult(pendingIntent.getIntentSender(), 1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0)); 电话呢 它

新的Android Billing v3文档和帮助程序代码在启动购买流时使用
startinentsenderforresult()
。我想从
片段
启动一个购买流(并接收结果)

例如,建议打电话

startIntentSenderForResult(pendingIntent.getIntentSender(),
    1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
    Integer.valueOf(0));
电话呢

它调用
startinentsenderforresult()

问题是,调用
startinentsenderforresult()
会导致在父
活动
上调用
onActivityResult()
,而不是在调用它的
片段
上(在
IabHelper
所在的位置)


我可以在父级
活动
中接收
onActivityResult()
,然后手动调用
片段
上的
onActivityResult()
,但是有没有办法调用
startinentsenderforresult()呢
从一个
片段
直接返回结果到该
片段的
onActivityResult()

我建议两种解决方案:

1.)将IabHelper mHelper放在活动上,并从片段调用IabHelper

比如:

要使用此解决方案,请在活动中将IABHeloper声明为public,并使用方法从片段调用启动器

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.)在onActivityResult中,调用包含IabHelper对象的适当片段。适当的片段可以具有对helper对象的访问方法

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);
    } 
}
你需要打电话

super.onActivityResult(requestCode, resultCode, data);
在活动和片段开始时,onActivityResult将结果级联到片段

在我的零碎活动中,这读作

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // No action here, call super to delegate to Fragments
    super.onActivityResult(requestCode, resultCode, data);
}
1) 您应该修改resultCode(RC_请求)以将片段索引放入其中

int rc_reqest = RC_REQUEST +  ((getActivity().getSupportFragmentManager().getFragments().indexOf(this)+1)<<16)  ;      
mHelper.launchPurchaseFlow(getActivity(), sku, rc_reqest ,mPurchaseFinishedListener, payload);


在我的案例中,我在活动中做了onActivityResult:

@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.");
    }

}
在片段中也是如此,这使得应用内计费工作正常

@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.");
    }

}

如果您有权访问基本活动类,我建议您在基本活动类中创建某种类型的通用处理方法

例如:

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();
        }
    }
}
公共抽象类BaseActivity扩展活动{
私有列表mResultHandlers
=新的ArrayList();
公共无效注册表ActivityResultHandler(ActivityResultHandler结果Handler){
mResultHandlers.add(resultHandler);
}
@凌驾
受保护的void onActivityResult(int请求代码、int结果代码、意图数据){
super.onActivityResult(请求代码、结果代码、数据);
对于(ActivityResultHandler结果处理程序:mresultHandler){
resultHandler.handle();
}
}
}

当然,您需要通过片段实现ActivityResultHandler接口,并在activity启动时注册它们。

关于上面LEO非常有用的第二个解决方案:

如果谷歌曾经用startinentsenderforresult解决了这个问题,现在它正确地将onActivityResult调用路由到片段,那么这个解决方案应该是经过未来验证的,以便 片段的onActivityResult不会被调用两次

我想提出以下由LEO提出的改进方案

在片段的父活动实现中:

@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);
    }
}
/**
 * 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);
    }
}
然后在IAB片段的实现中:

@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);
    }
}
/**
 * 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);
    }
}
RC\u请求
与您用于启动采购流程的请求相同


将此添加到活动的
onActivityResult
中。清单侦听器将为您生成所需的结果。(我知道这是一个临时修复,但对我有效)。

编辑:
android.support.v4.app.Fragment
现在包含一个向后兼容的
StartEntSenderForresult()
,所以这个答案已经过时了

旧答案:

从support library 23.2.0开始,修改
requestCode
不再有效:
FragmentActivity
现在跟踪其片段发出的请求。我将此方法添加到承载
片段的
FragmentActivity
(代码基于
FragmentActivity.startActivityFromFragment(片段、意图、int、Bundle)
):

public void startinentsenderFromfragment(Fragment Fragment,IntentSender intent,int requestCode,@Nullable intent fillinitent,int flagsMask,int flagsvalue,int extraFlags)抛出IntentSender.SendIntentException{
如果(请求代码==-1){
startIntentSenderForResult(意图、请求代码、填充内容、flagsMask、FlagsValue、extraFlags);
返回;
}
如果((请求代码&0xffff0000)!=0){
抛出新的IllegalArgumentException(“只能对requestCode使用较低的16位”);
}
试一试{
方法Method=FragmentActivity.class.getDeclaredMethod(“allocateRequestIndex”,Fragment.class);
方法setAccessible(true);
int requestIndex=(int)method.invoke(这个,片段);

startinentsenderforresult(intent,((requestIndex+1)来自SDK 24和更高版本,支持片段中也提供了一个方法,它可以按预期工作。 请注意,还有一个附加的Bundle参数,可以作为null传递。因此,最终代码为:

startIntentSenderForResult(pendingIntent.getIntentSender(),
    1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
    Integer.valueOf(0), null);

当然,对于API 23及以下版本,我们仍然需要使用其他答案中描述的技巧。

如果您想在片段上获得回调,而不是从活动中调用super.onActivityResult()

这将调用您的片段
onActivityResult()

不要忘记从片段上下文调用
startintentssenderforresult


不要使用活动上下文
getActivity()。startIntentSenderForResult

您需要将片段和数据传递给父活动,然后从父活动中的onActivityResult调用片段

像这样

片段:

HomeActivity=(HomeActivity)getActivity();

activity.purchaseLauncher(this,mHelper,productDTO.getSku(),RC_请求,mppurchaseFinishedListener,PAYLOAD);
我目前正在使用第二种方法,ho
if (requestCode == RC_REQUEST) 
{
    Intent intent = new Intent(ContainerAvtivity.this,ContainerAvtivity.class);
    startActivity(intent);
    finish();
}
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);
    }
}
startIntentSenderForResult(pendingIntent.getIntentSender(),
    1001, new Intent(), Integer.valueOf(0), Integer.valueOf(0),
    Integer.valueOf(0), null);