您的位置:首页 > 移动开发 > Android开发

Android.mk入门

2017-01-12 11:13 591 查看
这篇Blog主要记录向系统源码添加模块时使用的Makefile,和NDK编程使用的makefile有一些差异。

Android的mk文件是有很强的套路的,下面我在我的
<android源码路径>/packsges/app/
文件夹下建立一个名字叫做
MakefileDemo
的工程,里面的目录结构如图所示:![Alt text](./2017-01-11 14:34:49的屏幕截图.png)

libs存放了我使用的jar包这里目前只有一个
dom4j-1.6.1.jar
,src是存放源代码的目录,res是资源目录。

现在我们要编译这个apk(假设我们已经编译过一次系统源码)最主要的是Android,mk这个文件,这个文件告诉mmm(也可以是make,m,mm)命令如何去编译这个APK,下面就来一步一步写一下这个mk文件。

第一步

加入这段代码

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)


LOCAL_PATH := $(call my-dir)
,这句代码
LOCAL_PATH
是变量名,
$(call my-dir)
的意思是调用
my-dir
这个函数,这个函数的返回值是
Android.mk
这个文件所在的位置,对我这个项目来说该函数的返回值就是
<android源码路径>/packsges/app/MakefileDemo/
LOCAL_PATH
这个变量是一定要定义的,它告诉编译系统当前模块的位置。

include $(CLEAR_VARS)
这句代码的作用是清楚除了
LOCAL_PATH
变量之外的
LOCAL_XXX
变量,为什么要清楚这些变量呢?因为在编译
MakefileDemo
模块之前可能编译过别的模块,例如之前编译=过
Launcher3
这个模块人后这个模块升值了一个
LOCAL_MODULE
紧接着编译我们的
MakefileDemo
模块但是我们模块的mk文件不需要
LOCAL_MODULE
并且没有
include $(CLEAR_VARS)
清楚上个模块设置的变量,那么这个时候
Launcher3
LOCAL_MODULE
变量就会被Build系统误认为是我们的
MakefileDemo
的,这样就会产生不可预知的错误。

两句代码是
mk
文件的标配,基本上所有的mk文件都需要这两句代码。

注:LOCAL_PATH := (callmy−dir)这句代码必须放在include(CLEAR_VARS)这句代码前面,否则call my-dir不能返回正确的结果,具体原因请参照这篇博客

第二步

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES :=$(call all-subdir-java-files)


这里加入了
LOCAL_SRC_FILES :=$(call all-subdir-java-files)
这句代码,
LOCAL_SRC_FILES
变量代表需要编译的文件,这里调用了
all-subdir-java-files
函数,这个函数的返回值是
LOCAL_PATH
子目录的所有java文件,一般这样写已经满足大部分需求了,也可以直接写文件路径,多个文件使用
空格+\+换行
隔开,例如:

LOCAL_SRC_FILES := $(LOCAL_PATH)/src/com/example/yuanjize/Main.java \
$(LOCAL_PATH)/src/com/example/yuanjize/Demo.java \
$(LOCAL_PATH)/src/com/example/yuanjize/Demo2.java


第三步

由于我们要使用
framework.jar和dom4j-1.6.1.jar
的一些API,所以我们要引入这两个
jar
包,接下来代码就变成了这样:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES :=$(call all-subdir-java-files)
LOCAL_JAVA_LIBRARIES := framework
LOCAL_STATIC_JAVA_LIBRARIES := dom4j-1.6


这里就有一个问题了,为什么这两种jar包配置的方式不一样呢?答案就是:framework.jar存在于系统中,打包apk的时候不会打包进去而是apk在运行的时候从/system/frameword/frameword.jar加载

这里需要说明一下静态库和动态库。

我们在写Android程序的时候经常用的一些类例如
Activity,Handler,Service
都是来自
android.jar
这个
jar
包(位置在SDK目录下的/platforms/android-XX/android.jar)但是如果反编译apk可以发现我们并不能看到这些类的源码,为什么呢?因为这些类都是存在在android系统中的,apk在运行的时候会自动从系统存放
jar
包的目录中去加载这些类。这些
jar
包就是动态库。

那么静态库是什么样子的呢?我们的
dom4j-1.6.1.jar
就是静态库,因为我们的系统中没有这个jar包,apk打包的时候会把这个jar包打包,反编译一下可以从dex文件中看见它。

所以引用这两个jar包要使用不同的变量,
LOCAL_JAVA_LIBRARIES
用于引用动态jar,
LOCAL_STATIC_JAVA_LIBRARIES
引用静态jar。(这里设置的是库的别名,但是我们一般就是把jar的名写进去,例如
dom4j-1.6
,build脚本会到别名对应的目录下去找到
jar
包,Build脚本后面会说)

第四步

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES :=$(call all-subdir-java-files)
LOCAL_JAVA_LIBRARIES := framework
LOCAL_STATIC_JAVA_LIBRARIES := dom4j-1.6
LOCAL_PACKAGE_NAME := MakefileDemo


这句代码
LOCAL_PACKAGE_NAME := MakefileDemo
指定了apk名为
MakefileDemo
这个名字在系统中必须是独一无二的,系统中各个模块的
LOCAL_PACKAGE_NAME
不能是相同的。如果是相同的呢?假如我的
LOCAL_PACKAGE_NAME
设置成
Music
那么编译这个apk之后生成的apk会覆盖
out/target/product/XXXX定制版本/system/app/Music/Music.apk
,也就是覆盖了手机里面原本的Music。

第五步

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES :=$(call all-subdir-java-files)
LOCAL_JAVA_LIBRARIES := framework
LOCAL_STATIC_JAVA_LIBRARIES := dom4j-1.6
LOCAL_PACKAGE_NAME := MakefileDemo
LOCAL_MODULE_TAGS := optional


LOCAL_MODULE_TAGS
变量指明了编译的标签,当我们编译源码的时候需要调用
lunch
函数,
lunch
函数会打印出来源码包含的定制版本,可以分成
user
userdebug
eng
三种,选择不同的定制版本会编译不同的模块,一个模块是否在编译源码(也有可能使用make eng这种命令,这个命令编译包含
LOCAL_MODULE_TAGS
指定为
eng
的模块)的时候编译就是由这个标签决定,具体的编译策略这里不细说,以后研究Build系统时再深入讨论。

LOCAL_MODULE_TAGS
的取值有
user eng optional debug
(网上还查到有一个test选项不过不太了解),
optional
选项表明在所有版本都会被编译,
user
在user版本被编译,
debug
在userdebug版本被编译,
eng
就不说了和前几个一样,就是套路……,一般情况下为了方便我们编译自己apk的话为了方便都会选择
optional


第六步

我们写完了代码要发布的时候做的最后一件事—签名。

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES :=$(call all-subdir-java-files)
LOCAL_JAVA_LIBRARIES := framework
LOCAL_STATIC_JAVA_LIBRARIES := dom4j-1.6
LOCAL_PACKAGE_NAME := MakefileDemo
LOCAL_MODULE_TAGS := optional
LOCAL_CERTIFICATE := platform


LOCAL_CERTIFICATE
指定使用了模块使用的签名文件,这里是使用system级别的签名文件。

通常我们如果指定
android:sharedUserId="android.uid.system"
来获取
system
级别的权限的话,签名文件就一定要用
platform


LOCAL_CERTIFICATE
可以的取值有
testkey media platform shared
,使用情况和
platform
一样,看你想获取什么级别的权限。比如我想获取
media级别的
那么就在清单文件加入
android:sharedUserId="android.uid.media
然后让
LOCAL_CERTIFICATE
取值
media
。其实所有的签名文件都在
build/target/product/security
目录下面,这个目录包含一些
XXX.x509.pem(公钥文件),
文件和
XXX.pk8(私钥文件)
文件(公钥私钥是成对出现的),例如
share.pk8和share.x509.pem
就是当
LOCAL_CERTIFICATE := share
时使用的签名文件。
LOCAL_CERTIFICATE
也可以指定自己生生的签名文件,如果想了解可以看下这篇博客

第七步

指定编译脚本或者也可以看成想要编译成什么类型的文件

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES :=$(call all-subdir-java-files)
LOCAL_JAVA_LIBRARIES := framework
LOCAL_STATIC_JAVA_LIBRARIES := dom4j-1.6
LOCAL_PACKAGE_NAME := MakefileDemo
LOCAL_MODULE_TAGS := optional
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)


最后一步
include $(BUILD_PACKAGE)
指定了编译的脚本,我们想编译成
apk
所以我们指定的是
BUILD_PACKAGE
脚本。

然后我们现在编译这个模块
mmm -B packages/app/MakefileDemo/


然后就出现了下面这个错误

make: * No rule to make target out/target/common/obj/JAVA_LIBRARIES/dom4j-1.6.1_intermediates/classes.jack’, needed by out/target/common/obj/APPS/MakefileDemo_intermediates/with-local/classes.dex’. Stop.

出现这个错误的原因是:
MakefileDemo
这个apk依赖
dom4j-1.6.1.jar
然后make程序就去
out/target/common/obj/JAVA_LIBRARIES/
这个文件夹下寻找
dom4j-1.6.1_intermediates/classes.jack
文件,如果存在那么拿到这个jar继续build
MakefileDemo
,如果没有找到那么就会查看我们的
makefile
有没有语句可以生成
out/target/common/obj/JAVA_LIBRARIES/dom4j-1.6.1_intermediates/classes.jack'
这个target。

所以这里就需要修改makefile来解决这个问题。

修改如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES :=$(call all-subdir-java-files)
LOCAL_JAVA_LIBRARIES := framework
LOCAL_STATIC_JAVA_LIBRARIES := dom4j-1.6
LOCAL_PACKAGE_NAME := MakefileDemo
LOCAL_MODULE_TAGS := optional
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)

include $(CLEAR_VARS)
LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := dom4j-1.6.1:libs/dom4j-1.6.1.jar
include $(BUILD_MULTI_PREBUILT)


LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES
变量指定了需要进行预编译的库,指定的语法是
静态库别名:静态库所在文件夹
例如
dom4j-1.6.1:libs/dom4j-1.6.1.jar


再次build一次看下log,成功了:

Install: out/target/product/XXX/system/app/MakefileDemo/MakefileDemo.apk
target Prebuilt: dom4j-1.6.1 (out/target/common/obj/JAVA_LIBRARIES/dom4j-1.6.1_intermediates/classes.jar)
target Prebuilt: dom4j-1.6.1 (out/target/common/obj/JAVA_LIBRARIES/dom4j-1.6.1_intermediates/javalib.jar)
target Prebuilt: dom4j-1.6.1 (out/target/product/XXXX/obj/JAVA_LIBRARIES/dom4j-1.6.1_intermediates/javalib.jar)
make: Leaving directory `/home/yuanjize/android'
#### make completed successfully (6 seconds) ####


打开上面生成的
jar
包可以发现都是
dom4j-1.6.1
的代码,
BUILD_MULTI_PREBUILT
只是改了个名字,至于
classes.jack
文件是Android 6.0最新的编译工具,感兴趣可以搜索一下。

到了这里已经可以完美的编译这个apk了。

总结一下这个mk文件的结构。

Created with Raphaël 2.1.0Start设置LOCAL_PATH清除除了LOCAL_PATH以外的所有LOCAL_PATH变量指定MODULE_TAGS指定要编译的源码目录指定模块或者apk名称是否编译成apk?指定签名指定build脚本是否引入外部静态库?使用BUILD_MULTI_PREBUILT脚本来处理静态库?Endyesnoyesno

最后介绍几个BUILD_XXXX脚本

我们这里使用的BUILD脚本是
BUILD_PACKAGE
作用是把这个模块编译成一个
apk
,下面的表格介绍了一些build脚本和对应的功能。

脚本公能
BUILD_PACKAGE编译成apk
BUILD_JAVA_LIBRARY编译成动态JAVA库
BUILD_STATIC_JAVA_LIBRARY编译成静态JAVA库
BUILD_MULTI_PREBUILT定义了如何处理一个或多个已编译文件(拷贝操作)
BUILD_PREBUILT定义了如何处理一个已编译文件(拷贝操作,只能copy一个)
BUILD_STATIC_LIBRARY编译c/c++静态库
BUILD_SHARED_LIBRARY编译c/c++共享库(.so文件)
BUILD_EXECUTABLE编译成可执行程序
使用Build脚本生成的模块都在:

out/target/common/obj/
out/target/product/定制版本/obj/


这两个目录下。

生成的apk在

out/target/product/定制版本/system/app/app名称/
这个目录。

Build脚本的名字都是把宏的BUILD_前缀去掉,例如
BUILD_EXECUTABLE
的教本文件名字就是
EXECUTABLE.mk
,所有的脚本都在
build/core
目录下。

最后推荐一篇很好的博客:理解Android Build系统
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android系统