Android.mk入门

这篇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)清楚上个模块设置的变量,那么这个时候Launcher3LOCAL_MODULE变量就会被Build系统误认为是我们的MakefileDemo的,这样就会产生不可预知的错误。

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

注:LOCAL_PATH := (callmy−dir)这句代码必须放在include(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函数会打印出来源码包含的定制版本,可以分成useruserdebugeng三种,选择不同的定制版本会编译不同的模块,一个模块是否在编译源码(也有可能使用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文件的结构。

Start设置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 系统(180)---Android.mk入门相关推荐

  1. Android系统架构-[Android取经之路]

    摘要:本节主要来讲解Android的系统架构 阅读本文大约需要花费10分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢! 欢 ...

  2. 【android系统】android系统升级流程分析(二)---update升级包分析

    接下来我们将通过几篇文章来分析update.zip包在具体Android系统升级的过程,来理解Android系统中Recovery模式服务的工作原理.今天让我先来分析下升级包update.zip. 一 ...

  3. android 服务端技术,移动应用服务器端开发(基于JSP技术)-2017 Android系统构架 Android系统构架.docx...

    Android系统构架 PAGE 1 目 录 TOC \o "1-3" \h \z \u 一.Android系统构架 1 二.Linux内核层 2 三.系统运行库层 3 (一)系统 ...

  4. 【android系统】android系统升级流程分析(一)---recovery模式中进行update包升级流程分析

    今天我们直接来看下android中具体的升级过程是如何的. 升级流程概述 升级的流程图: 升级流程分析 第一步:升级包获取 升级获取可以通过远程下载,也可直接拷贝到指定目录即可. 第二步:准备升级 然 ...

  5. android log抓取方法,Android系统之Android抓取各种log的方法

    Android系统之Android抓取各种log的方法 2018年11月25日 | 萬仟网移动技术 | 我要评论 android之android抓取各种log的方法 1.logcat (四类log b ...

  6. Android 系统(71)---Android系统build.prop文件生成过程

    Android系统build.prop文件生成过程 Android系统build.prop生成过程 这个文件类似于windows的注册表文件,定义了系统初始的一些参数属性,功能的开放等,通过调整或增加 ...

  7. Android系统架构-----Android的系统体系架构

    一.Android的系统体系结构 Android其本质就是在标准的Linux系统上增加了Java虚拟机Dalvik,并在Dalvik虚拟机上搭建了一个JAVA的application framewor ...

  8. android系统语音合成,android 语音合成报错

    发现了2个问题 第一个貌似是复制离线的资源出错了(已经核对过读写等权限): 12-19 19:54:49.739 32006-32159/com.zhanglf.youxuanz I/NonBlock ...

  9. Android系统(62)-----Android 7.1 新特性之 Shortcuts 介绍

    Android 7.1 新特性之 Shortcuts 介绍 Android 7.1 允许 App 自定义 Shortcuts,类似 iOS 的 3D touch.通过在桌面长按 App 弹出 Shor ...

  10. Android 系统(11)---android 系统权限大全

    收集到的android权限都很实用的(permission)大全 1.android.permission.WRITE_USER_DICTIONARY 允许应用程序向用户词典中写入新词 2.andro ...

最新文章

  1. html离开页面时,js实现用户离开页面前提示是否离开此页面的方法(包括浏
  2. 洛谷【算法1-4】递推与递归
  3. 现代制造工程课堂笔记03:第二部分(含易考点与必考点)
  4. codeup 2044 暴力搜索
  5. Mysql学习总结(24)——MySQL多表查询合并结果和内连接查询
  6. python两个dataframe求差集_spark计算两个DataFrame的差集、交集、合集
  7. 企业进行ISO14001环境管理体系的认证实施究竟是为了什么?
  8. mac下读取ntfs
  9. excel如何快速查询银行卡号实名认证?
  10. 计算机上的字体太小怎么办,电脑字体太小怎么调 电脑字体调整方法有哪些
  11. android 剪切板监听_Android剪贴板操作
  12. 关于手画猫,耳朵涂颜色
  13. Git Conventional Commits (Git代码提交说明规范)
  14. Unity经纬度相互转换(WGS-84、GCJ-02、BD-09)
  15. 一次性下载《R语言实战2》全书的R包及常用的R包
  16. ps4 安卓 php,PS4遥控操作下载|PS4遥控操作 (PS4 remote play)1.0.015181官方最新版_ - 极光下载站...
  17. 01 PhantomReference没有进入ReferenceQueue
  18. #Paper Reading# Gradient Harmonized Single-stage Detector
  19. python数据分析:数据拆分,数据合并,数据筛选
  20. J2ee学习流程(zz)

热门文章

  1. java访问mysql_Java访问数据库
  2. I2C和Modbus通信
  3. ubuntu下ffmpeg编译安装
  4. vue 实现无限轮播_Vue 实现无缝轮播
  5. java并发:初探消费者和生产者模式
  6. Linux more和less
  7. Android App性能自动化评测方法
  8. MySQL数据类型之BLOB与TEXT及其最大存储限制
  9. Numpy 笔记: 多维数组的切片(slicing)和索引(indexing)【转】
  10. 麦步手表编程纪实(1)