原生接入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:如果之后遇到了新坑会继续补充