众所周知,应用宝sdk几乎是国内应用市场sdk中最难接入的Android sdk,很多人初始接入都会感觉很痛苦,文档多又乱,问题排查也是非常蛋疼的事情,所以,今天抽了个时间整理了一下应用宝sdk接入流程以及一些需要特别注意的事项
一、阅读对接文档
1、应用宝sdk接入文档,见:链接
2、上架应用宝商店的应用只需要接入微信、手Q登录与米大师支付即可,当然,若是你想接入应用宝更多其他功能也是没问题的
3、由于应用宝的在线对接文档排布的顺序比较乱,所以,建议初次接入应用宝sdk的新手,查看文档顺序如下图标注所示:
二、应用宝sdk接入
1、先下载好应用宝sdk
2、对照YSDK标准接入的说明,配置好下载好的应用宝sdk
3、修改配置assets/ysdkconf.ini文件的手Q、微信AppId与测试或正式环境
注意:手Q跟微信appid需要配置的地方有两个:
1)assets/ysdkconf.ini中配置
2)AndroidManifest.xml中的配置
4、AndroidMainfest修改
1)权限配置
android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" /> 2)组件配置 android:name="com.tencent.tauth.AuthActivity" android:launchMode="singleTask" android:noHistory="true" > android:name="com.tencent.connect.common.AssistActivity" android:configChanges="orientation|screenSize|keyboardHidden" android:screenOrientation="portrait" android:theme="@android:style/Theme.Translucent.NoTitleBar" /> android:name="com.tencent.tmgp.xmsjss2.wxapi.WXEntryActivity" android:excludeFromRecents="true" android:exported="true" android:launchMode="singleTop" android:taskAffinity="com.tencent.tmgp.xmsjss2.diff"> android:name="com.tencent.midas.proxyactivity.APMidasPayProxyActivity" android:theme="@android:style/Theme.Translucent.NoTitleBar" android:screenOrientation="portrait" android:configChanges="orientation|keyboardHidden|screenSize" /> android:name="com.tencent.midas.wx.APMidasWXPayActivity" android:theme="@android:style/Theme.Translucent.NoTitleBar" android:exported="true" /> android:name="com.tencent.midas.qq.APMidasQQWalletActivity" android:launchMode="singleTop" android:theme="@android:style/Theme.Translucent.NoTitleBar" android:configChanges="orientation|keyboardHidden" android:exported="true" > android:theme="@android:style/Theme.Translucent.NoTitleBar" android:windowSoftInputMode="stateAlwaysHidden"/> 注意:手Q跟微信appid的配置 com.tencent.tauth.AuthActivity的intent-filter中的 中tencent后填写游戏的手Q appid。 例如: 如果游戏的Activity为第一个启动Activity, 则需要在游戏Activity声明中添加android:configChanges="orientation|screenSize|keyboardHidden", 否则可能造成没有登录没有回调 游戏的Activity的launchMode需要设置为singleTop, 设置为singleTop以后在平台拉起游戏的场景下, 有可能会出现游戏Activity被拉起两个的情况, 所以游戏Activity的onCreate里面需要检测当前Activity是否是重复的游戏Activity, 如果是则要finish掉当前游戏Activity。 // 注意:游戏需要加上去重判断以及finish重复的实例的逻辑,否则可能发生重复拉起游戏的问题。 if (null != sActivity && !sActivity.equals(launchActivity)) { // TODO GAME 处理游戏被拉起的情况 YSDKApi.handleIntent(launchActivity.getIntent()); launchActivity.finish(); return; }else{ // TODO GAME YSDK初始化 YSDKApi.onCreate(launchActivity); // TODO GAME 处理游戏被拉起的情况 YSDKApi.handleIntent(launchActivity.getIntent()); } sActivity = launchActivity; 将WXEntryActivity.java放置在应用包名+.wxapi下面. 微信接入的Activity中有三处需要游戏自行修改,上面的注释有具体说明: ① WXEntryActivity的android:name需要修改为:包名.wxapi.WXEntryActivity ② WXEntryActivity的android:taskAffinity需要修改为:包名.diff ③ WXEntryActivity的data中android:scheme需要修改:微信appid 5、应用宝sdk初始化 【备注】:主Activity即是游戏运行的那个activity,不一定是设置intent-filter为:android.intent.action.MAIN的activity 1)在游戏第一个启动的Activity与游戏的主Activity的OnCreate()方法,都调用YSDK初始化函数onCreate(Activity activity),若是游戏一启动的Activity跟主Activity是同一个,只需调用一次 YSDKApi.onCreate(Activity activity); 2)在游戏主Activity中设置全局回调监听类YSDKUserListener YSDKApi.setUserListener(new YSDKUserListener()); YSDKApi.setBuglyListener(new YSDKBuglyListener()); 全局监听类的主要实现代码片段如下: public void OnLoginNotify(UserLoginRet ret) { L.d("OnLoginNotify ret.toString() = "+ret.toString()); if(activityRef.get() == null){ L.e("activityRef.get() == null"); return; } Activity act = activityRef.get(); switch (ret.flag) { case eFlag.Succ: //TODO 登录成功 if(YYBSdk.needCallback){ String nickName = ret.nick_name; String accessToken = ret.getAccessToken(); String openId = ret.open_id; String openType = ret.platform == ePlatform.QQ.val() ?"qq":"wx"; SaveUtil.saveOpenType(act,openType); SaveUtil.saveOpenId(act,openId); ChannelLoginResult loginResult = new ChannelLoginResult(); loginResult.setUserName(nickName); loginResult.setFinalResult(false); TreeMap channelData.put("opentype",openType); channelData.put("openid",openId); channelData.put("openkey",accessToken); loginResult.setChannelData(channelData); sCallback.onLoginSuccess(loginResult); //防止自动回调导致重复登录 YYBSdk.needCallback = false; }else { L.i("应用宝登录回调,此次不需要回调给cp"); } break; // 游戏逻辑,对登录失败情况分别进行处理 case eFlag.QQ_UserCancel: T.show(act,"用户取消授权,请重试"); yybLogout(); break; case eFlag.QQ_LoginFail: T.show(act,"QQ登录失败,请重试"); yybLogout(); break; case eFlag.QQ_NetworkErr: T.show(act,"QQ登录异常,请重试"); yybLogout(); break; case eFlag.QQ_NotInstall: T.show(act,"手机未安装手Q,请安装后重试"); yybLogout(); break; case eFlag.QQ_NotSupportApi: T.show(act,"手机手Q版本太低,请升级后重试"); yybLogout(); break; case eFlag.WX_NotInstall: T.show(act,"手机未安装微信,请安装后重试"); yybLogout(); break; case eFlag.WX_NotSupportApi: T.show(act,"手机微信版本太低,请升级后重试"); yybLogout(); break; case eFlag.WX_UserCancel: T.show(act,"用户取消授权,请重试"); yybLogout(); break; case eFlag.WX_UserDeny: T.show(act,"用户拒绝了授权,请重试"); yybLogout(); break; case eFlag.WX_LoginFail: T.show(act,"微信登录失败,请重试"); yybLogout(); break; case eFlag.Login_TokenInvalid: T.show(act,"您尚未登录或者之前的登录已过期,请重试"); yybLogout(); break; case eFlag.Login_NotRegisterRealName: // 显示登录界面 T.show(act,"您的账号没有进行实名认证,请实名认证后重试"); yybLogout(); break; default: // 显示登录界面 yybLogout(); break; } } /** * 登出 */ private void yybLogout() { new YYBSdk().logout(activityRef.get()); } public void OnWakeupNotify(WakeupRet ret) { L.d("OnLoginNotify ret.toString() = "+ret.toString()); // TODO GAME 游戏需要在这里增加处理异账号的逻辑 if (eFlag.Wakeup_YSDKLogining == ret.flag) { // 用拉起的账号登录,登录结果在OnLoginNotify()中回调 } else if (ret.flag == eFlag.Wakeup_NeedUserSelectAccount) { // 异账号时,游戏需要弹出提示框让用户选择需要登录的账号 L.e("异账号时,游戏需要弹出提示框让用户选择需要登录的账号"); } else if (ret.flag == eFlag.Wakeup_NeedUserLogin) { // 没有有效的票据,登出游戏让用户重新登录 L.e("没有有效的票据,登出游戏让用户重新登录"); yybLogout(); } else { yybLogout(); } } @Override public void OnRelationNotify(UserRelationRet relationRet) { String result = ""; result = result +"flag:" + relationRet.flag + "\n"; result = result +"msg:" + relationRet.msg + "\n"; result = result +"platform:" + relationRet.platform + "\n"; if (relationRet.persons != null && relationRet.persons.size()>0) { PersonInfo personInfo = (PersonInfo)relationRet.persons.firstElement(); StringBuilder builder = new StringBuilder(); builder.append("UserInfoResponse json: \n"); builder.append("nick_name: " + personInfo.nickName + "\n"); builder.append("open_id: " + personInfo.openId + "\n"); builder.append("userId: " + personInfo.userId + "\n"); builder.append("gender: " + personInfo.gender + "\n"); builder.append("picture_small: " + personInfo.pictureSmall + "\n"); builder.append("picture_middle: " + personInfo.pictureMiddle + "\n"); builder.append("picture_large: " + personInfo.pictureLarge + "\n"); builder.append("provice: " + personInfo.province + "\n"); builder.append("city: " + personInfo.city + "\n"); builder.append("country: " + personInfo.country + "\n"); result = result + builder.toString(); } else { result = result + "relationRet.persons is bad"; } L.d("OnRelationNotify" + result); } @Override public String OnCrashExtMessageNotify() { // 此处游戏补充crash时上报的额外信息 L.d(String.format(Locale.CHINA, "OnCrashExtMessageNotify called")); Date nowTime = new Date(); SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); return "new Upload extra crashing message for bugly on " + time.format(nowTime); } @Override public byte[] OnCrashExtDataNotify() { return null; } @Override public void OnPayNotify(PayRet ret) { L.d("OnPayNotify ret.toString() = "+ret.toString()); if(PayRet.RET_SUCC == ret.ret){ //支付流程成功 switch (ret.payState){ //支付成功 case PayRet.PAYSTATE_PAYSUCC: L.i("用户支付成功,支付金额"+ret.realSaveNum+";" + "使用渠道:"+ret.payChannel+";" + "发货状态:"+ret.provideState+";" + "业务类型:"+ret.extendInfo+";建议查询余额:"+ret.toString()); sCallback.onPaySuccess(new PayResult("",ret.ysdkExtInfo,false,true)); break; //取消支付 case PayRet.PAYSTATE_PAYCANCEL: L.i("用户取消支付:"+ret.toString()); sCallback.onPayFail("用户取消支付"); break; //支付结果未知 case PayRet.PAYSTATE_PAYUNKOWN: L.i("用户支付结果未知,建议查询余额:"+ret.toString()); sCallback.onPayFail("用户支付结果未知"); break; //支付失败 case PayRet.PAYSTATE_PAYERROR: L.i("支付异常"+ret.toString()); sCallback.onPayFail("支付异常"); break; } }else{ switch (ret.flag){ case eFlag.Login_TokenInvalid: T.show(activityRef.get(),"登录已过期,请重新登录"); yybLogout(); break; case eFlag.Pay_User_Cancle: //用户取消支付 L.i("用户取消支付:"+ret.toString()); sCallback.onPayFail("用户取消支付"); break; case eFlag.Pay_Param_Error: T.show(activityRef.get(),"支付失败,参数错误"); sCallback.onPayFail("支付失败,参数错误"); break; case eFlag.Error: default: T.show(activityRef.get(),"支付异常"); sCallback.onPayFail("支付异常"); break; } } } 2)游戏需要在主Activity的onResume方法中调用onResume(Activity activity) YSDKApi.onResume(Activity activity); 3)游戏需要在主Activity的onPause方法中调用onPause(Activity activity) YSDKApi.onPause(Activity activity); 4)游戏需要在主Activity的onStop方法中调用onStop(Activity activity) YSDKApi.onStop(Activity activity); 5)游戏需要在主Activity的onDestroy方法中调用onDestroy(Activity activity) YSDKApi.onDestroy(Activity activity); 6)游戏需要在主Activity的onRestart方法中调用onRestart(Activity activity) YSDKApi.onRestart(Activity activity); 7)游戏需要在第一个启动 的Activity的onNewIntent和onCreate方法中调用handleIntent(Intent intent) YSDKApi.handleIntent(this.getIntent()); 8)游戏需要在主Activity的onActivityResult方法中调用onActivityResult (int requestCode, int resultCode, Intent data) YSDKApi. onActivityResult(requestCode, resultCode,data); 三、微信、手Q登录接入 1、微信登录 YSDKApi.login(ePlatform.WX); 2、手Q登录 YSDKApi.login(ePlatform.QQ); 3、微信、手Q登录的结果都会回调到onCreate方法设置的全局回调监听类的OnLoginNotify(UserLoginRet ret)方法中,可以从传进来的UserLoginRet re获取到如下信息: String nickName = ret.nick_name; String accessToken = ret.getAccessToken(); String openId = ret.open_id; String openType = ret.platform == ePlatform.QQ.val() ?"qq":"wx"; int flag = ret.flag; String msg = ret.msg; String pf = ret.pf; String pf_key = ret.pf_key; 4、也可以在登录之后,在其他任何地方调用YSDKApi.getLoginRecord(ret)来获取登录信息 UserLoginRet ret = new UserLoginRet(); int platform = YSDKApi.getLoginRecord(ret); String accessToken = ret.getAccessToken(); String payToken = ret.getPayToken(); String openid = ret.open_id; int flag = ret.flag; String msg = ret.msg; String pf = ret.pf; String pf_key = ret.pf_key; 5、登出 YSDKApi.logout(); 6、注意事项: 1)游戏最后一次调用了YSDKApi.onCreate的Activity的onActivityResult是否调用了YSDKApi.onActivityResult。部分游戏有多个activity,确保要最后一个初始化的Activity里面调用onActivityResult。 2)YSDK会在三种情况下(每次游戏启动、后台运行一分钟以上切换回前台、在前台持续运行30分钟以上)将验证登录结果通过loginNotify回调给游戏,所以,游戏注意不要重复给回调,以防导致游戏重新加载,解决方案:可以在游戏调用login接口时候设置一个标识isLogin = true;然后在loginNotify判断到isLogin为true就回调给游戏,并将isLogin设置为false;这样就可以防止会给游戏回调多次导致游戏重复加载 3)微信需要调用‘SDK工具接口’的isPlatformInstalled(ePlatform platform);来判断一下玩家手机是否安装了微信,若是没有安装则不显示微信登录方式 boolean isInstall = YSDKApi.isPlatformInstalled(ePlatform.WX); 4)登录之后可以调用‘SDK工具接口’中的接口获取pf 、pfkey String pfkey = YSDKApi.getPfKey(); String pf = YSDKApi.getPf(); 四、米大师支付 1、网游接入的支付模式是:游戏币托管模式 1)玩家点击充值支付之后,玩家对应支付的金额就会按在应用宝开发者后台配置的兑换比例转换为对应的玩家账户余额保存在应用宝服务器中,服务端可以通过get_balance_m接口获取到这个余额 2)玩家支付成功之后,应用宝sdk会回调到全局监听类的OnPayNotify(PayRet ret)方法,客户端可以在这个方法中接收到支付成功与否的结果 3)客户端在接收到应用宝sdk支付成功的回调之后,就需要通知服务端通过get_balance_m去查询用户的余额是否增加来确认是否真的支付成功 4)若是查询玩家余额确实有增加,则服务端调用pay_m来扣除用户账户的余额,若是扣款成功,则通知游戏服务器给玩家发放对应的游戏道具/游戏币之类 2、支付接口 /** 充值游戏币 @param zoneId 大区id,一般默认传“1”即可 @param saveValue 充值数额,充值数额:可以是玩家需要充值的金额-玩家账号的余额 @param isCanChange 设置的充值数额是否可改,一般传false即可 @param resData 代币图标的二进制数据,图标的byte格式数据,随便传即可 @param ysdkExtInfo 透传参数,在回调的ret.ysdkExtInfo可以获取到这个值 @param listener 充值回调 / void recharge(String zoneId, String saveValue, boolean isCanChange,byte[] resData,String ysdkExtInfo,PayListener pListener); 示例: PayParam payParam = orderParam.getPayParam(); String zoneId = "1"; boolean isCanChange = false; ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] appResData = baos.toByteArray(); if(mYybSdkCallback == null){ mYybSdkCallback = new YYBSdkCallback(activity,sCallback); } String extraResult = orderParam.getExtraResult(); String amount = ""; String balance= ""; try { JSONObject extraJson = new JSONObject(extraResult); amount = extraJson.optString("amount"); balance = extraJson.optString("balance"); } catch (JSONException e) { e.printStackTrace(); } if (TextUtils.isEmpty(amount)) { amount = String.valueOf(payParam.getPrice()*payParam.getCount()); }else { // 说明上一单没有通知成功,还有余额可以直接抵扣 if("0".equals(amount)){ PayRet payRet = new PayRet(); payRet.payState = PayRet.PAYSTATE_PAYSUCC; payRet.ret = PayRet.RET_SUCC; payRet.ysdkExtInfo = orderParam.getSswlOrderId(); payRet.realSaveNum = payParam.getPrice()*payParam.getCount(); mYybSdkCallback.OnPayNotify(payRet); return; } } YSDKApi.recharge(zoneId, amount,isCanChange,appResData,orderParam.getSswlOrderId(),mYybSdkCallback); 注意事项: ①上述的第3步客户端通知服务端时候,可能因网络波动而出现通知失败的情况,所以,需要在收到应用宝sdk支付成功的通知之后,先把这笔订单在本地保存起来,然后再发起请求通知服务端支付成功,要是接收到服务端返回成功的通知,则可把本地保存那笔订单删除,若是等待服务端超时,则尝试重复发起请求,若是依然失败,则等待下次启动应用时候,再发起通知请求,确保能够通知到服务端支付结果 五、接入问题解决 1、微信、手Q登录有问题的,可以参照这里排查:登录接入常见问题 2、登录错误码的含义说明见:链接 3、支付有问题,重点看看:链接 4、YSDK接入常见问题之Android接入 六、前方高能全网唯一的接入问题总结 1、若是发现qq授权登陆之后没有收到的登录成功回调,那么有可能是.so文件没拷贝对,armeabi下的.so可以拷贝一份到armeabi-v7,各个cup架构目录下的.so文件数量一定要一样 2、若是发现qq授权登陆之后返回1002,那么有可能是api23之后没有授权或者在AndroidManifest里边没有把应用的名称改为与QQ开放平台上创建应用的名称相同或者未将移动应用关联到腾讯开放平台(QQ互联开放平台)或关联了但处于审核中,要等审核过了才能QQ授权登录 3、若发现qq授权页点击“授权并登录”按钮,没有成功跳转回去游戏,而是授权页的按钮文字变为“重新拉取授权信息”,那么可能是签名信息或者包名不对(Android查看应用签名方法) 4、要是YSDKApi.getLoginRecord(ret);获取到的UserLoginRet对象的openid之类的为空,那么有可能是生命周期函数没有调用 5、要是查询余额时候,提示“pfkey not valid”,有可能是openKey不对,openkey:手Q登陆时传手Q登陆回调里获取的paytoken值,微信登陆时传微信登陆回调里获取的传access_token值。 6、服务端去查询余额的也有可能会出现返回:1018,这时候可能是登录票据失效,需要重新登录再发起请求 7、如果游戏的Activity为Launch Activity, 则需要在游戏Activity声明中添加android:configChanges="orientation|screenSize|keyboardHidden", 否则可能造成登录没有回调 8、如果QQ授权登录返回之后,没有任何报错,也没有任何的登录回调,那么有可能是生命周期函数没有调用 9、新申请的应用宝参数,需要跑一遍测试环境,即打开ysdkconf.ini,去掉YSDK_URL=https://ysdktest.qq.com前的分号,否则登录会提示“client request's app is not existed”