写点什么

Android 应用开发编译框架流程与 IDE 及 Gradle 概要,android 游戏开发从入门到精通

用户头像
Android架构
关注
发布于: 刚刚

这也就是为何系统编译预置 app 源码后会看到 odex 和不含 dex 的 apk 文件,而独立 app 编译后只看见内含 dex 的 apk 文件的原因。之前在上家公司做盒子开发时有人曾经有过这个疑惑,当然我也有过!


dex 文件 65535 异常方法限制原因:


Android 的 Dalvik 和 ART 运行时环境都能够执行 dx 工具生成的.dex 文件,也就是说 Dalvik 和 ART 使用了同一套 Dalvik 指令集。通过相关资料查询可以知道 Dalvik 指令集使用了 16 位寄存器来保存项目中所有的方法引用,2 的 16 次方是 65536,也就是说一个 dex 文件最多只能引用 65536 个方法,所以对于 Dalvik 和 ART 运行时环境来说都有这个局限性。我勒个去!!!这不就是我们有时候编译项目时抛出 Android Dex 方法限制异常的原因么(上面也有介绍,不明白上翻回看),也就是说编译时抛出这个异常是因为项目包含的方法太多导致的,好在 Google 官方也意识到了这个缺陷,所以他们给出了解决方案,如下:


  • 使用 ProGuard 清除项目中无用方法,使用相关脚本对项目中没用到的第三方库中的方法进行清除处理;

  • 由于 Dalvik 运行时环境限制一个 apk 只能包含一个 classes.dex 文件,所以我们可以使用 Multidex Support Library 支持包让一个 apk 里支持多个.dex 文件,这样就可以突破 65536 的限制。


dx 过程中这个错误非常经典,一般都出现在大量使用了第三方库的情况下,所以需要注意一下。

2-2-4 apkbuilder 工具

关于 apkbuilder 工具这个叫法其实已经有些过时了,因为比较新版本的 SDK 中已经将 apkbuilder 工具去掉了,不过 apkbuilder 工具的实质其实是对/android-sdk-linux/tools/lib/sdklib.jar 中 ApkBuilderMain 等的一个封装而已,所以即使没有了该工具我们也可以自己实现封装,不过新的编译框架会自动帮助我们解决这一过程,我们无需手动处理。该过程的实质其实和压缩工具的性质差不多,只是它将相关资源、dex 文件等打包压缩成了一个指定压缩方式和深度等的 apk 文件而已。

2-2-5 keytool 与 jarsigner 工具

对 apkbuilder 打包压缩出来的 apk 进行签名的实质其实是在应用程序的特定字段写入特定的标记信息,以便用来表示该软件已经通过了签署者审核。签名的作用主要是识别应用的作者、检测应用程序是否已经改变、检测是否为同一个应用等。


一般我们可以通过 keytool 工具生成签名私钥,然后通过 jarsigner 工具使用私钥对应用进行签名。不过这一过程非常简单,这里就不再啰嗦了,自行脑补。

2-2-6 zipalign 工具

zipalign 工具可以对打包的应用进行优化,优化过的应用在运行时执行效率可以达到最大限度且会占用更少的 RAM(Random Access Memory)内存。zipalign 对 apk 文件中数据进行 4 字节对齐,也就是说编译器把 4 个字节作为一个单位来进行操作,这样 CPU 就能对代码进行高效访问,因为对齐后 Android 系统可以通过调用 mmap 函数读取文件,也就是说进程可以像读写内存一样操作我们 apk 中普通文件,所以当对齐的应用在系统中执行时通过共享内存 IPC 读取资源就能得到较高的性能,如果没有对齐处理则必须显示的调运 read 等方法去操作数据,也就是说运行过程会比较缓慢且会花费更多的内存,从而导致性能下降。


关于 zipalign 工具的使用这里也不再啰嗦了,因为通常编译框架允许我们直接配置脚本而不用手动执行命令。

2-2-7 ProGuard 工具

ProGuard 是一个压缩、优化和混淆 Java 字节码 class 文件的工具,它可以删除无用的类、字段、方法、属性及没用的注释等,最大限度地优化 class 字节码文件。它还可以使用简短的无意义名称来重命名已经存在的类、字段、方法和属性。我们通常用它来混淆最终的项目,然后稍微增加项目被反编译的难度,当然了,对于现在的技术来说反编译难度这个已经不是问题了,我们还是重点关注他的优化无用资源和简洁替代吧。


关于 ProGuard 工具的使用这里也不再啰嗦了,因为通常编译框架允许我们直接配置脚本而不用手动执行命令。

2-2-8 jobb 工具

其实这个工具不属于正常编译框架的流程,算是 Android 的一个拓展特性而已。从 Android 2.3 版本开始系统增加了一个 OBB 文件系统(权限访问限制隔离文件系统)和 StorageManager 类用来管理外部存储上的数据安全。


如果你之前在 Android 手机上安装过《纪念碑谷》或者《机械迷城》游戏,那你就能对这里讲的 jobb 工具和 OBB 文件系统有一个很好的理解。还记不记得在安装几十兆大小的游戏后你还需要下载一个两百多兆的 zip 压缩包放到文件系统的 Android/obb/[GamePackageName]目录下才能正常玩游戏。之所以这么做是因为我们的游戏工程中包含大量的资源(图片、视频、音乐等),直接编译为 APK 可能会高达好几百兆,系统在安装 APK 时又会对 APK 文件大小有一个限制,这么大的 APK 文件必定会导致 Android 系统无法正常安装该 APK;相信此时机智的你指定会说,我们把这些资源直接放到 SD 卡上不就完了?哈哈,你想没想过一问题,如果直接放到 SD 卡,系统的音乐、视频、图片等管理器岂不是直接可以索引到这些东东了,那得多不好(插一句,还可以将这些资源去掉后缀保存,这样这些媒体库就无法索引了,譬如 Android 系统邮件应用的附件就是这么设计的,真机智!)。好在 Android 2.3 的 OBB 文件可以很牛叉的解决这一系列问题。


既然这样的话,想必 OBB 文件系统一定会要求存储的文件必须符合一定的格式,jobb 就是解决这个问题的工具。jobb 允许我们生成加密或不加密的 OBB 格式扩展文件,OBB 文件可以作为 Android 应用程序的扩展资源文件,独立于 APK 文件存在。下面就是 jobb 工具的文档:



关于 jobb 工具这里就不深入说明了,一般游戏等大资源应用开发中才可能会考虑到这种设计,用到时再脑补也不迟,这里知道有这么回事就行了。


2-3 Android 应用编译 Jack 和 Jill 新工具链




到这里其实大家对常规的应用编译框架已经有了一个不错的认识了,But 问题来了,你是不是也觉得当前的 Android 编译构建流程相当蛋疼(编译构建巨慢)呢,其实 Google 官方似乎也意识到了这个问题,他们还在今年的 Google IO 大会上给出了当前阶段的一些优化交代,其中最值得尝试和一提的亮点是 Jack 和 Jill 两个新的编译器(当前官方声称还是 Experimental 试验性的编译器,还不够健壮,还在 bug 收集阶段,当前不支持注解处理,不支持 Java 8 等,所以还是慎重),官方说这两个编译器旨在简化安卓的编译流程,说白了也就是尝试加快编译构建速度。下面先来看下使用 Jack 和 Jill 编译器的构建流程:



可以看见,Jack 是一个基于 Java 编译器和 ProGuard 的工具,但是目前版本还不支持 ProGuard 的一些高级功能(譬如移除日志代码)。Jill 将 Java 库字节码转化成名为 jayce 的中间字节码.jack 文件,Jack 对 Java 源码和 jayce 字节码进行编译,生成经过优化的 dex 字节码。


想尝鲜使用 Jack 和 Jill 你需要保证你的 Build Tools version 是 21.1.1 版本或者更高。在 Gradle 中配置如下:


android {


...


buildToolsRevision ‘21.1.2’


defaultConfig {


// Enable the experimental Jack build tools.


useJack = true


}


...


}


总归一句话,现在还年轻,有何成就还得观望,看后面的发展趋势吧,反正目前我是没咋使用他,只是试验性的尝鲜了一把而已。


【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我


3 Android 应用 IDE 及编译相关特性


=========================


下面介绍一些使用 IDE 开发过程中高效的代码编写特性,同时包含一些编译相关的注意特性,具体如下会细说。


3-1 Android 应用 jar 包与 aar 包




我们在 Android Studio 中对一个自己的库进行编译时会同时生成 jar 与 aar 两个包。他们的具体路径如下:


| 包类型 | 在 AS 中的输出路径 |


| --- | --- |


| jar | /build/intermediates/bundles/debug[release]/classes.jar |


| aar | /build/outputs/aar/library.aar |


他们两者的区别如下(实质都是压缩包):


| 包类型 | 描述 |


| --- | --- |


| jar | 只包含 class 文件,不包含资源文件,用于纯 Java 编写的库。 |


| aar | 包含所有 class 及 res 等全部资源,类似 UI 库。 |


其实关于他们二者的区别我们通过解压缩即可直观的看出来,这里不再叙述。


3-2 Android Tools Attributes 编译说明




关于 Android 应用编译框架中还需要知道一个有用的工具属性,那就是 tools 命名空间属性,他的命名空间 URI 是http://schemas.android.com/tools,可以说这个命名空间是专门为开发者设计的,只在设计阶段有效,运行阶段无效。

3-2-1 绑定 tools 命名空间的方法

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"


//绑定命名空间


xmlns:tools="http://schemas.android.com/tools"


android:layout_width="match_parent"


android:layout_height="match_parent" >


....


tools 属性可以替换所有 Android 的属性,只在设计阶段有效,不会被带入最终的 apk 中,所以运行时无效。整个 tools 命名空间的属性分两大类,一类是 Lint 相关的、一类是设计相关的。下面我们列举一下 tools 相关的一些实用属性。

3-2-2 tools:ignore

告诉 Lint 忽略 xml 中某些警告。如下用法:


//忽略 Lint 对于多语言检测的警告,多个可以用逗号分开


<string name="show_all_apps" tools:ignore="MissingTranslation">All</string>

3-2-3 tools:targetApi

用来指定 API 等级,功能和 Java 文件中的 @TargetApi 注释类似,值为整数或者含义字符串。如下用法:


<GridLayout tools:targetApi="ICE_CREAM_SANDWICH" >

3-2-4 tools:locale

默认情况下 res/values/strings.xml 文件中的字符串会进行拼写检查,如果本地不是英语则会给出警告,我们可以通过这样来指定本地语言然后忽略警告。如下:


<resources xmlns:tools="http://schemas.android.com/tools" tools:locale="es">

3-2-5 tools:context

这个属性其实就是关联 Activity 属性,在 xml 中添加该属性后预览该 xml 文件就能知道采用啥主题来预览,同时关联了 Activity 文件与 xml 文件,可以从 java 文件直接跳转索引。如下:


<android.support.v7.widget.GridLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"


tools:context=".MainActivity" ... >

3-2-6 tools:layout

该属性用在 xml 的 fragment 节点中,在开发时告诉 IDE 该 fragment 在预览模式下显示的样子。如下:


<fragment android:name="com.example.master.ItemListFragment" tools:layout="@android:layout/list_content" />

3-2-7 tools:listitem / listheader / listfooter

很明显可以猜到用来给 ListView、AdapterView、GridView、ExpandableListView 指定预览时的 item 和头底。如下:


<ListView


android:id="@android:id/list"


android:layout_width="match_parent"


android:layout_height="match_parent"


tools:listitem="@android:layout/simple_list_item_2" />

3-2-8 tools:showIn

该属性被设置到一给被 include 的布局的根节点上,预览时可用。如下:


<?xml version="1.0" encoding="utf-8"?>


<TextView xmlns:android="http://schemas.android.com/apk/res/android"


xmlns:tools="http://schemas.android.com/tools"


android:text="@string/hello_world"


android:layout_width="wrap_content"


android:layout_height="wrap_content"


tools:showIn="@layout/activity_main" />

3-2-9 tools:menu

用来告诉 IDE 在预览时当前 xml 布局显示指定的 menu 样式。其实如果我们指定了 tools:context 属性,IDE 会很智能的在我们指定 Activity 文件中的 onCreateOptionsMenu 方法中寻找 menu 样式预览。当然了,我们可以通过该属性覆盖 Activity 中的 menu 预览。如下:


<?xml version="1.0" encoding="utf-8"?>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"


xmlns:tools="http://schemas.android.com/tools"


android:orientation="vertical"


android:layout_width="match_parent"


android:layout_height="match_parent"


tools:menu="menu1,menu2" />

3-2-10 tools:actionBarNavMode

指定 actionbar 的预览模式。可选值为”standard”、”list”、”tabs”。如下:


<?xml version="1.0" encoding="utf-8"?>


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"


xmlns:tools="http://schemas.android.com/tools"


android:orientation="vertical"


android:layout_width="match_parent"


android:layout_height="match_parent"


tools:actionBarNavMode="tabs" />

3-2-11 Designtime 设计时属性

tools 属性可以替换所有 Android 的属性,只在设计阶段有效,不会被带入最终的 apk 中,所以运行时无效。譬如我们最常见的在写一个 TextView 时不想在 xml 中给他初始文本内容,又想预览,这时候就可以用 tools 属性替代。如下:


<TextView


tools:text="Name:"


android:layout_width="wrap_content"


android:layout_height="wrap_content"


tools:visibility="invisible" />


可以看见,tools 命名空间属性可以加速我们开发的灵活度和可预测度,同时在编译框架中又会忽略这些属性,不把他们带入最终产物 apk 中,我们可以在开发中酌情选择是否使用这些属性。


3-3 Android Annotations 注解说明




在 Android Support Library 的 19.1 版本中加入了一个新的注解包,这个包用来注解代码,方便捕获程序中存在的问题和 bug,这个包内部的代码就已经使用了该注解,在 22.2 版本中又增加了 13 个新的注解,这些注解可以方便我们代码开发与调运规范和 bug 检查,具体用法下面会一一讲解。

3-3-1 注解 Library 依赖引用

annotations 注解包默认不会自动被包含,不过如果使用 appcompat 包则会自动包含,因为 appcompat 包里使用了注解。


在 Android 工程的 Gradle 文件中引入注解包如下:


dependencies {


compile 'com.android.support:support-annotations:22.2.0'


}


如果不是 Android 工程中想使用该注解包则可以如下写法(url 为本地 android sdk 的注解包路径):


repositories {


jcenter()


maven { url '<your-SDK-path>/extras/android/m2repository' }


}

3-3-2 执行注解

当我们在用 Android Studio 和 IntelliJ 时如果给使用了注解的方法传递错误类型参数,则 IDE 会实时标记提醒错误。如果使用的是 Gradle 1.3.0 版本以上且安装了 Android M Preview Tools 以上工具则可以通过命令行调用 gradle 的 lint 任务进行检查(nullness 注解会被忽略检查)。

3-3-3 Null 空类型判断注解

@Nullable 注解用来标注给定的参数或返回值可以为 null。


@NonNull 注解用来标注给定的参数或返回值不能为 null。


假设一个本地变量值为 null,且把它作为参数传递给一个方法,且该方法的参数被 @NonNull 标注,则 AS 会提醒存在一个潜在的崩溃。如下:


import android.support.annotation.NonNull;


import android.support.annotation.Nullable;


...


/**


  • Add support for inflating the <fragment> tag.


*/


@Nullable


@Override


public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) {


...

3-3-4 资源类型注解

Android 的资源值通常都是通过 R 文件映射的整型 id 来关联的,也就是说获取一个 layout 类型的资源参数很容易被误传递一个其他类型的资源参数,因为他们都是整型的资源 id,编译器很难区分。为了解决这种问题可以使用资源类型注解,因为注解提供类型检查。譬如下面是一个被 @LayoutRes 注解的整型参数却传递了一个 string 类型的资源参数,此时 IDE 会给出错误提示:


调运 setContentView 方法时传递错误参数:



setContentView 的资源注解实现方法:



实际上有很多不同的资源类型注解,譬如 @AnimatorRes、@AnimRes、@AnyRes、@ArrayRes、@AttrRes、@BoolRes、@ColorRes、@DimenRes、@DrawableRes、@FractionRes、@IdRes、@IntegerRes、@InterpolatorRes、@LayoutRes、@MenuRes、@PluralsRes、@RawRes、@StringRes、@StyleableRes、@StyleRes、@XmlRes 等,一般一个 foo 类型资源的相应资源类型注解就是 @FooRes。除此之外,还有一个名为 @AnyRes 的特殊资源类型注解,它被用来标注一个未知特殊类型的资源,且必须是一个资源类型。譬如在 Resources.getResourceName(@AnyRes int resId)上使用的时候,我们可以通过 getResources().getResourceName(R.drawable.icon)和 getResources().getResourceName(R.string.app_name)等方式来使用,但却不能通过 getResources().getResourceName(42)来使用。

3-3-5 IntDef/StringDef 类型注解

这种类型的注解是基于 Intellij 的魔数检查机制的,因为 Android 开发中很多时候出于性能考虑,我们会使用整型常量代替枚举类型。譬如 AppCompat 库里的一个例子:


import android.support.annotation.IntDef;


...


public abstract class ActionBar {


...


@IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})


@Retention(RetentionPolicy.SOURCE)


public @interface NavigationMode {}


public static final int NAVIGATION_MODE_STANDARD = 0;


public static final int NAVIGATION_MODE_LIST = 1;


public static final int NAVIGATION_MODE_TABS = 2;


@NavigationMode


public abstract int getNavigationMode();


public abstract void setNavigationMode(@NavigationMode int mode);


上面非注解的部分是原本的代码和 API,我们通过 @interface 创建了一个新注解 NavigationMode,并且用 @IntDef 标注它可能的取值,还添加了 @Retention(RetentionPolicy.SOURCE)告诉编译器这个新定义的注解不需要被记录在生成的.class 文件中;使用这个注解后,如果我们返回或传递的参数不在指定的常量值中则 IDE 会给出明显的错误提示。


我们也可以指定整型常量为一个标记性质的类型,因为这样就可以通过|、&等操作符同时传递多个整型常量。如下例子:


@IntDef(flag=true, value={


DISPLAY_USE_LOGO,


DISPLAY_SHOW_HOME,


DISPLAY_HOME_AS_UP,


DISPLAY_SHOW_TITLE,


DISPLAY_SHOW_CUSTOM


})


@Retention(RetentionPolicy.SOURCE)


public @interface DisplayOptions {}


@StringDef 和 @IntDef 的作用基本上一样,不同的是它针对的是字符串。关于类型注解更多信息点我获取

3-3-6 线程注解 @UiThread、@WorkerThread 等

线程注解是在 Support Library 22.2 及更高版本中才被支持的,如果我们的方法只能在指定的线程类型中被调用,则就可以使用以下 4 个注解来标注它们:


  • @UiThread

  • @MainThread

  • @WorkerThread

  • @BinderThread


如果一个类中的所有方法都有相同的线程需求则可以注解类本身。


关于线程注解使用的一个例子是 AsyncTask,如下:


@WorkerThread


protected abstract Result doInBackground(Params... params);


@MainThread


protected void onProgressUpdate(Progress... values) {


}


如果在使用该方法时没有按照注解线程执行则会报错,如下:


3-3-7 约束值注解 @Size、@IntRange、@FloatRange

如果我们的参数是 float 或 double 类型且限制在一个范围内,则可以使用 @FloatRange 注解,如下:


public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {


如果我们的参数是 int 或 long 类型则可以使用 @IntRange 注解来约束值的范围,如下:


public void setAlpha(@IntRange(from=0,to=255) int alpha) { … }


对于数据集合或字符串,我们可以用 @Size 注解来限定集合大小或字符串长度。譬如:


//集合不能为空


@Size(min=1)


//字符串最多只能有 23 个字符


@Size(max=23)


//数组只能有 2 个元素


@Size(2)


//数组大小必须是 2 的倍数


@Size(multiple=2)

3-3-8 权限注解 @RequiresPermission

如果我们的方法调用时需要特定权限,则可以用 @RequiresPermission 进行注解。如下:


@RequiresPermission(Manifest.permission.SET_WALLPAPER)


public abstract void setWallpaper(Bitmap bitmap) throws IOException;


如果方法至少需要权限集合中的一个则可以使用 anyOf 属性。如下:


@RequiresPermission(anyOf = {


Manifest.permission.ACCESS_COARSE_LOCATION,


Manifest.permission.ACCESS_FINE_LOCATION})


public abstract Location getLastKnownLocation(String provider);


如果方法同时需要多个权限则可以使用 allOf 属性。如下:


@RequiresPermission(allOf = {


Manifest.permission.READ_HISTORY_BOOKMARKS,


Manifest.permission.WRITE_HISTORY_BOOKMARKS})


public static final void updateVisitedHistory(ContentResolver cr, String url, boolean real) {


对于 Intent 的权限可以直接在定义的 Intent 常量字符串字段上标注权限需求(通常都已经被 @SdkConstant 注解标注过了)。如下:

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Android应用开发编译框架流程与IDE及Gradle概要,android游戏开发从入门到精通