unity anysdk android,Unity3d Android SDK接入解析(四)通用的Android SDK接入中间件

一、前言

接入Android SDK正式告一段落,在这段时间里面,依次接入了华为、应用宝、小米、360等等大大小小十来个SDK,也算对Unity接入渠道SDK有了较为全面的理解,对各个渠道的坑也算深有体会。。。。在接入过程中时间比较紧张,没办法抽空来进行总结深思。今天正好有空,便对之前的接入SDK的代码进行了一次重构,写了一个比较通用的Unity接入Android SDK的中间件,前人栽树,后人乘凉。

进入正题

如果有对一些只是有疑问的,可以看看我之前的三篇文章:

传送门:

二、关于中间件的一些思考

2.1 为什么不用第三方平台

为什么要自己做一个Unity接入Android SDK的中间件呢?市面上例如Anysdk、易接这种第三方渠道是可以满足接入一次,生成大部分的SDK的,但是由于大渠道的审查越来越严格,已经禁止了这种第三方的平台SDK。因此,这些大渠道则必须由我们自己来接入了(不过二、三线渠道仍然是可以用第三方进行接入的),如果每个渠道都使用一个单独工程来管理,这无疑是一种非常浪费时间而且难以维护的事情,因此我想做的就是一个Unity的接入Android SDK的插件,所有的SDK的逻辑都封装好在Android层面,不同的游戏都可以按照相同的规则来接入进来,只需要调用通用的接口,准备好对应的资源即可。

2.2 怎么样做方便

我希望把这个中间件做的尽量能够通用,而且能够方便拆分、迭代。对Unity层面逻辑透明,只需要关注接口调用的时机和传入的参数。

通用,形式简单

只关注SDK业务逻辑

调用简单

—— 通用,形式简单 ——

先说说通用吧:

对于Unity游戏来说,通常以Plugins的形式接入SDK比较方便,所以从这个思路出发,中间件的形式,我决定做成了jar包,而不是一个Library的工程,jar包里面只包含纯代码,没有资源和任何配置文件,调用方只用把jar包放在 Plugins/Android/libs 里面就可以使用了。对于不同游戏来说,没有差别。

最终的形式,就是一个uasdkinter的jar包,所有的SDK的逻辑会集成在这个jar里面。

95353102_1

形式简单的也有一层含义是,方便集成和拆分:

因此在package的设计上,每个渠道独立一个package放渠道代码,理论上我们导出jar包的时候,直接把所有渠道的代码都打进jar包就可以了,因为每次代码会根据传入的channel执行唯一的渠道,其他代码也就放在那了。

但是有些渠道会进行代码检测,例如360,会检测到包内含有小米支付相关的代码,审核不过,对应我们只需要在打jar的时候,勾选去掉对应的package就可以了。(因为是重构的代码框架,并没有包含很多sdk)

95353102_2

—— 只关注SDK业务逻辑 ——

中间件本身,只包含设计思想和一个简洁的框架。在接入渠道SDK的时候,我们只需要把渠道的SDK代码以一定的规则加入到中间件中即可。

所以这个中间件必须要健壮,框架写好之后,再添加新的SDK代码必须比较方便,无需对框架做大的改动。

因此我使用接口来实现,每个渠道SDK有两个class,一个管理账号信息,一个管理支付信息,账号与支付分离。

账号接口:

public interface UAGameInterf {

// 初始化

public void init(JSONObject sJson);

// 登录

public void login(JSONObject sJson);

// 登出

public void logout();

// 退出游戏

public void exit();

// 初始化参数检查

public boolean initParams(JSONObject sJson);

// 设置生命周期的函数

public void lifeCycle(int status);

// 存储用户信息

public void upUserInfo(JSONObject sJson);

}1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

231

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

支付接口:

public interface UAPayInterf {

// 支付

public void pay();

// 初始化参数

public boolean initParams(JSONObject sJson);

}1

2

3

4

5

6

71

2

3

4

5

6

7

在添加SDK的时候,只需要新建账号class和支付的class,分别实现对应的接口,而不用管外层是怎么调用的,消息是怎么返回Unity的,而只用具体的实现每个接口即可,做到只关注SDK的业务逻辑(具体接口的设计说明后面详叙)。

—— 调用简单 ——

调用上,C#初始化“包名+类名”的AndroidJavaClass对象,使用这个对象来调用对应功能,区别于新建一个Activity继承UnityPlayerActivity 的模式,这个方法避免了一系列的蛋疼的问题(例如中间件工程需要和游戏工程的包名一样)

C#的调用:

中间件工程的包名是: com.uainter.main

接口类名是:UAMain

因此可以在C#创建AndroidJavaClass对象:

AndroidJavaClass ajc_SDKCall ajc_SDKCall = new AndroidJavaClass("com.uainter.main.UAMain");11

为了方便调用,把对外的接口都做成了静态方法,所以用CallStatic去调用,因为每个渠道需要的参数类型和参数个数不确定,因为把传入参数定义成了一个Json。

例如调用小米渠道的Init方法:

小米渠道需要3个参数:appid、appkey、islandscape(登录与支付横屏还是竖屏显示)

string json = "{'channel':'11','debugmode':1,'appid':'xxx','appkey':'xxx','islandscape':false}";

ajc_SDKCall.CallStatic("uaInit",json);1

21

2

Login方法:

string json = "{}";

ajc_SDKCall.CallStatic("uaLogin",json);1

21

2

对于每个可能会有参数传入的方法,都设置了一个json对象作为参数,例如login方法,在接入应用宝的时候,需要在json数据中传入一个platform来判断是登录微信还是QQ。

中间键暴露出的接口有以下几个:

95353102_3

2.3 一些特殊操作的思考与处理

Activity生命周期的处理

理论我希望做到不需要修改启动的Activity,所以Activity生命周期的处理,放在了C#去控制,Android提供接口public void lifeCycle(int status); 在这个接口里面处理渠道SDK需要做的生命周期操作。

例如华为:

(Android 代码)

public void lifeCycle(int status) {

if (getActivity() == null) {

DybGSdkUtil.E("还未Init初始化,不执行生命周期操作 ");

return;

}

switch (status) {

case DybGSdkConstants.onStart:

break;

case DybGSdkConstants.onResume:

BuoyOpenSDK.getIntance().showSmallWindow(getActivity());

break;

case DybGSdkConstants.onPause:

BuoyOpenSDK.getIntance().hideSmallWindow(getActivity());

BuoyOpenSDK.getIntance().hideBigWindow(getActivity());

break;

case DybGSdkConstants.onStop:

break;

case DybGSdkConstants.onDestroy:

OpenHwID.releaseResouce();

BuoyOpenSDK.getIntance().destroy(getActivity());

break;

default:

break;

}

}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

261

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

(C#调用)

void OnApplicationPause(bool isPause)

{

if (isPause) {

string json = "{'status':'3'}";

ajc_SDKCall.CallStatic("uaLifeCycle",json);

}

}

void OnApplicationFocus(bool isFocus)

{

if (isFocus)

{

if (ajc_SDKCall != null){

string json = "{'status':'1'}";

ajc_SDKCall.CallStatic("uaLifeCycle",json);

json = "{'status':'2'}";

ajc_SDKCall.CallStatic("uaLifeCycle",json);

}

}

}

void OnApplicationQuit()

{

string json = "{'status':'5'}";

ajc_SDKCall.CallStatic("uaLifeCycle",json);

}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

261

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

(对应的status和生命周期)

// Android Activity生命周期

public static final int onStart = 1;

public static final int onResume = 2;

public static final int onPause = 3;

public static final int onStop = 4;

public static final int onDestroy = 5;

public static final int onRestart = 6;1

2

3

4

5

6

71

2

3

4

5

6

7

实在遇到需要Activity的地方怎么处理?

只有在接入应用宝的时候,遇到了需要接入Activity的两个方法onNewIntent和onActivityResult时,需要接受qq和微信的回调,这种情况我也没有想到什么好办法,只有创建一个Activity,然后实现这个两个方法,并修改这个Activity为AndroidManifest里面的启动Activity。

遇到需要自定application的情况呢?

跟Activity类似,这个时候我们新建一个Application即可。

大部分的SDK方法需要在UI线程中调用

这个之前有说过,在这里只是列出来不详述了:

例如登录:

public static void uaLogin(String jsonString) {

try {

final JSONObject sJson = new JSONObject(jsonString);

final UAGameInterf uaManager = getSdkObj(sChannel);

activity.runOnUiThread(new Runnable() {

@Override

public void run() {

uaManager.login(sJson);

}

});

} catch (JSONException e) {

e.printStackTrace();

}

}1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

171

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

怎么发送消息回Unity

// 发送消息回Unity3d

public static void dybCallback(JSONObject rjson) {

UnityPlayer.UnitySendMessage(UAMain.callBackobj, UAMain.callBackFun,

rjson.toString());

}1

2

3

4

5

61

2

3

4

5

6

其中callBackobj 和 callBackFun,分别对应接收返回值的对象的名称和回调方法。(此处我是写死的常量,也可以通过在init中传入对应的key来动态的修改这两个值)

可以看到,返回的也是一个json,里面包括了一个“callbackType”的key用来判断是哪个接口回调的结果,例如Init回调:

JSONObject jsonObj = new JSONObject();

String code = "1";

jsonObj.put("callbackType", "Init");

jsonObj.put("code", code);

UAMain.dybCallback(jsonObj);1

2

3

4

51

2

3

4

5

三、中间件对外接口说明

uaInit

public static void uaInit(String jsonString)11

主要用于各个渠道SDK的初始化,传入的json字符串中,必须包含的是,debugMode和channel这两个key,channel是用于区分目前调用的是哪个渠道,debugMode是用于区分调试模式还是正式模式(一般SDK都会有两种模式),这里的debugMode我也用来作为显示日志的开关。剩下的key就要根据不同SDK所需要的不同的参数来传入。

uaLogin

public static void uaLogin(String jsonString)11

一般SDK不用传入jsonString,直接传一个空的json字符串“{}”即可,当有些SDK需要在Login功能中加上切换账号功能是,我会传一个type进来,用来判断此时的操作是登录还是切换账号。

uaLogout

public static void uaLogout()11

用于账号的退出,这个接口不需要参数。

uaExit

public static void uaExit()11

用于退出游戏,一般SDK会有一个弹出框来显示一些论坛或者相关的广告信息,这个接口也不需要参数。

uaUpUserInfo

public static void uaUpUserInfo11

用于信息的打点,就是报备一些信息,例如创建角色、角色升级、退出等等。

uaLifeCycle

public static void uaLifeCycle11

用于生命周期函数的调用。

uaPay

public static void uaPay11

用于支付。

四、后续思考

最初写这个框架的时候,大概花费了2天的时间,后续接入中遇到了一些问题,也对这个中间件进行了一些修改,总的来说,能够满足基本上市面上大部分sdk的接入(至少我现在没有遇到接入不了的)。重构之后更加的简洁了,删掉了不少无用的东西。

但是对于需要监听onNewIntent等函数的SDK,虽然可以处理(新建一个Activity去处理),却不太满意。在思考是否做成一个Activity形式的中间件更好(而不是一个Java的class),当然,还有生命周期上的处理,感觉很多东西都有优化的空间。

花了整整一天写的东西,也希望“爬虫们”在转发的同时,也留个原文链接,因为不仅仅是我想把我拥有的知识去分享给他人,我也希望从他人的到宝贵的意见,指出我的错误和不足,这才是我写这篇文章的用意,是作为一个程序员最珍贵的东西,在此谢谢。

如果还有不明白,可以到群里交流

unity3d-sdk交流Q群:113659593

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/** * 初始化SDK */ private static void initSDK(String appid, String appkey) { ProxySelector defaultProxySelector = ProxySelector.getDefault(); Proxy proxy = null; List<Proxy> proxyList = null; try { proxyList = defaultProxySelector.select(new URI( "http://www.google.it")); } catch (URISyntaxException e) { e.printStackTrace(); } if (proxyList != null && proxyList.size() > 0) { proxy = proxyList.get(0); Log.d(TAG, "Current Proxy Configuration: " + proxy.toString()); } AppInfo appInfo = new AppInfo(); appInfo.setAppId(appid);// 应用ID appInfo.setAppKey(appkey);// 应用Key appInfo.setCtx(ctx); /* * VersionCheckLevelNormal 版本检查失败可以继续进行游戏 VersionCheckLevelStrict * 版本检查失败则不能进入游戏 默认取值为VersionCheckLevelStrict */ appInfo.setVersionCheckStatus(AppInfo.VERSION_CHECK_LEVEL_STRICT); // 初始化SDK Commplatform.getInstance().Init(0, appInfo, new CallbackListener<Integer>() { @Override public void callback(final int paramInt, Integer paramT) { ctx.runOnUiThread(new Runnable() { @Override public void run() { Log.i(TAG, "Init paramInt = " + paramInt); // ok.setEnabled(true); LogUtil.send("初始化: " + paramInt); } }); } }); } /** * 用户登录 * */ public static void loginEx(Context context) { Bundle bundle = new Bundle(); bundle.putString("nounce", UUID.randomUUID().toString() .replace("-", "")); Commplatform.getInstance().LoginEx(context, bundle, new CallbackListener<Bundle>() { @Override public void callback(int resultCode, Bundle bundle) { if (resultCode == ErrorCode.COM_PLATFORM_SUCCESS) { // 完成参数验签 // 处理登录成功逻辑 // HomeActivity.show(ctx); // String uin= bundle.getString("uin"); LogUtil.send("登录成功 :"); // String uin= bundle.get("nounce").toString(); String uin = Commplatform.getInstance() .getLoginUin(); LogUtil.send("登录成功 uin :" + uin); UnityPlayer.UnitySendMessage("MainScript", "HuaweiLoginBack", uin); } else { // 处理登录失败逻辑 LogUtil.send("登录失败"); } } }); } /** * 充值 * */ public static int pay(String currency) { isPaying = true; Payment payment = new Payment(); ProductBean productBean = productMap.get(currency); makeSerial(); payment.setTradeNo(orderKen); payment.setProductId(productBean.getProductId()); payment.setSubject(productBean.getTitle()); payment.setDesc(productBean.getDescription()); payment.setAmount(productBean.getPrice_amount()); payment.setCurrency(productBean.getPrice_currency_code()); payment.setNote(""); payment.setNotifyURL(""); payment.setThirdAppId(id); payment.setThirdAppName("Spot Battle"); payment.setThirdAppPkgname("com.testcrecool.mi"); final String tradeNo = payment.getTradeNo(); final String productId = payment.getProductId(); // 将订单的详细信息插入数据库 PaymentTableAdapter.insert(ctx, payment); int res = Commplatform.getInstance().UniPayExt(payment, ctx, new CallbackListener<PayResult>() { @Override public void callback(final int code, final PayResult arg1) { ctx.runOnUiThread(new Runnable() { public void run() { // 回调结果,即支付过程结束 isPaying = false; LogUtil.send("回调结果,即支付过程结束 code: " + code); if (code == ErrorCode.COM_PLATFORM_SUCCESS) { // TODO Example 1 // 根据final 的 productID 或者 orderId // 去处理商品,比如查询道具,发放道具等 // TODO Example 2 // 可以根据订单号查询订单详细信息,在做订单的处理,比如查询道具,发放道具等 // 如下: Payment payment = PaymentTableAdapter .queryByOrderId(ctx, orderKen); // 购买有结果,即删除此订单号 PaymentTableAdapter.deleteByOrderId(ctx, orderKen); Log.i(TAG, "COM_PLATFORM_SUCCESS"); LogUtil.send("成功"); UnityPlayer.UnitySendMessage("MainScript", "hwPayCallback", orderKen); } else if (code == ErrorCode.COM_PLATFORM_ERROR_PAY_FAILURE) { Log.i(TAG, "COM_PLATFORM_ERROR_PAY_FAILURE"); } else if (code == ErrorCode.COM_PLATFORM_ERROR_PAY_CANCEL) { // 购买失败 // 购买有结果,即删除此订单号 PaymentTableAdapter.deleteByOrderId(ctx, tradeNo); Log.i(TAG, "COM_PLATFORM_ERROR_PAY_CANCEL"); UnityPlayer.UnitySendMessage("MainScript", "hwPayCallback", "error"); } else if (code == ErrorCode.COM_PLATFORM_ERROR_PAY_CANCEL) { LogUtil.send("取消购买"); // 取消购买 // 购买有结果,即删除此订单号 PaymentTableAdapter.deleteByOrderId(ctx, tradeNo); UnityPlayer.UnitySendMessage("MainScript", "hwPayCallback", "error"); } else { LogUtil.send("Purchase failed. Error code:" + code); Log.i(TAG, "COM_PLATFORM_ERROR_UNKNOWN"); } } }); } }); if (res == 0) { LogUtil.send("000"); return 0; } else { // 返回错误,即支付过程结束 isPaying = false; LogUtil.send("返回错误,即支付过程结束"); return -1; } } /** * 获取应用内商品信息 **/ public static void querySkuDetail() { Commplatform.getInstance().getSkuDetails(ctx, new CallbackListener<List<SkuDetail>>() { @Override public void callback(int errorCode, List<SkuDetail> skuDetails) { if (errorCode == ErrorCode.COM_PLATFORM_SUCCESS && skuDetails != null) { for (SkuDetail detail : skuDetails) { ProductBean prBean = new ProductBean(); prBean.setProductId(detail.productId); prBean.setPrice(detail.price); prBean.setPrice_amount(detail.price_amount); prBean.setPrice_currency_code(detail.price_currency_code); prBean.setTitle(detail.title); prBean.setDescription(detail.description); productMap.put(detail.productId, prBean); } // showText.setText(buffer.toString()); LogUtil.send("获取应用内商品信息"); } else { // showText.setText("query error"); LogUtil.send("query error"); } } }); } /** * 生成订单号 * */ private static String makeSerial() { // 生成订单号 orderKen = UUID.randomUUID().toString().replaceAll("-", ""); return orderKen; } // 同步支付订单的漏单查询接口调用 private static void checkPay(final Payment paymentSerial) { QueryPayment queryPayment = new QueryPayment(); queryPayment.setTradeNo(paymentSerial.getTradeNo()); queryPayment.setThirdAppId(paymentSerial.getThirdAppId()); final String tradeNo= queryPayment.getTradeNo(); Commplatform.getInstance().queryPayment(queryPayment, ctx, new CallbackListener<PaymentState>() { @Override public void callback(int paramInt, PaymentState paramT) { if (paramInt == ErrorCode.COM_PLATFORM_SUCCESS) { // Step2:订单查询成功 从数据库删除此订单号 PaymentTableAdapter.deleteByOrderId(ctx,tradeNo); // 订单支付成功,可以根据订单号查询订单详细信息,在做订单的处理,比如查询道具,发放道具等 // TODO… 游戏代码 LogUtil.send("漏单查询成功!!!"); UnityPlayer.UnitySendMessage("MainScript", "hwPayCallback", orderKen); } else if (paramInt == ErrorCode.COM_PLATFORM_ERROR_UNEXIST_ORDER) { // Step2:订单不存在 从数据库删除此订单号 PaymentTableAdapter.deleteByOrderId(ctx, tradeNo); // 根据游戏自身的体验决定如何处理 UnityPlayer.UnitySendMessage("MainScript", "hwPayCallback", "error"); } else if (paramInt == ErrorCode.COM_PLATFORM_ERROR_PAY_FAILURE) { // Step2:订单支付失败 从数据库删除此订单号 PaymentTableAdapter.deleteByOrderId(ctx, tradeNo); // 根据游戏自身的体验决定如何处理 UnityPlayer.UnitySendMessage("MainScript", "hwPayCallback", "error"); } else if (paramInt == ErrorCode.COM_PLATFORM_ERROR_SERVER_RETURN_ERROR) { // Step2:服务端返回错误 从数据库删除此订单号 PaymentTableAdapter.deleteByOrderId(ctx, tradeNo); // 根据游戏自身的体验决定如何处理 UnityPlayer.UnitySendMessage("MainScript", "hwPayCallback", "error"); } else if (paramInt == ErrorCode.COM_PLATFORM_ERROR_PAY_REQUEST_SUBMITTED) { // 订单已提交 // 根据游戏自身的体验决定如何处理 // 后续还需要继续查询 UnityPlayer.UnitySendMessage("MainScript", "hwPayCallback", "error"); } else { // 未知错误 // 根据游戏自身的体验决定如何处理 // 后续还需要继续查询 } } }); }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值