您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Unity編譯Android的原理解析和apk打包分析是怎么樣的,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
最近由于想在Scene的腳本組件中,調(diào)用Android的Activity的相關接口,就需要弄明白Scene和Activity的實際對應關系,并對Unity調(diào)用Android的部分原理進行了研究。
主要探討Scene和Activity之間的關系,以及Unity打包apk和Android studio打包apk的差別在什么地方?找到這種差別之后,可以怎么運用起來?
下面需要用到的工具:
Android反編譯工具——apktool
Android studio自帶的反編譯功能
新建一個Unity項目,創(chuàng)建一個Scene,將Unity工程編譯打包成apk。
對編譯出來的apk,利用apktool進行反編譯:apktool d unityTest.apk
得到的AndroidManifest文件如下:
<?xml version="1.0" encoding="utf-8" standalone="no"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="preferExternal" package="com.xfiction.p1" platformBuildVersionCode="25" platformBuildVersionName="7.1.1"> <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:smallScreens="true" android:xlargeScreens="true"/> <application android:banner="@drawable/app_banner" android:debuggable="false" android:icon="@drawable/app_icon" android:isGame="true" android:label="@string/app_name" android:theme="@style/UnityThemeSelector"> <activity android:configChanges="locale|fontScale|keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode" android:label="@string/app_name" android:launchMode="singleTask" android:name="com.unity3d.player.UnityPlayerActivity" android:screenOrientation="fullSensor"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LEANBACK_LAUNCHER"/> </intent-filter> <meta-data android:name="unityplayer.UnityActivity" android:value="true"/> </activity> </application> <uses-feature android:glEsVersion="0x00020000"/> <uses-feature android:name="android.hardware.touchscreen" android:required="false"/> <uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false"/> <uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" android:required="false"/> </manifest>
由該AndroidManifest文件可知,系統(tǒng)仍然存在主Activity,名字為com.unity3d.player.UnityPlayerActivity。
言下之意,編譯只包含Scene的Unity工程,打包成Android apk,會以com.unity3d.player.UnityPlayerActivity作為主程序入口,那么問題來了,Scene如何加載顯示到這個UnityPlayerActivity呢?
2.1 UnityPlayerActivity
這個就要從UnityPlayerActivity源碼入手了,Android工程中使用UnityPlayerActivity需要依賴到Unity的Android插件classes.jar(位于Unity安裝目錄,可以用everything軟件查找查找得到),對其進行反編譯得到UnityPlayerActivity的部分源碼:
public class UnityPlayerActivity extends Activity { protected UnityPlayer mUnityPlayer; protected void onCreate(Bundle var1) { this.requestWindowFeature(1); super.onCreate(var1); this.getWindow().setFormat(2); this.mUnityPlayer = new UnityPlayer(this); this.setContentView(this.mUnityPlayer); this.mUnityPlayer.requestFocus(); } }
雖然經(jīng)過混淆,看起來比較費勁,但從代碼this.setContentView(this.mUnityPlayer)可以看出,最終的界面顯示需要依賴到UnityPlayer的實例。
另外由于Google也做了一套Unity VR的SDK,與UnityPlayerActivity相對應的類,就是GoogleUnityActivity,下面也對它進行分析。
2.2 從GoogleUnityActivity.java再入手分析
GoogleUnityActivity是google推出的VR SDK中,用于實現(xiàn)Unity Activity的類,通過google查詢其源碼發(fā)現(xiàn):
1. GoogleUnityActivity.java實際上的布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <FrameLayout android:id="@+id/android_view_container" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" /> </FrameLayout>
布局文件中沒有具體的內(nèi)容,只包含一個FrameLayout布局。
2.重點看GoogleUnityActivity的onCreate函數(shù):
public class GoogleUnityActivity extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback { protected void onCreate(Bundle savedInstanceState) { requestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setContentView(R.id.activity_main.xml) mUnityPlayer = new UnityPlayer(this); if (mUnityPlayer.getSettings().getBoolean("hide_status_bar", true)) { getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } ((ViewGroup) findViewById(android.R.id.content)).addView(mUnityPlayer.getView(), 0); mUnityPlayer.requestFocus(); } }
mUnityPlayer作為FrameLayoutView加入到view集合中進行顯示,注意這里查找的id是android.R.id.content。根據(jù)官方對這個id的解釋:
android.R.id.content gives you the root element of a view, without having to know its actual name/type/ID. Check out Get root view from current activity
由此可見,GoogleUnityActivity的實現(xiàn)原理,是創(chuàng)建一個只包含F(xiàn)rameLayout的空的幀布局,隨后通過addView將UnityPlayer中的View加載到GoogleUnityActivity中進行顯示。
看起來跟UnityPlayerActivity有異曲同工之妙,兩者牽涉的類都是UnityPlayer。
2.3.UnityPlayer究竟是一個什么類呢?
對classes.jar包進行反編譯得到UnityPlayer的部分代碼:
public class UnityPlayer extends FrameLayout implements com.unity3d.player.a.a { public static Activity currentActivity = null; public UnityPlayer(ContextWrapper var1) { super(var1); if(var1 instanceof Activity) { currentActivity = (Activity)var1; } } public View getView() { return this; } public static native void UnitySendMessage(String var0, String var1, String var2); private final native boolean nativeRender(); public void onCameraFrame(final com.unity3d.player.a var1, final byte[] var2) { final int var3 = var1.a(); final Size var4 = var1.b(); this.a(new UnityPlayer.c((byte)0) { public final void a() { UnityPlayer.this.nativeVideoFrameCallback(var3, var2, var4.width, var4.height); var1.a(var2); } }); } }
從代碼中可以發(fā)現(xiàn):
UnityPlayer實際上是繼承于FrameLayout;
并且自帶一個currentActivity的成員變量,在構造函數(shù)中,直接傳入Activity的相關參數(shù);
在getView函數(shù)中直接返回該FrameLayout;
GoogleUnityActivity通過UnityPlayer的構造函數(shù),將其context傳遞給UnityPlayer,并賦值給其成員變量currentActivity。
由于UnityPlayer類做了混淆,關于渲染的核心功能也封裝在native代碼中,關于Scene轉(zhuǎn)換到到UnityPlayer作為FrameLayout,只能做一個簡單的推測:通過調(diào)用Android的GL渲染引擎,在native層進行渲染,并同步到FrameLayout在UnityPlayerActivity上進行顯示。
從以上研究的內(nèi)容可知,假如要從要實現(xiàn)將Scene顯示在固定的Activity當中,則需要對Activity的oncreate部分的countview和unityplayer進行處理。最簡單的方法是寫一個直接繼承于UnityPlayerActivity或GoogleUnityActivity的類,并在類中寫所需要的Unity調(diào)用Android的方法。
這樣Scene就會加載在特定的Activity當中,Unity c#通過獲取currentActivity變量就可以獲取到該Activity,并調(diào)用其中的函數(shù)。
Android studio工程包含多個module的依賴,則需要將對應的module編譯的插件一起拷貝Plugins/Android/lib目錄當中。
在第一步驟下,可以直接刪除打包后的aar library目錄,尤其里面假如帶有unity的Android插件classesjar,否則會編譯報錯。
多個module編譯的時候,注意manifest lablel相關設置,另外就是build.gradle的minSDKVersion信息。否則會出現(xiàn)manifest merger失敗的錯誤。
關于Unity的Android Manifest文件合并:
Unity編寫一個Scene,Android studio寫一個包含主Activity的aar包,放在Plugins/Android目錄當中。用Unity編譯apk出來之后,反編譯他的AndroidManifest文件,兩個主Activity,默認顯示包含Scene的Activity。
解決方法:Unity的Manifest文件合并,把一個manifest放到Plugins/Android目錄下,就不會合并manifest了。
由于Unity開發(fā)Android時,常常設計到Unity + Visual和Android studio的環(huán)境切換,Unity的開發(fā)往往會更快一些,更多的是Android java側(cè)的代碼編寫和調(diào)試。
這種情況時,有沒有一種方法,能夠?qū)nity編譯好的Unity Scene和c#相關文件,放到Android studio中進行打包,從而實現(xiàn)直接在Android studio中進行調(diào)試?
方法原理倒是很簡單,通過對比Unity打包的apk,與普通的Android apk的文件差別,找出Unity文件存放的目錄,隨后對應存放到Android studio工程目錄中,最后通過Android studio完成對Unity相關文件的打包。
首先將apk添加zip的后綴,方便用beyond compare進行對比:
發(fā)現(xiàn)只是多了assert/bin目錄,在這個目錄之下,可以看到unity相關dll庫
將該文件,拷貝到Android studio工程的src/main/assert目錄之下;
在Android studio調(diào)試時,可以將aar library工程設置為app工程,這樣就可以編譯apk運行到手機了。
用Android studio對該工程進行編譯,發(fā)現(xiàn)assert/bin目錄成功被打包進去。
直接apk install 運行,可以看到跟Unity編譯打包的apk,是相同的效果。
相反,假如Android工程調(diào)試好之后,則直接編譯成app模式修改成library模式,進行build之后,就會生成aar庫,此時將aar庫拷貝到Plugins/Android/lib目錄當中,注意要刪除aar庫中的assert/bin,因為這個目錄是我們先前從Unity拷貝過去的,假如不刪除,在unity里面會出現(xiàn)重復打包導致的文件沖突的情況。
由于當將Unity打包之后的bin目錄拷貝到Android studio工程之后,Android studio此時是一個library工程,需要轉(zhuǎn)換為app工程。
關于這其中涉及到的Android studio library和app的轉(zhuǎn)換,通過設置build.gradle文件來實現(xiàn):
app模式:apply plugin: 'com.android.application'
library模式:apply plugin: 'com.android.library'
不過在設置這兩種模式時,需要注意applicationId "com.example.yin.myapplication"的設置,假如是library模式,則需要直接注釋掉。
假如Android的java部分重新調(diào)試好之后,重新將app模式改成library模式,進行build,將生成的aar包,拷貝到Unity Android Plugin目錄中,就可以直接在Unity看運行效果了。
不過一定要記得刪除Android studio打包的aar文件里面的assert/bin目錄,以防止在Unity中重復打包。
Unity中的Scene在Android中,其實對應于Activity的FrameLayout,每個Scene的運行都有其Activity環(huán)境,通過currentActivity變量可以獲取得到。
要實現(xiàn)自定義的Activity能夠具備直接加載Scene的功能,則需要其繼承于UnityPlayerActivity或者GoogleUnityActivity,再或者,直接自定義實現(xiàn)UnityActivity類。
提升Unity+Android Plugin項目開發(fā)效率的方法:
● 直接將Unity打包的apk中的assert/bin目錄拷貝到Android studio工程的src/main/assert目錄當中,并且將Android工程配置成app模式,就可以直接在Android studio上面,對整個Unity+android plugin的工程進行調(diào)試。
● Android studio部分調(diào)試好之后,需要修改build.gradle文件,重新將app模式修改為library模式,編譯出aar包文件,刪除原來拷貝過來的unity部分,放入到unity的Plugins/Android/lib目錄下進行使用即可。
關于Unity編譯Android的原理解析和apk打包分析是怎么樣的就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。