Android.mk文件分析

来源:互联网 发布:生态学现状知乎 编辑:程序博客网 时间:2024/06/04 18:59

转载自:http://blog.csdn.net/a345017062/article/details/6130264


从对Makefile一无所知开始,折腾了一个多星期,终于对Android.mk有了一个全面些的了解。了解了标准的Makefile后,发现android.mk其实是把真正的Makefile包装起来,做成了一个对使用者来说很简单的东西。使用它来编译程序时,不管是动态库、可执行的二进制文件,还是Jar库、APK包,只要沿着一个简单的思路来做三大步就可以了:清除旧变量,设置新变量,调用编译函数。

 

明白了以后,发现Makefile语法不是问题,有很多教程和高手。编译模块时如何清除变量、调用编译函数等也不是问题,源码当中无处不在这样的例子。而对初学者来说,更需要明白的可能是,Android如何让使用脚本的人从Makefile语法当中解放出来,简单地按照上面的三大步就可以编译出任何模块。

 

拿AlarmClock来做例子的话:

//清除旧变量

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

//设置新变量

LOCAL_SRC_FILES := $(call all-subdir-Java-files)

LOCAL_PACKAGE_NAME := AlarmClock

//调用编译函数

include $(BUILD_PACKAGE)

下面简单解释一下这三步:

1、清除旧变量,是因为Android.mk中所有的变量都是全局的,编译函数在编译时会调用这些变量。为了防止编译函数使用了编译其它模块时设置的变量,每次开始编译一个新的模块时清除所有的变量是个好习惯。

2、设置新变量就是把本次编译时用到的源码地址,包名等设置好。

3、调用编译函数其实就是include一个固定的mk文件,这个mk文件会根据设置的变量提取出编译模块需要的target,Command等信息并执行固定的编译命令。

 

看明白Android.mk后,最深的感受还是Android的这种封装思想,让我想起了很多以前没有思考过的东西。呵。。。

我会一些个人的理解记录Android一步步地演化出这种封装思想。真诚地欢迎批评指正。

 

Makefile这个东西,往最简单处说,就是这样的模式:

目标:信赖文件

执行命令

这里暂不考虑它的语法细节,举个不完整的例子,就是这样的:

AlarmClock.apk:AlarmClock.java

javac AlarmClock.java

JavaAlarmClock.class

要生成AlarmClock.apk,就需要AlarmClock.java这个信赖文件,然后执行后面两行的两个命令。假如我们简单粗暴地用这种写法去编译Android系统的话,会相当累。Android.mk就很巧妙地把它们进行了抽象。我们现在来模拟一下:

定义三个变量target,source,command,分别代表编译目标、信赖文件和编译命令,就成了这样:

$(target):$(source)

$(command)

这三个变量的值分别是:

target := AlarmClock.apk

source := AlarmClock.java

commnd := /

javac AlarmClock.java/

javaAlarmClock.class

这样的话,在编译每个APK时,只要分别给target、source、command赋值,然后再这样写就可以了:

$(target):$(source)

$(command)

嗯,还是有点儿麻烦,那再抽象一次。定义一个变量name,让它存放要编译的模块的名字:

name := AlarmClock

然后再更改一下前面的三个变量:

target := $(name).apk

source := $(name).java

commnd := /

javac $(name).java/

java $(name).class

现在要编译一个模块Contacts需要做些什么呢?

name := Contacts

$(target):$(source)

$(command)

三行代码就可以了,很简单,对吧?但后面两行还是每次都要写,那再抽象。把后面两行放到一个build_apk.mk文件当中。然后定义一个变量:

BUILD_PACKAGES := build_apk.mk

全部完了,这回编译一个模块Contacts时只要这样做就可以了:

name := Contacts

include $(BUILD_PACKAGES)

一个不够复杂,那就多点儿:

name := Contacts

include $(BUILD_PACKAGES)

name := Phone

include $(BUILD_PACKAGES)

name := Email

include $(BUILD_PACKAGES)

//================强壮的分隔线=========

现在我们来完善一下上面的过程,把它系统化。

定义一个专门用来清除变量的clear_vars.mk里面的内容如下:

name :=

target :=

source :=

command :=

再定义一个变量CLEAR_VARS := clear_vars.mk。上面的编译脚本就变成了这样:

include $(CLEAR_VARS)

name := Contacts

include $(BUILD_PACKAGES)

include $(CLEAR_VARS)

name := Phone

include $(BUILD_PACKAGES)

include $(CLEAR_VARS)

name := Email

include $(BUILD_PACKAGES)

这回,清除旧变量、设置新变量、调用编译函数三大步全都有了。

 

好了,现在我们进入到Android源码的build/core/目录下。先看其中的main.mk,config.mk这两个文件。

main.mk里面是具体的脚本,在这里控制编译模块。

config.mk中定义了下面这样的文件调用:

CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk

BUILD_HOST_STATIC_LIBRARY:= $(BUILD_SYSTEM)/host_static_library.mk

编译模块时,每一个include其实都是一次功能调用,或者对mk文件的调用,或者对变量和函数的调用。做为单独的功能模块抽象出来的mk文件都在build/core/目录下,而函数的定义全部在definitions.mk中。

 

万事开头难,有了上面的思路,再看这些脚本就很简单了。一路顺风,呵。。。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 宝宝烧到38.8度怎么办 小孩发烧到39度怎么办 宝贝发烧到40度怎么办 孩子发烧39度8怎么办 儿子发烧39度该怎么办 孩子发烧39度7怎么办 7个月婴儿发烧怎么办 感冒了头发很油怎么办 5岁宝宝发烧39度怎么办 婴儿烧到39.5度怎么办 1岁多宝宝39.5度怎么办 宝宝发烧40多度怎么办 7岁宝宝发烧了怎么办 宝宝反复发烧39度怎么办 一岁半宝宝反复发烧39度怎么办 七岁发烧38度怎么办 小孩一直37度1怎么办 婴儿一直37度多怎么办 1岁半高烧39度怎么办 反复发烧39度多怎么办 孩子不爱喝水怎么办%3f 8岁儿童不爱喝水怎么办 儿子14岁了不爱说话怎么办 我孩子长得老慢怎么办 老公嫌你烦了怎么办 2岁宝宝吃饭不嚼怎么办 2岁宝宝挑食厌食怎么办 孩子不爱和家长交流怎么办 孩子发烧在医院查不出病因怎么办 宝宝乳牙长歪了怎么办 两岁宝宝不爱吃水果怎么办 两岁宝宝不吃水果怎么办 一岁的宝宝上火了怎么办 吃水果嘴唇肿了怎么办 二岁宝宝不爱吃饭怎么办 小婴儿便秘但不爱喝水怎么办 宝宝只吃水果不吃饭怎么办 一岁宝宝不喜欢吃水果怎么办 1岁宝宝不吃水果怎么办 一岁半宝宝吃水果拉肚子怎么办 大人发烧了怎么办如何退烧