当前位置: 移动技术网 > IT编程>移动开发>Android > Android 系统---Android.mk入门

Android 系统---Android.mk入门

2018年09月13日  | 移动技术网IT编程  | 我要评论

ems官网价格,涂维发,dailifuwuqi

android.mk入门

这篇blog主要记录向添加模块时使用的makefile,和ndk使用的makefile有一些差异。

android的mk文件是有很强的套路的,下面我在我的/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这个文件所在的位置,对我这个项目来说该函数的返回值就是/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 :=(callmydir)这句代码必须放在include(callmydir)这句代码必须放在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继续buildmakefiledemo,如果没有找到那么就会查看我们的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目录下。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网