众所周知,应用宝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)权限配置
2)组件配置
注意:手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
中设置全局回调监听类YSDKUserListenerYSDKApi.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 = new 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
”