【经验分享】使用 Cocos Creator 进行 原生打包、发布、接入SDK

原生接入SDK一直是头疼的问题,cocos 不像 Unity ,SDK厂商会提供插件,因此麻烦许多。在接入N种SDK,踩了N次坑后,决定总结一下经验。希望之后引擎之后能完善 原生插件 的接口拓展,以方便开发原生SDK插件。

首先,大部分打包前的问题都不是问题,只要你能找到报错位置和原因,就一定能解决。但是这对于没有经验的人来说,原生接入SDK是个很头疼的问题。
耐心一点,慢慢排查,仔细阅读报错的情况,其中都有答案的线索。

本篇文章仅在 https://forum.cocos.org/ 发布和更新,文章使用 CC BY-SA 4.0协议,遵循协议情况下可以转载(附带原链接)。------- By WssSheep


一、配置环境和基本打包

安装配置原生环境以及基本打包

[ Android ]

1.配置环境

请按照官方文档配置好安卓环境。
https://docs.cocos.com/creator/manual/zh/publish/setup-native-development.html

2.测试构建安卓项目

可以使用creator 直接 构建、编译、打包,此时如果失败,多半是路径长度问题(控制台可以看到提示),所以最好将构建路径放在较短的根目录下。
如何打包发布,官方也有文档说明:
https://docs.cocos.com/creator/manual/zh/publish/publish-native.html

构建成功的项目地址一般是:
xxxx\jsb-link\frameworks\runtime-src\proj.android-studio

构建后可以通过 Android Studio 打开

3.替换APP图标:

使用生成网站: https://icon.wuruihong.com/ 生成

在 build\jsb-link\frameworks\runtime-src\proj.android-studio\app\res 文件夹下,mipmap-hdpi / mipmap-mdpi 等文件夹,替换默认的 ic_launcher.png

4.替换APP名称

找到 build\jsb-link\frameworks\runtime-src\proj.android-studio\app\res\values\strings.xml 文件,修改下面的APP NAME

<resources>
    <string name="app_name" translatable="false">APP NAME</string>
</resources>

如果 需要创建多语言的APP名称,需要创建 "values-xxxx"文件夹,创建另外一个 strings.xml,xxxx是语言地区码。比如: build\jsb-link\frameworks\runtime-src\proj.android-studio\app\res\values-zh-rCN\strings.xml

注意需要设置属性 translatable = true,才能翻译。

<resources>
    <string name="app_name" translatable="true">APP NAME</string>
</resources>

5.发布签名包:

需要正式发布各种商店的包,是需要进行签名的。如果在构建项目时候勾选了生成签名文件xx.keystore (注意授权年份,最好选择50年),那么可以直接使用那个签名进行发布。如果没有,也可以自己用工具生成一个签名。详细发布签名包的步骤参考这篇:https://www.cnblogs.com/gao-chun/p/4891275.html,请保管好签名文件。

有的SDK后台会要求填写 keystore 的指纹SHA1,

命令行运行 keytool -v -list -keystore keystore文件路径,

输入keystore对应密码(输入时不可见),然后就会输出证书指纹了。

详细输出 keystore 指纹的步骤参考这篇:https://www.cnblogs.com/lianghui66/p/6677814.html

[ IOS ]

1.环境配置和打包

IOS不需要配置环境,mac自带环境,所以直接构建xcode项目即可。找到构建后的路径,
xxxx\jsb-link\frameworks\runtime-src\proj.ios_mac,点击 xxxx.xcodeproj 即可打开项目

2.设置IOS的图标

首先准备一张1024x1024 图标,使用生成网站: https://icon.wuruihong.com/ 生成

生成后直接替换项目文件夹的 Images.xcassets 中的 AppIcon.appiconset 内的所有图标文件

3.更换APP的名称

参考: https://blog.csdn.net/chenyongkai1/article/details/52175107

4.发布签名包

IOS打包发布,需要开发者账号,以及对应的证书。因为有很多现成教程,就不细说了。可以参考这篇文章的说明来注册开发者账号和申请证书,以及最后的打包提交:

https://www.jianshu.com/p/d3dc262cffa4


二、第三方SDK 接入

[Android]

JAVA代码学习

确保你已经初步掌握基础的Java代码的语法: https://www.runoob.com/java/java-tutorial.html

JS 和 JAVA互相调用

使用jsb 调用 java 对应类的静态方法

jsb.reflection.callStaticMethod("org/cocos2dx/javascript/Test", "hello", "(Ljava/lang/String;)V", "this is a message from js");

官方对于JS 调用 JAVA 代码已经描述的非常详细了,这里不做展开,详细看官方文档的说明:

https://docs.cocos.com/creator/manual/zh/advanced-topics/java-reflection.html

唯一要提到的是, JAVA 调用 JS 。在Java 代码中这样写:

//str 就是你要调用的 JS代码    
public void  evalString(final String str){
        //在GL 线程中调用回调函数
        app.runOnGLThread(new Runnable() {
            @Override
            public void run() {
                Cocos2dxJavascriptJavaBridge.evalString(str);
            }
        });

    }

推荐使用(Creator 2.0 版本后):cc.director 来发送接受消息,这样比直接调用指定的类和方法更加安全,不会因为JS中缺少类或者方法而出错。游戏中使用cc.director.on 就可以监听消息了

Cocos2dxJavascriptJavaBridge.evalString("cc.director.emit('CallbackEvent')");

利用 SDKClass 绑定APP的生命周期

接入部分SDK的时候,会有SDK要求 生命周期函数中调用方法,如何解决呢?

除了直接写死在 AppActivity里,其实还可以使用 SDKWrapper。

首先打开 xxx\jsb-link\project.json 文件,如果你使用了 内置的service,可能会出现,“serviceClassPath”: 字段。没有也有没有关系,可以自己添加一个字段。

{
    "project_type": "javascript",

    "debugMode" : 1,
    "showFPS" : true,
    "frameRate" : 60,
    "id" : "gameCanvas",
    "renderMode" : 0,
    "engineDir":"frameworks/cocos2d-html5",
    "modules" : ["cocos2d"],
    "jsList" : [
        "src/resource.js",
        "src/app.js"
    ],
    "serviceClassPath": [
        "org.cocos2dx.javascript.service.MyClass",
        "org.cocos2dx.javascript.service.CustomNative",
    ]
}

serviceClassPath 会由 SDKWrapper进行加载,在运行时,会从project.json 文件中读取路径(比如 org.cocos2dx.javascript.service.MyClass),然后绑定对应位置的 java类 ,在主AppActivity触发对应的生命周期函数后,这些类中的生命周期函数也会被调用。

首先在 proj.android-studio\app\src\org\cocos2dx\javascript\service 路径文件夹下创建一个类MyClass,然后MyClass 还需要继承 SDKClass,这样才能传递生命周期函数。

public class MyClass extends SDKClass {
}

SDKClass 中含有的 生命周期函数,一般接入SDK常用的生命周期函数就这些,一些特殊的情况有其他的做法:

详细了解查看: https://blog.csdn.net/xiajun2356033/article/details/78741121

  public interface SDKInterface {
    void init(Context context);
    void setGLSurfaceView(GLSurfaceView view);
    void onResume();
    void onPause();
    void onDestroy();
    void onActivityResult(int requestCode, int resultCode, Intent data);
    void onNewIntent(Intent intent);
    void onRestart();
    void onStop();
    void onBackPressed();
    void onConfigurationChanged(Configuration newConfig);
    void onRestoreInstanceState(Bundle savedInstanceState);
    void onSaveInstanceState(Bundle outState);
    void onStart();
}

这样以后接入SDK时,可以单独在脚本里处理,不必每次都去修改AppActivity 文件。通过js调用java对应的静态方法 ( 上面说的 jsb.reflection.callStaticMethod),就能调用对应的SDK了。

不知道怎么写的,可以勾选cocos 的数据分析服务,导出后 service 文件夹可以找到 官方写的 java脚本,可以用来参考。

[IOS]

OC代码学习

要接入IOS,需要学会Objective-C的语法,OC语法和传统C系语法区别较大,具体参考这篇文章: https://www.runoob.com/w3cnote/objective-c-tutorial.html

JS 和 IOS 互相调用

详细查看官方文档: https://docs.cocos.com/creator/manual/zh/advanced-topics/oc-reflection.html ,官方没提到的细节部分看下面的补充。

JS 调用OC 代码

注意和JS调用 JAVA代码不同,不需要写 类似 (Ljava/lang/String;)V 的参数,className 也 不需要填写完整路径,唯一需要注意的是方法名得完整传入,类似 func:param0:param1:param2: 这种结构,方法名+’:’ + 参数名+’:’

let result = jsb.reflection.callStaticMethod(className, methodName, arg1, arg2, .....);

具体看上面的官方文档,有比较详细的说明。

OC 调用 JS 代码

注意这里需要直接使用OC调用C++的代码,因此创建的文件不是.m文件(objective c+),而是.mm文件 (objective c++),这样才能调到C++ 代码。

//引入JSB头文件
#include <cocos/scripting/js-bindings/jswrapper/SeApi.h>

//定义evalString 方法
-(void) evalString:(NSString *)str
{
    std::string *string = new std::string([str UTF8String]);
    const char *p = string->c_str();
    se::ScriptEngine::getInstance()->evalString(p);
}


定义好之后,只需要调用 evalString,就能调用JS代码了。

利用 SDKDelegate.h 绑定APP的生命周期

SDKDelegate.h 文件在 xxx\jsb-link\frameworks\runtime-src\proj.ios_mac\ios\service 内,你可以将自己接入的SDK脚本,也放入这个文件使用。

生命周期的说明可以查看: https://www.jianshu.com/p/d60b388b19f5

 - (void) optionalFunction;
 - (void)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
 - (void)applicationDidBecomeActive:(UIApplication *)application;
 - (void)applicationWillResignActive:(UIApplication *)application;
 - (void)applicationDidEnterBackground:(UIApplication *)application;
 - (void)applicationWillEnterForeground:(UIApplication *)application;
 - (void)applicationWillTerminate:(UIApplication *)application;

IOS 中 也会用到 project.json 文件的 serviceClassPath ,但是对于你填写的地址,比如 “org.cocos2dx.javascript.service.CustomNative”,默认会直接取最末尾的 “CustomNative” 作为 Service 需要的类名读取。IOS和 Android不同,类在这里是全局的,只要你添加了引用,就能被调用到。

Cocoapod 安装

部分接入SDK会采用 Cocoapods 的方式来接入SDK,这个类似于npm 是一个管理包的工具。

可以参考这篇文章来安装和使用 cocoapods:

https://www.jianshu.com/p/417b0e8bb027

ps: Cocos 直接使用Cocoapods 可能会出现问题,cocapods 的配置可能会不能引用成功,导致你无法编译成功,具体参考下面提到 Cocoapods 的错误。


三、原生接入常见错误

[Android]

1.65535问题:

一般报错出现了数字65535,很可能就是Dex文件最大方法数超过了65535,一般这是
使用集成SDK 时容易发生的错误。

trouble writing output: 
Too many field references: 131000; max is 65536. 
You may try using –multi-dex option.

解决方法就是使用 Multi-Dex。

什么是 Multi-Dex 可以查看 android开发文档相关内容: developer.android.com/tools/building/multidex.html

首先修改主项目的 gradle 文件, 就是 build.gradle(Module:构建项目名) 这个文件,

android {
    defaultConfig {
        ...
        minSdkVersion 18
        targetSdkVersion 28
        multiDexEnabled true //加入这个配置使用  Multi-Dex
    }
    ...
}
 ...
dependencies {
    //androidx 使用 multidex
    implementation 'androidx.multidex:multidex:2.0.0'
    //没有使用 androidx, 可以使用  'com.android.support:multidex:1.0.3' 代替
}

MultiDexApplication 需要一个 Application 作为载体,cocos 默认构建没有 Application 的,使用 MultiDex 需要自己创建一个 Application 类继承自 android.support.multidex.MultiDexApplication。

public class MyApplication extends SomeOtherApplication {
  @Override
  protected void attachBaseContext(Context base) {
     super.attachBaseContext(base);
     MultiDex.install(this);
  }
}

创建后 将完整类名填写 替换 游戏名/mainfests/AndroidManifest.xml 文件中的 com.xxx.MyApplication

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <application
            android:name="com.xxx.MyApplication" >
        ...
    </application>
</manifest>

详细步骤可以参考这篇:
https://www.jianshu.com/p/78f2e2d9484a

2.报错 No such file or directory / package … does not exist

文件或者库找不到,检查以下

  • 引用SDK的路径名称拼写是否正确
  • 文件是否存在目录
  • 文件夹是否有可读权限。

3.Android Support 冲突 问题

AndroidX 和 anroid.support 库冲突的解决方案

首先在 gradle.properties 文件中添加

// 表示使用 androidx
android.useAndroidX=true
// 表示将第三方库迁移到 androidx
android.enableJetifier=true

然后菜单栏 Refactor -> Migrate to Androidx 就可以了,Android Studio 会自动把你项目中的依赖切换到 Androidx,并且修改项目中使用到依赖库的路径。

4.失败 :transformClassesWithDexForDebug FAILED

JVM虚拟机的内存空间太小、重新 Clean Build 项目,重启Android Studio 再次打包

5.提示错误: 类重复

往往是SDK中包含了一个版本的库,其他SDK或者自己导入项目的jar\arr 文件发生了冲突,解决方法有两种:

要么引入的时候剔除掉:(适合冲突较少的情况)

implementation('com.aliyun.dpa:oss-android-sdk:+') {
exclude(group: "com.squareup.okhttp3")
}

要么强制指定SDK的版本:(适合冲突较多的情况)

configurations.all {
resolutionStrategy.force "com.squareup.okhttp3:3.12.3"
}

6.安卓使用 cc.sys.localStorage 进行存档和读档会掉帧,

cc.sys.localStorage是用sqlite数据库存储的key-value,可能会在读写时卡顿。如果发生卡顿现象,尝试使用jsb读写文件代替 localStorage

jsb.fileUtils.writeStringToFile(string, filename)
jsb.fileUtils.getStringFromFile(filename)

[IOS]

1. cocoapods 配置错误

使用 cocoapods 接入,cocos项目需要引用 cocoapods 的文件

diff: /Podfile.lock: No such file or directory
diff: /Manifest.lock: No such file or directory
error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.

报这个错误是pods配置文件未包含的问题,可以找到项目文件夹 ios ,UserConfigIOS.debug.xcconfig 和 UserConfigIOS.release.xcconfig 文件,分别添加如下引用引用pods中的配置文件:

//UserConfigIOS.debug.xcconfig 
#include "../Pods/Target Support Files/Pods-xxx-mobile/Pods-xxx-mobile.debug.xcconfig"

//UserConfigIOS.release.xcconfig
#include "../Pods/Target Support Files/Pods-xxx-mobile/Pods-xxx-mobile.release.xcconfig"

2. 报错,xx duplicate symbols for architecture armv7

一般是类冲突,很可能是cocoapods 有这个类库,主项目也引入了
同样的类库,检查报错内容,删除项目中重复的库文件。


四、打包后的闪退问题

  • 类名被混淆,导致缺少类:
    检查编译时 Android Studio 出现的警告,虽然不是红色的error。
    最好也要根据警告的提示,修改Proguard 文件,防止某些类被混淆。
    因为后续可能会导致某些SDK调用不到需要的类而导致闪退。
    https://www.jianshu.com/p/a8a2e3f1ca60

  • SDK本身的问题:

    需要排查SDK的初始化调用是否正常,合理使用 logcat 打印日志来检查SDK是否调用正常。比如接入Admob 时候,如果未在 AndroidMainfest.xml 配置正确 APPID,则会初始化闪退。


…后续待补充,PS:如果之后遇到了新坑会继续补充

144赞

不得不说,文章很赞

mark!

搞cocos 3年第一次看到 SDKClass用法
:sweat_smile:

mark!

赞mark!

很全面,对新手很友好,希望以后多出这种攻略

赞。。。。

感谢楼主,multidex那里没看懂,没找到Application在哪里啊

mark mark

cocos 默认构建没有 Application 的,自己创建一个Application 继承自 android.support.multidex.MultiDexApplication
(文章中细化了这部分说明,可以回看以下)

插眼,准备要打apk

插眼,好文

很详细,几乎是我当年的踩坑历史

接了这么多,还是羡慕Unity的生态,有原生插件系统,各个SDK开发商 都会弄个接入插件。

unity 接的SDK小还是很方便的,基本不需要自己动手,比如firebase, facebook 等,都有插件和辅助工具。但是接的SDK功能多了的时候,一样需要自己配置gradle,难度也就增加了。不如cocos 直接、简单了。

谢谢楼主!!
好希望官方可以跟SDK开发商们多搞搞合作,以后可以像Unity那样简简单单的接入SDK啊~~

各有利弊吧,一些只用第三方服务的中小项目还是Unity打包方便,不过 Unity 也可以直接导出 安卓项目/IOS项目。
Cocos 虽然可以用自定义定制模板打包,但是相对来说还是有些麻烦,希望之后更易用一些。

mark!

android studio com.android.tools.build:gradle:3.2.0 下载不下来怎么办 老是超时