一、使用aab+PAD的原因
一切的根源都在谷歌商店。
谷歌商店一直對(duì)上架的應(yīng)用和游戲有嚴(yán)格的要求。最早期的時(shí)候,谷歌商店要求apk容量限制在50mb內(nèi),后來(lái)隨著應(yīng)用的普遍容量增大,谷歌商店把a(bǔ)pk的容量限制放寬到100mb。
但對(duì)于游戲來(lái)說(shuō),100mb的容量明顯不夠用。于是谷歌在早期提出了apk+obb(Opaque Binary Blob安卓游戲通用數(shù)據(jù)包)的組合,每個(gè)應(yīng)該可以包含一個(gè)100mb以內(nèi)的apk安裝包,然后附帶2個(gè)obb資源文件,一個(gè)main.obb,一個(gè)patch.obb。這樣游戲開發(fā)者可以在不改變apk內(nèi)容的情況下,單純改變main.obb的資源內(nèi)容,或者通過(guò)patch.obb給游戲資源打補(bǔ)丁。這樣用戶在下載應(yīng)用和更新應(yīng)用的時(shí)候,可以不需要下載全部?jī)?nèi)容。
不過(guò)對(duì)于obb的版本管理,是需要游戲開發(fā)者自己去控制的,比如你在谷歌市場(chǎng)上傳了新的obb內(nèi)容,谷歌商店會(huì)幫用戶下載到手機(jī),但用戶手機(jī)里面的舊的obb文件,谷歌是不會(huì)幫用戶刪掉的,為了不會(huì)無(wú)端端占用用戶手機(jī)多余的硬盤空間,研發(fā)需要自己寫代碼去刪掉舊的obb文件。然后手機(jī)里面同時(shí)存在多個(gè)obb文件,游戲應(yīng)該加載哪一個(gè),也是需要游戲研發(fā)自己寫邏輯去判斷。
后來(lái)谷歌商店提出了aab+PAD的方式上架應(yīng)用,其中aab文件的容量不得超過(guò)150mb,然后資源包PAD的容量上限為2gb。不過(guò)由于打包和資源管理方式比較特殊,一直沒(méi)有得到研發(fā)方的廣泛支持。
到了2021年8月,谷歌商店全面停止apk+obb的應(yīng)用上架方式,而強(qiáng)制研發(fā)團(tuán)隊(duì)改用aab+PAD的組合上架游戲應(yīng)用。于是,很多研發(fā)團(tuán)隊(duì)都開始研究怎樣發(fā)布aab+PAD了。
二、aab和PAD是什么?
aab:Android App Bundle
直接翻譯應(yīng)該是安卓應(yīng)用包。
由于安卓手機(jī)的硬件和系統(tǒng)一直都非常復(fù)雜,舉個(gè)簡(jiǎn)單的例子,早期的手機(jī)是32位的armv7,后來(lái)出的手機(jī)是64位的arm64,有些更早期的手機(jī)和模擬器,甚至是x86的。之前如果一個(gè)手機(jī)應(yīng)用要支持32位手機(jī)和64位手機(jī),還要支持x86的模擬器,必須在手機(jī)安裝包里面內(nèi)置3套代碼庫(kù)。這樣對(duì)于一臺(tái)手機(jī)來(lái)說(shuō),其實(shí)安裝包里面有一些內(nèi)容是不需要的。
aab的設(shè)計(jì)思路是,在研發(fā)導(dǎo)出安裝包的時(shí)候,把這些不同的支持庫(kù)都包含在aab里面,然后在用戶下載的時(shí)候,谷歌商店會(huì)根據(jù)用戶的硬件和系統(tǒng)的實(shí)際情況,從支持庫(kù)里面挑選出用戶需要的部分,然后組裝成一個(gè)最符合用戶實(shí)際情況的apk來(lái)給用戶安裝使用。
PAD:Play Asset Delivery
直接翻譯應(yīng)該是谷歌Play商店的資源分離
為什么強(qiáng)調(diào)是谷歌Play商店?因?yàn)檫@套PAD資源,其實(shí)是包含了cdn下載服務(wù)的,谷歌商店免費(fèi)提供了cdn內(nèi)容分發(fā)服務(wù)給上架谷歌商店的應(yīng)用使用,所有的PAD文件,都是存放在谷歌商店的服務(wù)器上面的。
按照谷歌商店的想法,所有上架谷歌商店的應(yīng)用和游戲,基于谷歌提供的cdn服務(wù),可以實(shí)現(xiàn)游戲的資源下載和熱更新,而且是免費(fèi)的。很美好的想法。
實(shí)際情況是,這一套PAD只能用在谷歌商店,而且打包、加載的方式都是獨(dú)特的,并不具有通用性。所以,我個(gè)人認(rèn)為,除非是游戲一開始就打算只上谷歌商店,然后整個(gè)游戲的資源加載框架都是針對(duì)谷歌商店來(lái)寫的,不然,游戲?yàn)榱松掀渌赖纳痰?,還是要自己重新搞一套通用的加載和熱更新的框架,并花錢租用資源存儲(chǔ)和cdn分發(fā)服務(wù)。
三、Unity對(duì)aab+PAD的支持
早在unity2017的某個(gè)版本開始,Unity已經(jīng)有對(duì)發(fā)布aab的支持。
但這個(gè)功能發(fā)布的aab,是把資源全部打在安裝包的base里面,如果游戲的容量很大,會(huì)很容易超過(guò)谷歌商店對(duì)于aab文件不得超過(guò)150mb的容量限制。
至于PAD分離和加載功能,unity似乎一直沒(méi)有很明確的官方支持,都是需要取谷歌官網(wǎng)下載支持Unity的庫(kù)導(dǎo)入項(xiàng)目使用。unity之前用于分離obb資源的功能,并不適用于aab模式。
谷歌插件對(duì)Unity的支持
插件下載:
https://github.com/google/play-unity-plugins/releases?spm=a2c6h.12873639.article-detail.4.dff34bbfofz9NZ
下載完之后導(dǎo)入U(xiǎn)nity工程項(xiàng)目
導(dǎo)入了項(xiàng)目里面后,會(huì)看到出現(xiàn)了谷歌的工具欄,里面有關(guān)于PAD的設(shè)置,還有打包aab的選項(xiàng)。
打開Asset Delivery設(shè)置工具,用戶可以在這里添加文件夾,并逐個(gè)(注意是逐個(gè))對(duì)資源設(shè)置類型。
具體的類型有:
1.install-time:在安裝apk的時(shí)候會(huì)同時(shí)下載并安裝到手機(jī)里的資源。只要安裝完apk,這部分的資源就能正常的在本地訪問(wèn)得到。
2.fast-follow:在安裝完apk之后,這類型的資源會(huì)立刻進(jìn)行下載。如果安裝完apk立刻打開應(yīng)用,這部分的內(nèi)容并不一定能保證下載到本地。
3.on-demand:這種類型的資源在安裝完apk之后,并不會(huì)自動(dòng)下載,而是需要游戲自己寫邏輯在需要的時(shí)候從服務(wù)器上下載。
從工具上看,似乎谷歌提供的功能還算比較合理。但實(shí)際上應(yīng)用起來(lái),會(huì)有3個(gè)問(wèn)題:
1、雖然可以通過(guò)選擇文件夾來(lái)批量導(dǎo)入文件,但進(jìn)行設(shè)置的時(shí)候,居然是逐個(gè)文件設(shè)置的。如果有1萬(wàn)個(gè)文件,呵呵了
2、做這個(gè)插件界面功能的人好像比較敷衍,如果加載的文件很多,會(huì)真的把一萬(wàn)個(gè)文件全部列出來(lái),導(dǎo)致Unity卡死,無(wú)法操作。如果不小心導(dǎo)入了很多文件導(dǎo)致卡死,可以退進(jìn)程后,把這個(gè)文件刪掉:Library/PlayAssetPackConfig.json
3、這個(gè)工具只認(rèn)Unity的AssetBundle,而且選擇文件夾的時(shí)候,必須是選擇原生打包AssetBundle的文件夾,意思是文件夾里面必須包含一個(gè)和文件夾名一樣的manifest文件,才能選擇。而一般的游戲項(xiàng)目,都會(huì)有自己的資源加密手段,這樣就導(dǎo)致了沒(méi)法通過(guò)文件夾來(lái)選擇了。
基于這3個(gè)問(wèn)題,基本上可以認(rèn)為,谷歌提供的這個(gè)Unity編輯器工具是不能正常使用的。幸好,他還提供了API,可以自己寫代碼去批量設(shè)置并生成配置文件。
四、代碼設(shè)置PAD
代碼設(shè)置PAD的pack配置,需要有3個(gè)要素
1、pack的名稱(packName)
2、放置這個(gè)pack的路徑
3、這個(gè)pack的類型是什么
然后,我們就可以調(diào)用api,把整個(gè)文件夾設(shè)置成同一個(gè)pack了。
至于一個(gè)游戲里面的資源,究竟要分成多少個(gè)pack,這是和游戲本身的加載策略有關(guān)系的。既可以把每一個(gè)文件設(shè)置成單獨(dú)的pack,也可以把某一種類別的東西設(shè)置成pack,也可以把所有資源設(shè)置成同一個(gè)pack
設(shè)置一個(gè)pack的代碼大概是這樣的:
string packName = “pad”;//這里是你想打包的pack的名稱,我這里隨便命名為pad
string path = GetPADPath();//這里是指定一個(gè)pack生成后存放的路徑,可以自己指定
var assetPackConfig = new Google.Android.AppBundle.Editor.AssetPackConfig();
assetPackConfig.AddAssetsFolder(packName, path, Google.Android.AppBundle.Editor.AssetPackDeliveryMode.InstallTime); Google.Android.AppBundle.Editor.AssetPacks.AssetPackConfigSerializer.SaveConfig(assetPackConfig);
上面主要的方法就是assetPackConfig.AddAssetsFolder,調(diào)用這個(gè)方法,就會(huì)把pack名稱、路徑和類型傳進(jìn)去assetPackConfig里面。如果有多個(gè)需要設(shè)置的pack,那么就調(diào)用AddAssetsFolder添加多次,最后再調(diào)用SaveConfig把設(shè)置保存下來(lái)。
保存后的配置路徑是Library/PlayAssetPackConfig.json
五、打包aab+PAD
Unity自帶的打包功能,是沒(méi)有讀取上面我們說(shuō)的PlayAssetPackConfig.json,所以他并不會(huì)把自定義的pack打到aab里面。
我們需要自定義pack的時(shí)候,就不能用Unity自帶的API打包。而要改成使用谷歌API提供的AppBundleBuilder類來(lái)打包。
我們直接調(diào)用谷歌API提供的
Google.Android.AppBundle.Editor.Internal.AppBundlePublisher.BuildWithPath(path)方法,把需要生成aab的路徑傳進(jìn)去,就可以了。
如果有興趣可以具體看看實(shí)現(xiàn),他是先指定了打包的文件名和路徑,從PlayAssetPackConfig.json里面讀取了PAD的信息,然后再把config數(shù)據(jù)傳到Build方法里面打包。
需要注意的是,如果你自定義了AndroidManifest.xml,那么直接打包是不能正常把包打出來(lái)的,需要設(shè)置一下gradle模板。
在ProjectSetting——Player——Publishing Settings里面,找到Custom Main Gradle Template和Custom Launcher Gradle Template這兩項(xiàng),打鉤。
然后會(huì)在Assets\Plugins\Android文件夾下出現(xiàn)mainTemplate.gradle和launcherTemplate.gradle這兩個(gè)文件。
分別在這兩個(gè)文件里面加上
packagingOptions {
exclude ‘AndroidManifest.xml’
}
這時(shí)候打包應(yīng)該就能通過(guò)了。
六、aab文件結(jié)構(gòu)分析
把打包出來(lái)的aab文件的后綴名改成壓縮包(比如rar或者zip之類),就可以把a(bǔ)ab文件解壓縮出來(lái)。
可以看到里面包含的內(nèi)容,其中base文件夾里面的,是對(duì)應(yīng)正常apk里面的包內(nèi)內(nèi)容,比如Unity的StreamingAssets文件夾內(nèi)容會(huì)放在base/assets/下
由于我剛才設(shè)置了一個(gè)自定義的pack,把所有資源都放到里面去,名字叫做pad,所以在aab文件里面,也會(huì)出現(xiàn)一個(gè)pad文件夾,里面就是我們的自定義pack的文件內(nèi)容。
值得注意的是,pad里面的assets文件夾里面還有一個(gè)assetpack,assetpack里面才是真正的自定義pack文件內(nèi)容,所以結(jié)構(gòu)是pad/assets/assetpack/。如果我們直接打包aab文件并使用谷歌API讀取,不需要關(guān)心這個(gè)問(wèn)題。如果是導(dǎo)出安卓項(xiàng)目打包,并手動(dòng)放入pack,則需要保證路徑不要錯(cuò),很多人可能會(huì)漏了assetpack那一層。
七、aab+PAD安裝后的資源文件的讀取方式
1、Resources和StreamingAssets文件夾里面的內(nèi)容
放在Resources文件夾和StreamingAssets文件夾里面的內(nèi)容,打包時(shí)會(huì)放到base文件夾,其實(shí)就等于正常的apk結(jié)構(gòu)而已,所以,原來(lái)apk是怎樣讀取的,現(xiàn)在也怎樣讀取就可以了。包括了Resources.Load方法,AssetBundle.LoadFromFile等方法都是可以使用的。
2、自定義pack的內(nèi)容
首先說(shuō)明一點(diǎn),PAD的pack內(nèi)容根據(jù)類型不同,不一定直接可以在本地找到,所以按道理應(yīng)該是需要自己判斷fast-follow和on-demand的pack是否在本地,如果不在,還需要請(qǐng)求下載之后才能使用。
不過(guò)由于我個(gè)人覺(jué)得谷歌為我們精心打造的熱更新功能并沒(méi)有太大意義,所以我并沒(méi)有花時(shí)間去研究。
以下我們直接討論肯定已經(jīng)在本地的install-time類型。
1.在最開始的時(shí)候,我是考慮能不能不使用谷歌API提供的方法,而使用類似StreamingAssets的加載方式,找到pack里面資源的具體路徑,然后通過(guò)I/O方法或者AssetBundle的相關(guān)方法來(lái)讀取。不過(guò)實(shí)際操作之后,發(fā)現(xiàn)我們是獲取不到具體資源的路徑,只能獲取到資源所在的pack的apk路徑。所以這個(gè)思路走不通
2.如果使用谷歌API來(lái)加載,可以配合API文檔來(lái)看:
https://developer.android.google.cn/reference/unity
下面會(huì)用一些簡(jiǎn)單的代碼例子來(lái)說(shuō)明幾個(gè)比較重要的方法。
3.我們先需要讀取到對(duì)應(yīng)的pack的信息,通過(guò)PlayAssetDelivery.RetrieveAssetPackAsync(packName);方法,返回一個(gè)PlayAssetPackRequest對(duì)象,然后如果以后需要讀取對(duì)應(yīng)pack的內(nèi)容,都是從這個(gè)PlayAssetPackRequest對(duì)象里面獲取
4.同步加載資源
由于谷歌API并沒(méi)有提供直接同步加載的方法,所以我們只能通過(guò)其他手段加載。
假設(shè)我獲取了PlayAssetPackRequest對(duì)象root,里面通過(guò)資源的相對(duì)了路徑path,
AssetLocation asset = root.GetAssetLocation(path);得到了一個(gè)AssetLocation對(duì)象,這個(gè)對(duì)象里面,包括了資源的apk路徑,字節(jié)偏移和長(zhǎng)度等信息,我們可以加載到bytes,然后再通過(guò) AssetBundle.LoadFromMemory系列方法生成AssetBundle。大概的代碼實(shí)現(xiàn)如下:
AssetLocation asset = root.GetAssetLocation(path);
FileStream assetFileStream = File.OpenRead(asset.Path);
byte[] bs = new byte[(long)asset.Size];
assetFileStream.Seek((long)asset.Offset, SeekOrigin.Begin);
assetFileStream.Read(bs, 0, bs.Length);
AssetBundle ab = AssetBundle.LoadFromMemory(bs);
5.異步加載資源
在谷歌API提供的PlayAssetPackRequest里,自帶了異步加載AssetBundle的方法,返回一個(gè)Unity的API的AssetBundleCreateRequest。
代碼寫法是:
假設(shè)我獲取了PlayAssetPackRequest對(duì)象root
AssetBundleCreateRequest ab = root.LoadAssetBundleAsync(abName);
網(wǎng)上有文章說(shuō),這個(gè)方法同時(shí)加載數(shù)量較大的資源時(shí),會(huì)出現(xiàn)out of memory
報(bào)錯(cuò),我自己暫時(shí)沒(méi)發(fā)現(xiàn)這個(gè)問(wèn)題。
如果怕存在這個(gè)問(wèn)題,也可以先讀取bytes,然后用AssetBundle.LoadFromMemoryAsync方法來(lái)獲得AssetBundleCreateRequest。
代碼寫法:
byte[] bs = LoadAssetBytesByPath(path);
AssetBundleCreateRequest ab = AssetBundle.LoadFromMemoryAsync(bs);
6.個(gè)人的一些看法
使用AssetBundle.LoadFromMemory來(lái)同步加載AssetBundle,由于需要讀取整個(gè)文件的bytes,然后一起加載到內(nèi)存去,比如會(huì)存在加載時(shí)間長(zhǎng)和內(nèi)存占用大的問(wèn)題,這樣的做法肯定沒(méi)有AssetBundle.LoadFromFile那么好,但由于Unity官方暫時(shí)沒(méi)有直接的讀取方法支持,所以好像也沒(méi)什么更好的處理方式。
異步加載如果使用AssetBundle.LoadFromMemoryAsync,也同樣是有加載時(shí)間和內(nèi)存占用的問(wèn)題。至于谷歌API提供的LoadAssetBundleAsync方法是不是需要占用全部?jī)?nèi)存,還是只是一個(gè)引用,這個(gè)不是很清楚。
現(xiàn)階段,其實(shí)只是停留在能用原來(lái)的Unity版本導(dǎo)出aab+PAD,然后能正常讀取的程度。在實(shí)際運(yùn)行中,能感覺(jué)除了是比之前直接用Unity的API加載資源是更卡頓的。這個(gè)問(wèn)題,應(yīng)該只有在后續(xù)版本的Unity里面官方直接提供解決方案支持,才能更好的解決這個(gè)問(wèn)題。
八、aab文件的安裝測(cè)試
aab文件是直接上傳谷歌商店的,它并不是一個(gè)像apk一樣能直接安裝的文件。
如果我們要測(cè)試它,則需要根據(jù)實(shí)際情況。
fast-follow和on-demand這兩種需要服務(wù)器支持的類型,似乎只能上傳谷歌商店來(lái)測(cè)試,我也沒(méi)用具體的研究過(guò)。
install-time這種類型的資源,除了上傳谷歌商店,還可以本地模擬安裝測(cè)試的。
這個(gè)時(shí)候我們需要在電腦上面安裝JAVA,并需要一個(gè)bundletool的工具(網(wǎng)上搜一下應(yīng)該都有),我使用的版本是bundletool-all-1.8.1.jar
具體的生成步驟是:
1、從aab生成apks
注意這里生成的是apks而不是apk。
我們通過(guò)命令行工具,定位到bundletool所在的文件夾,然后輸入
java -jar bundletool-all-1.8.1.jar build-apks --bundle=你的aab文件路徑 --output=需要輸出apks文件的路徑
比如我的bundletool文件在D盤的bundletool下,然后test.aab文件在D盤的aab文件夾下,想在D盤的apks文件夾下生成test.apks,那么命令可以這樣:
java -jar D:\bundletool\bundletool-all-1.8.1.jar build-apks --bundle=D:\aab\test.aab --output=D:\apks\test.apks
需要注意的是,需要生成的apks文件必須不是已經(jīng)存在的文件,因?yàn)檫@個(gè)命令不會(huì)覆蓋原有文件,如果之前就已經(jīng)有了一個(gè)D:\apks\test.apks文件,那么執(zhí)行命令的時(shí)候會(huì)提示你文件已存在,并生成失敗。
2、將apks安裝到設(shè)備
生成了apks之后,還需要把這個(gè)apks安裝到指定的設(shè)備上。
在命令行工具輸入:
java -jar bundletool-all-1.8.1.jar install-apks --apks=你的apks路徑 --device-id = 你想安裝的設(shè)備id
設(shè)備id不是必須的,如果你的電腦當(dāng)前只連接了一個(gè)設(shè)備,那么device-id可以不填
查看設(shè)備id的方法是,在命令行工具輸入adb devices文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-423302.html
假如我們現(xiàn)在需要把剛才生成在D盤的apks安裝到設(shè)備,那么命令這樣寫:
java -jar D:\bundletool\bundletool-all-1.8.1.jar install-apks --apks=D:\apks\test.apks --device-id = 127.0.0.1:62001
如果只連接了一個(gè)設(shè)備,就可以簡(jiǎn)略寫成:
java -jar D:\bundletool\bundletool-all-1.8.1.jar install-apks --apks=D:\apks\test.apks
運(yùn)行命令之后,會(huì)現(xiàn)在C:\Users\你的用戶名\AppData\Local\Temp\play-unity-build下生成一個(gè)臨時(shí)文件,然后再將臨時(shí)文件安裝到設(shè)備上。
由于這個(gè)臨時(shí)文件是不會(huì)自動(dòng)刪除,而且容量很大,所以在安裝完之后,記得去c盤把臨時(shí)文件刪掉。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-423302.html
到了這里,關(guān)于Unity項(xiàng)目發(fā)布谷歌AAB+PAD的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!