最近做了Unity跟Android對接項目,有些經(jīng)驗分享一下:
一,跑Hello World:
1、打包需要給Unity調(diào)用的aar(gradle中點擊assemble Debug或者Release 對應的assemble),會在對應的module的build/outputs/aar下面生成對應的aar,現(xiàn)在網(wǎng)上的教程大部分是只有一個module的,但我們實際項目的module可能會有多個,此種情況可以點擊AS右側(cè)Gradle中的Tasks/other/bundleDebugAar會生成所有module中的aar,去對應的module的build下面就能看到,得到的aar放到Unity項目中的Plugins目錄下(實測,其實aar放到其他目錄也可以,不必要非放到這個目錄下)
2、AndroidManifest.xml文件,放到Plugins/Android目錄下(Mainfest文件必須放到這個目錄下,不放會直接報錯)Manifest中的unityplayer.UnityActivity 的修改不再贅述,網(wǎng)上很多了。
3、Unity運行到Android機器上的包名設(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,并把aar拷貝到Unity的Plugins目錄下,Unity才能識別到這個修改。 所以我們這里需要做的一個事情就是每次改Android的時候需要生成aar并且拷貝到對應的Plugins目錄下。上面我們也說了可以用gradle的bundleXXXAar來生成對應的aar文件。
可以在對應的gradle android閉包下加此代碼,copyAarFiles是你自己的拷貝aar函數(shù)。
我是寫在了一個copyAar.gradle目錄下。然后用applyfrom: ‘copyAar.gradle’ 引進來,寫在一個單獨的gradle文件中的好處就是,如果你有多個module可以需要下面的android.libraryVariants.all一段代碼就好了,直接復用 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如下(腳本中的目錄對應到你自己的本地路徑)
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調(diào)用就能看到Android的修改了。
到這里,日常生產(chǎn)沒問題了,那我們遇到的下個問題是打包,下面說打包。
三,打包
1、點擊Unity的打包
Unity給我們提供了打包選項。Android的PlayerSettings下面Publishing Settings,填寫包名,keystore和alies,然后用Unity給你生成的mainTemplate.gradle,他提供給你的proguard.cfg。我們可以完成打包。
當然,作為一個Android開發(fā)人員,我們不是來介紹Unity提供給我們的這些打包工具的,或者說我們并不滿足于這些。因為這可以滿足基本的打包需求,如果我們有更靈活的選擇的話就不需要在它給我們的條條框框里面做事情,況且它這個其實并不滿足一些需求。我們都知道,其實每個公司每個團隊都會有一些代碼積累,可能我們每次開新項目的時候并不需要從網(wǎng)絡框架圖像加載統(tǒng)計曝光框架這些基礎(chǔ)的東西選起,因為我們之前的項目已經(jīng)做過了,我們只需要把之前的module拿過來,引用進來就可以直接用了。
那這個時候問題就來了,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里面的冗余,就相當于把多個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)域就好說了,我們可以把這文件當成我們Android里面的打包文件,直接可以在這里寫gradle打包設(shè)置,他的那些東西我們其實都拋開不要就好了。(這里忘了說個東西,網(wǎng)上很多都說把我們gradle依賴的文件下載下來,搞成jar包放到Plugins里面,依賴的依賴也要搞進來,麻煩的一批,其實不用,只要把 api 'com.google.code.gson:gson:2.8.5’ 這玩意兒寫到mainTemplate.gradle里面就好了,gradle會幫我們搞定),同時我們上面說的ApplicationId跟Manifest的目錄名字就可以分開了。
所以說,這就是最終解決方案了嗎?當然不是,如果我們要在Jenkins上面自動打包的話,這兒其實還是很難受的。我們知道Unity還給我們提供了Export的功能,就是說把Unity導出成一個Android工程,導出來之后我們?nèi)タ垂こ讨械腷uild.gradle文件其實就是mainTemplate.gradle替換了**這些玩意兒之后的內(nèi)容。我們更好操作了,我們完全可以把這個Export出來的工程當做我們的打包工作目錄,連aar都不需要,也就是上面說的混淆問題的解決方案。
3、操作Export自動打包
我們完全可以把Export出來的工程當做打包目錄,因為這里完全是Android環(huán)境,我們可以用gradle做任何事。
這里我們用Jenkins的話,我們就用shell來做就好了,這里我們做的主要是這幾點,
3.1、把我們那些module的代碼拉下來,git或者svn,放到工程目錄下
3.2、把我們預設(shè)的那些gradle拷貝過來
3.3、拷貝gradle-wrapper
3.4、其他你需要做的.....
這個看你自己的項目,目的只有一個:把這個目錄搞成一個完整的Android目錄,需要的加上,不需要的刪掉。完事兒之后你就按照一個Android工程來對待它就好了。這樣Jenkins上你就得到了一個Android工程,做你對應的自動打包就好了。比如我現(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對應的gradle版本不一樣,比如我mac下Unity軟件包目錄下,gradle的文件目錄如下:
/Applications/Unity/Hub/Editor/2019.1.0b4/PlaybackEngines/AndroidPlayer/Tools/gradle/lib/ **
可以去看一下當前用的版本是多少,現(xiàn)在official的Unity 2018.3.9f1版本對應的gradle是4.6了,但如果已經(jīng)用某個版版本開發(fā)了很久,突然發(fā)現(xiàn)了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回調(diào)方式
1.1、UnityPlayer.UnitySendMessage("objectName", "functionName","value");
這種方式相當于靜態(tài)回調(diào),指定某個腳本的某個方法獲取。
優(yōu)點:方便快捷,直接調(diào)用就好了。
缺點:只能傳一個String,源碼UnityPlayer里可以看到,就只能傳一個String
1.2、
public class UnityCallbackListener : AndroidJavaProxy
{
public UnityCallbackListener() : base(“com.yocn.base.UnityCallbackListener")
{
}
}
相當于反射的方式獲取到java的某個類,然后調(diào)用java的方法設(shè)置給java,回調(diào)的時候重寫的同名的方法可以回調(diào)到C#
優(yōu)點:什么都可傳,足夠靈活,可以直接調(diào)用傳回來的類的方法
缺點:相比第一種復雜,需要設(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é)一下
- 基礎(chǔ):
1.1 生成多個aar
1.2 aar跟Manifest位置
1.3 包名設(shè)置 - 日常:調(diào)試gradle自動化生成aar并拷貝到對應的位置
- 打包:
3.1 Unity打包利弊
3.2 自定義打包
3.3 Jenkins自動打包