Android Unity調(diào)用心得

最近做了Unity跟Android對接項目年柠,有些經(jīng)驗分享一下:

一酿雪,跑Hello World:

1粹淋、打包需要給Unity調(diào)用的aar(gradle中點擊assemble Debug或者Release 對應(yīng)的assemble)寸潦,會在對應(yīng)的module的build/outputs/aar下面生成對應(yīng)的aar舱禽,現(xiàn)在網(wǎng)上的教程大部分是只有一個module的,但我們實際項目的module可能會有多個,此種情況可以點擊AS右側(cè)Gradle中的Tasks/other/bundleDebugAar會生成所有module中的aar眨补,去對應(yīng)的module的build下面就能看到管削,得到的aar放到Unity項目中的Plugins目錄下(實測,其實aar放到其他目錄也可以撑螺,不必要非放到這個目錄下)

2含思、AndroidManifest.xml文件,放到Plugins/Android目錄下(Mainfest文件必須放到這個目錄下甘晤,不放會直接報錯)Manifest中的unityplayer.UnityActivity 的修改不再贅述含潘,網(wǎng)上很多了。

3安皱、Unity運行到Android機(jī)器上的包名設(shè)置调鬓,需要跟AndroidManifest.xml中一致(網(wǎng)上都是這么說的,但實際項目中我們的包名其實是gradle中的ApplicationId決定的酌伊,那其實是可以跟AndroidMainfest中的不一致的,但是要注意的是unity會用這個包名來找Android的類缀踪,需要保證AndroidManifest中注冊的Activity和Application能夠找到)

最基本的設(shè)置就是上面的內(nèi)容居砖。這里再說一下一些調(diào)試跟打包的東西,這里更深入驴娃,需要gradle跟shell腳本的基礎(chǔ)奏候,不熟悉的可以去查看下gradle groovy基礎(chǔ)跟shell基礎(chǔ)。

二唇敞、調(diào)試蔗草。

Unity在Android上的調(diào)試還是挺難受的,如果沒有寫一些自動化的腳本疆柔,還是蠻惡心的咒精。那我們知道我們的Android SDK代碼是通過aar給Unity調(diào)用的,所以我們只要是改了哪怕一行代碼也需要重新生成aar旷档,并把a(bǔ)ar拷貝到Unity的Plugins目錄下模叙,Unity才能識別到這個修改。 所以我們這里需要做的一個事情就是每次改Android的時候需要生成aar并且拷貝到對應(yīng)的Plugins目錄下鞋屈。上面我們也說了可以用gradle的bundleXXXAar來生成對應(yīng)的aar文件范咨。

可以在對應(yīng)的gradle android閉包下加此代碼,copyAarFiles是你自己的拷貝aar函數(shù)厂庇。

我是寫在了一個copyAar.gradle目錄下渠啊。然后用applyfrom: ‘copyAar.gradle’ 引進(jìn)來,寫在一個單獨的gradle文件中的好處就是权旷,如果你有多個module可以需要下面的android.libraryVariants.all一段代碼就好了替蛉,直接復(fù)用 copyAar中的copyAarFiles函數(shù)。

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如下(腳本中的目錄對應(yīng)到你自己的本地路徑)

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是否有修改灭返,如果有修改會在對應(yīng)的module下面生成aar盗迟,然后你的gradle腳本會把這些aar拷貝到Unity的Plugins下面,Android代碼對接到Unity的自動化就完成了熙含。這時候你去Unity調(diào)用就能看到Android的修改了罚缕。

到這里,日常生產(chǎn)沒問題了怎静,那我們遇到的下個問題是打包邮弹,下面說打包。

三蚓聘,打包

1腌乡、點擊Unity的打包

Unity給我們提供了打包選項。Android的PlayerSettings下面Publishing Settings夜牡,填寫包名铡俐,keystore和alies,然后用Unity給你生成的mainTemplate.gradle发绢,他提供給你的proguard.cfg备图。我們可以完成打包。

當(dāng)然蹦肴,作為一個Android開發(fā)人員僚碎,我們不是來介紹Unity提供給我們的這些打包工具的,或者說我們并不滿足于這些阴幌。因為這可以滿足基本的打包需求勺阐,如果我們有更靈活的選擇的話就不需要在它給我們的條條框框里面做事情,況且它這個其實并不滿足一些需求矛双。我們都知道渊抽,其實每個公司每個團(tuán)隊都會有一些代碼積累,可能我們每次開新項目的時候并不需要從網(wǎng)絡(luò)框架圖像加載統(tǒng)計曝光框架這些基礎(chǔ)的東西選起背零,因為我們之前的項目已經(jīng)做過了腰吟,我們只需要把之前的module拿過來,引用進(jìn)來就可以直接用了徙瓶。

那這個時候問題就來了毛雇,Unity給我們提供了一個總的proguard.cfg≌煺颍可以保證Unity調(diào)用灵疮,但是我們給過來的是一個個的aar,那我們aar之間如果有依賴的話壳繁,比如說我有兩個module震捣,moduleA.aar,moduleB.aar荔棉,A是依賴于B的,那我A去調(diào)用B的東西的時候其實是找不到的蒿赢,因為像我們Android開發(fā)的時候最終apk里面是一個dex润樱,我們知道dex就是去掉的jar里面的冗余,就相當(dāng)于把多個jar拆開重新封裝成了一個大的jar羡棵,那我其實只需要一個proguard.cfg就好了壹若,但是我們給Unity調(diào)用的時候其實是多個aar,一個混淆文件無法保證多個module的互相調(diào)用皂冰。這個時候要不然把module合成一個大module店展,要不然給每個module都搞一個混淆文件,這兩個選項都是生命不可承受之重秃流!具體解決方案下面講赂蕴。

2、拋開Unity自定義打包

我們先拋磚引玉請出今天的大頭mainTemplate.gradle舶胀,這位大爺是我們點了Unity-Publishing Settings-Custom Gralde Template后Unity幫我們生成的概说。我們可以去看一下它的內(nèi)容,貼一部分來看

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開發(fā)人員會看著很親切嚣伐,我們可以看出來席怪,這就是一個Android打包配置文件。里面有一些** 星號框起來的東西纤控,這些東西就是上面Unity的打包配置了,Unity最終會把這些玩意兒替換成我們Run Unity時設(shè)置的東西碉纺。那來到我們的領(lǐng)域就好說了船万,我們可以把這文件當(dāng)成我們Android里面的打包文件,直接可以在這里寫gradle打包設(shè)置骨田,他的那些東西我們其實都拋開不要就好了耿导。(這里忘了說個東西,網(wǎng)上很多都說把我們gradle依賴的文件下載下來态贤,搞成jar包放到Plugins里面舱呻,依賴的依賴也要搞進(jìn)來,麻煩的一批悠汽,其實不用箱吕,只要把 api 'com.google.code.gson:gson:2.8.5’ 這玩意兒寫到mainTemplate.gradle里面就好了,gradle會幫我們搞定)柿冲,同時我們上面說的ApplicationId跟Manifest的目錄名字就可以分開了茬高。

所以說,這就是最終解決方案了嗎假抄?當(dāng)然不是怎栽,如果我們要在Jenkins上面自動打包的話丽猬,這兒其實還是很難受的。我們知道Unity還給我們提供了Export的功能熏瞄,就是說把Unity導(dǎo)出成一個Android工程脚祟,導(dǎo)出來之后我們?nèi)タ垂こ讨械腷uild.gradle文件其實就是mainTemplate.gradle替換了**這些玩意兒之后的內(nèi)容。我們更好操作了强饮,我們完全可以把這個Export出來的工程當(dāng)做我們的打包工作目錄由桌,連aar都不需要,也就是上面說的混淆問題的解決方案胡陪。

3沥寥、操作Export自動打包

我們完全可以把Export出來的工程當(dāng)做打包目錄,因為這里完全是Android環(huán)境柠座,我們可以用gradle做任何事邑雅。
這里我們用Jenkins的話,我們就用shell來做就好了妈经,這里我們做的主要是這幾點淮野,
3.1、把我們那些module的代碼拉下來吹泡,git或者svn骤星,放到工程目錄下
3.2、把我們預(yù)設(shè)的那些gradle拷貝過來
3.3爆哑、拷貝gradle-wrapper
3.4洞难、其他你需要做的.....

這個看你自己的項目,目的只有一個:把這個目錄搞成一個完整的Android目錄揭朝,需要的加上队贱,不需要的刪掉。完事兒之后你就按照一個Android工程來對待它就好了潭袱。這樣Jenkins上你就得到了一個Android工程柱嫌,做你對應(yīng)的自動打包就好了。比如我現(xiàn)在工程里面的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 "文件夾存在 不創(chuàng)建文件夾"
else
echo "文件夾不存在 創(chuàng)建文件夾"
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!----------------------------"

到這里打包的內(nèi)容就大致完事兒了屯换,只是具體說了思想编丘,自己的工程還是要自己修改的。這里只是說了思想彤悔,看到的不至于走彎路嘉抓,我上面說的這條路是完全可以跑通的,如果大家覺著還可以也可以回去試一試蜗巧。
這里還有個需要注意的點是每個Unity對應(yīng)的gradle版本不一樣掌眠,比如我mac下Unity軟件包目錄下,gradle的文件目錄如下:
/Applications/Unity/Hub/Editor/2019.1.0b4/PlaybackEngines/AndroidPlayer/Tools/gradle/lib/ **
可以去看一下當(dāng)前用的版本是多少幕屹,現(xiàn)在official的
Unity 2018.3.9f1版本對應(yīng)的gradle是4.6了蓝丙,但如果已經(jīng)用某個版版本開發(fā)了很久级遭,突然發(fā)現(xiàn)了gradle不匹配的問題,能否還是用當(dāng)前的Unity版本渺尘,但是升級新的gradle版本呢挫鸽?答案是可以的,替換掉上面我貼出來的路徑里的gradle就好了鸥跟。
比如我想用gradle 5.3的new feature丢郊,但是還想使用
Unity 2018.3.9f1應(yīng)該怎么辦呢?我需要把gradle-5.3-all.zip下載下載医咨,解壓出來覆蓋本機(jī)Unity 2018.3.9f1**的gradle目錄(參考上面貼出來的目錄)就好了枫匾。

還有一些其他的要注意的也寫在下面吧:

1、Unity回調(diào)方式

1.1拟淮、UnityPlayer.UnitySendMessage("objectName", "functionName","value");
這種方式相當(dāng)于靜態(tài)回調(diào)干茉,指定某個腳本的某個方法獲取。
優(yōu)點:方便快捷很泊,直接調(diào)用就好了角虫。
缺點:只能傳一個String,源碼UnityPlayer里可以看到,就只能傳一個String
1.2委造、

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

相當(dāng)于反射的方式獲取到j(luò)ava的某個類戳鹅,然后調(diào)用java的方法設(shè)置給java,回調(diào)的時候重寫的同名的方法可以回調(diào)到C#
優(yōu)點:什么都可傳昏兆,足夠靈活枫虏,可以直接調(diào)用傳回來的類的方法
缺點:相比第一種復(fù)雜,需要設(shè)置給某個類爬虱,回調(diào)的時候需要獲取到這個對象

2模软、回調(diào)Unity線程

Unity可能會需要回調(diào)的時候在unity的主線程里面通過打印Thread.getName可以知道,Unity的主線程叫做UnityMain饮潦。我用的方式就是在Unity調(diào)用Android的時候在他的線程里面創(chuàng)建一個Handler,我們知道在什么線程里面創(chuàng)建Handler會把什么線程的Looper綁定到這個Handler上面携狭,我們回調(diào)的時候就用這個handler發(fā)消息就是在UnityMain線程里面了继蜡。

總結(jié)一下

  1. 基礎(chǔ):
    1.1 生成多個aar
    1.2 aar跟Manifest位置
    1.3 包名設(shè)置
  2. 日常:調(diào)試gradle自動化生成aar并拷貝到對應(yīng)的位置
  3. 打包:
    3.1 Unity打包利弊
    3.2 自定義打包
    3.3 Jenkins自動打包
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市逛腿,隨后出現(xiàn)的幾起案子稀并,更是在濱河造成了極大的恐慌,老刑警劉巖单默,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碘举,死亡現(xiàn)場離奇詭異,居然都是意外死亡搁廓,警方通過查閱死者的電腦和手機(jī)引颈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門耕皮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蝙场,你說我怎么就攤上這事凌停。” “怎么了售滤?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵罚拟,是天一觀的道長。 經(jīng)常有香客問我完箩,道長赐俗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任弊知,我火速辦了婚禮阻逮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吉捶。我一直安慰自己夺鲜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布呐舔。 她就那樣靜靜地躺著币励,像睡著了一般。 火紅的嫁衣襯著肌膚如雪珊拼。 梳的紋絲不亂的頭發(fā)上食呻,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機(jī)與錄音澎现,去河邊找鬼仅胞。 笑死,一個胖子當(dāng)著我的面吹牛剑辫,可吹牛的內(nèi)容都是我干的干旧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼妹蔽,長吁一口氣:“原來是場噩夢啊……” “哼椎眯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起胳岂,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤编整,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后乳丰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掌测,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年产园,在試婚紗的時候發(fā)現(xiàn)自己被綠了汞斧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片夜郁。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖断箫,靈堂內(nèi)的尸體忽然破棺而出拂酣,到底是詐尸還是另有隱情,我是刑警寧澤仲义,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布婶熬,位于F島的核電站,受9級特大地震影響埃撵,放射性物質(zhì)發(fā)生泄漏赵颅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一暂刘、第九天 我趴在偏房一處隱蔽的房頂上張望饺谬。 院中可真熱鬧,春花似錦谣拣、人聲如沸募寨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拔鹰。三九已至,卻和暖如春贵涵,著一層夾襖步出監(jiān)牢的瞬間列肢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工宾茂, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留瓷马,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓跨晴,卻偏偏與公主長得像欧聘,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子端盆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355