1. 程式人生 > >Unity編譯Android的原理解析和apk打包分析

Unity編譯Android的原理解析和apk打包分析

本文主要探討Scene和Activity之間的關係,以及Unity打包apk和Android studio打包apk的差別在什麼地方?找到這種差別之後,可以怎麼運用起來?

本文需要用到的工具:

  • Android反編譯工具——apktool
  • Android studio自帶的反編譯功能

一、將Unity的Scene編譯成apk,apk的程式入口會是什麼?

  1. 新建一個Unity專案,建立一個Scene,將Unity工程編譯打包成apk。
  2. 對編譯出來的apk,利用apktool進行反編譯:apktool d unityTest.apk
  3. 得到的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檔案可知,系統仍然存在主Activity,名字為com.unity3d.player.UnityPlayerActivity。

言下之意,編譯只包含Scene的Unity工程,打包成Android apk,會以com.unity3d.player.UnityPlayerActivity作為主程式入口,那麼問題來了,Scene如何載入顯示到這個UnityPlayerActivity呢?

二、UnityPlayerActivity如何載入Unity中的Scene?

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();
    }
}

雖然經過混淆,看起來比較費勁,但從程式碼this.setContentView(this.mUnityPlayer)可以看出,最終的介面顯示需要依賴到UnityPlayer的例項。
另外由於Google也做了一套Unity VR的SDK,與UnityPlayerActivity相對應的類,就是GoogleUnityActivity,下面也對它進行分析。

2.2 從GoogleUnityActivity.java再入手分析

GoogleUnityActivity是google推出的VR SDK中,用於實現Unity Activity的類,通過google查詢其原始碼發現:
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>

佈局檔案中沒有具體的內容,只包含一個FrameLayout佈局。

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。根據官方對這個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的實現原理,是建立一個只包含FrameLayout的空的幀佈局,隨後通過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);
            }
        });
    }
}

從程式碼中可以發現:

  1. UnityPlayer實際上是繼承於FrameLayout;
  2. 並且自帶一個currentActivity的成員變數,在建構函式中,直接傳入Activity的相關引數;
  3. 在getView函式中直接返回該FrameLayout;
  4. GoogleUnityActivity通過UnityPlayer的建構函式,將其context傳遞給UnityPlayer,並賦值給其成員變數currentActivity。

由於UnityPlayer類做了混淆,關於渲染的核心功能也封裝在native程式碼中,關於Scene轉換到到UnityPlayer作為FrameLayout,只能做一個簡單的推測:通過呼叫Android的GL渲染引擎,在native層進行渲染,並同步到FrameLayout在UnityPlayerActivity上進行顯示。

三、 如何將Scene顯示在自定義的Activity當中

從以上研究的內容可知,假如要從要實現將Scene顯示在固定的Activity當中,則需要對Activity的oncreate部分的countview和unityplayer進行處理。最簡單的方法是寫一個直接繼承於UnityPlayerActivity或GoogleUnityActivity的類,並在類中寫所需要的Unity呼叫Android的方法。
這樣Scene就會載入在特定的Activity當中,Unity c#通過獲取currentActivity變數就可以獲取到該Activity,並呼叫其中的函式。

四、 Unity Android 外掛需要注意的問題

  1. Android studio工程包含多個module的依賴,則需要將對應的module編譯的外掛一起拷貝Plugins/Android/lib目錄當中。
  2. 在第一步驟下,可以直接刪除打包後的aar library目錄,尤其裡面假如帶有unity的Android外掛classesjar,否則會編譯報錯。
  3. 多個module編譯的時候,注意manifest lablel相關設定,另外就是build.gradle的minSDKVersion資訊。否則會出現manifest merger失敗的錯誤。
  4. 關於Unity的Android Manifest檔案合併:
    Unity編寫一個Scene,Android studio寫一個包含主Activity的aar包,放在Plugins/Android目錄當中。用Unity編譯apk出來之後,反編譯他的AndroidManifest檔案,兩個主Activity,預設顯示包含Scene的Activity。
    解決方法:Unity的Manifest檔案合併,把一個manifest放到Plugins/Android目錄下,就不會合並manifest了。

五、Unity打包Android apk的結構探究

由於Unity開發Android時,常常設計到Unity + Visual和Android studio的環境切換,Unity的開發往往會更快一些,更多的是Android java側的程式碼編寫和除錯。

這種情況時,有沒有一種方法,能夠將Unity編譯好的Unity Scene和c#相關檔案,放到Android studio中進行打包,從而實現直接在Android studio中進行除錯?

方法原理倒是很簡單,通過對比Unity打包的apk,與普通的Android apk的檔案差別,找出Unity檔案存放的目錄,隨後對應存放到Android studio工程目錄中,最後通過Android studio完成對Unity相關檔案的打包。

首先將apk新增zip的字尾,方便用beyond compare進行對比:

  1. 發現只是多了assert/bin目錄,在這個目錄之下,可以看到unity相關dll庫
  2. 將該檔案,拷貝到Android studio工程的src/main/assert目錄之下;
  3. 在Android studio除錯時,可以將aar library工程設定為app工程,這樣就可以編譯apk執行到手機了。
  4. 用Android studio對該工程進行編譯,發現assert/bin目錄成功被打包進去。
  5. 直接apk install 執行,可以看到跟Unity編譯打包的apk,是相同的效果。

相反,假如Android工程除錯好之後,則直接編譯成app模式修改成library模式,進行build之後,就會生成aar庫,此時將aar庫拷貝到Plugins/Android/lib目錄當中,注意要刪除aar庫中的assert/bin,因為這個目錄是我們先前從Unity拷貝過去的,假如不刪除,在unity裡面會出現重複打包導致的檔案衝突的情況。

由於當將Unity打包之後的bin目錄拷貝到Android studio工程之後,Android studio此時是一個library工程,需要轉換為app工程。
關於這其中涉及到的Android studio library和app的轉換,通過設定build.gradle檔案來實現:

  • app模式:apply plugin: 'com.android.application'
  • library模式:apply plugin: 'com.android.library'

不過在設定這兩種模式時,需要注意applicationId "com.example.yin.myapplication"的設定,假如是library模式,則需要直接註釋掉。

假如Android的java部分重新除錯好之後,重新將app模式改成library模式,進行build,將生成的aar包,拷貝到Unity Android Plugin目錄中,就可以直接在Unity看執行效果了。
不過一定要記得刪除Android studio打包的aar檔案裡面的assert/bin目錄,以防止在Unity中重複打包。

四、結論:

  1. Unity中的Scene在Android中,其實對應於Activity的FrameLayout,每個Scene的執行都有其Activity環境,通過currentActivity變數可以獲取得到。
  2. 要實現自定義的Activity能夠具備直接載入Scene的功能,則需要其繼承於UnityPlayerActivity或者GoogleUnityActivity,再或者,直接自定義實現UnityActivity類。
  3. 提升Unity+Android Plugin專案開發效率的方法:
    ● 直接將Unity打包的apk中的assert/bin目錄拷貝到Android studio工程的src/main/assert目錄當中,並且將Android工程配置成app模式,就可以直接在Android studio上面,對整個Unity+android plugin的工程進行除錯。
    ● Android studio部分除錯好之後,需要修改build.gradle檔案,重新將app模式修改為library模式,編譯出aar包檔案,刪除原來拷貝過來的unity部分,放入到unity的Plugins/Android/lib目錄下進行使用即可。

最後套句名言:log打得好,bug解得早

相關推薦

Unity編譯Android原理解析apk打包分析

本文主要探討Scene和Activity之間的關係,以及Unity打包apk和Android studio打包apk的差別在什麼地方?找到這種差別之後,可以怎麼運用起來? 本文需要用到的工具: Android反編譯工具——apktoolAndroid studio自帶的反編譯功能 一、將Unity的Scen

Android中的Apk的加固 加殼 原理解析實現

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android中微信搶紅包外掛原理解析開發實現

一、前言自從去年中微信新增搶紅包的功能,微信的電商之旅算是正式開始正式火爆起來。但是作為Android開發者來說,我們在搶紅包的同時意識到了很多問題,就是手動去搶紅包的速度慢了,當然這些有很多原因導致了。或許是網路的原因,而且這個也是最大的原因。但是其他的不可忽略的因素也是要

Apk原始碼的加固(加殼)原理解析實現

好久沒寫部落格了,要深刻檢討下! 前言: 在Android中沒有經過加密的Apk給人的感覺就是在裸奔,通過apktool,dex2jar,AndroidKill等各式各樣的反編譯工具就可以輕鬆的獲取其smail程式碼,如這個叫SourceProject的he

UnityAndroid版)Android原生APP簡單實現版本更新

directory 代碼 server 頁面 提示框 自動安裝 obj nis 查看 版本檢測接口說明:(1)請求post,無參數(2)調用地址:http://www.baidu.com/rs/ver/info(3)返回結果:{ "verCode": "2",

KOA2框架原理解析實現

什麼是koa框架? koa是一個基於node實現的一個新的web框架,它是由express框架的原班人馬打造的。它的特點是優雅、簡潔、表達力強、自由度高。它更express相比,它是一個更輕量的node框架,因為它所有功能都通過外掛實現,這種插拔式的架構設計模式,很符合unix哲學。 koa框架現在更新到

Mybatis(四):MyBatis核心元件介紹原理解析原始碼解讀 java中代理,靜態代理,動態代理以及spring aop代理方式,實現原理統一彙總

Mybatis核心成員 Configuration        MyBatis所有的配置資訊都儲存在Configuration物件之中,配置檔案中的大部分配置都會儲存到該類中 SqlSession         &

Mybatis(四):MyBatis核心組件介紹原理解析源碼解讀

轉換成 返回 env resource turn 源碼 做了 sta lec Mybatis核心成員 Configuration MyBatis所有的配置信息都保存在Configuration對象之中,配置文件中的大部分配置都會存儲到該類中 SqlSessi

Unity 開啟Android手機相簿攝像頭

需求:要實現開啟手機的相簿和攝像頭,選擇照片或者拍照後,在unity進行。   1.android外掛 我使用的是AndroidStuido來寫外掛,下面是一步步介紹流程 (1)建立android工程 注意紅框裡面的東西,要修改兩個地方:

vue 陣列遍歷方法forEachmap的原理解析實際應用

一、前言 forEach和map是陣列的兩個方法,作用都是遍歷陣列。在vue專案的處理資料中經常會用到,這裡介紹一下兩者的區別和具體用法示例。 二、程式碼 1. 相同點 都是陣列的方法 都用來遍歷陣列 兩個函式都有4個引數:匿名函式中可傳3個引數item(當前項), index(當前項的

vue 數組遍歷方法forEachmap的原理解析實際應用

second 直接 price each var bool 原理 棧內存 == 一、前言 forEach和map是數組的兩個方法,作用都是遍歷數組。在vue項目的處理數據中經常會用到,這裏介紹一下兩者的區別和具體用法示例。 二、代碼 1. 相同點 都是數組的方法 都用來遍

Spark學習(五)---RDD原理解析spark執行架構

這次我們介紹RDD的原理和spark執行機制 RDD依賴關係 RDD快取 RDD容錯機制 spark執行架構 spark任務排程 1. RDD原理 首先我們對之前的單詞統計的程式碼做一個畫圖展示 1.1 RDD依賴關係 RDD和它依賴的父RDD的關係有兩

GET請求中的亂碼原理解析解決方案

2. 亂碼問題解決 基礎知識 1)瀏覽器會在中文的UTF-8後加上上%得到URL編碼   例如: %e8%b4%b9%e7%94%a8%e6%8a%a5%e9%94%80 2)以get的請求傳送到tomcat伺服器後又會以預設的(ISO8859-1)解碼!! 3)所以在a

關於Spring AOP 原理解析舉例!

反射實現 AOP 動態代理模式(Spring AOP 的實現 原理) ,其實AOP的意思就是面向切面程式設計.  OO注重的是我們解決問題的方法(封裝成Method),而AOP注重的是許多解決解決問題的方法中的共同點,是對OO思想的一種補充!  舉的一個例子說吧:  通常我

AccessibilityService2016終極解決方案包括(微信搶紅包外掛原理解析開發實現)

一、前言 自從去年中微信新增搶紅包的功能,微信的電商之旅算是正式開始正式火爆起來。但是作為Android開發者來說,我們在搶紅包的同時意識到了很多問題,就是手動去搶紅包的速度慢了,當然這些有很多原因導致了。或許是網路的原因,而且這個也是最大的原因。但是其他的不可忽略的因

Velocity工作原理解析優化

目錄 Velocity總體架構 APP模組 Context模組 Runtime模組 RuntimeInstance類 JJTree渲染過程解析 #set語法 #if、#elseif和#else語法

Android中dbapk分離

我們在做Android應用尤其是商業應用的時候,很多時候都需要後期版本升級,如果我們的資料庫檔案非常大,比如遊戲之類的,這時候就不應該每次版本更新都去重新複製資料庫。將資料庫和安裝包分離,下面來詳細介紹: (1)判斷是否是第一次安裝 try { //獲取程式的當

Skinned Mesh原理解析一個最簡單的實現示例

Skinned Mesh原理解析和一個最簡單的實現示例2018年1月:本文以及demo程式已儲存到github上 :作者:n52008-10月Histroy:Version:1.02 Date:2010

Skinned Mesh 原理解析一個最簡單的實現示例

Mesh space 是建模時使用的空間, mesh 中頂點的位置相對於這個空間的原點定義。比如在 3d max 中建模時(視 xy 平面為地面, +z 朝上),可將模型兩腳之間的中點作為 Mesh 空間的原點,並將其放置在世界原點,這樣左腳上某一頂點座標是( 10 , 10 , 2

Android ffmpeg解析合成gif

概述Android專案中使用ffmpeg3.0.9通過命令列呼叫的方式解析和合成gif。簡單學習總結一下。解析gif,由gif得到一堆png/** * 解析gif圖片 * @param gifPath gif圖片地址 * @param pngDir 解析後圖片儲存目錄