U8SDK技术博客:http://www.uustory.com/,欢迎来坐坐。
百度传课已经停运,最新U8SDK视频教程已经转移至B站:U8SDK最新视频教程
题记:很多做游戏开发的人,估计都或多或少地接过渠道SDK,什么UC,当乐,91,小米,360......据统计国内市场当前不下于100家渠道,还包括一些没有SDK的小渠道。每个渠道SDK接入的方法呢,多是大同小异。但是,正是这些小异,又让SDK的接入,产生了无穷无尽的变数。所以,接入SDK之前,如果你没有经验,或者没有被SDK坑过,那么当你看到这系列文章的时候,你很幸运,你可以避免这一切了。如果你之前被坑过,而且还在继续被坑着,那么现在,就是你解脱的时刻。
先将之前的每一篇做个索引,方便亲们查阅:
上一篇我们展示了游戏在需要接入SDK时的调用。它仅仅调用抽象层提供的各个插件的单例包装类就可以了。而每个单例包装类里面,就是引用对应的插件接口。那么这个接口的实例化是怎么做了,上上一篇,我们说到,他是从asssets下面读取的配置。然后根据配置里填写的完整类名来实例化的。这个实现类就是在接入各个渠道的时候实现的。那么,本篇我们就来以UC渠道为例,将其接入到我们的U8 SDK中来。
首先,根据UC提供的文档,我们知道,需要接入登陆和支付两大功能。那么,对应的,在我们这里,我们就需要两个类,一个类实现抽象SDK的IUser接口,一个实现抽象SDK的IPay接口。那么,我们这里再回顾下,实现类需要注意的地方。在抽象层讲解的文章中,我们看各个插件的实例化过程:
public Object initComponent(int type){
Class localClass = null;
try {
if(!isSupportComponent(type)){
Log.e("U8SDK", "The config of the U8SDK is not support plugin type:"+type);
return null;
}
String name = getComponentName(type);
localClass = Class.forName(name);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
try {
return localClass.getDeclaredConstructor(new Class[]{Activity.class}).newInstance(new Object[]{U8SDK.getInstance().getContext()});
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
我们可以看到,我们是调用实现类的带有一个Activity参数的构造函数进行实例化的。这就要求我们在实现插件接口的时候,需要定义一个带有Activity参数的构造函数。我们看看UC的登陆实现类和支付实现类。
package com.u8.sdk;
import com.u8.sdk.IUser;
import com.u8.sdk.U8SDK;
import android.app.Activity;
public class UCUser implements IUser {
private Activity context;
public UCUser(Activity context){
this.context = context;
this.initSDK();
}
public void initSDK(){
UCSDK.getInstance().initSDK(this.context, U8SDK.getInstance().getSDKParams());
}
@Override
public void login() {
UCSDK.getInstance().login(this.context, U8SDK.getInstance().getSDKParams());
}
}
支付:
package com.u8.sdk;
import com.u8.sdk.IPay;
import com.u8.sdk.PayParams;
import android.app.Activity;
public class UCPay implements IPay {
private Activity context;
public UCPay(Activity context){
this.context = context;
}
@Override
public void pay(PayParams data) {
UCSDK.getInstance().pay(this.context, data);
}
}
我们先看上面的登陆插件实现类,可以看到它实现了login接口,同时,定义了一个以Activity为参数的构造函数。在login方法里面,通过调用UCSDK这个单例类的login来完成登陆界面的调用。同时,注意到,登陆实现类里面还有一个initSDK方法,在实例化的时候调用。这个是因为UC SDK要求必须在游戏刚开始运行的时候,就初始化SDK。
同样的,支付实现类里面,实现了pay方法,也是通过调用UCSDK单例来完成支付界面的调用。
那么,大家看到,现在所有的问题都简单化了。就是需要在UCSDK这个单例类里面来实现所有UC SDK需要实现的功能。我们先看下UCSDK里面的代码:
package com.u8.sdk;
import org.json.JSONException;
import org.json.JSONObject;
import com.u8.sdk.ActivityCallbackAdapter;
import com.u8.sdk.LoginResult;
import com.u8.sdk.PayParams;
import com.u8.sdk.SDKConfigData;
import com.u8.sdk.U8Code;
import com.u8.sdk.U8SDK;
import com.u8.sdk.utils.SDKTools;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.util.Log;
import cn.uc.gamesdk.UCCallbackListener;
import cn.uc.gamesdk.UCCallbackListenerNullException;
import cn.uc.gamesdk.UCFloatButtonCreateException;
import cn.uc.gamesdk.UCGameSDK;
import cn.uc.gamesdk.UCGameSDKStatusCode;
import cn.uc.gamesdk.UCLogLevel;
import cn.uc.gamesdk.UCLoginFaceType;
import cn.uc.gamesdk.UCOrientation;
import cn.uc.gamesdk.info.FeatureSwitch;
import cn.uc.gamesdk.info.GameParamInfo;
import cn.uc.gamesdk.info.OrderInfo;
import cn.uc.gamesdk.info.PaymentInfo;
public class UCSDK {
enum SDKState{
StateDefault,
StateIniting,
StateInited,
StateLogin,
StateLogined
}
private SDKState state = SDKState.StateDefault;
private boolean loginAfterInit = false;
private ProgressDialog loadingActivity = null;
private static UCSDK instance;
private UCLogLevel logLevel = UCLogLevel.DEBUG;
private int cpId;
private int gameId;
private int channel;
private boolean debugMode = true;
private UCSDK(){
}
public static UCSDK getInstance(){
if(instance == null){
instance = new UCSDK();
}
return instance;
}
private void parseSDKParams(SDKConfigData params) {
this.gameId = params.getInt("UCGameId");
this.cpId = params.getInt("UCCpId");
this.channel = U8SDK.getInstance().getCurrChannel();
if(this.channel <= 0){
this.channel = params.getInt("UCChannel");
}
this.debugMode = params.getBoolean("UCDebugMode");
}
public void initSDK(Activity context, SDKConfigData params)
{
this.parseSDKParams(params);
this.initSDK(context);
}
public void login(Activity context, SDKConfigData params){
this.parseSDKParams(params);
this.login(context);
}
/**
* 必接功能<br>
* sdk初始化功能<br>
*/
public void initSDK(final Activity context) {
this.state = SDKState.StateIniting;
try {
showWaitDialog(context);
U8SDK.getInstance().setActivityCallback(new ActivityCallbackAdapter(){
@Override
public void onBackPressed() {
ucSdkExit(context);
}
@Override
public void onDestroy() {
ucSdkDestoryFloatButton(context);
}
});
if(loginAfterInit){
//showProgressDialog(context);
}
GameParamInfo gpi = new GameParamInfo();// 下面的值仅供参考
gpi.setCpId(this.cpId);
gpi.setGameId(this.gameId);
gpi.setServerId(0); // 服务器ID可根据游戏自身定义设置,或传入0
// 在九游社区设置显示查询充值历史和显示切换账号按钮,
// 在不设置的情况下,默认情况情况下,生产环境显示查询充值历史记录按钮,不显示切换账户按钮
// 测试环境设置无效
gpi.setFeatureSwitch(new FeatureSwitch(true, false));
// 设置SDK登录界面为横屏,个人中心及充值页面默认为强制竖屏,无法修改
// UCGameSDK.defaultSDK().setOrientation(UCOrientation.LANDSCAPE);
// 设置SDK登录界面为竖屏
UCGameSDK.defaultSDK().setOrientation(UCOrientation.LANDSCAPE);
// 设置登录界面:
// USE_WIDGET - 简版登录界面
// USE_STANDARD - 标准版登录界面
UCGameSDK.defaultSDK().setLoginUISwitch(UCLoginFaceType.USE_WIDGET);
// setUIStyle已过时,不需调用。
// UCGameSDK.defaultSDK().setUIStyle(UCUIStyle.STANDARD);
UCGameSDK.defaultSDK().initSDK(context, this.logLevel,
this.debugMode, gpi,
new UCCallbackListener<String>() {
@Override
public void callback(int code, String msg) {
Log.e("UCGameSDK", "UCGameSDK初始化接口返回数据 msg:" + msg
+ ",code:" + code + ",debug:"
+ debugMode + "\n");
hideWaitDialog(context);
if(code == UCGameSDKStatusCode.SUCCESS){
if(state != SDKState.StateIniting){
U8SDK.getInstance().onResult(U8Code.CODE_INIT_FAIL, "uc sdk init fail. not initing. curr state is:"+state);
return;
}
state = SDKState.StateInited;
U8SDK.getInstance().onResult(U8Code.CODE_INIT_SUCCESS, "uc sdk init success");
if(loginAfterInit){
login(context);
}
}else{
hideWaitDialog(context);
state = SDKState.StateDefault;
U8SDK.getInstance().onResult(U8Code.CODE_INIT_FAIL, "uc sdk init fail.err:"+msg);
}
}
});
} catch (UCCallbackListenerNullException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private LoginResult encodeLoginResult(String sid){
String channel = "" + getChannel();
String expansion = "";
JSONObject ext = new JSONObject();
try {
ext.put("ext", "u8");
expansion = ext.toString();
} catch (JSONException e) {
e.printStackTrace();
}
return new LoginResult(sid, channel, expansion);
}
public void login(final Activity context){
if(state.ordinal() < SDKState.StateInited.ordinal()){
loginAfterInit = true;
initSDK(context);
return;
}
if(!SDKTools.isNetworkAvailable(context)){
U8SDK.getInstance().onResult(U8Code.CODE_NO_NETWORK, "The network now is unavailable");
return;
}
try {
state = SDKState.StateLogin;
UCGameSDK.defaultSDK().login(context, new UCCallbackListener<String>() {
@Override
public void callback(int code, String msg) {
Log.e("UCGameSDK", "UCGameSdk登录接口返回数据:code=" + code
+ ",msg=" + msg);
// 登录成功。此时可以获取sid。并使用sid进行游戏的登录逻辑。
// 客户端无法直接获取UCID
if (code == UCGameSDKStatusCode.SUCCESS) {
state = SDKState.StateLogined;
String sid = UCGameSDK.defaultSDK().getSid();
U8SDK.getInstance().onResult(U8Code.CODE_LOGIN_SUCCESS, sid);
LoginResult result = encodeLoginResult(sid);
U8SDK.getInstance().onLoginResult(result);
// 执行悬浮按钮创建方法
ucSdkCreateFloatButton(context);
// 执行悬浮按钮显示方法
ucSdkShowFloatButton(context);
}else{
state = SDKState.StateInited;
U8SDK.getInstance().onResult(U8Code.CODE_LOGIN_FAIL, msg);
}
// 登录失败。应该先执行初始化成功后再进行登录调用。
if (code == UCGameSDKStatusCode.NO_INIT) {
// 没有初始化就进行登录调用,需要游戏调用SDK初始化方法
initSDK(context);
}
// 登录退出。该回调会在登录界面退出时执行。
if (code == UCGameSDKStatusCode.LOGIN_EXIT) {
// 登录界面关闭,游戏需判断此时是否已登录成功进行相应操作
}
}
});
} catch (UCCallbackListenerNullException e) {
e.printStackTrace();
}
}
private PaymentInfo decodePayParams(PayParams payParams){
Log.i("UCSDK", "The payParams is "+payParams.toString());
PaymentInfo pInfo = new PaymentInfo(); // 创建Payment对象,用于传递充值信息
// 设置充值自定义参数,此参数不作任何处理,
// 在充值完成后,sdk服务器通知游戏服务器充值结果时原封不动传给游戏服务器传值,字段为服务端回调的callbackInfo字段
if(!SDKTools.isNullOrEmpty(payParams.getExtension())){
pInfo.setCustomInfo(payParams.getExtension());
}
// 非必选参数,可不设置,此参数已废弃,默认传入0即可。
// 如无法支付,请在开放平台检查是否已经配置了对应环境的支付回调地址,如无请配置,如有但仍无法支付请联系UC技术接口人。
pInfo.setServerId(0);
pInfo.setRoleId(payParams.getRoleId()); // 设置用户的游戏角色的ID,此为必选参数,请根据实际业务数据传入真实数据
pInfo.setRoleName(payParams.getRoleName()); // 设置用户的游戏角色名字,此为必选参数,请根据实际业务数据传入真实数据
pInfo.setGrade(""+payParams.getRoleLevel()); // 设置用户的游戏角色等级,此为可选参数
// 非必填参数,设置游戏在支付完成后的游戏接收订单结果回调地址,必须为带有http头的URL形式。
//pInfo.setNotifyUrl("http://192.168.1.1/notifypage.do");
// 当传入一个amount作为金额值进行调用支付功能时,SDK会根据此amount可用的支付方式显示充值渠道
// 如你传入6元,则不显示充值卡选项,因为市面上暂时没有6元的充值卡,建议使用可以显示充值卡方式的金额
pInfo.setAmount(payParams.getPrice());// 设置充值金额,此为可选参数
return pInfo;
}
public void pay(Activity context, PayParams data){
try {
if(!isInited()){
U8SDK.getInstance().onResult(U8Code.CODE_INIT_FAIL, "The sdk is not inited.");
return;
}
if(!SDKTools.isNetworkAvailable(context)){
U8SDK.getInstance().onResult(U8Code.CODE_NO_NETWORK, "The network now is unavailable");
return;
}
PaymentInfo pInfo = decodePayParams(data);
UCGameSDK.defaultSDK().pay(context, pInfo,
new UCCallbackListener<OrderInfo>() {
@Override
public void callback(int code, OrderInfo orderInfo) {
if (code == UCGameSDKStatusCode.NO_INIT) {
// 没有初始化就进行登录调用,需要游戏调用SDK初始化方法
U8SDK.getInstance().onResult(U8Code.CODE_INIT_FAIL, "The SDK is not inited");
}
if (code == UCGameSDKStatusCode.SUCCESS) {
// 成功充值
if (orderInfo != null) {
String ordereId = orderInfo.getOrderId();// 获取订单号
float orderAmount = orderInfo.getOrderAmount();// 获取订单金额
int payWay = orderInfo.getPayWay();
String payWayName = orderInfo.getPayWayName();
System.out.print(ordereId + "," + orderAmount + ","
+ payWay + "," + payWayName);
U8SDK.getInstance().onResult(U8Code.CODE_PAY_SUCCESS, "uc pay success.");
}
}else{
U8SDK.getInstance().onResult(U8Code.CODE_PAY_FAIL, "uc pay failed.");
}
if (code == UCGameSDKStatusCode.PAY_USER_EXIT) {
// 用户退出充值界面。
U8SDK.getInstance().onResult(U8Code.CODE_PAY_FAIL, "The user is exit.");
}
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 必接功能<br>
* 悬浮按钮创建及显示<br>
* 悬浮按钮必须保证在SDK进行初始化成功之后再进行创建需要在UI线程中调用<br>
*/
private void ucSdkCreateFloatButton(final Activity context) {
U8SDK.getInstance().runOnMainThread(new Runnable() {
public void run() {
try {
// 创建悬浮按钮。该悬浮按钮将悬浮显示在GameActivity页面上,点击时将会展开悬浮菜单,菜单中含有
// SDK 一些功能的操作入口。
// 创建完成后,并不自动显示,需要调用showFloatButton(Activity,
// double, double, boolean)方法进行显示或隐藏。
UCGameSDK.defaultSDK().createFloatButton(context,
new UCCallbackListener<String>() {
@Override
public void callback(int statuscode, String data) {
Log.d("SelectServerActivity`floatButton Callback",
"statusCode == " + statuscode
+ " data == " + data);
}
});
} catch (UCCallbackListenerNullException e) {
e.printStackTrace();
} catch (UCFloatButtonCreateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
/**
* 必接功能<br>
* 悬浮按钮显示<br>
* 悬浮按钮显示需要在UI线程中调用<br>
*/
private void ucSdkShowFloatButton(final Activity context) {
U8SDK.getInstance().runOnMainThread(new Runnable() {
public void run() {
// 显示悬浮图标,游戏可在某些场景选择隐藏此图标,避免影响游戏体验
try {
UCGameSDK.defaultSDK().showFloatButton(context, 100, 50, true);
} catch (UCCallbackListenerNullException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
/**
* 必接功能<br>
* 悬浮按钮销毁<br>
* 悬浮按钮销毁需要在UI线程中调用<br>
*/
private void ucSdkDestoryFloatButton(final Activity context) {
U8SDK.getInstance().runOnMainThread(new Runnable() {
public void run() {
// 悬浮按钮销毁功能
UCGameSDK.defaultSDK().destoryFloatButton(context);
}
});
}
/**
* 必接功能<br>
* 当游戏退出前必须调用该方法,进行清理工作。建议在游戏退出事件中进行调用,必须在游戏退出前执行<br>
* 如果游戏直接退出,而不调用该方法,可能会出现未知错误,导致程序崩溃<br>
*/
private void ucSdkExit(final Activity context) {
UCGameSDK.defaultSDK().exitSDK(context, new UCCallbackListener<String>() {
@Override
public void callback(int code, String msg) {
if (UCGameSDKStatusCode.SDK_EXIT_CONTINUE == code) {
// 此加入继续游戏的代码
} else if (UCGameSDKStatusCode.SDK_EXIT == code) {
// 在此加入退出游戏的代码
ucSdkDestoryFloatButton(context);
System.exit(0);
}
}
});
}
public int getChannel(){
return this.channel;
}
public boolean isInited(){
return this.state.ordinal() >= SDKState.StateInited.ordinal();
}
public boolean isLogined(){
return this.state.ordinal() >= SDKState.StateLogined.ordinal();
}
private void showWaitDialog(Activity context){
if(loadingActivity != null){
return;
}
loadingActivity = new ProgressDialog(context);
loadingActivity.setIndeterminate(true);
loadingActivity.setCancelable(true);
loadingActivity.setMessage("正在初始化,请稍后...");
loadingActivity.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface arg0) {
state = SDKState.StateDefault;
}
});
loadingActivity.show();
}
private void hideWaitDialog(Activity context){
if(loadingActivity == null){
return;
}
loadingActivity.dismiss();
loadingActivity = null;
}
}
这个类似乎有点庞大。但是,没关系。我们慢慢地剥皮抽丝。首先,从我们实现类的调用入口进入。首先是initSDK。我们看看initSDK做了些什么:
initSDK首先进行了参数的解析:看UCUser的initSDK方法里面,我们通过U8SDK.getInstance().getSDKParams()方法获取到了当前SDK需要的参数。那么这些参数是什么呢。这些参数就是每个SDK在运行时,需要传入的参数。也就是你在接入渠道SDK之前,向渠道方申请的appID,appKey等信息。大家这里可能会有疑问,这些信息按说各个渠道都是不同的。这里怎么能通过抽象层获得到呢?说的没错,抽象层如何得到呢?这个要归功于我们后面要说的打包工具,我们会通过一个巧妙的设计来完成这一工作。这里你先知道,所有渠道的接入需要的appID等信息,这里都直接通过U8SDK.getInstance().getSDKParams()方法来获取就可以了。
紧接着,我们看到initSDK里面设置了U8SDK的IActivityListener接口。这是因为UC的SDK需要在activity的某些系统事件中完成相应的工作。
接下来,大家可以看到就是按照UC的Demo里面往下走就可以了。关键是初始化好之后的回调里,不管初始化成功还是失败,最好调用下U8SDK.getInstance().onResult()方法来向抽象层抛出一个状态信息。这样你在debug调试的时候,在游戏层实现的接口里加上输出或者Toast就可以及时看到这些状态信息,方便调试和查错。
然后,我们看login方法,login方法也一样,直接调用UC提供的登陆方法,关键是在登陆回调中,我们除了调用onResult方法之外,如果登陆成功,我们还需要调用U8SDK.getInstance().onLoginResult(result)方法。这个是因为游戏层会在onLoginResult中来处理登陆成功的回调,同时需要将SDK返回的数据封装到LoginResult类中。
最后,我们来看pay方法,pay方法也一样的简单。只是多了支付参数的解析。之前在说抽象层的实现时,我们说到了支付参数PayParams。这个类里所有的信息是游戏里面可以提供的。但是,每个渠道需要的可能各不相同。所以,这里各个渠道需要根据渠道自身的需要,按需所取。比如这里,我们通过decodePayParams方法从PayParams里面取到UC需要的参数。然后,同样的,我们在回调中调用onResult方法来提示状态信息。
对于其他的方法,向什么隐藏悬浮图标,显示悬浮图标都在UCSDK这里接入就可以了。需要在对应的地方加以调用。
那么,到这里我们UC SDK就算接入完毕了。接好之后他的目录结构大致如下:
这里大家也许看到了工程里面,有sdk_confgi.xml和sdk_manifest.xml。这里,我们先留个悬念。后面我们说到打包工具的时候,在回头来说。因为这里大家可以看到,我们没有提SDK需要在AndroidManifest.xml中设置的权限信息和一些Activity或者Service等数据。也不知道这样接好了之后,然后怎么用呢,怎么测试,怎么维护呢?所有这些我们后面来说。
本文作者:小黑