Unity编译Android的原理解析和apk打包分析

作者介绍:张坤

最近由于想在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解得早

相关阅读

【腾讯云的1001种玩法】安卓加固在腾讯云上的使用(附反编译结果)

深度了解Android 7.0 ,你准备好了吗?



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

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

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

时间: 2024-10-06 08:45:13

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

flutter android端 原理解析

首先看MainActivity, 继承FlutterActivity 再看FlutterActivity代码,继承Activity,实现Provider.PluginRegistry.ViewFactory 12345678910111213141516 private final FlutterActivityDelegate delegate = new FlutterActivityDelegate(this, this);private final FlutterActivityEven

Android应用setContentView与LayoutInflater加载解析机制源码分析

[工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1 背景 其实之所以要说这个话题有几个原因: 理解xml等控件是咋被显示的原理,通常大家写代码都是直接在onCreate里setContentView就完事,没怎么关注其实现原理. 前面分析<Android触摸屏事件派发机制详解与源码分析三(Activity篇)>时提到了一些关于布局嵌套的问题,当时没有深入解释. 所以接下来主要分析的就是View或者ViewGroup对象是如何添加至应用程

Unity编译时找不到AndroidSDK的问题 | Unable to list target platforms

转自:http://www.jianshu.com/p/fe4c334ee9fe 现象 在用 Unity 编译 Android 平台的应用时,遇到 Unable to list target platforms 的问题. 错误提示 详细错误描述如下: Error:Invalid command androidUnityEditor.BuildPlayerWindow:BuildPlayerAndRun() CommandInvokationFailure: Unable to list targ

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

原文:http://www.jianshu.com/p/fe4c334ee9fe 现象 在用 Unity 编译 Android 平台的应用时,遇到 Unable to list target platforms 的问题. 错误提示 详细错误描述如下: Error:Invalid command androidUnityEditor.BuildPlayerWindow:BuildPlayerAndRun() CommandInvokationFailure: Unable to list targ

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

Android中的Apk的加固(加壳)原理解析和实现 标签: android 2015-09-13 13:58 42287人阅读 评论(49) 收藏 举报 本文章已收录于:  Android知识库  分类: Android(140)  版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[+] 一.前言 今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理.现阶段.我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个

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

一.前言 今天又到周末了,憋了好久又要出博客了,今天来介绍一下Android中的如何对Apk进行加固的原理.现阶段.我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk,结果被人反编译了,那心情真心不舒服.虽然我们混淆,做到native层,但是这都是治标不治本.反编译的技术在更新,那么保护Apk的技术就不能停止.现在网上有很多Apk加固的第三方平台,最有名的应当属于:爱加密和梆梆加固了.其实加固有些人认为很高深的技术,其实不然,说的简单点就是对源Apk进行加密,然后

爱加密Android APk 原理解析

爱加密Android APK加壳原理解析 一.什么是加壳? 加壳是在二进制的程序中植入一段代码,在运行的时候优先取得程序的控制权,做一些额外的工作.大多数病毒就是基于此原理.PC EXE文件加壳的过程如下: 二.加壳作用 加壳的程序可以有效阻止对程序的反汇编分析,以达到它不可告人的目的.这种技术也常用来保护软件版权,防止被软件破解. 三.Android Dex文件加壳原理 PC平台现在已存在大量的标准的加壳和解壳工具,但是Android作为新兴平台还未出现APK加壳工具.Android Dex文

Android&quot;挂逼&quot;修炼之行---微信摇骰子和猜拳作弊器原理解析

一.前言 在之前的一篇文章中我们已经详细介绍了Android中Hook工作的一款神器Xposed工具:Xposed框架原理解析和使用案例分析 在那一篇文章中我们介绍了如何安装Xposed框架,以及如何使用Xposed库编写第一个模块来做到修改系统方法功能的效果,同时也说到了一个非常重要的知识点就是:在Hook过程中最重要的一点就是如何找到Hook点,而对于这一点很多同学都会感觉到非常的困难,因为对于修改系统方法还好,因为可以简单的去查看具体的Android源码即可,但是如果说要去编写一些应用和游

Android应用市场省流量更新(增量升级)原理解析

一.前言 最近在看热修复相关的框架,之前我们已经看过了阿里的Dexposed和AndFix这两个框架了,不了解的同学可以点击这里进行查看:Dexposed框架原理解析 和 AndFix热修复框架原理解析,然后还有最近很火的一个是腾讯的Tinker热修复框架,再看他的原理实现的时候,发现了他使用到了开源的文件差分工具bsdiff/bspatch,所以就单独用这篇文章来详细介绍一下这个工具,因为这个工具有一个很大的用途就是增量更新,也就是我们看到现在大部分的应用市场推出的省流量更新应用的效果: 看到