本文主要探讨 Scene 和 Activity 之间的关系,以及 Unity 打包 apk 和 Android studio 打包 apk 的差别在什么地方?找到这种差别之后,可以怎么运用起来?
本文需要用到的工具:
- "1.0" encoding="utf-8" standalone="no"><manifestxmlns:android="http://schemas.android.com/apk/res/android"android:installLocation="preferExternal"package="com.xfiction.p1"platformBuildVersionCode="25"platformBuildVersionName="7.1.1"><supports-screensandroid:anyDensity="true"android:largeScreens="true"android:normalScreens="true"android:smallScreens="true"android:xlargeScreens="true"/><applicationandroid:banner="@drawable/app_banner"android:debuggable="false"android:icon="@drawable/app_icon"android:isGame="true"android:label="@string/app_name"android:theme="@style/UnityThemeSelector"><activityandroid: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><actionandroid:name="android.intent.action.MAIN"/><categoryandroid:name="android.intent.category.LAUNCHER"/><categoryandroid:name="android.intent.category.LEANBACK_LAUNCHER"/></intent-filter><meta-dataandroid:name="unityplayer.UnityActivity"android:value="true"/></activity></application><uses-featureandroid:glEsVersion="0x00020000"/><uses-featureandroid:name="android.hardware.touchscreen"android:required="false"/><uses-featureandroid:name="android.hardware.touchscreen.multitouch"android:required="false"/><uses-featureandroid: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 呢?
2.1 UnityPlayerActivity
这个就要从 UnityPlayerActivity 源码入手了,Android 工程中使用 UnityPlayerActivity 需要依赖到 Unity 的 Android 插件 classes.jar(位于 Unity 安装目录,可以用 everything 软件查找查找得到),对其进行反编译得到 UnityPlayerActivity 的部分源码:
- publicclassUnityPlayerActivityextendsActivity {protected UnityPlayer mUnityPlayer;protectedvoidonCreate(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
- "1.0" encoding="utf-8"><FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent" ><FrameLayoutandroid: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 函数:
- publicclassGoogleUnityActivityextendsActivityimplementsActivityCompat.OnRequestPermissionsResultCallback {protectedvoidonCreate(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 的部分代码:
- publicclassUnityPlayerextendsFrameLayoutimplementscom.unity3d.player.a.a {publicstatic Activity currentActivity =null;publicUnityPlayer(ContextWrapper var1) {super(var1);if(var1instanceof Activity) {
- currentActivity = (Activity)var1;
- }
- }public ViewgetView() {returnthis;
- }publicstaticnativevoidUnitySendMessage(String var0, String var1, String var2);privatefinalnativebooleannativeRender();publicvoidonCameraFrame(final com.unity3d.player.a var1,finalbyte[] var2) {finalint var3 = var1.a();final Size var4 = var1.b();this.a(new UnityPlayer.c((byte)0) {publicfinalvoida() {
- 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 解得早
来源: http://www.cnblogs.com/qcloud1001/p/6650023.html