返回顶部

海外SDK集成文档(Android版本)

(文档包括内购、广告、统计、Facebook等业务)

接入前必读

  • gradle版本必须大于等于3.2,minSdkVersion必须大于等于19,targetSdkVersion必须大于等于29;
  • 测试内购、广告、Facebook SDK时请打开VPN;
  • 所有SDK的初始化部分发布前务必改为正式环境(内购、AdMob、Adjust);
  • manifest文件务必去除所有敏感权限声明(READ_PHONE_STATE、WRITE_EXTENAL_STORAGE等);

1. Google内购接入

 内购接入必要步骤:
    1)参数准备
    2)添加SDK依赖
    3)AndroidManifest文件配置,添加必要的权限
    4)初始化,SDK所有接口调用前必须初始化成功
    5)发起内购,购买商品,处理回调
    6)验证订单,发送数据到Ars和Adjust统计后台

1.1 准备工作

运营人员需准备好计费点id。

1.2 添加SDK依赖

目前只支持通过Gradle下载SDK依赖,不支持手动集成jar。

在 Android Studio中project根目录下的build.gradle的dependencies中添加如下引用:

 allprojects {
     repositories {
         ...
         maven { url "http://mvn.yifants.cn/artifactory/fineboost" }
     }
 }

在app目录下的build.gradle的dependencies中添加如下引用:

 implementation 'com.fineboost.sdk:googleiap:3.1.3'
 implementation 'com.android.billingclient:billing:3.0.1'

 implementation 'com.fineboost.sdk:utils:1.2.1'
 implementation 'com.google.protobuf:protobuf-javalite:3.11.0'

 //以下依赖如果已经添加则不用理会,不要重复添加
 implementation 'com.google.android.gms:play-services-location:17.0.0'
 implementation 'com.google.android.gms:play-services-basement:17.0.0'

1.3 AndroidManifest文件配置

在AndroidManifest文件中添加如下权限:

 <uses-permission android:name="android.permission.INTERNET" />

1.4 接口调用

接口包括内购接口和验证订单接口。

1.4.1 初始化

在Activity中的onCreate()方法中进行初始化,代码示例如下:

 googleBillingUtil = GoogleBillingUtil.getInstance()
                  .setDebugAble(isDebugModel)//准备发布前务必设为false 
                  .setAutoVerifyPurchase(verifyPurchaseUtil) // 可选,在购买成功或失败后自动调用验证接口进行调用(2.0.0版本新增)
                  .setInAppSKUS(inAppSKUS) //设置内购计费点,传字符串数组
                  .setSubsSKUS(subsSKUS) //设置订阅计费点,传字符串数组,没有订阅可以不调用或者设置位null
                  .setAutoConsumeAsync(isAutoConsumeAsunc) //设置自动消耗,此处设置最佳实践请参考下方接口说明
                  .setOnStartSetupFinishedListener(onStartSetupFinishedListener) //设置初始化监听器
                  .setOnQueryFinishedListener(onQueryFinishedListener) //设置查询商品和订阅详情信息结果监听器
                  .setOnQueryUnConsumeOrderListener(onQueryUnConsumeOrderListener) //查询已购买的且未被消耗的商品监听器
                  .setOnPurchaseFinishedListener(onPurchaseFinishedListener) //设置内购结果监听器
                  .setOnConsumeFinishedListener(onConsumeFinishedListener) //设置商品消耗监听器
                  .setOnQueryHistoryQurchaseListener(onQueryHistoryQurchaseListener) // 查询当前Google账户在本App中对每个产品ID的最新'购买'与'订阅'的监听器
                  .build(TestActivity.this); 

  verifyPurchaseUtil = VerifyPurchaseUtil.getInstance()
                  .setMaxVerifyTime(15) // 设置等待内购验证结果回调的最大时限,单位:秒 (不设置,默认10秒)
                  .setOnVerifyPurchaseListener(onVerifyPurchaseListener) // 设置内购订单验证结果监听器
                  .build(TestActivity.this); 

初始化监听器实现

    private GoogleBillingUtil.OnStartSetupFinishedListener mOnStartSetupFinishedListener = new GoogleBillingUtil.OnStartSetupFinishedListener() {
        @Override
        public void onSetupSuccess() {
            LogUtil.d("init iap success");
        }

        @Override
        public void onSetupFail(int code) {
            /**
             * 如果code=5,请检查以下事项:
             * 1.计费点是否配置错误
             * 2.apk的包名、版本号、签名是否与后台发布的版本一致
             */
            LogUtil.d("init iap fail,code = " + code);
        }

        @Override
        public void onSetupError() {
            LogUtil.d("init iap error");
        }
    };

1.4.2 发起支付

内购

消耗型商品内购,内购结果见OnPurchaseFinishedListener:

 googleBillingUtil.purchaseInApp(TestActivity.this, skuid);//skuid为计费点Id

订阅(无订阅可不接入)

非消耗型商品订阅,订阅结果见OnPurchaseFinishedListener:

 googleBillingUtil.purchaseSubs(TestActivity.this, skuid);//skuid为计费点Id

内购结果监听器实现

    private GoogleBillingUtil.OnPurchaseFinishedListener mOnPurchaseFinishedListener = new GoogleBillingUtil.OnPurchaseFinishedListener() {
        @Override
        public void onPurchaseCompleted(int responseCode, Purchase purchase) {
            // 内购成功时回调,此处无需埋点,付费成功埋点请参考OnVerifyPurchaseListener验证订单监听器
            LogUtil.d("pay success ,sku = " + purchase.getSku());
            verifyPurchaseUtil.verifyPurchase(responseCode,purchase);
        }

        @Override
        public void onPurchasePending(int responseCode, Purchase purchase) {
            // 内购未完成,需等到用户完成付费等操作时回调
            if (!autoVerifyAble) {
                verifyPurchaseUtil.verifyPurchase(responseCode, purchase);
            }
            LogUtil.d("Purchase on Pending");
        }

        @Override
        public void onPurchaseCanceled() {
            //用户放弃购买时回调
            LogUtil.d("onPurchaseCanceled");
             //付费失败Firebase埋点,Firebase接入请参考3.2章节
            Bundle bundle = new Bundle();
            bundle.putString("product_id",currentPaurchseSKU);//传计费点名称或者Id
            bundle.putString("location","main_page");//传入发起支付时的位置或者场景
            bundle.putString("respondcode",1);//取消支付必须传1
            FirebaseAnalytics.getInstance(PayActivity.this).logEvent("iap_pay_fail",bundle);           
        }

        @Override
        public void onPurchaseFailed(int responseCode) {
            //购买时发生错误时回调(如responseCode=7 发生重复购买等)
            LogUtil.d("onPurchaseFailed,responseCode = " + responseCode);

            //付费失败Firebase埋点,Firebase接入请参考3.2章节
            Bundle bundle = new Bundle();
            bundle.putString("product_id",currentPaurchseSKU);//传计费点名称或者Id
            bundle.putString("location","main_page");//传入发起支付时的位置或者场景
            bundle.putString("respondcode",responseCode);//传返回码
            FirebaseAnalytics.getInstance(PayActivity.this).logEvent("iap_pay_fail",bundle);            
            if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Purchase purchase = hasBuyInApp(currentPaurchseSKU);
                        if (purchase != null){
                            Toast.makeText(PayActivity.this, "你已经购买了本商品,开始自动消耗 " + currentPaurchseSKU, Toast.LENGTH_LONG).show();
                            googleBillingUtil.consumeAsync(purchase.getPurchaseToken());
                        }

                    }
                });
            }
        }
        @Override
        public void onPurchaseError(String msg) {
            // 购买时发生异常时回调,如初始化失败的情况下进行购买等
            LogUtil.d("onPurchaseFailed,msg = " + msg);
            //付费失败Firebase埋点,Firebase接入请参考3.2章节
            Bundle bundle = new Bundle();
            bundle.putString("product_id",currentPaurchseSKU);//传计费点名称或者Id
            bundle.putString("location","main_page");//传入发起支付时的位置或者场景
            bundle.putString("respondcode",5);//固定传5
            FirebaseAnalytics.getInstance(PayActivity.this).logEvent("iap_pay_fail",bundle);                    
        }
    };

1.4.3 验证订单

为了降低用户刷单等问题,服务器端在校验订单有效后将自动向Adjust后台发送收入统计事件,而开发者再也不需要在App中发送相关事件 从而提高统计的准确性。

如果调用了setAutoVerifyPurchase()接口开启购买后自动验证,验证订单接口请不要再在onPurchaseFinishedListener回调下调用。 对于需要在进行订单验证时附加拓展数据notes的请不要调用setAutoVerifyPurchase()接口,而是自行调用验证接口。

该接口调用的理想位置是在 OnPurchaseFinishedListener的回调接口下进行:

 // 以下两个接口选择其中一个接入即可
 verifyPurchaseUtil.verifyPurchase(responseCode, purchase);//验证结果见OnVerifyPurchaseListener

 verifyPurchaseUtil.verifyPurchase(responseCode, purchase, notes); // 增加可自定义拓展字段notes(最大长度50), 用于开发者自行定义该笔订单附加说明等

验证结果监听器实现

    private VerifyPurchaseUtil.OnVerifyPurchaseListener mVerifyPurchaseListener = new VerifyPurchaseUtil.OnVerifyPurchaseListener() {
        @Override
        public void onVerifyFinish(GooglePurchase googlePurchase) {
            SkuDetails skuDetails = googleBillingUtil.getSkuDetail(googlePurchase.getProductId());
            LogUtil.d("[onVerifyFinish] 订单验证完成 sku: " + googlePurchase.getProductId() + "\n" +
                    "price = "+skuDetails.getPriceCurrencyCode()+" " + skuDetails.getPrice());
            // 内购状态 -1:还未验证 0:购买 1:取消 2:超时时伪验证
            if (googlePurchase.getPurchaseState() == -1) {
                LogUtil.d("[onVerifyFinish] 还未验证");
            } else if (googlePurchase.getPurchaseState() == 0) {
                LogUtil.d("[onVerifyFinish] 订单有效");
                //付费成功Firebase埋点,Firebase接入请参考3.2章节
                Bundle bundle = new Bundle();
                bundle.putString("product_id",currentPaurchseSKU);//传计费点名称或者Id
                bundle.putString("location","main_page");//传入发起支付时的位置或者场景
                if(isFirstPay){
                    //首次付费上报付费用户属性
                    FirebaseAnalytics.getInstance(PayActivity.this).setUserProperty("isPayUser", 1);
                }
                FirebaseAnalytics.getInstance(PayActivity.this).logEvent("iap_pay_success",bundle);                     
            } else if (googlePurchase.getPurchaseState() == 2) {
                LogUtil.d("[onVerifyFinish] 超时时伪验证");
            } else {
                LogUtil.d("[onVerifyFinish] 订单无效");
            }
        }

        @Override
        public void onVerifyError(int responseCode, GooglePurchase googlePurchase) {
            LogUtil.d("onVerifyError ,responseCode = " + responseCode + ",productId = " + googlePurchase.getProductId());
        }
    };

1.4.4 其他接口说明

剩下的接口根据需求选择性接入即可。

1.4.4.1 获取商品货币类型和价格

可以通过以下接口获取商品要显示的价格信息

   SkuDetails skuDetails = googleBillingUtil.getSkuDetail(productId);
   LogUtil.d("price = "+skuDetails.getPriceCurrencyCode()+" " + skuDetails.getPrice());
1.4.4.2 获取已经购买未消耗的商品

如果出现重复购买的现象时,即支付失败code=7的时候,可以调用以下接口获取还未消耗的商品:

List<Purchase> inappPurchaseList = googleBillingUtil.queryPurchasesInApp();//内购

List<Purchase> inappPurchaseList = googleBillingUtil.queryPurchasesSubs();//订阅
1.4.4.3 消耗商品

如果存在未消耗的商品时,可以调用以下接口进行消耗商品,消耗成功了才能进行下一次购买。初始化时调用setAutoConsumeAsync(true)设置了自动消耗无需关心此接口:

 googleBillingUtil.consumeAsync(purchase.getPurchaseToken());

消耗结果监听器实现

    private GoogleBillingUtil.OnConsumeFinishedListener mOnConsumeFinishedListener = new GoogleBillingUtil.OnConsumeFinishedListener() {
        @Override
        public void onConsumeSuccess(String purchaseToken) {
            LogUtil.d("onConsumeSuccess purchaseToken = " + purchaseToken);
            if (inappPurchaseList != null && inappPurchaseList.size() > 0) {
                for (Purchase purchase : inappPurchaseList) {
                    if (purchase.getPurchaseToken().equals(purchaseToken)) {
                        String sku = purchase.getSku();
                        if (inAppSKUS[0].equals(sku)) {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    Toast.makeText(PayActivity.this, inAppSKUS[0] +" 消耗成功", Toast.LENGTH_LONG).show();
                                }
                            });
                            break;
                        }
                    }
                }
            }
        }

        @Override
        public void onConsumeFail(int responseCode, String purchaseToken) {
            LogUtil.d("onConsumeFail,responseCode = " + responseCode + ",purchaseToken = " + purchaseToken);
        } 
1.4.4.4 其他接口

查询未被消耗的内购商品,查询结果依赖购买成功后的内购验证是否被执行。查询结果见 OnQueryUnConsumeOrderListener

 void queryUnConsumeOrders(Context)

查询当前Google账户在本App中对每个产品ID的最新'购买'的Purchase(Purchase可能是expired, cancelled, or consumed等)。 查询结果见OnQueryHistoryQurchaseListener

 void queryHistoryInApp()//内购

 void queryHistorySubs() //订阅

异步查询setInAppSKUS(inAppSKUS)中配置的内购商品详情信息。查询结果见 OnQueryFinishedListener

  void queryInventoryInApp()//内购

  void queryInventorySubs()//订阅

查询未被消耗的内购商品,查询结果依赖购买成功后的内购验证是否被执行。查询结果见 OnQueryUnConsumeOrderListener

 void queryUnConsumeOrders(Context)

1.5 测试验证与接入demo

1.5.1 测试与验证

请务必按照以下标准进行测试:

1)支持Google Service并安装Google Play;

2)国内测试时请使用可用的VPN(在Google Play应用的‘游戏’-‘付费’选项下可以拉取到游戏列表数据)

3)请对待测试的游戏包使用与Google Play后台上传的包一致的签名文件进行签名;

4)请使用Google Play测试账号进行测试(最好是保证一台手机只登陆了一个Google Play账号)

以下步骤验证成功代表接入完成,遇到问题请查看SDK日志,过滤Tag “SDK_YiFans”:

 1) 在各个监听器的回调方法里必要的日志;

 2) 初始化成功,SDK会回调OnStartSetupFinishedListener监听器的onSetupSuccess方法;

 3) 成功拉起支付界面并支付成功,SDK会回调OnPurchaseFinishedListener监听器的onPurchaseSuccess方法,此时游戏成功发货;

 4) 在支付成功回调的onPurchaseSuccess方法中发起验证订单,SDK会回调OnVerifyPurchaseListener监听器的onVerifyFinish方法,你会在此方法中看到你添加的日志。

 5) 在Ars后台和Adjust后台看到相应的统计数据(Adjust后台数据一般要延迟五分钟显示)。

1.5.2 接入demo

点击查看

2.广告接入

广告采用AdMob聚合广告平台,广告接入必接步骤大致如下:

 1)集成admob sdk;
 2)集成admob第三方广告平台网络适配器,是admob为了适配第三方广告的插件sdk;
 3)集成第三方广告网络sdk,admob可以聚合多个第三方广告网络;
 4)初始化并集成广告格式代码;
 5)测试验证。

2.1 添加SDK依赖

2.1.1 通过Gradle远程下载依赖(推荐)

在project根目录下的build.gradle中添加如下配置:

 allprojects {
     repositories {
         ...     
         google()
         jcenter()
         maven {
            url 'https://dl.bintray.com/ironsource-mobile/android-sdk'
         }
     }
 }

在app目录下的build.gradle的depencies中添加如下引用:

    //AdMob
    implementation 'com.google.android.gms:play-services-ads:20.2.0'

    // AppLovin
    implementation 'com.google.ads.mediation:applovin:10.3.0.0'

    // Facebook Audience Network
    implementation 'com.google.ads.mediation:facebook:6.5.0.0'

    // Unity Ads
    implementation 'com.unity3d.ads:unity-ads:3.7.1'
    implementation 'com.google.ads.mediation:unity:3.7.1.0'

    // ironSource
    implementation 'com.google.ads.mediation:ironsource:7.1.6.0'

2.1.2 通过下载到本地引入依赖(采用2.1.1方式导入SDK忽略此步骤)

Facebook adapter下载地址

Facebook SDK下载地址

IronSource adapter下载地址

IronSource SDK下载地址

UnityAds adapter下载地址

UnityAds SDK下载地址

AppLovin adapter下载地址

AppLovin SDK下载地址

2.2.1 application节点配置

配置Admob AppId

将以下内容配置到application节点下:

<manifest>
     <application>
         ... 
        <!--注意!value中的值替换成自己的AdMob AppId -->
        <meta-data
            android:name="com.google.android.gms.ads.APPLICATION_ID"
            android:value="AdMob_AppId"/>   
         ...
      </application>
</manifest>

Android9.0以上网络安全文件配置

Android 9.0(API 28)默认阻止明文(非HTTPS)流量,这可能会阻止广告正常投放。为了缓解这种情况,其应用在Android 9.0或更高版本上运行的发布商应确保添加网络安全配置文件。这样,白名单会明确指定流量并允许非HTTPS广告投放。

如果res目录下没有名为xml的目录,请在res目录下新建一个名为xml的目录,然后在xml目录里新建文件network_security_config.xml,文件配置如下:

 <?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true">
        <trust-anchors>
            <certificates src="system" />
        </trust-anchors>
    </base-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">127.0.0.1</domain>
        <domain includeSubdomains="true">47.241.190.10</domain>
    </domain-config>

</network-security-config>

在application标签里添加属性:

 <manifest>
     <application
         ...
         android:networkSecurityConfig="@xml/network_security_config"
         ...>
     </application>
 </manifest>

针对视频广告启用硬件加速

<application 
     android:hardwareAccelerated="true">

</application>

2.3 接口调用

2.3.1 初始化

在Activity的onCreate生命周期函数中添加下面代码,务必在初始化完成后再进行请求广告,初始化一次即可:

    MobileAds.initialize(this, new OnInitializationCompleteListener() {
         @Override
         public void onInitializationComplete(InitializationStatus initializationStatus) {
             Log.d(TAG,"admob init finish");
             //可以开始请求广告
         }
    });

2.3.2 横幅广告

横幅广告应在初始化完成后加载,加载完成后banner会自动展示并且会自动刷新。

创建横幅广告实例

在Activity的onCreate()方法中添加AdView:

 AdView adView = new AdView(this);
 adView.setAdUnitId("xxxxxxxxxxx"); // 替换成你的banner id
 adView.setAdSize(getAdSize());
 adView.setAdListener(this);
 //控制banner显示位置
 FrameLayout decorView = (FrameLayout)getWindow().getDecorView().findViewById(android.R.id.content);
 FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM);
 decorView.addView(adView,layoutParams);

自适应banner适配

自适应banner会根据设备屏幕设备宽高进行自适应

     private AdSize getAdSize() {

        Display display = getWindowManager().getDefaultDisplay();
        DisplayMetrics outMetrics = new DisplayMetrics();
        display.getMetrics(outMetrics);

        float widthPixels = outMetrics.widthPixels;
        float density = outMetrics.density;

        int adWidth = (int) (widthPixels / density);

        return AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(this, adWidth);
    }

设置广告价值回传监听器

 adView.setOnPaidEventListener(new OnPaidEventListener() {
     @Override
     public void onPaidEvent(@NonNull AdValue adValue) {
        Bundle params = new Bundle();
        params.putLong("valuemicros", adValue.getValueMicros());
        params.putString("currency", adValue.getCurrencyCode());
        params.putString("precision", String.valueOf(adValue.getPrecisionType()));
        params.putString("adUnitid", AD_UNIT_ID);
        params.putString("network", adView.getResponseInfo().getMediationAdapterClassName());
        mFirebaseAnalytics.logEvent("ads_revenue", params);     
    }
});

请求并展示横幅广告

load方法请求时机必须要在初始化完成之后,load之后如果触发了onBannerLoaded回调,banner会自动显示出来:

  if (adView != null){
      AdRequest adRequest = new AdRequest.Builder().build();
      adView.loadAd(adRequest);
  }

隐藏横幅广告

如果有隐藏横幅广告的需求,可以做以下设置:

 public void hideBanner(){
    if (adView != null){
       adView.setVisibility(View.GONE);
    }
 }

设置监听器

在Acticity中实现BannerAdListener:

 public class ExampleActivity extends Activity implements AdListener {
    @Override
    public void onAdLoaded() {
     LogUtil.d("Banner successfully loaded.");
    }

    @Override
    public void onAdFailedToLoad(LoadAdError adError) {
        LogUtil.d("onBannerFailed,errorCode = " + adError.toString());
    }

    @Override
    public void onAdClicked() {
        LogUtil.d("onBannerClicked ");
    }

    @Override
    public void onAdOpened() {
        LogUtil.d("onBannerOpened ");
    }

    @Override
    public void onAdLeftApplication() {
        LogUtil.d("onBannerLeftApplication ");

    }   
    @Override
    public void onAdClosed() {
        LogUtil.d("onBannerClosed ");
    }
 }

销毁横幅广告

还需要在Activity的onDestroy()生命周期方法中销毁横幅广告视图:

 @Override
 protected void onDestroy() {
   adView.destroy();
   super.onDestroy();
 }

2.3.3 插屏广告

插屏广告第一次加载应在初始化完成后立即加载,然后在需要展示广告的时候手动展示,展示完成应重新加载新的广告(最佳时机是在onAdClosed()回调中加载)以便下一次能顺利展示。

加载广告

注意!插屏广告对象可以复用,只需创建一次即可。

 //请求加载广告
 //Firebase埋点事件,必须添加
 FirebaseAnalytics.getInstance(PayActivity.this).logEvent("ads_interstitial_request",null);

 AdRequest adRequest = new AdRequest.Builder().build();
InterstitialAd.load(this,"ca-app-pub-3940256099942544/1033173712", adRequest, new InterstitialAdLoadCallback() {
    @Override
    public void onAdLoaded(@NonNull InterstitialAd interstitialAd) {
        // The mInterstitialAd reference will be null until
        // an ad is loaded.
        mInterstitialAd = interstitialAd;
        Log.i(TAG, "onAdLoaded");

        // 广告加载成功
        //Firebase埋点事件,必须添加
        FirebaseAnalytics.getInstance(PayActivity.this).logEvent("ads_interstitial_loaded",null);
    }

    @Override
    public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
        // 广告加载失败
        Log.i(TAG, loadAdError.getMessage());
        mInterstitialAd = null;
    }
});

设置广告价值回传监听器

 mInterstitialAd.setOnPaidEventListener(new OnPaidEventListener() {
     @Override
     public void onPaidEvent(@NonNull AdValue adValue) {
        Bundle params = new Bundle();
        params.putLong("valuemicros", adValue.getValueMicros());
        params.putString("currency", adValue.getCurrencyCode());
        params.putString("precision", String.valueOf(adValue.getPrecisionType()));
        params.putString("adUnitid", AD_UNIT_ID);
        params.putString("network", mInterstitialAd.getResponseInfo().getMediationAdapterClassName());
        mFirebaseAnalytics.logEvent("ads_revenue", params);     
    }
});

展示广告

 if (mInterstitialAd != null) {
    mInterstitialAd.show(MyActivity.this);
 } else {
    // 广告还没准备好
 }

设置插屏监听器回调方法

    mInterstitialAd.setFullScreenContentCallback(new FullScreenContentCallback(){
      @Override
      public void onAdDismissedFullScreenContent() {
            // 广告被关闭
            // 这里可以进行缓存下一个
            loadInterstitial()
            Log.d("TAG", "The ad was dismissed.");
      }

      @Override
      public void onAdFailedToShowFullScreenContent(AdError adError) {
        //广告展示失败
        Log.d("TAG", "The ad failed to show.");
      }

      @Override
      public void onAdShowedFullScreenContent() {
          // 广告展示成功
          //Firebase埋点事件,必须添加
          Bundle bundle = new Bundle();
          bundle.putString("location","level_end");//传展示的场景
          FirebaseAnalytics.getInstance(PayActivity.this).logEvent("ads_interstitial_show",bundle); 
          mInterstitialAd = null;
          Log.d("TAG", "The ad was shown.");
      }
    });    

2.3.4 激励视频

激励视频广告第一次加载应在初始化完成后立即加载,然后在需要展示广告的时候手动展示,展示完成应重新加载新的广告(最佳时机是在onRewardedVideoClosed回调中加载)以便下一次能顺利展示。

加载广告

 private void loadVideoAd(){
     //Firebase埋点事件,必须添加
     FirebaseAnalytics.getInstance(PayActivity.this).logEvent("ads_RV_request",null);

     //第一个参数是Activity上下文,第二个参数是广告单元id,测试正式广告和上线前需要替换为我方运营提供的激励视频广告单元Id
     RewardedAd.load(MainActivity.this,"ca-app-pub-3940256099942544/5224354917",new AdRequest.Builder().build(),new RewardedAdLoadCallback(){
         @Override
         public void onAdLoaded(@NonNull RewardedAd rewardedAd) {
             super.onAdLoaded(rewardedAd);
             //广告加载成功
             mRewardedAd = rewardedAd;

             Log.d(TAG,"onAdLoaded");
             Log.d(TAG,"network = " + rewardedAd.getResponseInfo().getMediationAdapterClassName() );
         }

         @Override
         public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
             super.onAdFailedToLoad(loadAdError);
             //广告加载失败
             Log.d(TAG,"onAdFailedToLoad " + loadAdError.toString());
         }
     });
  }

设置广告价值回传监听器(重要)

mRewardedAd.setOnPaidEventListener(new OnPaidEventListener() {
    @Override
    public void onPaidEvent(@NonNull AdValue adValue) {
        Bundle params = new Bundle();
        params.putLong("valuemicros", adValue.getValueMicros());
        params.putString("currency", adValue.getCurrencyCode());
        params.putString("precision", String.valueOf(adValue.getPrecisionType()));
        params.putString("adUnitid", AD_UNIT_ID);
        params.putString("network", rewardedAd.getResponseInfo().getMediationAdapterClassName());
        mFirebaseAnalytics.logEvent("ads_revenue", params);
    }
 });

设置广告事件监听器

 mRewardedAd.setFullScreenContentCallback(new FullScreenContentCallback() {
     @Override
     public void onAdFailedToShowFullScreenContent(@NonNull AdError adError) {
         super.onAdFailedToShowFullScreenContent(adError);
         //展示失败
         Log.d(TAG,"onAdFailedToShowFullScreenContent");
     }

    @Override
    public void onAdShowedFullScreenContent() {
        super.onAdShowedFullScreenContent();
        //展示全屏广告成功
        Log.d(TAG,"onAdShowedFullScreenContent");
    }

    @Override
    public void onAdDismissedFullScreenContent() {
        super.onAdDismissedFullScreenContent();
        //广告关闭回调
        Log.d(TAG,"onAdDismissedFullScreenContent");

        //此时可以进行缓存下一条广告
        loadRVAd();
    }

    @Override
    public void onAdImpression() {
        super.onAdImpression();
        //广告展示成功
        Log.d(TAG,"onAdImpression");
         //Firebase埋点事件,必须添加
         Bundle bundle = new Bundle();
         bundle.putString("location","daily_reward");//传展示的场景
         FirebaseAnalytics.getInstance(PayActivity.this).logEvent("ads_RV_show",bundle);   
    }
});

展示广告

   if (mRewardedAd != null){
       mRewardedAd.show(MainActivity.this, new OnUserEarnedRewardListener() {
           @Override
           public void onUserEarnedReward(@NonNull RewardItem rewardItem) {
               //奖励回调
               Log.d(TAG,"发放奖励");

               //Firebase埋点事件,必须添加
               Bundle bundle = new Bundle();
               bundle.putString("location","daily_reward");//传展示的场景
               FirebaseAnalytics.getInstance(PayActivity.this).logEvent("ads_RV_complete",bundle);   
           }
       });
   }

2.4 接入GDPR欧盟隐私条款(欧洲地区上线必接)

GDPR简单据介绍

产品上线欧盟地区必须要在游戏首次启动时弹出GDPR欧盟隐私条款,让玩家选择是否同意遵循条款。如果玩家选择不同意,则游戏就不能获取玩家的个人信息数据。

是否需要接入以我方运营需求为准

2.4.1 定制用户协议界面

在AdMob后台左侧 “隐私权和消息” 栏目中进行定制用户协议界面。

此步骤研发无需关心,由我方运营进行

2.4.2 导入SDK

在app目录下的build.gradle的depencies中添加如下引用:

 implementation 'com.google.android.ump:user-messaging-platform:1.0.0'

2.4.3 接口调用

请求协议同意状态

在游戏启动时调用以下接口:

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import com.google.android.ump.ConsentForm;
import com.google.android.ump.ConsentInformation;
import com.google.android.ump.ConsentRequestParameters;
import com.google.android.ump.FormError;
import com.google.android.ump.UserMessagingPlatform;

public class MainActivity extends AppCompatActivity {
    private ConsentInformation consentInformation;
    private ConsentForm consentForm;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //添加测试设备
        ConsentDebugSettings debugSettings = new ConsentDebugSettings.Builder(this)
                .setDebugGeography(ConsentDebugSettings.DebugGeography.DEBUG_GEOGRAPHY_EEA)//DebugGeography.DEBUG_GEOGRAPHY_EEA 表示模拟欧洲用户,DEBUG_GEOGRAPHY_NOT_EEA 表示模拟非欧洲用户
                .addTestDeviceHashedId("设备ID")//设备Id请参考2.5.2中获取
                .build();

        //配置信息
        ConsentRequestParameters params = new ConsentRequestParameters
            .Builder()
            .setConsentDebugSettings(debugSettings)
            .build();

        consentInformation = UserMessagingPlatform.getConsentInformation(this);
        //开始请求
        consentInformation.requestConsentInfoUpdate(
            this,
            params,
            new ConsentInformation.OnConsentInfoUpdateSuccessListener() {
                @Override
                public void onConsentInfoUpdateSuccess() {
                    //同意信息状态已更新,可以开始加载协议内容
                }
            },
            new ConsentInformation.OnConsentInfoUpdateFailureListener() {
                @Override
                public void onConsentInfoUpdateFailure(FormError formError) {
                   //请求失败,处理异常
                }
            });
    }
}

加载协议弹窗

 ...
       consentInformation.requestConsentInfoUpdate(
           this,
           params,
           new ConsentInformation.OnConsentInfoUpdateSuccessListener() {
               @Override
               public void onConsentInfoUpdateSuccess() {

                   if (consentInformation.isConsentFormAvailable()) {
                       loadForm();
                   }
               }
           },
           new ConsentInformation.OnConsentInfoUpdateFailureListener() {
               @Override
               public void onConsentInfoUpdateFailure(FormError formError) {
                   // Handle the error.

               }
            });
    }

    public void loadForm() {
        UserMessagingPlatform.loadConsentForm(
        this,
        new UserMessagingPlatform.OnConsentFormLoadSuccessListener() {
            @Override
            public void onConsentFormLoadSuccess(ConsentForm consentForm) {
                MainActivity.this.consentForm = consentForm;
            }
        },
        new UserMessagingPlatform.OnConsentFormLoadFailureListener() {
            @Override
            public void onConsentFormLoadFailure(FormError formError) {

            }
        }
    );

    }
}

展示弹窗

 public void loadForm(){
    UserMessagingPlatform.loadConsentForm(
        this,
        new UserMessagingPlatform.OnConsentFormLoadSuccessListener() {
            @Override
            public void onConsentFormLoadSuccess(ConsentForm consentForm) {
                MainActivity.this.consentForm = consentForm;
                if(consentInformation.getConsentStatus() == ConsentInformation.ConsentStatus.REQUIRED) {
                    /**
                        ConsentStatus.UNKNOWN: 未知状态
                        ConsentStatus.REQUIRED: 用户需要授权但是还没同意授权
                        ConsentStatus.NOT_REQUIRED: 用户不需要授权,比如用户并非欧洲用户
                        ConsentStatus.OBTAINED: 用户已经授权
                    **/
                    consentForm.show(
                        MainActivity.this,
                            new ConsentForm.OnConsentFormDismissedListener() {
                                @Override
                                public void onConsentFormDismissed(@Nullable FormError formError) {

                                    loadForm();
                                }
                            });

                }

            }
        },
        new UserMessagingPlatform.OnConsentFormLoadFailureListener() {
            @Override
            public void onConsentFormLoadFailure(FormError formError) {
                /// Handle Error.
            }
        }
   );
}

重置同意状态

测试时如果需要重置同意状态可以调用以下代码:

 consentInformation.reset();

2.5 测试验证

测试前设备需要开启VPN(建议美国地区),否则可能会有加载不到广告的现象。

注意!对于激励视频和插屏广告强烈不建议在广告加载失败回调中进行加载广告,如果一定要这么做,务必限制重新加载次数,避免造成死循环。

激励视频和插屏广告建议在以下时机进行请求广告:

  1. 首次请求在游戏启动广告SDK初始化成功时;

  2. 缓存下一个广告在广告关闭的回调里进行加载;

  3. 在展示广告前的某个时机应该提前判断是否有广告,没有广告就去进行加载广告。

对于第三点,比如关卡结束时有广告埋点,游戏应该在关卡开始时先判断有没有广告已经填充,如果没有就要去进行加载广告。

2.5.1 使用测试广告单元Id测试,确保AdMob集成无误

Android

横幅广告    ca-app-pub-3940256099942544/6300978111
插页式广告   ca-app-pub-3940256099942544/1033173712
激励视频广告  ca-app-pub-3940256099942544/5224354917

2.5.2 测试中介联盟广告,确保第三方广告集成无误

添加测试设备

以下添加方式选择一种即可

  • AdMob后台添加,联系我方运营进行添加;
  • 代码中添加,如下步骤所示:
1)运行游戏,发起一个广告请求;
2)检查控制台或者logcat的日志,查找以下内容:
     I/Ads: Use
      RequestConfiguration.Builder
        .setTestDeviceIds(Arrays.asList("33BE2250B43518CCDA7DE426D04EE231"))
      to get test ads on this device.


3)复制日志中的设备id;
4)代码中设置测试设备:
//可以在初始化前添加
List<string> deviceIds = new List<string>();
deviceIds.Add("33BE2250B43518CCDA7DE426D04EE231");
RequestConfiguration requestConfiguration = new RequestConfiguration
    .Builder()
    .SetTestDeviceIds(deviceIds)
    .build();
MobileAds.SetRequestConfiguration(requestConfiguration);

导入测试套件SDK

在app目录下的build.gradle的depencies中添加如下引用:

 implementation 'com.google.android.ads:mediation-test-suite:1.5.0'

展示测试套件

在Activity的onCreate()方法调用一下代码开启测试套件:

 MediationTestSuite.launch(MainActivity.this);

注意!正式发布时,需要把测试套件代码注释!

在设备上运行游戏

运行游戏,启动时会弹出测试套件界面,逐一对每个平台进行加载、展示广告即可。

3. 统计接入

统计需要接入以下两个平台。

Adjust用于归因分析,Firebase用于事件整合分析,用户行为分析。

 统计接入必要步骤:
    1)参数准备,Adjust的apptoken,eventId,Firebase配置文件等
    2)添加SDK依赖
    3)AndroidManifest文件配置,包括权限、application等
    4)初始化,进行事件打点前必须进行初始化,否则无法在后台看到数据
    5)事件打点,根据运营需求进行事件埋点
    6)测试验证,查看后台数据

3.1 Adjust接入

3.1.1 添加SDK依赖

在app目录下的build.gradle的depencies中添加如下引用:

 implementation 'com.adjust.sdk:adjust-android:4.28.2'
 implementation 'com.android.installreferrer:installreferrer:2.2'
 implementation 'com.google.android.gms:play-services-ads-identifier:17.0.0'

3.1.2 AndroidManifest文件

权限配置
 <!-- 必须权限-->
 <uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
 <!-- 可选权限,发布非谷歌商店使用-->
 <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

3.1.3 初始化

自定义Application类在onCreate方法中进行初始化

 import com.adjust.sdk.Adjust;
 import com.adjust.sdk.AdjustConfig;

 public class GlobalApplication extends Application {
     @Override
     public void onCreate() {
         super.onCreate();
         //后台获取的app token
         String appToken = "{YourAppToken}";
         //运行环境,ENVIRONMENT_SANDBOX为沙箱环境,ENVIRONMENT_PRODUCTION为正式发布环境
         String environment = AdjustConfig.ENVIRONMENT_SANDBOX;
         String environment = AdjustConfig.ENVIRONMENT_PRODUCTION;

         AdjustConfig config = new AdjustConfig(this, appToken, environment);
         config.setLogLevel(isDebug?LogLevel.LogLevelDebug:LogLevel.INFO);//日志级别

         //归因回传,用于Firebase adjust_conversion事件统计,
         config.setOnAttributionChangedListener(new OnAttributionChangedListener() {
            @Override
             public void onAttributionChanged(AdjustAttribution attribution) {
                 if (attribution == null){
                    Log.d(TAG,"attribution is null");
                    return;
                 }
                String network = attribution.network;
                String campaign = attribution.campaign;
                String adgroup = attribution.adgroup;
                String creative = attribution.creative;

                 //设置来源属性,必须设置
                mFirebaseAnalytics.setUserProperty("network", network);
                mFirebaseAnalytics.setUserProperty("campaign", campaign);

                //来源事件统计,必须上报
                Bundle bundle  = new Bundle();
                bundle.putString("network",TextUtils.isEmpty(network)?"unknow":network);//注意判空
                bundle.putString("campaign",TextUtils.isEmpty(campaign)?"unknow":campaign);
                bundle.putString("adgroup",TextUtils.isEmpty(adgroup)?"unknow":adgroup);
                bundle.putString("creative",TextUtils.isEmpty(creative)?"unknow":creative);
                mFirebaseAnalytics.LogEvents(context,"adjust_conversion",bundle);

             }
        });
        Adjust.onCreate(config);
     }
 }

注册ActivityLifecycleCallback来追踪会话

在Application的onCreate()方法中注册ActivityLifecycleCallback:

 public class GlobalApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        //...

        registerActivityLifecycleCallbacks(new AdjustLifecycleCallbacks());
        //...
    }

   private static final class AdjustLifecycleCallbacks implements ActivityLifecycleCallbacks {
         @Override
         public void onActivityResumed(Activity activity) {
             Adjust.onResume();
         }

         @Override
         public void onActivityPaused(Activity activity) {
             Adjust.onPause();
         }

         //...
     }
  }

注意!如果应用API level 在14以下,请参考此处配置

3.1.4 事件打点

Adjust事件跟踪

在应用中需要跟踪的地方添加如下代码:

 AdjustEvent adjustEvent = new AdjustEvent("abc123");//注意这里要要替换为后台的token
 Adjust.trackEvent(adjustEvent);

abc123为事件token,需要打点的话必须先在后台创建事件获取这个token。

3.1.5 测试与验证

以下步骤验证成功代表接入完成:

 1)SDK依赖库正确添加;

 2)打开debug日志,初始化成功,日志过滤“Adjust”能看到归因信息;

 3)后台有安装、会话等数据(如果是沙箱环境要在后台切换沙箱数据);

 4)如果有事件统计的话,触发打点事件,并在后台看到打点数据。

3.2 Firebase接入

3.3.1 添加配置文件

运营会给到一个google-services.json文件,此文件中包含项目的参数信息,将这个文件添加到AndroidStudio项目的app目录下即可。

3.2.2 添加SDK依赖

在project根目录下的build.gradle中添加如下配置:

 buildscript {
   repositories {
        // Add the following repositories:
        google()  // Google's Maven repository
    }

   dependencies {
     // ...
     classpath 'com.google.gms:google-services:4.3.5'
     classpath 'com.google.firebase:firebase-crashlytics-gradle:2.5.2'
   }
 }

 allprojects {
   // ...
   repositories {
     google()
   }
 }

在appa目录下的build.gradle的depencies中添加如下引用:

 dependencies {
  // ...
  //firebase核心库
  implementation 'com.google.firebase:firebase-core:18.0.2'
  //崩溃报告
  implementation 'com.google.firebase:firebase-crashlytics:17.4.1'

 }

 apply plugin: 'com.google.gms.google-services' 
 apply plugin: 'com.google.firebase.crashlytics'

3.2.3 自定义事件

Firebase统计以事件为核心进行用户行为的分析,每个事件下面可以附带不同的参数进行上传,示例如下:

 FirebaseAnalytics firebaseAnalytics = FirebaseAnalytics.getInstance(this);
 //带参数事件
 Bundle bundle = new Bundle();
 bundle.putInt("state",2);//可以传int类型数据
 bundle.putDouble("price",0.24); //可以传Double类型数据
 bundle.putString("location","cart_win"); //可以传String类型数据
 mFirebaseAnalyics.logEvent("rewardvedio",bundle);


 //不带参数事件
 mFirebaseAnalyics.logEvent("rewardvedio",null);

注意!具体要统计的事件和规范请联系我方运营。

3.2.4 设置用户属性

用户属性用于记录用户长期不变或者变化缓慢的属性,可以将用户进行动态分群,后台可以直接将这个属性作为过滤条件,每次事件的上报都会附带用户属性一起上报。代码示例如下:

 //第一个参数是用户属性名称,第二个参数是属性值
 mFirebaseAnalytics.setUserProperty("favorite_food", mFavoriteFood);

属性值设置为null的话可以将该用户移除这个属性。

3.2.5 测试验证

由于Firebase后台数据有很长时间的延迟(24小时左右),测试数据没办法在后台马上能看到,要看实时测试数据需要用到Firebase后台的DebugView。请按以下步骤进行测试:

 1)将手机打开USB调试连接电脑,注意手机电脑都要翻墙;
 2)在手机上安装好游戏apk,注意此时不要启动游戏;
 3)打开Firebase后台DebugView;
 4)打开cmd终端输入以下命令(例如包名是com.yunbu.apptest,实际测试替换为游戏包名):
    adb shell setprop debug.firebase.analytics.app com.yunbu.apptest
 5)启动游戏,稍等十几秒DebugView左上角会出现你的设备名称,主面板会依次出现你打的事件名称

3.2.6 Firebase远程配置接入(可选)

远程配置用于动态参数调整,可以根据后台配置的参数,来在客户端做出不同的行为调整。

添加SDK依赖

在appa目录下的build.gradle的depencies中添加如下引用:

 implementation 'com.google.firebase:firebase-config:19.2.0'
接口调用

获取远程配置实例

 FirebaseRemoteConfig mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
 FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
        .setMinimumFetchIntervalInSeconds(3600)//设置后台参数值最小生效时间
        .build();
 mFirebaseRemoteConfig.setConfigSettingsAsync(configSettings);

设置默认值

如果出现获取不到后台设置的参数值,可以获取本地存储的默认值:

 mFirebaseRemoteConfig.setDefaultsAsync(R.xml.remote_config_defaults);

默认值示例:

<?xml version="1.0" encoding="utf-8"?>
<defaultsMap >
    <entry>
        <key>ads_value_high</key>
        <value>1</value>
    </entry>
    <entry>
        <key>ads_value_midle</key>
        <value>1</value>
    </entry>
    <entry>
        <key>ads_value_low</key>
        <value>1</value>
    </entry>
    <entry>
        <key>ads_roi_day</key>
        <value>7</value>
    </entry>
</defaultsMap>

获取后台参数配置

通过调用fetchAndActivate()方法可以向服务器请求参数配置,请求成功后所有参数配置会缓存到本地,此方法建议在初始化时调用:

    mFirebaseRemoteConfig.fetchAndActivate()
            .addOnCompleteListener(this, new OnCompleteListener<Boolean>() {
                @Override
                public void onComplete(@NonNull Task<Boolean> task) {
                    if (task.isSuccessful()){
                        //获取成功
                        boolean result = task.getResult();
                        LogUtil.i("result = "+result);
                    }else{
                        //获取失败
                        LogUtil.i("Fetch failed");
                    }


                }
            });

获取参数中的值

根据游戏业务场景来获取相应的值,从而做出不同的策略,获取示例如下:

  //参数的key由后台创建,请联系我方运营
  LogUtil.d("string params value = "+mFirebaseRemoteConfig.getString("string_key");//string类型数据
  LogUtil.d("boolean params value = "+mFirebaseRemoteConfig.getString("boolean_key");//boolean类型数据
  LogUtil.d("double params value = "+mFirebaseRemoteConfig.getString("double_key");//double类型数据

4.推送接入

暂时无需接入推送。

5.Facebook相关

5.1 Facebook生成密钥散列

Facebook的所有功能都必须生成密钥散列以确保应用与Facebook互动的真实性。

5.1.1 生成开发密钥散列

每个Android开发环境都将会有一个唯一的开发密钥散列。

Mac系统生成方式

打开终端窗口,运行以下命令:

 keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64

Windows系统生成方式

必须环境:

打开终端窗口,请在JDK文件夹的命令提示符下运行以下命令:

 keytool -exportcert -alias androiddebugkey -keystore "C:\Users\USERNAME\.android\debug.keystore" | "PATH_TO_OPENSSL_LIBRARY\bin\openssl" sha1 -binary | "PATH_TO_OPENSSL_LIBRARY\bin\openssl" base64

最后将开发密钥配置到后台。

5.1.2 生成发布密钥散列

需要对进行了正式签名的APP生成发布密钥散列。请在Mac或者Windows系统内运行以下命令,并替换你的发布密钥别名和keystore路径:

MAC

 keytool -exportcert -alias <RELEASE_KEY_ALIAS> -keystore <RELEASE_KEY_PATH> | openssl sha1 -binary | openssl base64

Windows

 keytool -exportcert -alias <RELEASE_KEY_ALIAS> -keystore <RELEASE_KEY_PATH> | PATH_TO_OPENSSL_LIBRARY\bin\openssl sha1 -binary | PATH_TO_OPENSSL_LIBRARY\bin\openssl base64  

最后将发布密钥配置到后台。

5.2 Facebook Analytics(必接)

Facebook Analytics统计事件分为自动记录事件,标准事件,和自定义事件,这里我们只需统计自动记录事件,只需要集成好SDK依赖和配置AndroidManifest文件即可。

5.2.1 添加SDK依赖

你可以通过Gradle或者下载到本地两种方式引入SDK依赖,选择其中一种方式引入即可,推荐使用Gradle远程下载。

【方式一】通过Gradle远程下载依赖(推荐)

在project根目录下的build.gradle中添加如下配置:

 allprojects {
     repositories {
         ...
         mavenCentral()

     }
  }

在app目录下的build.gradle中的depencies添加如下引用:

 implementation 'com.facebook.android:facebook-android-sdk:[5,6)'

【方式二】通过下载到本地引入facebook.aar,下载地址

下载下来是一个zip压缩包,里面包含了Facebook所有工程的SDK,你需要在"facebook"、"facebook-common"、"facebook-core"这三个工程目录下分别提取三个aar文件即可。

5.2.2 AndroidManifest文件配置

权限配置

 <uses-permission android:name="android.permission.INTERNET"/>

application节点配置

打开您的 /app/res/values/strings.xml 文件,添加以下代码:

 <!--替换成你的FacebookAppId-->
 <string name="facebook_app_id">1303125753067834</string> 

application 元素中添加以下 meta-data 元素、一个针对 Facebook 的 activity 元素以及一个针对 Chrome 自定义选项卡的 activity 元素和意向筛选条件:

 <meta-data android:name="com.facebook.sdk.ApplicationId" android:value="@string/facebook_app_id"/>

5.3 Facebook登录(可选)

注意,Facebook Analytics中已经配置好了Facebook AppId和必要权限,这里就不重复说明。

5.3.1 添加SDK依赖

可以通过Gradle或者下载到本地两种方式引入SDK依赖,选择其中一种方式引入即可,推荐使用Gradle远程下载。

【方式一】通过Gradle远程下载依赖(推荐)

在project根目录下的build.gradle中添加如下配置:

 allprojects {
     repositories {
         ...
         jcenter()

     }
  }

在app目录下的build.gradle中的depencies添加如下引用:

 implementation 'com.facebook.android:facebook-login:[5,6)'

【方式二】通过下载到本地引入facebook-login.aar,下载地址

下载下来是一个zip压缩包,里面包含了Facebook所有工程的SDK,只需在"facebook-login"这个工程目录下提取aar文件即可。

5.3.2 添加Facebook登录按钮

先在layout文件中添加布局:

 <com.facebook.login.widget.LoginButton
    android:id="@+id/login_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:layout_marginTop="30dp"
    android:layout_marginBottom="30dp" /> 

注册回调

callbackManager = CallbackManager.Factory.create(); 
    loginButton = (LoginButton) findViewById(R.id.login_button);
    loginButton.setReadPermissions("email");
    // 如果你的按钮是添加在fragment中请加上这行代码
    loginButton.setFragment(this);    

    // Callback registration
    loginButton.registerCallback(callbackManager, new FacebookCallback<LoginResult>() {
        @Override
        public void onSuccess(LoginResult loginResult) {
            // App code
        }

        @Override
        public void onCancel() {
            // App code
        }

        @Override
        public void onError(FacebookException exception) {
            // App code
        }
    });

回传登录结果

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        callbackManager.onActivityResult(requestCode, resultCode, data);
        super.onActivityResult(requestCode, resultCode, data);
    }

检查登录状态

    AccessToken accessToken = AccessToken.getCurrentAccessToken();
    boolean isLoggedIn = accessToken != null && !accessToken.isExpired();

5.3.3 测试与验证

以下步骤验证完成代表接入完成:

 1在回调中添加日志或者Toast例如
  LoginManager.getInstance().registerCallback(callbackManager, new FacebookCallback<LoginResult>() {
            @Override
            public void onSuccess(LoginResult loginResult) {
                AccessToken accessToken = loginResult.getAccessToken();
                Log.d(Constants.TAG,"Login Success!accessToken = "+accessToken);
                Toast.makeText(getActivity(),"login success!",Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onCancel() {
                Toast.makeText(getActivity(),"login cancel!",Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onError(FacebookException error) {
                Log.d(Constants.TAG,"Login error!error = "+error.toString());
                Toast.makeText(getActivity(),"login error!error = "+error.getMessage(),Toast.LENGTH_SHORT).show();
            }
        });
 2)点击登录按钮日志中出现"Login Success",表示登录成功

5.4 Facebook分享(可选)

集成Facebook分享必须先集成Facebook登录,所以这里不再重复与Facebook登录重复配置的说明。

5.4.1 添加SDK依赖

【方式一】通过Gradle远程下载依赖(推荐)

在project根目录下的build.gradle中添加如下配置:

 allprojects {
     repositories {
         ...
         mavenCentral()

     }
  }

在app目录下的build.gradle中的depencies添加如下引用:

 implementation 'com.facebook.android:facebook-share:[5,6)'

【方式二】同Facebook登录,提取分享模块"facebook-share"工程目录下的aar文件即可

5.4.2 AndroidManifest文件配置

组件配置

在applicatioin节点下添加以下组件

 <!--{APP_ID}替换成你的Facebook AppId-->
 <provider android:authorities="com.facebook.app.FacebookContentProvider{APP_ID}"
          android:name="com.facebook.FacebookContentProvider"
          android:exported="true"/>

5.4.3 内容建模

分享链接

 ShareLinkContent content = new ShareLinkContent.Builder()
        .setContentUrl(Uri.parse("https://developers.facebook.com"))
        .build();

分享图片

 Bitmap image = ...
 SharePhoto photo = new SharePhoto.Builder()
         .setBitmap(image)
         .build();
 SharePhotoContent content = new SharePhotoContent.Builder()
         .addPhoto(photo)
         .build();

分享视频

 Uri videoFileUri = ...
 ShareVideo = new ShareVideo.Builder()
        .setLocalUrl(videoUrl)
        .build();
 ShareVideoContent content = new ShareVideoContent.Builder()
        .setVideo(video)
        .build();

分享多媒体

 SharePhoto sharePhoto1 = new SharePhoto.Builder()
    .setBitmap(...)
    .build();
 SharePhoto sharePhoto2 = new SharePhoto.Builder()
    .setBitmap(...)
    .build();
 ShareVideo shareVideo1 = new ShareVideo.Builder()
    .setLocalUrl(...)
    .build();
 ShareVideo shareVideo2 = new ShareVideo.Builder()
    .setLocalUrl(...)
    .build();

 ShareContent shareContent = new ShareMediaContent.Builder()
    .addMedium(sharePhoto1)
    .addMedium(sharePhoto2)
    .addMedium(shareVideo1)
    .addMedium(shareVideo2)
    .build();

添加分享按钮

 ShareButton shareButton = (ShareButton)findViewById(R.id.fb_share_button);
 shareButton.setShareContent(content);

分享对话框

创建对话框实例:

 public class MainActivity extends FragmentActivity {
    CallbackManager callbackManager;
    ShareDialog shareDialog;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        callbackManager = CallbackManager.Factory.create();
        shareDialog = new ShareDialog(this);
        // this part is optional
        shareDialog.registerCallback(callbackManager, new FacebookCallback<Sharer.Result>() { ... });
  }

然后显示对话框:

 if (ShareDialog.canShow(ShareLinkContent.class)) {
    ShareLinkContent linkContent = new ShareLinkContent.Builder()
            .setContentUrl(Uri.parse("http://developers.facebook.com/android"))
            .build();
    shareDialog.show(linkContent);
 }

最后处理callbackManager的响应:

 @Override
 protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
     super.onActivityResult(requestCode, resultCode, data);
     callbackManager.onActivityResult(requestCode, resultCode, data);
 }

5.4.4 测试与验证

以下步骤验证完成代表接入完成:

 1)在回调中添加日志,例如:
            @Override
            public void onSuccess(Sharer.Result result) {
                Log.d(Constants.TAG,"Sharer Success!result = "+result.toString());
                Toast.makeText(getActivity(),"Sharer success!",Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onCancel() {
                Log.d(Constants.TAG,"Sharer onCancel! ");
                Toast.makeText(getActivity(),"Sharer onCancel!",Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onError(FacebookException error) {
                Log.d(Constants.TAG,"Sharer onError!error = "+error.toString());
                Toast.makeText(getActivity(),"Sharer onError!",Toast.LENGTH_SHORT).show();
            }
 2)点击分享按钮,弹出分享页面;
 3)编辑分享内容,发送分享;
 4)日志中出现"Sharer Success!",并在Facebook APP你的个人动态中看见你分享的内容。

6.混淆配置

将以下内容配置到你的ProGuard文件中

Google内购

 -keep class com.android.vending.billing.**

Adjust

发布谷歌商店配置如下:

 -keep class com.adjust.sdk.** { *; }
 -keep class com.google.android.gms.common.ConnectionResult {
    int SUCCESS;
 }
 -keep class com.google.android.gms.ads.identifier.AdvertisingIdClient {
    com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context);
 }
 -keep class com.google.android.gms.ads.identifier.AdvertisingIdClient$Info {
    java.lang.String getId();
    boolean isLimitAdTrackingEnabled();
 }
 -keep public class com.android.installreferrer.** { *; }

发布非谷歌商店配置如下:

 -keep public class com.adjust.sdk.** { *; }

7. 其他

7.1 各平台官方文档链接

如遇到sdk版本与官网不同,以官网版本为准。

Google内购

AdMob聚合广告

Adjust统计

Firebase统计

Facebook

7.2 兑换码功能(可选)

游戏如果想要接入兑换码功能,请自行提供输入兑换码的UI,然后访问我们API接口,根据接口的返回计费点Id进行发货。

查看接口文档

7.3 FAQ

7.3.1 内购相关

Q:测试内购提示"初始化失败"、无法购买您要买的商品",responCode = 4或5?

A:确保一下内容都正确:

​ 1)确保你客户端与后台计费点Id配置一致;

​ 2)确保你的包的版本号、签名与后台传的包一致;

​ 3)确保你的谷歌账号是已被添加为测试账号并且点击了测试邀请链接;

Q:应该在什么时候发货?

A:对于内购产品的增加或者减少不影响其他玩家情况下,为增强用户体验,可以在支付成功回调OnPurchaseFinishedListener的onPurchaseSuccess方法里直接发货。

Q:购买成功但是ARS后台没有看到测试数据?

A: 检查是否调用了验证订单接口。

7.3.2 广告相关

Q:无法显示广告?

A:确保以下内容都无误:

​ 1)确保你的SDK集成以及代码添加无误;

​ 2)确保你的VPN正常开启;

​ 3)确保广告Id配置无误并初始化成功;

​ 4)确保后台广告网络处于活跃状态;

​ 5)确保第三方广告网络版位Id配置无误;

​ 6)以上都确保无误情况下,请将设备连接AndroidStudio调试查看日志具体定位问题。

Q:请求广告返回“No ads found”?

A:1)检查SDK依赖、适配器是否添加无误;

​ 2)检查后台配置是否正确;

​ 3)VPN开到美国地区;

​ 4)可能确实是广告平台没有广告填充,但不是必现,必现的话请认真检查前面三步。

7.3.3 编译打包相关

Q:方法数超过65535问题

A:1)在你app目录下的build.gradle文件中defaultConfig里添加multiDexEnabled true;

​ 2)在你的dependencies里面添加implementation 'com.android.support:multidex:1.0.3';

​ 3)在你的AndroidManifest文件的application标签里添加name属性:android:name="android.support.multidex.MultiDexApplication",如果你已经有application类,请把你的application继承自MultiDexApplication。

Q:AndroidX和support包冲突问题 ?

A:AndroidX是谷歌对所有supportb包最新的一个封装依赖,所以二者不能共存。解决办法如下:

​ 在Android Studio菜单栏上选择Refactor-->Migrate to AndroidX将support包转成AndroidX,转换完成后如果你的代码里有引用support包的类,需要手动去重新导包。

后续待补充,欢迎提出问题。