制作一个可以将小游戏快速接入APP的SDK——安卓篇

编程入门 行业动态 更新时间:2024-10-09 16:31:04

制作一个可以将<a href=https://www.elefans.com/category/jswz/34/1769974.html style=小游戏快速接入APP的SDK——安卓篇"/>

制作一个可以将小游戏快速接入APP的SDK——安卓篇

写在前面

  今年以来,虽然入职的是游戏行业,其实一直在做原生这块的东西,主要是做一个聚合类的SDK,方便其他厂商快速接入,目前安卓这块已经完成了,发现现在市场上对于小游戏转制APP的需求量比较大,其实与我做的聚合SDK有很多相似之处,因此提取出一部分主要思想,共同探讨,因为本人使用的是CocosCreator(以下简称CCC)引擎,所以主要参考用例为CCC,当然本身是支持任意引擎的,当然也包含纯H5游戏。

SDK制作

  本文本身需要一定的安卓基础,但考虑到众多没有安卓经验,但是游戏开发者的需求,略微介绍下SDK制作的方式,如果你已有这方面的经验,可以忽略,众多基本介绍请自行百度,下面以主流且力推的AndroidStudio(以下简称AS)为例。

首先创建一个工程


  目前官方推荐kotlin+androidx,但考虑游戏行业的sdk多没有跟上,推荐各位选择java+android.support

将应用设置成库

  如上,我们只是创建了一个应用,这并不是APP,所以我们需要打开项目(注意工程与项目的概念,与Eclipse不同,简单讲AS的根目录就是工程,项目是app、game这类文件夹(本身命名是可自定义的))的build.gradle,将appliction修改成library,以及删除applicationId。方便各位看到我修改的地方,请看我注释的两行。

编写入口

应用入口

  如果对安卓比较熟悉,就知道库文件是没有上下文的,因此我们要项目传入上下文,相信接过安卓SDK的人都知道,sdk初始化常常是***.init/initSdk(Context context);因此我们也要编写我们的应用入口。

  • 入门级
public static void init(Application app, String appId){// TODO
}
  • 个人推荐
public static void init(Application app, SdkConfig config){// TODO
}
public class SdkConfig{private String appId;// TODOpublic static class Builder {private String appId;public Builder setAppId(String appId){this.appId = appid;return this;}public SdkConfig build(){SdkConfig config = new SdkConfig();config.appId = appId;// TODOreturn config;}} 
}

  这样的好处是能够扩展参数,且是目前流行的链式语法。

生命周期

  类比CCC的组件声明周期,Android也有一套声明周期,一些统计事件可能需生命周期的回调,所以我们也要提供接口。

  • 初级写法
    简单的方式是由用户自行调用,比如
	public static void onResume(Contextcontext){// TODO}
  • 高级写法
      如果实现该写法,则尽量要保证用户调用sdk的时间够早(PS:其实可以要求用户必须在Application里面调用。)。该方法的实现思想就是使用Android自身的声明周期回调,如下
public static void initSdk(Application app, SdkConfig config){// TODOapp.registerActivityLifecycleCallbacks();
}

编写桥接文件

  虽然前面写了一大堆,其实都是准备工作,在这里才进行到我们的主题,既然原来的方式是用小游戏(js),那么需要在原生使用,则必须要桥接层。本章节主要介绍如何编写。

js脚本

  js脚本的主要作用是实现微信的api,然后借助各引擎的交互连接sdk,为了使用结构清晰,推荐各位根据各平台独立编写一个单独的js文件。例如我编写的针对CCC的脚本文件(部分):

(function(){var clzName = "com/dc/sdk/DCJSBridge";function BannerAd(id, style) {var self = this;this.id = id;this.showResolve = null;this.showReject = null;this.loadCallbacks = [];this.resizeCallbacks = [];this.errorCallbacks = [];this.style = {width: style.width,height: style.height,_top: style.top,_left: style.left};this.style.__defineGetter__('top', function () { return this._top });this.style.__defineSetter__('top', function (top) {try {jsb.reflection.callStaticMethod(clzName, "setBannerAdTop", "(II)V", self.id, top);} catch (e) { }self._top = top;});this.style.__defineGetter__('left', function () { return this._left });this.style.__defineSetter__('left', function (left) {try {jsb.reflection.callStaticMethod(clzName, "setBannerAdLeft", "(II)V", self.id, left);} catch (e) { }self._left = left;});/*** 显示 banner 广告* @returns {Promise} banner 广告显示操作的结果*/this.show = function () {return new Promise(function (resolve, reject) {self.showResolve = resolve;self.showReject = reject;try {jsb.reflection.callStaticMethod(clzName, "showBannerAd", "(I)V", self.id);} catch (e) {reject();}});};/*** 监听 banner 广告尺寸变化事件* @param {Function} callback banner 广告尺寸变化事件的回调函数*/this.onResize = function (callback) {this.resizeCallbacks.push(callback);};/*** 监听 banner 广告加载事件* @param {Function} callback banner 广告加载事件的回调函数*/this.onLoad = function (callback) {this.loadCallbacks.push(callback);};/*** 监听 banner 广告错误事件* @param {Function} callback banner 广告错误事件的回调函数*/this.onError = function (callback) {this.errorCallbacks.push(callback);};/*** 取消监听 banner 广告尺寸变化事件* @param {Function} callback banner 广告尺寸变化事件的回调函数*/this.offResize = function (callback) {if (null == callback) {this.resizeCallbacks = [];return;}for (var i = 0; i < this.resizeCallbacks.length; i++) {if (callback == this.resizeCallbacks[i]) {this.resizeCallbacks.splice(i, 1);i--;break;}}};/*** 取消监听 banner 广告加载事件* @param {Function} callback banner 广告加载事件的回调函数*/this.offLoad = function (callback) {if (null == callback) {this.loadCallbacks = [];return;}for (var i = 0; i < this.loadCallbacks.length; i++) {if (callback == this.loadCallbacks[i]) {this.loadCallbacks.splice(i, 1);i--;break;}}};/*** 取消监听 banner 广告错误事件* @param {Function} callback banner 广告错误事件的回调函数*/this.offError = function (callback) {if (null == callback) {this.errorCallbacks = [];return;}for (var i = 0; i < this.errorCallbacks.length; i++) {if (callback == this.errorCallbacks[i]) {this.errorCallbacks.splice(i, 1);i--;break;}}};/*** 隐藏 banner 广告*/this.hide = function () {try {jsb.reflection.callStaticMethod(clzName, "hideBannerAd", "(I)V", this.id);} catch (e) {}};/*** 销毁 banner 广告*/this.destroy = function () {try {jsb.reflection.callStaticMethod(clzName, "destroyBannerAd", "(I)V", this.id);} catch (e) {}};return this;}var dc = window.dc || function(){};dc.getSystemInfoSync = function(){try {var str = jsb.reflection.callStaticMethod(clzName, "getSystemInfo", "()Ljava/lang/String;");// 防止特殊字符等,进行了Base64编码if(null == str || "" == str) {return null;}var msg = atob(str);return msg;} catch(e) {return null;}};var dc.bannerAds = [];dc.createBannerAd = function (object) {// TODO 判断是否缺少必须参数try {// 如果想性能高点,则使用对应参数// var id = jsb.reflection.callStaticMethod(clzName, "createBannerAd", "(Ljava/lang/String;IIIII)I", object.adUnitId, object.adIntervals || 30, object.style.left, object.style.top, object.style.width, object.style.height);// 如果想扩展方便,则可以直接传字符串,然后在java层进行解析var id = jsb.reflection.callStaticMethod(clzName, "createBannerAd", "(Ljava/lang/String;)I", JSON.stringify(object));if (id < 0) {return null;}dc.bannerAds[id] = new BannerAd(id, object.style);return dc.bannerAds[id];} catch (e) {return null;}};
})();

各引擎调用的方法基本类似,各位如果有对应的经验,其实非常简单。

原生代码

  这里其实是工作的一大重点,但是考虑各位这篇文章应该有基本的Sdk集成经验,而且工作相对重复且多,本文可能没法进行详述。
  需要注意的是,这是sdk开发,我们不能像以往接sdk一样,某个地方突然想调用一下js就调用,而应该集中起来,先调用java,然后由java调用对应的js代码,好处是java原生游戏其实也能用这套逻辑,另外一点是下面提到的调用脚本的实现。
  推荐各位多使用代理类,实现各个方法的接口,然后由代理类去调用各个sdk的具体实现。

加载js脚本

  js对安卓而言仅仅是一个资源,要引擎加载,则还需要引擎加载,H5的话,直接在index中加载该文件即可。而CCC则在main.js中。

调用脚本

  现在我们解决了引擎调用原生的问题,那原生如何调用脚本呢?如果你是CCC开发人员,你可能马上想到了引擎提供的Cocos2dxJavascriptJavaBridge.evalString(String str);方法,可是我们是Sdk啊,总不能还集成CCC的引擎层吧,而且还有laya等其它引擎,这该怎么办呢?其实各位这里就不要陷入死胡同了,我们要做的是提供原生能力,而不是考虑实现,所以这里我们应该提供一个入口给原生,有用户决定js代码如何实现。

js回调监听

public interface JSEval {void callJS(String code);
}

传入监听

    private static List<JSEval> evals = new ArrayList<>();public static void registerJSEval(JSEval eval){evals.add(eval);}

用户层实现

        DCAgent.registerJSEval(new DCAgent.JSEval() {@Overridepublic void callJS(final String jsCode) {runOnGLThread(new Runnable() {@Overridepublic void run() {Cocos2dxJavascriptJavaBridge.evalString(jsCode);}});}});

这样就简单的将跨引擎的事移交(甩锅)给用户了。

打包

  打包的方式有多种,可以是aar,或者流行的maven仓库,这个网上博客较多,不在赘述,仅提一句:注意混淆。

H5大厅游戏APP如何修改

  这个sdk其实更适合那些想把小游戏变成原生游戏的用户使用,但是H5大厅模式的,其实原理一样,只不过sdk就变成自己使用了,自己集成sdk,然后将主页变成大厅,根据点击实现各H5游戏的实现,即可实现一个简单的H5大厅游戏,当然其中的性能优化、对接就需要各位继续探索了。

结尾

  一开始准备写的细节,写着写着突然就不知道该总结到哪,比如sdk的集成方式,广告的实现逻辑,如果单独一个章节,感觉又偏离主题,更像一个安卓开发的主题,不写,总感觉重要的东西丢失。只能蜻蜓点水提上几句,只能期待后期继续改进。
  本文的经验来自我在公司的工作内容,因此有些不想关或者涉及重要核心的地方都省略了,另外代码也重新手敲,难免有些错误,敬请谅解。本文可能更加注重的是这个开发思维,更多细节,希望与我一起讨论。

更多推荐

制作一个可以将小游戏快速接入APP的SDK——安卓篇

本文发布于:2024-02-27 15:55:57,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1707235.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:小游戏   快速   APP   SDK   安卓篇

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!