1. 程式人生 > >unity安卓打包——說明——轉載

unity安卓打包——說明——轉載

隨著Unity、cocos2dx等優秀跨平臺遊戲引擎的出現,開發者可以把自己從繁重的Android、iOS原生臺開發中解放出來,把精力放在遊戲的創作。原來做一款跨平臺的遊戲可能需要開發者懂得Java、Objective-C、C#甚至是C、C++,現在藉助Unity我們開發者只需要懂得很少的原生應用開發知識就能夠打造一款優秀的遊戲。特別是在鵝廠,有了Apollo這樣的元件,原生的接入更加簡單,可能每個專案組只需要有1-2個人懂Android,iOS開發就夠了。但是也正因為如此,很多同事有了充足的理由不去學習、接觸Android和iOS的開發,等到真正需要做接入的時候才開始找人找資料,難免會踩坑。基於此,本文的目的就是通過介紹基礎的Android開發知識以及部分的實際操作,讓大家有一定的Android基礎知識儲備。又或者是當作一份Unity接入Android SDK/外掛的基礎教程,只要照著做,就基本上不會錯了。

本文將會從大家熟悉的Unity為出發點來介紹如何將自己寫的或者第三方的Android外掛整合到自己的遊戲中。

1. Unity是怎麼打包APK檔案的?

2. Android開發基礎以及匯入到Unity

Unity是怎麼打包APK檔案的?

大家看過一些第三方元件的接入文件都知道,在Unity裡面有幾個特殊的資料夾是跟打包APK有關的。首先我們就來了解一下,這些資料夾裡面的內容是經歷了哪些操作才被放到APK裡面的呢?

在Unity的Assets目錄下,Plugins/Android無疑是其中的重中之重,首先我們先來看一個常見的Plugins/Android目錄是什麼樣子的。

-Android

-- ApolloBase

-- ApolloPlugins

-- assets

-- libs

-- res

-- AndroidManifest.xml

後面的四個是Android工程的檔案。前面兩個資料夾是我們引用的第三方庫,他們也會被打包到APK中。我們這個時候如果點進去前兩個資料夾,我們會發現他們的目錄結構跟Android這個目錄也很像,大概是一下這個樣子的。

-ApolloPlugins

-- libs

-- res

-- AndroidManifest.xml

-- project.properties

比較上下兩層的目錄介面我們可以發現有很多相似的部分,如:libs、res、assets資料夾以及AndroidManifest.xml檔案。這些其實都是一個標準的Android專案的所需要的檔案。Unity自帶的Android打包工具的作用就是把上述這幾個資料夾裡面的內容以固定的方式組織起來壓縮到APK檔案裡面。

接下來我們分別來看看Android打包工具都會做什麼樣的操作。

● libs資料夾裡面有很多*.jar檔案,以及被放在固定名字的資料夾裡面的*.so檔案。*.jar檔案是Java編譯器把.java程式碼編譯後的檔案,Android在打包的時候會把專案裡面的所有jar檔案進行一次合併、壓縮、重新編譯變成classes.dex檔案被放在APK根目錄下。當應用被執行的時候Android系統內的Java虛擬機器(Dalvik或者Art),會去解讀classes.dex裡面的位元組碼並且執行。把眾多jar包編譯成classes.dex檔案是打包Android應用不可或缺的一步。

看到這裡有人可能會想不對啊,這一步只將jar包打成dex檔案,那之前的java檔案生成jar檔案難道不是在這一步做嗎?沒錯,這裡用的jar包一般是由其他Android的IDE生成完成後再拷貝過來的。本文後面的部分會涉及到怎麼使用Android的IDE並且生成必要的檔案。

● libs資料夾的*.so檔案則是可以動態的被Android系統載入的庫檔案,一般是由C/C++撰寫而成然後編譯成的二進位制檔案。要注意的是,由於實際執行這些二進位制庫的CPU的架構不一樣,所以同樣的C\C++程式碼一般會針對不同的CPU架構生成幾分不同的檔案。這就是為什麼libs資料夾裡面通常都有armeabi-v7a、armeabi、x86等幾個固定的資料夾,而且裡面的.so檔案也都是有相同的命名方式。Java虛擬機器在載入這些動態庫的時候會根據當前CPU的架構來選擇對應的so檔案。有時候這些so檔案是可以在不同的CPU架構上執行的,只是在不對應的架構上執行速度會慢一些,所以當追求速度的時候可以給針對每個架構輸出對應的so檔案,當追求包體大小的時候輸出一個armeabi的so檔案就可以了。

● assets資料夾,這個裡面的東西最簡單了,在打包APK的時候,這些檔案裡面的內容會被原封不動的被拷貝到APK根目錄下的assets資料夾。這個資料夾有幾個特性。

√ 裡面的檔案基本不會被Android的打包工具修改,應用裡面要用的時候可以讀出來。

√ 打出包以後,這個資料夾是隻讀的,不能修改。

√ 讀取這個資料夾裡面的內容的時候要通過特定的Android API來讀取,參考getAssets()。

√ 基於上述兩點,在Unity中,要讀取這部分內容要通過WWW來進行載入。

除了Plugins/Android內的所有assets資料夾裡面的檔案會連同StreamingAssets目錄下的檔案一起被放到APK根目錄下的assets資料夾。

● res資料夾裡面一般放的是xml檔案以及一些圖片素材檔案。xml檔案一般來說有以下幾種:

√ 佈局檔案,被放在res中以layout開頭的資料夾中,檔案裡描述的一般都是原生介面的佈局資訊。由於Unity遊戲的顯示是直接通過GL指令來完成的,所以我們一般不會涉及到這些檔案。

√ 字串定義檔案,一般被放到values資料夾下,這個裡面可以定義一些字串在裡面,方便程式做國際

化還有本地化用。當然有時候被放到裡面的還有其他xml會引用到的字串,一般常見的是app的名稱。

√ 動畫檔案,一般定義的是Android原生介面元素的動畫,對於Unity遊戲,我們一般也不會涉及他。

√ 圖片資源,一般放在以drawable為開頭的資料夾內。這些資料夾的字尾一般會根據手機的畫素密度來來進行區分,這樣我們可以往這些資料夾內放入對應畫素密度的圖片資源。

例如字尾為ldpi的drawable資料夾裡面的圖片的尺寸一般來說會是整個系列裡面最小的,因為這個資料夾的內容會被放到畫素密度最低的那些手機上執行。而一般1080p或者2k甚至4k的手機在讀取圖片的時候會從字尾為xxxxhdpi的資料夾裡面去讀,這樣才可以保證應用內的影象清晰。圖片資源在打包過程中會被放到APK的res資料夾內的對應目錄。

√ Android還有其他一些常見的xml檔案,這裡就不一一列舉了。

res資料夾下的xml檔案在被打包的時候會被轉換成一種讀取效率更高的一種特殊格式(也是二進位制的格式),命名的時候還是以xml為結尾被放到APK包裡面的res資料夾下,其目錄結構會跟打包之前的目錄結構相對應。

除了轉換xml之外,Android的打包工具還會把res資料夾下的資原始檔跟程式碼靜態引用到的資原始檔的對映給建立起來,放到APK根目錄的resources.arsc檔案。這一步可以確保安卓應用啟動的時候可以加載出正確的介面,是打包Android應用不可或缺的一步。

● AndroidManifest.xml,這份檔案太重要了,這是一份給Android系統讀取的指引,在Android系統安裝、啟動應用的時候,他會首先來讀取這個檔案的內容,分析出這個應用分別使用了那些基本的元素,以及應該從classes.dex檔案內讀取哪一段程式碼來使用又或者是應該往桌面上放哪個圖示,這個應用能不能被拿來debug等等。在後面的部分會有詳細解釋。打包工具在處理Unity專案裡面的AndroidManifest檔案時會將所有AndroidManifest檔案的內容合併到一起,也就是說主專案引用到的庫專案裡面如果也有AndroidManifest文

件,都會被合併到一起。這樣就不需要手動複製貼上。需要說明的是,這份檔案在打包Android程式的時候是必不可少的,但是在Unity打包的時候,他會先檢查Plugins目錄下有沒有這份檔案,如果沒有就會用一個自帶的AndroidManifest來代替。此外,Unity還會自動檢查專案中AndroidManifest裡面的某些資訊是不是預設值,如果是的話,會拿Unity專案中的值來進行替換。例如,遊戲的App名稱以及圖示等。

● project.properties,這份檔案一般只有在庫專案裡面能看得到,裡面的內容極少,就只有一句話android.library=true。但是少了這份檔案Android的打包工具就不會認為這個資料夾裡面是個Android的庫專案,從而在打包的時候整個資料夾會被忽略。這有時候不會影響到打包的流程,打包過程中也不會報錯,但是打出的APK包缺少資源或者程式碼,一跑就崩潰。關於這份檔案,其實在Unity的官方文件上並沒有詳細的描述(因為他實際上是Android專案的基礎知識),導致很多剛剛接觸Unity-Android開發的開發者在這裡栽坑。曾

經有個很早就開始用Unity做Android遊戲的老前輩告訴我要搞定Unity中的Android庫依賴的做法是用Eclipse開啟Plugins/Android資料夾,把裡面的所有的專案依賴處理好就行了。殊不知這樣將Unity專案跟Eclipse專案耦合在一起的做法是不太合理的,會造成Unity專案開啟的時候緩慢。

● 其他資料夾例如aidl以及jni在Unity生成APK這一步一般不會涉及到,這裡不展開。

看到了上述介紹的Unity打包APK的基礎知識我們知道了往Plugins/Android目錄下放什麼樣的檔案會對APK包產生什麼樣的影響。但是實際上上述的內容只是著重的講了Unity是怎麼打包APK,所以接下來會簡述一下打包這個步驟到底是怎麼完成的。

Android提供了一個叫做aapt的工具,這個工具的全稱是Android Asset Packaging Tool,這個工具完成了上述大部分的對資原始檔處理的工作,而Unity則是通過對Android提供的工具鏈(Android Build Tools)的一系列呼叫從而完成打包APK的操作。這裡感覺有點像我們寫了個bat/bash指令碼,這個指令碼按照順序呼叫Android提供的工具一樣。在一些常見的Android IDE裡面,這樣的“bat/bash指令碼”往往是一個完整的構建系統。最早的Android IDE是Eclipse,他的構建系統是Ant,是基於XML配置的構建系統。後來Android團隊推出了Android專用的IDE——Android Studio(這個在文章後面會有詳述),他的構建系統則是換成了gradle,從基於xml的配置一下子升級到了語言(DSL, Domain Specific Language)的層級,給使用Android Studio的人帶來更多的彈性。

寫到這裡我想很多人都清楚了要怎麼把Android的SDK/外掛放到Unity裡面並且打包到Unity裡面。這時候應該有人會說,光會放這些檔案不夠啊,我還需要知道自己怎麼寫Android的程式碼並且輸出相應的SDK/外掛給Unity使用啊。

本文接下來的內容將會一步一步描述怎麼寫Android程式碼並且輸出庫檔案給Unity。

Android開發基礎以及匯入到Unity

1

開始你的第一個Android程式

安裝完Android Studio並且配置好代理以後我們就可以開啟它,在彈出的框中選擇“Start a new Android Studioproject”。

在接下來彈出的介面裡面輸入應用名稱,公司域名(這個其實不怎麼重要)以包名(Package Name),其中我認為最重要的是包名,畢竟看一個應用的包名可以看得出一個開發者的逼格如何。。。

接下來選擇要開發什麼型別的App,這裡勾上Phone and Tablet就可以了。SDK的選擇一般來說根據專案的需要,最低一般不低於API 9: Android 2.3(Gingerbread),這也是Unity能接受的最低SDK。如果有些外掛不能執行在這麼低的Android SDK環境下的話可以酌情考慮提升到API 15: Android 4.3(IceCreamSandwich),這個等級的API一般也是可以相容絕大多數近3-4年的機器。

因為我們要輸出的內容是給Unity用的,這裡可以先選擇不帶有Activity(就是承載遊戲畫面的基礎部件),後續用到再說。

點選OK以後Android Studio就會開始初始化當前的這個Android專案。初始化會需要一段時間,因為AndroidStudio有可能會去下載一些必要的框架或者更新Android工具的版本。初始化完成以後到左邊按照圖裡面的步驟點開就可以看到整個專案目錄樹的情況。

通過上圖我們可以知道,一個Android Studio的專案(Project)可以由許多小的模組(Module)組成,這些模組可以是帶有Activity的應用類模組,也可以是不帶有Activity的庫模組等等。這些小的模組之間可以有引用關係。我們可以把一些完成基礎功能或者容易被複用的模組單獨拆出來。

如果要新建一個模組我們可以在上圖的列表中點右鍵選擇New Module,在彈出的介面中我們可以選擇要新建什麼樣的模組,或者從Eclipse匯入舊的專案也可以。一般來說給Unity遊戲開發外掛最常用的就是庫模組(AndroidLibrary)。同樣的,在接下來彈出的視窗中填寫好模組名稱、包名以及最低執行的SDK。

簡單的看一下Android專案的目錄結構。如下圖所示:

● libs目錄跟本文第一部分介紹的libs目錄的功用是一樣的,把依賴到的庫放在這裡面就可以了。

● src/main/res目錄也是跟本文第一部分介紹的res目錄的功能和結構是一樣的,把對應資源放進去就可以了。

● 接下來是java程式碼所在的目錄src/main/java,這個目錄有點特殊,他的子路徑跟java檔案裡面定義的包名(package name)要對應的上。

● AndroidManifest.xml也是跟第一部分介紹的AndroidManifest的功能是一樣的。

● build資料夾是Android Studio動態生成的,打出的APK包(應用模組)或者AAR包(庫模組)會被放到這裡面的output資料夾。需要注意的是這個資料夾不應該被放提交到svn裡面,要不然會造成專案成員之間的衝突,切記。

● src/test以及src/androidTest是做單元測試用的,本文不涉及。

至此,我們就可以開始動手寫程式碼了,這裡我們寫一個可以彈出Android的Toast提示的Activity來替換掉Unity預設的Activity。

簡述一下Unity跟Activity的關係:在Android系統中,開啟一個應用,就是開啟該應用指定的啟動Activity。

Unity裡面有個預設的Activity,他的作用就是在系統啟動應用的時候載入Unity的Player,這個Player就是就相當於是Unity應用的“播放器”,他會執行我們在Unity專案中創作的內容,並且通過GL指令渲染到指定的SurfaceView中,而SurfaceView則是被置於Activity裡面的一個特殊的View。

首先,我們在Android Studio中找到src/main/java(如上圖所示),然後點選右鍵,選擇新建Empty Activity。

在彈出的視窗中給你的Activity取個符合Java程式碼規範的名字,然後再想個合理的包名(當然,也可以直接用預設專案的包名也可以)。可以參考下圖的配置:

其中的Generate Layout File,我們在製作給Unity遊戲用的Activity是不需要勾上的。Launcher Activity勾上以後Android Studio會幫你在當前模組的AndroidManifest.xml中宣告本Activity是應用的入口之一。作為一個庫專案我們這邊其實也不需要這個選項。點選Finish之後Android Studio就會幫我們在指定目錄下建立一個很簡單的Activity。裡面的內容如下:

需要注意的是這只是一個最基礎的Android Activity,他還不會去載入我們的Unity出來,所以我們要讓他繼承自Unity的Activity而不是預設的。為此,我們要先將Unity相應的jar包引入到我們的模組當中。首先找到Unity的安裝目錄,然後找到以下子目錄Editor\Data\PlaybackEngines\AndroidPlayer\Variations\il2cpp\Release\Classes\裡面的classes.jar,這個就是被打包成jar包的Unity預設的Activity。我們把這個jar包複製到當前模組的libs目錄下(可以把這個jar包改成你想要的名字,便於管理)。(這個jar包的原始碼在Editor\Data\PlaybackEngines\AndroidPlayer\Source\com\unity3d\player這個目錄下。感興趣的同學可以翻閱一下原始碼,就可以理解Unity播放器的載入機制。)

接下來,我們可以在Android Studio左邊的Project View中找到當前的模組以後點選右鍵,選擇“Open ModuleSetting”或者直接按F4。在彈出的視窗中我們選到最右邊的頁籤“Dependencies”,然後選擇右邊綠色的加號-JarDependency。

從專案的libs資料夾中找到剛剛匯入的jar包,點選OK即可。接下來有一個比較關鍵的步驟就是,我們改變這個jar包的scope屬性,因為預設的scope屬性(Compile)是會將該jar包裡面的內容跟本模組裡面Java程式碼合併到一起。這在之後Unity打包這個模組的jar包的時候會報錯,因為Unity裡面內建了剛剛這個jar包。所以我們可以參考下圖把這個jar包的scope設定成provided。

然後刪除上述列表的第一行,因為他會把所有libs資料夾下的jar包都打包到一起。跟我們剛剛做完的provided設定會有衝突。

搞定了這步驟以後我們就可以回到剛剛新創建出來的Activity把他的父類改成UnityPlayerActivity,同時別忘記引用一下相應的package,改完之後的程式碼是這樣的:

到這一步,如果我們的Activity如果能被執行的話,他應該能夠藉助他的父類UnityPlayerActivity裡面的程式碼來執行Unity。接下來,我們來給這個Activity新增一方法,當這個方法被呼叫的時候會展示一個系統預設的Toast提示。

看得出來,裡面最核心的一個方法其實就只是呼叫Android裡面的Toast元件而已,沒啥好解釋的。相反,是外面的runOnUiThread是值得大家注意的,在Android程式設計中,所有涉及到對UI的操作必須要放在UI執行緒裡面來做,否則會造成其他執行緒修改UI執行緒裡面的資料然後崩潰。由於我們寫的這個ShowMessage方法最後會被Unity那邊呼叫,而來自Unity的呼叫可能不是UI執行緒,所以我們要給他做適當的保護。

在Android中有很多種排程方法可以把某段程式碼放到UI執行緒裡面來跑。上面這段程式碼的runOnUiThread的寫法是最簡便的一種寫法。如果遇到比較複雜的邏輯可以考慮使用Messenger或者Handle來排程執行緒,感興趣的同學可以上網查一下。

2

匯入到Unity並且編譯

完成Activity的程式碼編寫之後就可以輸出這個模組到Unity專案中去。在Android Studio中選擇Build - Make Project或者是在左邊的專案檢視中選中要匯出的模組然後選擇Build - Make Module。選擇完了之後就可以看到下面有個Gradle的進度條,待進度條完成了以後我們就可以到該模組的build/outputs/aar目錄下去找輸出的檔案。開啟這個資料夾,可以看到有個*.aar的檔案。這個就是該模組所編譯出來的結果,如果你用解壓縮軟體去解壓縮它,你會發現他幾乎就是一個完整的Android工程。根據本文第一部分所說的內容,我們只要在Unity工程中的Plugins/Android目錄下新建一個資料夾,然後把這個檔案解壓縮以後整個丟進去,再手寫一個名字叫project.properties,內容是android.library=true的檔案放到新建的資料夾裡面就可以了。

勝利在望,我們接下來只要把Unity工程裡面的AndroidManifest.xml檔案的入口Activity從Unity預設的的改成我們剛剛寫的這個就可以了。需要注意的是,如果是舊的Unity工程,可能已經有人寫過相關的AndroidManifest檔案放在了Plugins/Android目錄下,但是如果是全新的Unity專案的話,就沒有這份檔案了。在打包的時候,如果Unity發現Plugins/Android目錄下沒有這份檔案,他會複製一份預設的檔案並且修改其中跟專案有關的內容。這裡我們可以從Unity的安裝目錄的Editor\Data\PlaybackEngines\AndroidPlayer\Apk資料夾內找到AndroidManifest.xml這份檔案,把它複製一份到Unity工程的Plugins/Android目錄下。接下來就是修改裡面的內容。

這裡解釋一下這份檔案裡面的一些關鍵內容。

● package="com.unity3d.player"這裡的內容如果放著不動,打包的時候Unity會將其修改為Player Setting的Bundle Identifier。

● android:versionCode以及android:versionName這兩部分的內容則在打包時會根據Player Setting裡面的Version以及Bundle Version Code的內容來進行修改。

● android:icon以及android:label這兩個對應的是應用的圖示以及應用名稱。如果不改的話,Unity也會自動根據Player Setting裡面的內容來進行修改。

● android:debuggable="true"這個在打包的時候Unity也會自動根據Build Setting裡面的Development Build選項自動進行修改。

● activity裡面的android:name,這個name只的是該activity需要執行的哪個Java的Activity的類。如果不修改,載入的就是Unity預設Activity的類。這篇文章需要把預設的Activity改成剛剛我們的實現,所以,我們把剛剛寫好的那個Activity的完整名稱寫上去(包括包名還有類名)。

● activity裡面的android:label,這個是在桌面上圖示下面寫的那一行文字,也是應用的名稱。不修改的話Unity會幫你維護。

● meta-data的這一行的name值是key,value值就是這個key對應的內容。meta-data可以根據需要自定義多個,但是key值不能重複,上面程式碼裡面的unityplayer.UnityActivity應該是寫給Unity看的,讓Unity知道他自己是執行在這個Activity上。

這裡我們基本上只要修改activity裡面的android:name這一項。修改完成後,我們就可以通過Unity自帶Build功能來出Android包了。出包之前請檢查一下Player Setting裡面的Bundle Identifier,不能留預設的包名在這裡,會造成編譯失敗。編譯過程中,可能會出現一些錯誤,下面羅列幾個常見的錯誤,可以嘗試解決:

1. 合併Manifest檔案出錯,一般來說是在合併所有的AndroidManifest檔案的時候出的錯,常見的有重複定義了activity、裡面的最低sdk寫錯了。模組的最低sdk不可低於專案的最低sdk。

2. jar檔案dex錯誤,當你的專案中不小心存在了一個以上的相同的jar檔案,就會出這個錯誤,把重複的刪掉,只留一個就好了。

3. 找不到Android SDK裡面的工具,這個一般來講是Unity自己的bug,Unity一般不能相容最新的Android SDK的工具,所以要手動降級才行。

除了上述這些之外,在打包Android專案的過程中還會出現這些那些的錯誤,大家看到以後不要慌張,會報錯總是好的,而且一般的錯誤你把錯誤資訊貼在萬能的Google上,都能找到解決方案。

3

Unity對Android程式碼的呼叫

文章到這裡為止,說清楚了怎麼把Android這邊寫成的外掛打包到Unity的專案中去。但其實並沒有涉及到Unity中怎麼呼叫剛剛寫好在Android的Activity中的程式碼。這一部對於一個Unity開發來說其實非常簡單,只要以Unity提供的AndroidJavaClass還有AndroidJavaObject來做為中介就可以在Unity和Java中互傳資料。這兩個類的呼叫給人一種通過反射來呼叫Java程式碼的感覺。只要你能通過包名和類名拿到某個Java物件,就可以直接通過成員變數名稱或者方法名稱直接呼叫到Java那邊的程式碼。舉個例子,假如要在Unity中呼叫剛剛我們寫的那個類的ShowMessage類的話我們需要在Unity中準備以下程式碼。

簡單介紹一下這段程式碼的幾個關鍵點:

1. 通過UnityPlayer可以很方便的拿到當前Activity的Java物件例項。

2. 對Java物件例項的方法的呼叫實際上很簡單,只要呼叫Call就可以了。

3. 注意用巨集來區隔Native程式碼。UNITY_ANDROID && !UNITY_EDITOR這個推薦的寫法,如果不過濾掉UNITY_EDITOR會在執行的時候報錯。

4. 推薦在new出AndroidJavaClass還有AndroidJavaObject的地方用using來進行保護,確保執行結束後Unity會自動回收相應的程式碼。

其他的部分在這篇文章裡面我們不展開。



作者:騰訊課堂
連結:https://www.jianshu.com/p/947686c84a96
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。