最近由于想在Scene的脚本组件中,调用Android的Activity的相关接口,就需要弄明白Scene和Activity的实际对应关系,并对Unity调用Android的部分原理进行了研究。
本文主要探讨Scene和Activity之间的关系,以及Unity打包apk和Android studio打包apk的差别在什么地方?找到这种差别之后,可以怎么运用起来?
本文需要用到的工具:
<?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呢?
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布局。
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解得早
相关推荐
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。