U8SDK——接入Google SDK
这两天因为一客户需要在U8SDK中集成Google支付,因为深度合作关系,由这边帮客户方接入Google登录,Facebook登录和Google支付。之前U8SDK中,我们自己还没有接入过海外相关的SDK,但是不少使用U8SDK的同学,已经在U8SDK框架中集成了包括Google登录支付等海外SDK,也多多少少听他们说过其中各种坑。
通过本次接入发现, 接入其实没有多少难度,但是其中繁琐和耗费时间的点主要在测试。 因为国内特殊条件以及Google不支持国内地区, 所以测试的时候,需要梯子,以及境外信用卡(或者国内双币种信用卡,或者购买Google Play礼包卡)等。
本文接下来就从头一步一步来从头开始,分析讲解代码接入的过程、测试过程,以及其中可能遇到的问题。限于篇幅,我们分两篇博客来介绍接入的过程。
接入开始前,我们先确定一下接入的方式。我们采用直接先在AndroidStudio中接入Google和Facebook相关的接口和实现,然后提供统一的API调用,然后我们再将该工程生成jar包提供给U8SDK接入工程调用。
为什么要采用这种方式呢?
1、这个是Google SDK和Facebook等目前推荐的接入方式, 我们直接在AS中接入,可以很方便地添加Google和Facebook相关服务和依赖。(最主要的原因)
2、在AS中接入之后,我们可以直接在AS中测试接入的正确性, 这个时候,我们不用管U8SDK的处理方式,只需要检查Google登录,支付,Facebook登录等功能是否正常。
3、后面如果需要生成其他的海外版本,我们可以直接基于该库来开发,而不用从头再来。
4、不需要手动合并资源,通过该工程,我们打包apk之后,反编译该apk,然后将反编译后的res拷贝出来,就是合并之后的资源了。
开发工具准备
Android Studio最新版本(本文写的时候,更新到3.1版本), Android SDK也需要升级到最新版本(本文写的时候,更新到Android 8.1)。
实现功能
实现完成之后, 大概的包视图如下:
限于篇幅,我们这里抽出几个主要的类介绍一下, 设计统一的调用类GFApi供外部调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
//对外接口访问 public class GFApi { //SDK初始化接口,初始化成功,登录成功,支付成功等逻辑可以在ISDKListener回调接口中处理 public static void initSDK(final Activity context, final GameInitParams params, final ISDKListener listener){ USDKPlatform.getInstance().initSDK(context, params, listener); } //登录接口,loginType是登录方式,可以选择google还是facebook public static void login(final Activity context, int loginType){ USDKPlatform.getInstance().login(context, loginType); } //登出接口 public static void logout(Activity context){ USDKPlatform.getInstance().logout(context); } //切换帐号接口 public static void switchLogin(final Activity context, int loginType){ USDKPlatform.getInstance().switchLogin(context, loginType); } //支付接口 public static void pay(final Activity context, UPayParams payParams){ USDKPlatform.getInstance().pay(context, payParams); } //在游戏启动Activity的onDestroy中,调用该接口 public static void onDestroy(Activity context) { USDKPlatform.getInstance().onDestroy(context); } //在游戏启动Activity的onActivityResult函数中,调用该接口 public static void onActivityResult(int requestCode, int resultCode, Intent data) { USDKPlatform.getInstance().onActivityResult(requestCode, resultCode, data); } } |
Google登录实现代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public void login(Activity context, int loginType, ILoginListener listener) { try{ if(!inited){ Log.e("U8SDK", "google login plugin not inited success. please init first."); if(listener != null) listener.onFailed(Constants.LoginType.GOOGLE, "login failed. google login not init"); return; } GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(context); if(account != null ){ Log.d("U8SDK", "google account already login . just auto logout and switch..."); switchAccount(context, loginType, listener); return; } this.loginListener = listener; Intent signInIntent = googleSignInClient.getSignInIntent(); context.startActivityForResult(signInIntent, RC_SIGN_IN); }catch (Exception e){ if(loginListener != null){ loginListener.onFailed(Constants.LoginType.GOOGLE, "google login failed."+e.getMessage()); } e.printStackTrace(); } } private void handleSignInResult(Task<GoogleSignInAccount> completedTask) { try { GoogleSignInAccount account = completedTask.getResult(ApiException.class); if(loginListener != null){ UserInfo user = new UserInfo(); user.forGoogle(account.getIdToken(), account.getId(), account.getServerAuthCode(), account.getEmail()); loginListener.onSuccess(Constants.LoginType.GOOGLE, user); } Log.d("U8SDK","google signInResult. success."); } catch (ApiException e) { e.printStackTrace(); // The ApiException status code indicates the detailed failure reason. // Please refer to the GoogleSignInStatusCodes class reference for more information. Log.w("U8SDK", "google signInResult:failed code=" + e.getStatusCode()); if(loginListener != null){ loginListener.onFailed(Constants.LoginType.GOOGLE, "google login failed. "+e.getMessage()); } } } |
Google支付实现代码片段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
@Override public void pay(Activity context, UPayParams payParams, IPayListener listener) { if(paying){ Log.d("U8SDK", "last pay is running. please wait it to finish"); return; } paying = true; productID = payParams.productID; lastPayParams = payParams; payContext = context; payListener = listener; Log.d("U8SDK", "the product id of google is :"+productID); if(!payOK){ Log.w("U8SDK", "google pay init failed. or not inited."); initPay(); return; } if(mHelper == null || mHelper.isServiceDisconnected()){ Log.w("U8SDK", "service disconnected. now to reinit."); initPay(); return; } checkProductBeforePurchase(productID); } public void checkProductBeforePurchase(final String product) { try { mHelper.queryInventoryAsync(new IabHelper.QueryInventoryFinishedListener(){ @Override public void onQueryInventoryFinished(IabResult result, Inventory inv) { if(result.isFailure()){ Log.e("U8SDK", "query products failed."+result); payInternal(payContext, payListener); return; } if(inv.hasPurchase(product)){ //上次已经购买,这里继续完成支付 Purchase p = inv.getPurchase(product); mHelper.consumeAsync(p, mConsumeFinishedListenerQuery); }else{ payInternal(payContext, payListener); } } }); } catch (Exception ex) { ex.printStackTrace(); payInternal(payContext, payListener); } } private void payInternal(final Activity context, IPayListener listener){ try{ new Handler().postDelayed(new Runnable(){ public void run() { mHelper.launchPurchaseFlow(context, productID, RC_REQUEST, mPurchaseFinishedListener, ""); } }, 100); }catch (Exception e){ if(listener != null){ listener.onFailed(Constants.PayType.GOOGLE, e.getMessage()); } paying = false; e.printStackTrace(); } } |
Facebook登录实现代码片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public void login(Activity context, int loginType, ILoginListener listener) { try{ if(loginManager == null){ if(listener != null){ listener.onFailed(Constants.LoginType.FACEBOOK, "login failed. loginManager is null. are you call init first?"); } return; } this.loginListener = listener; AccessToken accessToken = AccessToken.getCurrentAccessToken(); if (accessToken != null) { if(!accessToken.isExpired()){ //当前已经登录,直接返回成功 Log.d("U8SDK", "facebook already login. just auto login..."); if(listener != null){ UserInfo user = new UserInfo(); user.forFacebook(accessToken.getToken(), accessToken.getUserId()); listener.onSuccess(Constants.LoginType.FACEBOOK, user); } return; } loginManager.logOut(); } loginManager.logInWithReadPermissions(context, Arrays.asList("public_profile")); }catch (Exception e){ if(listener != null){ listener.onFailed(Constants.LoginType.FACEBOOK, "login failed with exception. "+e.getMessage()); } e.printStackTrace(); } } |
实现好Google登录,Facebook登录,Google支付之后,我们可以直接在该工程中添加一个测试用的Activity,界面上加上Google登录按钮,Facebook登录按钮,Google支付按钮,点击的时候,调用GFApi中对应的接口,来测试这些功能,是否正常可用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
public class MainActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn_login = (Button)findViewById(R.id.button_login_google); btn_login.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { GFApi.login(MainActivity.this, Constants.LoginType.GOOGLE); } }); Button btn_loginFacebook = (Button)findViewById(R.id.button_login_facebook); btn_loginFacebook.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { GFApi.login(MainActivity.this, Constants.LoginType.FACEBOOK); } }); Button btn_pay = (Button)findViewById(R.id.button_pay); btn_pay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { UPayParams payParams = new UPayParams(); payParams.payType = Constants.PayType.GOOGLE; payParams.productID = "android.test.purchased"; GFApi.pay(MainActivity.this, payParams); } }); String serverApiClient = "332607799340-uaj1jotn2urt6v6rag07qpdddtewss.apps.googleusercontent.com"; String payPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFA44445BCgKCAQEAsqJffvoSO38oh/d+HapTcab7zM57HBkUMD+yV6Gf43yIIEwXXyiKUbpmWuzLdL6s/vR+BuAXG9Ze/hA4JOH5eyyihwU2FOT270mrKxmKLM3IDN+7ytd+vmUdP5/KCrq76HVkC+x9tQmCte32XOrD8onVs54333CId4r9xC4/gsoD7j4klAbpo0sewpOk8S56i+UJdTUS3zpltTik0LLK/0//bxdg6wsTh9upkuBRGh/7RvO4KIb1vgssCULdvNlNHj6123454Qitcqogjt7RFWHScGuFpYd8bG8HUWf/9dW6NF1dGhczMvh0uGSfQh5oqoLnrnBnrUdAx9YWUnQIDAQAB"; GFApi.initSDK(this, new GameInitParams(serverApiClient, payPublicKey), new ISDKListener() { @Override public void onInitSuccess() { Log.d("U8SDK", "init success"); } @Override public void onInitFailed() { Log.d("U8SDK", "init failed"); } @Override public void onLoginSuccess(UserInfo user) { Log.d("U8SDK", "login success"); } @Override public void onLoginFailed(String msg) { Log.d("U8SDK", "login failed:"+";msg:"+msg); } @Override public void onPaySuccess(PayResult product) { Log.d("U8SDK", "pay success"); } @Override public void onPayFailed(String msg) { Log.d("U8SDK", "pay failed:"+";msg:"+msg); } }); } public void onDestroy(){ super.onDestroy(); GFApi.onDestroy(this); } public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); GFApi.onActivityResult(requestCode, resultCode, data); } } |
测试Google登录
先去Google 文档这里(https://developers.google.com/identity/sign-in/android/start),登录之后,去Google Api Console创建一个应用,Android客户端的client id,然后设置好包名以及打包使用的keystore签名(sha-1信息). 注意,这里包名和keystore签名, 确定了以后出google的包,就需要用这个包名和签名,如果错了,是无法登录的。
创建之后,应该会自动创建一条Web client,如果没有自动创建,请手动创建一条OAuth client id凭据,类型选择网页应用。创建之后, 会生成一个web client id。
MainActivity中调用GFApi的init函数时,需要传入GameInitParams,里面有一个serverClientID,请将该参数设置为上面生成的web client id。
然后,我们build一个apk出来, 运行在Android模拟器上, 如果你在国内测试,请在模拟器中安装”梯子”。然后点击界面上Google登录按钮,看下能否正常弹出google的授权界面,并授权登陆成功。 授权不成功的话, 请检查测试apk的包名和使用的keystore签名和Google API Console中设置的是否一致。
测试Facebook登录
先去Facebook开发者平台(https://developers.facebook.com/docs/facebook-login/android)创建一个应用,然后Facebook会生成一个appID给你, 将该AppID按照文档要求,配置到AndroidManifest.xml中即可。
Facebook后台, 填好应用的包名,启动类名称, 以及按照他的说明生成apk使用的keystore对应的密钥散列值进行配置。
然后我们重新build一个apk出来,运行在Android模拟器上,
如果你在国内测试,请在模拟器中安装”梯子”,然后点击界面上Facebook登录按钮,看下能否正常弹出facebook授权页面,并授权登录成功。
测试Google支付
要测试Google支付,需要先去Google Play开发者平台(https://play.google.com/apps/publish),注册一个开发者帐号(25美元),注册成功之后, 在控制台创建应用,并上传apk包(注意上传apk包的包名,签名,versionCode和versionName)。
然后,按照他的提示和要求, 可能先要做一些应用分级问卷等,如实答写即可。 然后应用创建之后, 在开发工具下面->服务和API里面,有该应用的RSA公共密钥,请将该密钥拷贝出来
设置到MainActivity中调用GFApi的init函数时,传入GameInitParams中。
为了测试Google支付, 我们需要发布一个Alpha版本(封闭测试版本),在Google Play控制台->应用版本中,添加Alpha测试版本,并发布。如下图:
添加Alpha测试版本的时候, 我们选择一个上传的apk,如果重新上传apk,需要将versionCode和versionName提高一下。然后将发布的国家全部选中(因为如果你没有选择对应的国家,然后你的ip地址所在的国家不在支持列表里面,是无法支付的),然后添加测试人员帐号(请不要添加开发者自己的帐号,自己的帐号是无法购买自己创建的应用内道具的)。
发布alpha版本之后, 会生成测试人员加入的链接, 可以将该链接发给上面添加的测试人员,然后该测试人员点击链接,加入到该应用的测试中。接下来测试Google支付,就用该帐号测试。
为了测试Google支付,我们还需要在Google Play控制台,添加对应的商品。在商店发布菜单->应用内商品,创建一条”受管理的商品”,比如商品ID为:com.u8.sdk.product1。 后面我们测试支付的时候, 将商品ID设置为该值,价格设置为1美元。
好了,到这里,该应用已经满足测试条件了, 我们也准备对其进行测试了。因为Google的政策原因,目前Google Play不向大陆提供服务,所以使用国内的信用卡或者普通借记卡等是无法使用Google Play服务的。所以,我们需要准备一张境外的信用卡,或者国内的双币种信用卡(visa卡等)。不过这边测试,绑定了一张招行的visa卡,购买道具的时候,提示无法购买,测试了去Google Play应用商店中购买其他游戏, 也提示无法购买,应该是信用卡无法使用。后来,我们去某宝购买了10美金的Google Play的礼包卡,然后用礼包卡来进行了支付测试。你也可以如此操作进行测试。
接下来,我们安装Google Play上面Alpha测试的apk包, 这里可以安装到原生的Google设备上进行测试,也可以使用最新的模拟器(Android 8.1,注意需要安装Google Play服务支持)。
然后我们运行apk,在界面上点击Google支付, 看下是否正确弹出Google支付界面,并购买成功。
中间可能会遇到一些问题, 调不起来Google支付页面。 这个时候,可以检查keystore签名,包名, Google 支付密钥等参数,商品ID 以及versionCode和versionName是否和后台apk一致。
好了,到这里Google登录,Facebook登录,Google支付基本测试都OK了。后面的步骤,就按照U8接入新渠道SDK的流程来,将其接入到U8SDK中即可。
本文出自 U8SDK技术博客,转载时请注明出处及相应链接。
本文永久链接: http://www.uustory.com/?p=2214