⾯向Unity程序员的Android快速上⼿教程
作者:Poan,腾讯移动客户端开发⼯程师
商业转载请联系腾讯WeTest获得授权,⾮商业转载请注明出处。
WeTest 导读
随着Unity、cocos2dx等优秀跨平台游戏引擎的出现,开发者可以把⾃⼰从繁重的Android、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 Studio
3. Android开发基础以及导⼊到Unity
⼀、Unity是怎么打包APK⽂件的?
⼤家看过⼀些第三⽅组件的接⼊⽂档都知道,在Unity⾥⾯有⼏个特殊的⽂件夹是跟打包APK有关的。⾸先我们就来了解⼀下,这些⽂件夹⾥⾯的内容是经历了哪些操作才被放到APK⾥⾯的呢?
在Unity的Assets⽬录下,Plugins/Android⽆疑是其中的重中之重,⾸先我们先来看⼀个常见的Plugins/Android⽬录是什么样⼦的。
后⾯的四个是Android⼯程的⽂件。前⾯两个⽂件夹是我们引⽤的第三⽅库,他们也会被打包到APK中。我们这个时候如果点进去前两个⽂件夹,我们会发现他们的⽬录结构跟Android这个⽬录也很像,⼤概是⼀下这个样⼦的。
⽐较上下两层的⽬录接⼝我们可以发现有很多相似的部分,如:libs、res、assets⽂件夹以及l⽂件。这些其实都是⼀个标准的Android项⽬的所需要的⽂件。Unity⾃带的Androi
d打包⼯具的作⽤就是把上述这⼏个⽂件夹⾥⾯的内容以固定的⽅式组织起来压缩到APK⽂件⾥⾯。
接下来我们分别来看看Android打包⼯具都会做什么样的操作。
● libs⽂件夹⾥⾯有很多*.jar⽂件,以及被放在固定名字的⽂件夹⾥⾯的*.so⽂件。*.jar⽂件是Java编译器把.java代码编译后的⽂
件,Android在打包的时候会把项⽬⾥⾯的所有jar⽂件进⾏⼀次合并、压缩、重新编译变成classes.dex⽂件被放在APK根⽬录下。当应⽤被执⾏的时候Android系统内的Java虚拟机(Dalvik或者Art),会去解读classes.dex⾥⾯的字节码并且执⾏。把众多jar包编译成classes.dex ⽂件是打包Android应⽤不可或缺的⼀步。
安卓app开发用什么框架看到这⾥有⼈可能会想不对啊,这⼀步只将jar包打成dex⽂件,那之前的java⽂件⽣成jar⽂件难道不是在这⼀步做吗?没错,这⾥⽤的jar包⼀般是由其他Android的IDE⽣成完成后再拷贝过来的。本⽂后⾯的部分会涉及到怎么使⽤Android的IDE并且⽣成必要的⽂件。
● libs⽂件夹的*.so⽂件则是可以动态的被Android系统加载的库⽂件,⼀般是由C/C++撰写⽽成然后编译成的⼆进制⽂件。要注意的是,由于实际执⾏这些⼆进制库的CPU的架构不⼀样,所以同样的C\C++代码⼀般会针对不同的CPU架构⽣成⼏分不同的⽂件。这就是为什么libs ⽂件夹⾥⾯通常都有ar
meabi-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应⽤不可或缺的⼀步。
● l,这份⽂件太重要了,这是⼀份给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”。
在接下来弹出的界⾯⾥⾯输⼊应⽤名称,公司域名(这个其实不怎么重要)以包名(Package Name),其中我认为最重要的是包名,毕竟看⼀个应⽤的包名可以看得出⼀个开发者的逼格如何。。。
接下来选择要开发什么类型的App,这⾥勾上Phone and Tablet就可以了。SDK的选择⼀般来说根据项⽬的需要,最低⼀般不低于API 9: Android 2.3(Gingerbread),这也是Unity能接受的最低SDK。如果有些插件不能运⾏在这么低的Android SDK环境下的话可以酌情考虑提升到API 15: Android 4.3(IceCreamSandwich),这个等级的API⼀般也是可以兼容绝⼤多数近3-4年的机器。
因为我们要输出的内容是给Unity⽤的,这⾥可以先选择不带有Activity(就是承载游戏画⾯的基础部件),后续⽤到再说。
点击OK以后Android Studio就会开始初始化当前的这个Android项⽬。初始化会需要⼀段时间,因为An
droidStudio有可能会去下载⼀些必要的框架或者更新Android⼯具的版本。初始化完成以后到左边按照图⾥⾯的步骤点开就可以看到整个项⽬⽬录树的情况。
通过上图我们可以知道,⼀个Android Studio的项⽬(Project)可以由许多⼩的模块(Module)组成,这些模块可以是带有Activity的应⽤类模块,也可以是不带有Activity的库模块等等。这些⼩的模块之间可以有引⽤关系。我们可以把⼀些完成基础功能或者容易被复⽤的模块单独拆出来。
如果要新建⼀个模块我们可以在上图的列表中点右键选择New Module,在弹出的界⾯中我们可以选择要新建什么样的模块,或者从Eclipse 导⼊旧的项⽬也可以。⼀般来说给Unity游戏开发插件最常⽤的就是库模块(AndroidLibrary)。同样的,在接下来弹出的窗⼝中填写好模块名称、包名以及最低运⾏的SDK。
简单的看⼀下Android项⽬的⽬录结构。如下图所⽰:
● libs⽬录跟本⽂第⼀部分介绍的libs⽬录的功⽤是⼀样的,把依赖到的库放在这⾥⾯就可以了。
● src/main/res⽬录也是跟本⽂第⼀部分介绍的res⽬录的功能和结构是⼀样的,把对应资源放进去就可以了。
●接下来是java代码所在的⽬录src/main/java,这个⽬录有点特殊,他的⼦路径跟java⽂件⾥⾯定义的
包名(package name)要对应的上。
● l也是跟第⼀部分介绍的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。
在弹出的窗⼝中给你的Activity取个符合Java代码规范的名字,然后再想个合理的包名(当然,也可以直接⽤默认项⽬的包名也可以)。可以参考下图的配置:
其中的Generate Layout File,我们在制作给Unity游戏⽤的Activity是不需要勾上的。Launcher Activity勾上以后Android Studio会帮你在当前模块的l中声明本Activity是应⽤的⼊⼝之⼀。作为⼀个库项⽬我们这边其实也不需要这个选项。点击Finish之后Android Studio就会帮我们在指定⽬录下创建⼀个很简单的Activity。⾥⾯的内容如下:
需要注意的是这只是⼀个最基础的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\Playb
ackEngines\AndroidPlayer\Source\com\unity3d\player这个⽬录下。感兴趣的同学可以翻阅⼀下源码,就可以理解Unity播放器的加载机制。)
接下来,我们可以在Android Studio左边的Project View中到当前的模块以后点击右键,选择“Open ModuleSetting”或者直接按F4。在弹出的窗⼝中我们选到最右边的页签“Dependencies”,然后选择右边绿⾊的加号-JarDependency。
从项⽬的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⼯程⾥⾯的l⽂件的⼊⼝Activity从Unity默认的的改成我们刚刚写的这个就可以了。需要注意的是,如果是旧的Unity⼯程,可能已经有⼈写过相关的AndroidManifest⽂件放在了Plugins/Android⽬录下,但是如果是全新的Unity 项⽬的话,就没有这份⽂件了。在打包的时候,如果Unity发现Plugins/Android⽬录下没有这份⽂件,他会复制⼀份默认的⽂件并且修改其中跟项⽬有关的内容。这⾥我们可以从Unity的安装⽬录的Editor\Data\PlaybackEngines\AndroidPlayer\Apk⽂件夹内到l这份⽂件,把它复制⼀份到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上。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。