Android Unity调用心得

最近做了Unity跟Android对接项目,有些经验分享一下:

一,跑Hello World:

1、打包需要给Unity调用的aar(gradle中点击assemble Debug或者Release 对应的assemble),会在对应的module的build/outputs/aar下面生成对应的aar,现在网上的教程大部分是只有一个module的,但我们实际项目的module可能会有多个,此种情况可以点击AS右侧Gradle中的Tasks/other/bundleDebugAar会生成所有module中的aar,去对应的module的build下面就能看到,得到的aar放到Unity项目中的Plugins目录下(实测,其实aar放到其他目录也可以,不必要非放到这个目录下)

2、AndroidManifest.xml文件,放到Plugins/Android目录下(Mainfest文件必须放到这个目录下,不放会直接报错)Manifest中的unityplayer.UnityActivity 的修改不再赘述,网上很多了。

3、Unity运行到Android机器上的包名设置,需要跟AndroidManifest.xml中一致(网上都是这么说的,但实际项目中我们的包名其实是gradle中的ApplicationId决定的,那其实是可以跟AndroidMainfest中的不一致的,但是要注意的是unity会用这个包名来找Android的类,需要保证AndroidManifest中注册的Activity和Application能够找到)

最基本的设置就是上面的内容。这里再说一下一些调试跟打包的东西,这里更深入,需要gradle跟shell脚本的基础,不熟悉的可以去查看下gradle groovy基础跟shell基础。

二、调试。

Unity在Android上的调试还是挺难受的,如果没有写一些自动化的脚本,还是蛮恶心的。那我们知道我们的Android SDK代码是通过aar给Unity调用的,所以我们只要是改了哪怕一行代码也需要重新生成aar,并把aar拷贝到Unity的Plugins目录下,Unity才能识别到这个修改。 所以我们这里需要做的一个事情就是每次改Android的时候需要生成aar并且拷贝到对应的Plugins目录下。上面我们也说了可以用gradle的bundleXXXAar来生成对应的aar文件。

可以在对应的gradle android闭包下加此代码,copyAarFiles是你自己的拷贝aar函数。

我是写在了一个copyAar.gradle目录下。然后用applyfrom: ‘copyAar.gradle’ 引进来,写在一个单独的gradle文件中的好处就是,如果你有多个module可以需要下面的android.libraryVariants.all一段代码就好了,直接复用 copyAar中的copyAarFiles函数。

android.libraryVariants.all { variant ->
    def debugOrRelease = variant.name
    tasks.all {
        if (it.name.equalsIgnoreCase("bundle${debugOrRelease.capitalize()}Aar")) {
            it.doLast {
                copyAarFiles(project.name, debugOrRelease)
            }
        }
    }
}

copyAar.gradle如下(脚本中的目录对应到你自己的本地路径)

ext.copyAarFiles = { name, debugOrRelease ->
    def aarDebugPath = rootProject.rootDir.path + "/${name}/build/outputs/aar/${name}-debug.aar"
    def aarReleasePath = rootProject.rootDir.path + "/${name}/build/outputs/aar/${name}-release.aar"
    def androidManifestPath = rootProject.rootDir.path + "/${name}/src/main/AndroidManifest.xml"
    def destAARPath = "../../Unity/Assets/Plugins/Android/"
    def destManifestPath
    if (name.equals("UnityInterface")) {
        destManifestPath = "../../Unity/Assets/Plugins/Android/"
    } else {
        destManifestPath = "../../Unity/Assets/Plugins/Android/${name}/"
    }
    def aarPath
    if ("${debugOrRelease.capitalize()}".equalsIgnoreCase("debug")) {
        aarPath = aarDebugPath;
    } else {
        aarPath = aarReleasePath;
    }
    copy {
        from file(aarPath)
        into destAARPath
        rename { fileName ->
            if (fileName.startsWith(name)) {
                fileName.replace(fileName, "${name}.aar")
            }
        }
    }
    copy {
        from file(androidManifestPath)
        into destManifestPath
    }
    //------------------------------------------拷贝到Unity的目录-----------------------------------------------
//    def destUnityAARPath = "../../../unityAssets/Plugins/Android/"
//    def destUnityManifestPath
//
//    if (name.equals("UnityInterface")) {
//        destUnityManifestPath = "../../../unity/Assets/Plugins/Android/"
//    } else {
//        destUnityManifestPath = "../../../unity/Assets/Plugins/Android/${name}/"
//    }
//
//    copy {
//        from file(aarPath)
//        into destUnityAARPath
//        rename { fileName ->
//            if (fileName.startsWith(name)) {
//                fileName.replace(fileName, "${name}.aar")
//            }
//        }
//    }
//    copy {
//        from file(androidManifestPath)
//        into destUnityManifestPath
//    }
//
//    println("aarPath->${aarPath}");
//    println("destAARPath->${destAARPath}");
    println("copyRes----------name->${name} debugOrRelease->${debugOrRelease}  complete!!!")
    //------------------------------------------拷贝proguard文件-----------------------------------------------
    if (name.equals("UnityInterface")) {
        def srcProguardPath = rootProject.rootDir.path + "/AndroidUnityInterface/proguard.cfg"
        def destProguardPath = "../../../unity/_ExportProject/Android/unity/"
        copy {
            from file(srcProguardPath)
            into destProguardPath
        }
    }
}

做了这些之后,你修改了Android之后点一下gradle的bundleDebugAar就会检测你各个module是否有修改,如果有修改会在对应的module下面生成aar,然后你的gradle脚本会把这些aar拷贝到Unity的Plugins下面,Android代码对接到Unity的自动化就完成了。这时候你去Unity调用就能看到Android的修改了。

到这里,日常生产没问题了,那我们遇到的下个问题是打包,下面说打包。

三,打包

1、点击Unity的打包

Unity给我们提供了打包选项。Android的PlayerSettings下面Publishing Settings,填写包名,keystore和alies,然后用Unity给你生成的mainTemplate.gradle,他提供给你的proguard.cfg。我们可以完成打包。

当然,作为一个Android开发人员,我们不是来介绍Unity提供给我们的这些打包工具的,或者说我们并不满足于这些。因为这可以满足基本的打包需求,如果我们有更灵活的选择的话就不需要在它给我们的条条框框里面做事情,况且它这个其实并不满足一些需求。我们都知道,其实每个公司每个团队都会有一些代码积累,可能我们每次开新项目的时候并不需要从网络框架图像加载统计曝光框架这些基础的东西选起,因为我们之前的项目已经做过了,我们只需要把之前的module拿过来,引用进来就可以直接用了。

那这个时候问题就来了,Unity给我们提供了一个总的proguard.cfg。可以保证Unity调用,但是我们给过来的是一个个的aar,那我们aar之间如果有依赖的话,比如说我有两个module,moduleA.aar,moduleB.aar,A是依赖于B的,那我A去调用B的东西的时候其实是找不到的,因为像我们Android开发的时候最终apk里面是一个dex,我们知道dex就是去掉的jar里面的冗余,就相当于把多个jar拆开重新封装成了一个大的jar,那我其实只需要一个proguard.cfg就好了,但是我们给Unity调用的时候其实是多个aar,一个混淆文件无法保证多个module的互相调用。这个时候要不然把module合成一个大module,要不然给每个module都搞一个混淆文件,这两个选项都是生命不可承受之重!具体解决方案下面讲。

2、抛开Unity自定义打包

我们先抛砖引玉请出今天的大头mainTemplate.gradle,这位大爷是我们点了Unity-Publishing Settings-Custom Gralde Template后Unity帮我们生成的。我们可以去看一下它的内容,贴一部分来看

apply plugin: 'com.android.application'
dependencies {
**DEPS**}

android {
    compileSdkVersion **APIVERSION**
    buildToolsVersion '**BUILDTOOLS**'

    defaultConfig {
        minSdkVersion **MINSDKVERSION**
        targetSdkVersion **TARGETSDKVERSION**
        applicationId 'philm.vilo.im'
        ndk {
            abiFilters **ABIFILTERS**
        }
        versionCode **VERSIONCODE**
        versionName '**VERSIONNAME**'

    }

    lintOptions {
        abortOnError false
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    aaptOptions {
        noCompress '.unity3d', '.ress', '.resource', '.obb'**STREAMING_ASSETS**
    }**SIGN**

    //packageBuildConfig(false)

    buildTypes {
        debug {
            minifyEnabled **MINIFY_DEBUG**
            useProguard **PROGUARD_DEBUG**
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-unity.txt'**USER_PROGUARD**
            jniDebuggable true
        }
        release {
            minifyEnabled **MINIFY_RELEASE**
            useProguard **PROGUARD_RELEASE**
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.cfg'**USER_PROGUARD****SIGNCONFIG**
        }
    }**PACKAGING_OPTIONS****SPLITS**
**BUILT_APK_LOCATION**
}**SPLITS_VERSION_CODE****SOURCE_BUILD_SETUP**

如果是Android开发人员会看着很亲切,我们可以看出来,这就是一个Android打包配置文件。里面有一些** 星号框起来的东西,这些东西就是上面Unity的打包配置了,Unity最终会把这些玩意儿替换成我们Run Unity时设置的东西。那来到我们的领域就好说了,我们可以把这文件当成我们Android里面的打包文件,直接可以在这里写gradle打包设置,他的那些东西我们其实都抛开不要就好了。(这里忘了说个东西,网上很多都说把我们gradle依赖的文件下载下来,搞成jar包放到Plugins里面,依赖的依赖也要搞进来,麻烦的一批,其实不用,只要把 api 'com.google.code.gson:gson:2.8.5’ 这玩意儿写到mainTemplate.gradle里面就好了,gradle会帮我们搞定),同时我们上面说的ApplicationId跟Manifest的目录名字就可以分开了。

所以说,这就是最终解决方案了吗?当然不是,如果我们要在Jenkins上面自动打包的话,这儿其实还是很难受的。我们知道Unity还给我们提供了Export的功能,就是说把Unity导出成一个Android工程,导出来之后我们去看工程中的build.gradle文件其实就是mainTemplate.gradle替换了**这些玩意儿之后的内容。我们更好操作了,我们完全可以把这个Export出来的工程当做我们的打包工作目录,连aar都不需要,也就是上面说的混淆问题的解决方案。

3、操作Export自动打包

我们完全可以把Export出来的工程当做打包目录,因为这里完全是Android环境,我们可以用gradle做任何事。
这里我们用Jenkins的话,我们就用shell来做就好了,这里我们做的主要是这几点,
3.1、把我们那些module的代码拉下来,git或者svn,放到工程目录下
3.2、把我们预设的那些gradle拷贝过来
3.3、拷贝gradle-wrapper
3.4、其他你需要做的.....

这个看你自己的项目,目的只有一个:把这个目录搞成一个完整的Android目录,需要的加上,不需要的删掉。完事儿之后你就按照一个Android工程来对待它就好了。这样Jenkins上你就得到了一个Android工程,做你对应的自动打包就好了。比如我现在工程里面的shell例子:
copy.sh

#!/bin/bash
module_names=('Module1' 'Module2' 'Module3')
for name in ${module_names[@]}
do
    #
    SVNPath="svn://192.168.0.1/unity/trunk/Unity/Android/$name"
    test -d $name || svn co $SVNPath $name --username "xxx" --password “xxx"
    cd $name
    svn upgrade --username “xxx" --password “xxx"
    svn up --username “xxx" --password “xxx"
    cd ..
    rm -f "libs/${name}.aar"
    echo "------------------------libs/${name}.aar delete!----------------------------"
    echo "------------------------${SVNPath} svn upgrade done!----------------------------"
done

SRC_BUILD_GRADLE="template/build.gradle"
SRC_COPY_GRADLE="template/copyAar.gradle"
SRC_SETTING_GRADLE="template/settings.gradle"
DEST_BUILD_GRADLE="build.gradle"
DEST_COPY_GRADLE="copyAar.gradle"
DEST_SETTING_GRADLE="settings.gradle"
rm $DEST_BUILD_GRADLE
rm $DEST_COPY_GRADLE
rm $DEST_SETTING_GRADLE
cp $SRC_BUILD_GRADLE $DEST_BUILD_GRADLE
cp $SRC_COPY_GRADLE $DEST_COPY_GRADLE
cp $SRC_SETTING_GRADLE $DEST_SETTING_GRADLE
echo "------------------------copy template gradle done!----------------------------"
SRC_WRAPPER="template/wrapper"
DEST_WRAPPER="gradle/"
if [ -d $DEST_WRAPPER ];then
echo "文件夹存在 不创建文件夹"
else
echo "文件夹不存在 创建文件夹"
mkdir -p $DEST_WRAPPER
fi
cp -r $SRC_WRAPPER $DEST_WRAPPER
echo "------------------------copy wrapper done!----------------------------"

#aliJar="alipaySdk-20170725.jar"
#SEC_JAR_PATH="libs/$aliJar"
#DEST_ALI_PATH="template/ali/"
#mkdir $DEST_ALI_PATH
#cp $SEC_JAR_PATH $DEST_ALI_PATH
#cd $DEST_ALI_PATH
#unzipJarCmd="jar -xvf $aliJar"
#$unzipJarCmd
#rm -rf "com/ta/"
#rm -rf "com/ut/"
#rm -rf $aliJar
#zipJarCmd="jar -cvf $aliJar ./"
#$zipJarCmd
#cdCmd="cd ../../"
#$cdCmd
#path=${PWD}
#echo ${path}
#echo "$DEST_ALI_PATH$aliJar"
#rm -f $SEC_JAR_PATH
#mv "$DEST_ALI_PATH$aliJar" $SEC_JAR_PATH
#rm -rf $DEST_ALI_PATH
#echo "------------------------delete utdid from alipay done!----------------------------"

到这里打包的内容就大致完事儿了,只是具体说了思想,自己的工程还是要自己修改的。这里只是说了思想,看到的不至于走弯路,我上面说的这条路是完全可以跑通的,如果大家觉着还可以也可以回去试一试。
这里还有个需要注意的点是每个Unity对应的gradle版本不一样,比如我mac下Unity软件包目录下,gradle的文件目录如下:
/Applications/Unity/Hub/Editor/2019.1.0b4/PlaybackEngines/AndroidPlayer/Tools/gradle/lib/ **
可以去看一下当前用的版本是多少,现在official的
Unity 2018.3.9f1版本对应的gradle是4.6了,但如果已经用某个版版本开发了很久,突然发现了gradle不匹配的问题,能否还是用当前的Unity版本,但是升级新的gradle版本呢?答案是可以的,替换掉上面我贴出来的路径里的gradle就好了。
比如我想用gradle 5.3的new feature,但是还想使用
Unity 2018.3.9f1应该怎么办呢?我需要把gradle-5.3-all.zip下载下载,解压出来覆盖本机Unity 2018.3.9f1**的gradle目录(参考上面贴出来的目录)就好了。

还有一些其他的要注意的也写在下面吧:

1、Unity回调方式

1.1、UnityPlayer.UnitySendMessage("objectName", "functionName","value");
这种方式相当于静态回调,指定某个脚本的某个方法获取。
优点:方便快捷,直接调用就好了。
缺点:只能传一个String,源码UnityPlayer里可以看到,就只能传一个String
1.2、

public class UnityCallbackListener : AndroidJavaProxy
{
    public UnityCallbackListener() : base(“com.yocn.base.UnityCallbackListener")
    {
    }
}

相当于反射的方式获取到java的某个类,然后调用java的方法设置给java,回调的时候重写的同名的方法可以回调到C#
优点:什么都可传,足够灵活,可以直接调用传回来的类的方法
缺点:相比第一种复杂,需要设置给某个类,回调的时候需要获取到这个对象

2、回调Unity线程

Unity可能会需要回调的时候在unity的主线程里面通过打印Thread.getName可以知道,Unity的主线程叫做UnityMain。我用的方式就是在Unity调用Android的时候在他的线程里面创建一个Handler,我们知道在什么线程里面创建Handler会把什么线程的Looper绑定到这个Handler上面,我们回调的时候就用这个handler发消息就是在UnityMain线程里面了。

总结一下

  1. 基础:
    1.1 生成多个aar
    1.2 aar跟Manifest位置
    1.3 包名设置
  2. 日常:调试gradle自动化生成aar并拷贝到对应的位置
  3. 打包:
    3.1 Unity打包利弊
    3.2 自定义打包
    3.3 Jenkins自动打包

你可能感兴趣的:(Android Unity调用心得)