作者介绍:张坤

最近由于想在Scene的脚本组件中,调用Android的Activity的相关接口,就需要弄明白Scene和Activity的实际对应关系,并对Unity调用Android的部分原理进行了研究。

本文主要探讨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布局。

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

从代码中可以发现:

  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解得早

 

此文已由作者授权腾讯云技术社区发布,转载请注明文章出处,获取更多云计算技术干货,可请前往腾讯云技术社区

原文链接:https://www.qcloud.com/community/article/492233001484608835

欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~

Unity编译Android的原理解析和apk打包分析的更多相关文章

  1. Request 接收参数乱码原理解析三:实例分析

    通过前面两篇<Request 接收参数乱码原理解析一:服务器端解码原理>和<Request 接收参数乱码原理解析二:浏览器端编码原理>,了解了服务器和浏览器编码解码的原理,接下 ...

  2. unity编译android包时提示android sdk路径有问题

    如果你有洁癖喜欢把各种软件各种IDE都更新到最新,那么就恭喜你也会遇到我的问题: 重装了公司的imac,下载了最新的android sdk,uinty各种编译失败,真是耽误时间,其实不是android ...

  3. webpack4.0源码解析之esModule打包分析

    入口文件index.js采用esModule方式导入模块文件,非入口文件index1.js分别采用CommonJS和esmodule规范进行导出. 首先,init之后创建一个简单的webpack基本的 ...

  4. unity开发android游戏

    环境搭建: Unity+JDK+Android Studio+Android SDK(+NDK) 教程:unity开发android游戏(一)搭建Unity安卓开发环境 注意“Build System ...

  5. Unity编译时找不到AndroidSDK的问题 | Unable to list target platforms(转载)

    原文:http://www.jianshu.com/p/fe4c334ee9fe 现象 在用 Unity 编译 Android 平台的应用时,遇到 Unable to list target plat ...

  6. > 软件编程 > 安卓开发 > Unity编译时找不到AndroidSDK的问题:Unable to list target pla

    http://www.qingpingshan.com/rjbc/az/228769.html 现象 在用 Unity 编译 Android 平台的应用时,遇到 Unable to list targ ...

  7. 爱加密Android APk 原理解析

    转载请标明出处:http://blog.csdn.net/u011546655/article/details/45921025 爱加密Android APK加壳原理解析 一.什么是加壳? 加壳是在二 ...

  8. Android中的Apk的加固(加壳)原理解析和实现

    一.前言 今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理.现阶段.我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk, ...

  9. Android中的Apk的加固(加壳)原理解析和实现(转)

    一.前言 今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理.现阶段.我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk, ...

随机推荐

  1. Spring源码解析三:IOC容器的依赖注入

    一般情况下,依赖注入的过程是发生在用户第一次向容器索要Bean是触发的,而触发依赖注入的地方就是BeanFactory的getBean方法. 这里以DefaultListableBeanFactory ...

  2. css4激动人心的新特性及浏览器支持度

    CSS3的选择器提供了很多像:nth-child这样有用的选择器,并且得到浏览器支持.CSS的第四代 选择器CSS4选择器),经我们带来了更多有用的选择器. 1.否定伪类:not 否定伪类选择器其实在 ...

  3. angular : ngModel 内部流程

    angular 1.5 beta link NgModelController provides API for the ngModel directive. The controller conta ...

  4. css中的那些布局

    因为最近心血来潮,就总结了一下css中的几种常见的多列布局. 两列自适应布局 两列自适应布局算是css布局里面最基础的一种布局了,不少网站在使用. 这种布局通常是左侧固定,右边自适应,当然也有反过来的 ...

  5. UVALive 7045 Last Defence

    ProblemK. Last Defence Description Given two integersA and B. Sequence S is defined as follow: • S0 ...

  6. centOS7 mini配置linux服务器(一)安装centOs7

    1. 准备centos-7 (minni镜像) 官网地址http://isoredirect.centos.org/centos/7/isos/x86_64/CentOS-7-x86_64-Minim ...

  7. Bzoj超级经验大放送题集(好评如潮哦~~~)

    其实这些是因为没有数据才形成的...唯一可惜的是这些都是需要300软妹币才能打开的萌萌哒权限题*^_^* 好啦,吾来教你如何快速AC么么哒 pascal: 1 begin end. //Pascal ...

  8. python list 切片实验

    list[start:stop:step] >>> a_list=['hito','bb','cc','dd','ee','ff']>>> a_list[::-1] ...

  9. win10环境下jdk1.8+Android Developer Tools Build: v22.3.0-887826的问题

    最进换了新电脑,配置开发环境,最新的android studio 要求jdk1.8,所以想都没想就下载1.8. 之后为了一个原来的老项目,得使用adt,遂装之,遇到一下问题 1.ADT新建项目src下 ...

  10. 笔记本win10关机异常解决

    自从使用了win10 以后,小编已经情不自禁的爱上了她——迄今为止最NB的windows系统 但令人头疼的问题也随之而来,前几天购置了一款三星的SSD固态硬盘,马上就装了win10,某天晚上关机以后发 ...