鹅厂分享会丨面向Unity程序员的Android快速上手教程

随着Unity、x等优秀跨平台游戏引擎的出现,开发者能够把本身从繁重的、iOS原生台开发中解放出来,把精力放在游戏的创做。原来作一款跨平台的游戏可能须要开发者懂得Java、Objective-C、C#甚至是C、C++,如今借助Unity咱们开发者只须要懂得不多的原生应用开发知识就可以打造一款优秀的游戏。特别是在鹅厂,有了Apollo这样的组件,原生的接入更加简单,可能每一个项目组只须要有1-2我的懂Android,iOS开发就够了。可是也正由于如此,不少同事有了充足的理由不去学习、接触Android和iOS的开发,等到真正须要作接入的时候才开始找人找资料,不免会踩坑。基于此,本文的目的就是经过介绍基础的Android开发知识以及部分的实际操做,让你们有必定的Android基础知识储备。又或者是看成一份Unity接入Android SDK/插件的基础教程,只要照着作,就基本上不会错了。

本文将会从你们熟悉的Unity为出发点来介绍如何将本身写的或者第三方的Android插件集成到本身的游戏中。

1. Unity是怎么打包APK文件的?
2. Android开发基础以及导入到Unity

Unity是怎么打包APK文件的?
你们看过一些第三方组件的接入文档都知道,在Unity里面有几个特殊的文件夹是跟打包APK有关的。首先咱们就来了解一下,这些文件夹里面的内容是经历了哪些操做才被放到APK里面的呢?

在Unity的Assets目录下,Plugins/Android无疑是其中的重中之重,首先咱们先来看一个常见的Plugins/Android目录是什么样子的。

-Android
-- ApolloBase
-- ApolloPlugins
-- assets
-- libs
-- res
-- AndroidManifest.xml

后面的四个是Android工程的文件。前面两个文件夹是咱们引用的第三方库,他们也会被打包到APK中。咱们这个时候若是点进去前两个文件夹,咱们会发现他们的目录结构跟Android这个目录也很像,大概是一下这个样子的。

-ApolloPlugins
-- libs
-- res
-- AndroidManifest.xml
-- project.properties

比较上下两层的目录接口咱们能够发现有不少类似的部分,如:libs、res、assets文件夹以及AndroidManifest.xml文件。这些其实都是一个标准的Android项目的所须要的文件。Unity自带的Android打包工具的做用就是把上述这几个文件夹里面的内容以固定的方式组织起来压缩到APK文件里面。



接下来咱们分别来看看Android打包工具都会作什么样的操做。
● libs文件夹里面有不少*.jar文件,以及被放在固定名字的文件夹里面的*.so文件。*.jar文件是Java编译器把.java代码编译后的文件,Android在打包的时候会把项目里面的全部jar文件进行一次合并、压缩、从新编译变成classes.dex文件被放在APK根目录下。当应用被执行的时候Android系统内的Java虚拟机(Dalvik或者Art),会去解读classes.dex里面的字节码而且执行。把众多jar包编译成classes.dex文件是打包Android应用不可或缺的一步。
看到这里有人可能会想不对啊,这一步只将jar包打成dex文件,那以前的java文件生成jar文件难道不是在这一步作吗?没错,这里用的jar包通常是由其余Android的IDE生成完成后再拷贝过来的。本文后面的部分会涉及到怎么使用Android的IDE而且生成必要的文件。



● libs文件夹的*.so文件则是能够动态的被Android系统加载的库文件,通常是由C/C++撰写而成而后编译成的二进制文件。要注意的是,因为实际执行这些二进制库的CPU的架构不同,因此一样的C\C++代码通常会针对不一样的CPU架构生成几分不一样的文件。这就是为何libs文件夹里面一般都有armeabi-v7a、armeabi、x86等几个固定的文件夹,并且里面的.so文件也都是有相同的命名方式。Java虚拟机在加载这些动态库的时候会根据当前CPU的架构来选择对应的so文件。有时候这些so文件是能够在不一样的CPU架构上执行的,只是在不对应的架构上执行速度会慢一些,因此当追求速度的时候能够给针对每一个架构输出对应的so文件,当追求包体大小的时候输出一个armeabi的so文件就能够了。



● assets文件夹,这个里面的东西最简单了,在打包APK的时候,这些文件里面的内容会被原封不动的被拷贝到APK根目录下的assets文件夹。这个文件夹有几个特性。

√ 里面的文件基本不会被Android的打包工具修改,应用里面要用的时候能够读出来。
√ 打出包之后,这个文件夹是只读的,不能修改。
√ 读取这个文件夹里面的内容的时候要经过特定的Android API来读取,参考getAssets()。
√ 基于上述两点,在Unity中,要读取这部份内容要经过WWW来进行加载。
除了Plugins/Android内的全部assets文件夹里面的文件会连同StreamingAssets目录下的文件一块儿被放到APK根目录下的assets文件夹。
● res文件夹里面通常放的是xml文件以及一些图片素材文件。xml文件通常来讲有如下几种:
√ 布局文件,被放在res中以layout开头的文件夹中,文件里描述的通常都是原生界面的布局信息。因为Unity游戏的显示是直接经过GL指令来完成的,因此咱们通常不会涉及到这些文件。

√ 字符串定义文件,通常被放到values文件夹下,这个里面能够定义一些字符串在里面,方便程序作国际
化还有本地化用。固然有时候被放到里面的还有其余xml会引用到的字符串,通常常见的是app的名称。
√ 动画文件,通常定义的是Android原生界面元素的动画,对于Unity游戏,咱们通常也不会涉及他。
√ 图片资源,通常放在以drawable为开头的文件夹内。这些文件夹的后缀通常会根据手机的像素密度来来进行区分,这样咱们能够往这些文件夹内放入对应像素密度的图片资源。

例如后缀为ldpi的drawable文件夹里面的图片的尺寸通常来讲会是整个系列里面最小的,由于这个文件夹的内容会被放到像素密度最低的那些手机上运行。而通常1080p或者2k甚至4k的手机在读取图片的时候会从后缀为xxxxhdpi的文件夹里面去读,这样才能够保证应用内的图像清晰。图片资源在打包过程当中会被放到APK的res文件夹内的对应目录。



√ Android还有其余一些常见的xml文件,这里就不一一列举了。
res文件夹下的xml文件在被打包的时候会被转换成一种读取效率更高的一种特殊格式(也是二进制的格式),命名的时候仍是以xml为结尾被放到APK包里面的res文件夹下,其目录结构会跟打包以前的目录结构相对应。



除了转换xml以外,Android的打包工具还会把res文件夹下的资源文件跟代码静态引用到的资源文件的映射给创建起来,放到APK根目录的resources.arsc文件。这一步能够确保安卓应用启动的时候能够加载出正确的界面,是打包Android应用不可或缺的一步。



● AndroidManifest.xml,这份文件过重要了,这是一份给Android系统读取的指引,在Android系统安装、启动应用的时候,他会首先来读取这个文件的内容,分析出这个应用分别使用了那些基本的元素,以及应该从classes.dex文件内读取哪一段代码来使用又或者是应该往桌面上放哪一个图标,这个应用能不能被拿来debug等等。在后面的部分会有详细解释。打包工具在处理Unity项目里面的AndroidManifest文件时会将全部AndroidManifest文件的内容合并到一块儿,也就是说主项目引用到的库项目里面若是也有AndroidManifest文



件,都会被合并到一块儿。这样就不须要手动复制粘贴。须要说明的是,这份文件在打包Android程序的时候是必不可少的,可是在Unity打包的时候,他会先检查Plugins目录下有没有这份文件,若是没有就会用一个自带的AndroidManifest来代替。此外,Unity还会自动检查项目中AndroidManifest里面的某些信息是否是默认值,若是是的话,会拿Unity项目中的值来进行替换。例如,游戏的App名称以及图标等。



● project.properties,这份文件通常只有在库项目里面能看获得,里面的内容极少,就只有一句话android.library=true。可是少了这份文件Android的打包工具就不会认为这个文件夹里面是个Android的库项目,从而在打包的时候整个文件夹会被忽略。这有时候不会影响到打包的流程,打包过程当中也不会报错,可是打出的APK包缺乏资源或者代码,一跑就崩溃。关于这份文件,其实在Unity的官方文档上并无详细的描述(由于他其实是Android项目的基础知识),致使不少刚刚接触Unity-Android开发的开发者在这里栽坑。曾经有个很早就开始用Unity作Android游戏的老前辈告诉我要搞定Unity中的Android库依赖的作法是用Eclipse打开Plugins/Android文件夹,把里面的全部的项目依赖处理好就好了。却不知这样将Unity项目跟Eclipse项目耦合在一块儿的作法是不太合理的,会形成Unity项目开启的时候缓慢。




● 其余文件夹例如aidl以及jni在Unity生成APK这一步通常不会涉及到,这里不展开。
看到了上述介绍的Unity打包APK的基础知识咱们知道了往Plugins/Android目录下放什么样的文件会对APK包产生什么样的影响。可是实际上上述的内容只是着重的讲了Unity是怎么打包APK,因此接下来会简述一下打包这个步骤究竟是怎么完成的。



Android提供了一个叫作aapt的工具,这个工具的全称是Android Asset Packaging Tool,这个工具完成了上述大部分的对资源文件处理的工做,而Unity则是经过对Android提供的工具链(Android Build Tools)的一系列调用从而完成打包APK的操做。这里感受有点像咱们写了个bat/bash脚本,这个脚本按照顺序调用Android提供的工具同样。在一些常见的Android IDE里面,这样的“bat/bash脚本”每每是一个完整的构建系统。最先的Android IDE是Eclipse,他的构建系统是Ant,是基于XML配置的构建系统。后来Android团队推出了Android专用的IDE——Android Studio(这个在文章后面会有详述),他的构建系统则是换成了gradle,从基于xml的配置一会儿升级到了语言(DSL, Domain Specific Language)的层级,给使用Android Studio的人带来更多的弹性。



写到这里我想不少人都清楚了要怎么把Android的SDK/插件放到Unity里面而且打包到Unity里面。这时候应该有人会说,光会放这些文件不够啊,我还须要知道本身怎么写Android的代码而且输出相应的SDK/插件给Unity使用啊。



本文接下来的内容将会一步一步描述怎么写Android代码而且输出库文件给Unity。

Android开发基础以及导入到Unity

开始你的第一个Android程序
安装完Android Studio而且配置好代理之后咱们就能够打开它,在弹出的框中选择“Start a new Android Studioproject”。

html




在接下来弹出的界面里面输入应用名称,公司域名(这个其实不怎么重要)以包名(Package Name),其中我认为最重要的是包名,毕竟看一个应用的包名能够看得出一个开发者的逼格如何。。。

java




接下来选择要开发什么类型的App,这里勾上Phone and Tablet就能够了。SDK的选择通常来讲根据项目的须要,最低通常不低于API 9: Android 2.3(Gingerbread),这也是Unity能接受的最低SDK。若是有些插件不能运行在这么低的Android SDK环境下的话能够酌情考虑提高到API 15: Android 4.3(IceCreamSandwich),这个等级的API通常也是能够兼容绝大多数近3-4年的机器。

android




由于咱们要输出的内容是给Unity用的,这里能够先选择不带有Activity(就是承载游戏画面的基础部件),后续用到再说。

编程




点击OK之后Android Studio就会开始初始化当前的这个Android项目。初始化会须要一段时间,由于AndroidStudio有可能会去下载一些必要的框架或者更新Android工具的版本。初始化完成之后到左边按照图里面的步骤点开就能够看到整个项目目录树的状况。

bash

<ignore_js_op>  


经过上图咱们能够知道,一个Android Studio的项目(Project)能够由许多小的模块(Module)组成,这些模块能够是带有Activity的应用类模块,也能够是不带有Activity的库模块等等。这些小的模块之间能够有引用关系。咱们能够把一些完成基础功能或者容易被复用的模块单独拆出来。


若是要新建一个模块咱们能够在上图的列表中点右键选择New Module,在弹出的界面中咱们能够选择要新建什么样的模块,或者从Eclipse导入旧的项目也能够。通常来讲给Unity游戏开发插件最经常使用的就是库模块(AndroidLibrary)。一样的,在接下来弹出的窗口中填写好模块名称、包名以及最低运行的SDK。
简单的看一下Android项目的目录结构。以下图所示:

架构

<ignore_js_op>  


● libs目录跟本文第一部分介绍的libs目录的功用是同样的,把依赖到的库放在这里面就能够了。
● src/main/res目录也是跟本文第一部分介绍的res目录的功能和结构是同样的,把对应资源放进去就能够了。
● 接下来是java代码所在的目录src/main/java,这个目录有点特殊,他的子路径跟java文件里面定义的包名(package name)要对应的上。
● AndroidManifest.xml也是跟第一部分介绍的AndroidManifest的功能是同样的。
● build文件夹是Android Studio动态生成的,打出的APK包(应用模块)或者AAR包(库模块)会被放到这里面的output文件夹。须要注意的是这个文件夹不该该被放提交到svn里面,要否则会形成项目成员之间的冲突,切记。
● src/test以及src/androidTest是作单元测试用的,本文不涉及。

至此,咱们就能够开始动手写代码了,这里咱们写一个能够弹出Android的Toast提示的Activity来替换掉Unity默认的Activity。
简述一下Unity跟Activity的关系:在Android系统中,打开一个应用,就是开启该应用指定的启动Activity。


Unity里面有个默认的Activity,他的做用就是在系统启动应用的时候加载Unity的Player,这个Player就是就至关因而Unity应用的“播放器”,他会执行咱们在Unity项目中创做的内容,而且经过GL指令渲染到指定的SurfaceView中,而SurfaceView则是被置于Activity里面的一个特殊的View。



首先,咱们在Android Studio中找到src/main/java(如上图所示),而后点击右键,选择新建Empty Activity。

app




在弹出的窗口中给你的Activity取个符合Java代码规范的名字,而后再想个合理的包名(固然,也能够直接用默认项目的包名也能够)。能够参考下图的配置:

框架




其中的Generate Layout File,咱们在制做给Unity游戏用的Activity是不须要勾上的。Launcher Activity勾上之后Android Studio会帮你在当前模块的AndroidManifest.xml中声明本Activity是应用的入口之一。做为一个库项目咱们这边其实也不须要这个选项。点击Finish以后Android Studio就会帮咱们在指定目录下建立一个很简单的Activity。里面的内容以下:

ide




须要注意的是这只是一个最基础的Android Activity,他还不会去加载咱们的Unity出来,因此咱们要让他继承自Unity的Activity而不是默认的。为此,咱们要先将Unity相应的jar包引入到咱们的模块当中。首先找到Unity的安装目录,而后找到如下子目录
Editor\Data\PlaybackEngines\AndroidPlayer\Variations\il2cpp\Release\Classes\里面的classes.jar,这个就是被打包成jar包的Unity默认的Activity。咱们把这个jar包复制到当前模块的libs目录下(能够把这个jar包改为你想要的名字,便于管理)。(这个jar包的源码在
Editor\Data\PlaybackEngines\AndroidPlayer\Source\com\unity3d\player这个目录下。感兴趣的同窗能够翻阅一下源码,就能够理解Unity播放器的加载机制。)



接下来,咱们能够在Android Studio左边的Project View中找到当前的模块之后点击右键,选择“Open ModuleSetting”或者直接按F4。在弹出的窗口中咱们选到最右边的页签“Dependencies”,而后选择右边绿色的加号-JarDependency。

svn




从项目的libs文件夹中找到刚刚导入的jar包,点击OK便可。接下来有一个比较关键的步骤就是,咱们改变这个jar包的scope属性,由于默认的scope属性(Compile)是会将该jar包里面的内容跟本模块里面Java代码合并到一块儿。这在以后Unity打包这个模块的jar包的时候会报错,由于Unity里面内置了刚刚这个jar包。因此咱们能够参考下图把这个jar包的scope设置成provided。



而后删除上述列表的第一行,由于他会把全部libs文件夹下的jar包都打包到一块儿。跟咱们刚刚作完的provided设置会有冲突。
搞定了这步骤之后咱们就能够回到刚刚新建立出来的Activity把他的父类改为UnityPlayerActivity,同时别忘记引用一下相应的package,改完以后的代码是这样的:



到这一步,若是咱们的Activity若是能被运行的话,他应该可以借助他的父类UnityPlayerActivity里面的代码来运行Unity。接下来,咱们来给这个Activity添加一方法,当这个方法被调用的时候会展现一个系统默认的Toast提示。





看得出来,里面最核心的一个方法其实就只是调用Android里面的Toast组件而已,没啥好解释的。相反,是外面的runOnUiThread是值得你们注意的,在Android编程中,全部涉及到对UI的操做必需要放在UI线程里面来作,不然会形成其余线程修改UI线程里面的数据而后崩溃。因为咱们写的这个ShowMessage方法最后会被Unity那边调用,而来自Unity的调用可能不是UI线程,因此咱们要给他作适当的保护。

在Android中有不少种调度方法能够把某段代码放到UI线程里面来跑。上面这段代码的runOnUiThread的写法是最简便的一种写法。若是遇到比较复杂的逻辑能够考虑使用Messenger或者Handle来调度线程,感兴趣的同窗能够上网查一下。


导入到Unity而且编译
完成Activity的代码编写以后就能够输出这个模块到Unity项目中去。在Android Studio中选择Build - Make Project或者是在左边的项目视图中选中要导出的模块而后选择Build - Make Module。选择完了以后就能够看到下面有个Gradle的进度条,待进度条完成了之后咱们就能够到该模块的build/outputs/aar目录下去找输出的文件。打开这个文件夹,能够看到有个*.aar的文件。这个就是该模块所编译出来的结果,若是你用解压缩软件去解压缩它,你会发现他几乎就是一个完整的Android工程。根据本文第一部分所说的内容,咱们只要在Unity工程中的Plugins/Android目录下新建一个文件夹,而后把这个文件解压缩之后整个丢进去,再手写一个名字叫project.properties,内容是android.library=true的文件放到新建的文件夹里面就能够了。



胜利在望,咱们接下来只要把Unity工程里面的AndroidManifest.xml文件的入口Activity从Unity默认的的改为咱们刚刚写的这个就能够了。须要注意的是,若是是旧的Unity工程,可能已经有人写过相关的AndroidManifest文件放在了Plugins/Android目录下,可是若是是全新的Unity项目的话,就没有这份文件了。在打包的时候,若是Unity发现Plugins/Android目录下没有这份文件,他会复制一份默认的文件而且修改其中跟项目有关的内容。这里咱们能够从Unity的安装目录的Editor\Data\PlaybackEngines\AndroidPlayer\Apk文件夹内找到AndroidManifest.xml这份文件,把它复制一份到Unity工程的Plugins/Android目录下。接下来就是修改里面的内容。







这里解释一下这份文件里面的一些关键内容。
● package="com.unity3d.player"这里的内容若是放着不动,打包的时候Unity会将其修改成Player Setting的Bundle Identifier。
● android:versionCode以及android:versionName这两部分的内容则在打包时会根据Player Setting里面的Version以及Bundle Version Code的内容来进行修改。
● android:icon以及android:label这两个对应的是应用的图标以及应用名称。若是不改的话,Unity也会自动根据Player Setting里面的内容来进行修改。
● android:debuggable="true"这个在打包的时候Unity也会自动根据Build Setting里面的Development Build选项自动进行修改。
● activity里面的android:name,这个name只的是该activity须要运行的哪一个Java的Activity的类。若是不修改,加载的就是Unity默认Activity的类。这篇文章须要把默认的Activity改为刚刚咱们的实现,因此,咱们把刚刚写好的那个Activity的完整名称写上去(包括包名还有类名)。
● activity里面的android:label,这个是在桌面上图标下面写的那一行文字,也是应用的名称。不修改的话Unity会帮你维护。
● meta-data的这一行的name值是key,value值就是这个key对应的内容。meta-data能够根据须要自定义多个,可是key值不能重复,上面代码里面的unityplayer.UnityActivity应该是写给Unity看的,让Unity知道他本身是运行在这个Activity上。
这里咱们基本上只要修改activity里面的android:name这一项。修改完成后,咱们就能够经过Unity自带Build功能来出Android包了。出包以前请检查一下Player Setting里面的Bundle Identifier,不能留默认的包名在这里,会形成编译失败。编译过程当中,可能会出现一些错误,下面罗列几个常见的错误,能够尝试解决:
1. 合并Manifest文件出错,通常来讲是在合并全部的AndroidManifest文件的时候出的错,常见的有重复定义了activity、里面的最低sdk写错了。模块的最低sdk不可低于项目的最低sdk。



2. jar文件dex错误,当你的项目中不当心存在了一个以上的相同的jar文件,就会出这个错误,把重复的删掉,只留一个就行了。
3. 找不到Android SDK里面的工具,这个通常来说是Unity本身的bug,Unity通常不能兼容最新的Android SDK的工具,因此要手动降级才行。
除了上述这些以外,在打包Android项目的过程当中还会出现这些那些的错误,你们看到之后不要慌张,会报错老是好的,并且通常的错误你把错误信息贴在万能的Google上,都能找到解决方案。


Unity对Android代码的调用
文章到这里为止,说清楚了怎么把Android这边写成的插件打包到Unity的项目中去。但其实并无涉及到Unity中怎么调用刚刚写好在Android的Activity中的代码。这一部对于一个Unity开发来讲其实很是简单,只要以Unity提供的AndroidJavaClass还有AndroidJavaObject来作为中介就能够在Unity和Java中互传数据。这两个类的调用给人一种经过反射来调用Java代码的感受。只要你能经过包名和类名拿到某个Java对象,就能够直接经过成员变量名称或者方法名称直接调用到Java那边的代码。举个例子,假如要在Unity中调用刚刚咱们写的那个类的ShowMessage类的话咱们须要在Unity中准备如下代码。







简单介绍一下这段代码的几个关键点:
1. 经过UnityPlayer能够很方便的拿到当前Activity的Java对象实例。
2. 对Java对象实例的方法的调用实际上很简单,只要调用Call就能够了。
3. 注意用宏来区隔Native代码。UNITY_ && !UNITY_EDITOR这个推荐的写法,若是不过滤掉UNITY_EDITOR会在运行的时候报错。
4. 推荐在new出AndroidJavaClass还有AndroidJavaObject的地方用using来进行保护,确保执行结束后Unity会自动回收相应的代码。

相关文章
相关标签/搜索