騰訊雲技術社區-掘金主頁持續爲你們呈現雲計算技術文章,歡迎你們關注!javascript
最近因爲想在Scene的腳本組件中,調用Android的Activity的相關接口,就須要弄明白Scene和Activity的實際對應關係,並對Unity調用Android的部分原理進行了研究。java
本文主要探討Scene和Activity之間的關係,以及Unity打包apk和Android studio打包apk的差異在什麼地方?找到這種差異以後,能夠怎麼運用起來?android
本文須要用到的工具:git
<?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。github
言下之意,編譯只包含Scene的Unity工程,打包成Android apk,會以com.unity3d.player.UnityPlayerActivity做爲主程序入口,那麼問題來了,Scene如何加載顯示到這個UnityPlayerActivity呢?c#
2.1 UnityPlayerActivityapp
這個就要從UnityPlayerActivity源碼入手了,Android工程中使用UnityPlayerActivity須要依賴到Unity的Android插件classes.jar(位於Unity安裝目錄,能夠用everything軟件查找查找獲得),對其進行反編譯獲得UnityPlayerActivity的部分源碼:ide
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佈局。
2.重點看GoogleUnityActivity的onCreate函數:
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);
}
});
}
}複製代碼
從代碼中能夠發現:
因爲UnityPlayer類作了混淆,關於渲染的核心功能也封裝在native代碼中,關於Scene轉換到到UnityPlayer做爲FrameLayout,只能作一個簡單的推測:經過調用Android的GL渲染引擎,在native層進行渲染,並同步到FrameLayout在UnityPlayerActivity上進行顯示。
從以上研究的內容可知,假如要從要實現將Scene顯示在固定的Activity當中,則須要對Activity的oncreate部分的countview和unityplayer進行處理。最簡單的方法是寫一個直接繼承於UnityPlayerActivity或GoogleUnityActivity的類,並在類中寫所須要的Unity調用Android的方法。
這樣Scene就會加載在特定的Activity當中,Unity c#經過獲取currentActivity變量就能夠獲取到該Activity,並調用其中的函數。
因爲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進行對比:
相反,假如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文件來實現:
不過在設置這兩種模式時,須要注意applicationId "com.example.yin.myapplication"的設置,假如是library模式,則須要直接註釋掉。
假如Android的java部分從新調試好以後,從新將app模式改爲library模式,進行build,將生成的aar包,拷貝到Unity Android Plugin目錄中,就能夠直接在Unity看運行效果了。
不過必定要記得刪除Android studio打包的aar文件裏面的assert/bin目錄,以防止在Unity中重複打包。
最後套句名言:log打得好,bug解得早
相關閱讀
深度瞭解Android 7.0 ,你準備好了嗎?
【騰訊雲的1001種玩法】安卓加固在騰訊雲上的使用(附反編譯結果)