隨着Unity、x等優秀跨平臺遊戲引擎的出現,開發者能夠把本身從繁重的、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
開始你的第一個Android程序
安裝完Android Studio而且配置好代理之後咱們就能夠打開它,在彈出的框中選擇「Start a new Android Studioproject」。
html
在接下來彈出的界面裏面輸入應用名稱,公司域名(這個其實不怎麼重要)以包名(Package Name),其中我認爲最重要的是包名,畢竟看一個應用的包名能夠看得出一個開發者的逼格如何。。。
java
接下來選擇要開發什麼類型的App,這裏勾上Phone and Tablet就能夠了。SDK的選擇通常來講根據項目的須要,最低通常不低於API 9: Android 2.3(Gingerbread),這也是Unity能接受的最低SDK。若是有些插件不能運行在這麼低的Android SDK環境下的話能夠酌情考慮提高到API 15: Android 4.3(IceCreamSandwich),這個等級的API通常也是能夠兼容絕大多數近3-4年的機器。
android
由於咱們要輸出的內容是給Unity用的,這裏能夠先選擇不帶有Activity(就是承載遊戲畫面的基礎部件),後續用到再說。
編程
點擊OK之後Android Studio就會開始初始化當前的這個Android項目。初始化會須要一段時間,由於AndroidStudio有可能會去下載一些必要的框架或者更新Android工具的版本。初始化完成之後到左邊按照圖裏面的步驟點開就能夠看到整個項目目錄樹的狀況。
bash
經過上圖咱們能夠知道,一個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。
app
在彈出的窗口中給你的Activity取個符合Java代碼規範的名字,而後再想個合理的包名(固然,也能夠直接用默認項目的包名也能夠)。能夠參考下圖的配置:
框架
其中的Generate Layout File,咱們在製做給Unity遊戲用的Activity是不須要勾上的。Launcher Activity勾上之後Android Studio會幫你在當前模塊的AndroidManifest.xml中聲明本Activity是應用的入口之一。做爲一個庫項目咱們這邊其實也不須要這個選項。點擊Finish以後Android Studio就會幫咱們在指定目錄下建立一個很簡單的Activity。裏面的內容以下:
ide
須要注意的是這只是一個最基礎的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。
svn
從項目的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來調度線程,感興趣的同窗能夠上網查一下。
導入到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上,都能找到解決方案。
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_ && !UNITY_EDITOR這個推薦的寫法,若是不過濾掉UNITY_EDITOR會在運行的時候報錯。
4. 推薦在new出AndroidJavaClass還有AndroidJavaObject的地方用using來進行保護,確保執行結束後Unity會自動回收相應的代碼。