android:readPermission="android.permission.READ_SMS"
android:authorities="com.android.mms.SuggestionsProvider" >
android:pathPrefix="/search_suggest_query"
android:readPermission="android.permission.GLOBAL_SEARCH"
/>
android:pathPrefix="/search_suggest_shortcut"
android:readPermission="android.permission.GLOBAL_SEARCH" />
11.2.3 权限验证
通过权限对组件进行保护带来了一个问题,即如何判断调用方是否具有相应的权限。android为此提供了多种方法,分别应用与不同的应用场景。
仅在受保护的组件的上下文中进行权限验证,方法如下:
public abstract int checkCallingOrSelfPermission( String permission )
public abstract int checkCallingPermission( String permission )
public abstract int checkPermission( String permission, int pid, int uid )
要在第三方上下文验证,其方法如下:
public abstract void enforcePermission( String permission, int pid, int uid, String message )
当前,android仅支持对调用者的进程信息进行提取,方法如下:
Binder.getCallingPid() Binder.getCallingUid()
对特定的Uri,有读权限和写权限,进行权限验证的方法如下:
public abstract int checkCallingUriPermission( Uri uri, int modeFlags )
public abstract int checkUriPermission( Uri uri, int pid, int uid, int modeFlags )
public abstract int checkUriPermission( Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags )
如果调用方拥有相应的权限,则权限验证的返回值为 PackageManager.PERMISSION_GRANTED, 否则返回 PackageManager.PERMISSION_DENIED。
11.2.4 接入服务
提供敏感信息支持的服务必须保证对非法接入的阻止,在android中,这是通过权限和证书等机制来实现的。以墙纸为例,要对服务进行权限保护,方法如下:
android:permission="android.permission.BIND_WALLPAPER">
要在服务的方法被调用时,验证接入的合法性,方法如下:
private int enforceAccessPermission(){
int ret = mContext.checkCallingOrSelfPermission("android.permission.BIND_WALLPAPER");
return ret;
}
然后再调用的方法开始出进行接入合法性判断,方法如下:
public boolean getSth(){
if(enforceAccessPermission() != PackageManager.PERMISSION_GRANTED){
return false;
}
}
11.2.5 框架层接入限制
在
框架层,为了隐藏某些方法或实现类,避免被第三方开发者开发的应用程序(基于SDK)调用,提供hide限制的方法
hide 限制根据作用的对象不同,可以分为hide方法限制和hide类限制
hide 方法限制的实现方法如下:
/**
* @hide
*/
@Override
protected boolean isVerticalScrollBarHidden(){
return mFastScroller != null;
}
hide 类限制的实现方法如下:
/**
* @hide
*/
public class WifiService extends IWifiManager.Stub{
}
当框架层的API发生变化时,需要先通过make update-api来更新current.xml,否则会因为一致性问题导致系统编译上的失败。
如果确实需要在基于SDK开发时,在应用层引用隐藏的方法或类,可以采用java反射的机制达到这一目的,但考虑到兼容性的问题,除非确实必要,否则不建议此采用此类方式来实现。
基于源代码开发时,hide限制无效。
11.3 数字证书
在android中,每个应用在发布时,均必须拥有自己的数字证书签名, 这
个数字证书签名用于在应用的作者和应用程序之间建立信任关系。
事实上,数字证书一直存在,及时在通过SDK进行开发阶段,每次运行应用程序时,SDK均会自动生成一个用于调试模式的数字证书签名。只有在应用程序发布时,才会用到发布模式的数字证书签名。
数
字证书签名机制有利于程序的升级,除了判断包名外,只有当新版应用和旧版应用的数字签名相同时,android才会认为这两个程序是同一个应用的不同版本。另外,android允许拥有相同数字签名的应用运行在同一个进程中,这同样有理与在多个应用中共享数据。
在进行数字签名时,需要考虑证书的有效期问题,对于从应用商店上下载的应用,如果证书过期,意味着持有该数字签名证书的应用将不能正常升级。android Markets强制要求所有应用程序的数字证书的有效期持续到2033年10月22日以后。
在发布应用时,开发者通过两种方式为自己的APK签名
》在命令行方式下,利用Keytool来生成数字证书,并利用 Jarsigner 来为APK 进行数字签名
》使用ADT Export Wizard进行签名
使用Keytool生成数字证书的方式如下:
#keytool -genkey -v -keystore android.keystore -alias miaozl -keyalg RSA -validity 2000
上述代码中,keystore android.keystore 表示生成的数字证书为android.keystore,可以加上路径(默认在用户主目录下); alias miaozl 表示数字证书的别名是 miaozl; keyalg RSA 表示采用的是RSA算法;validity 20000表示数字证书的有效期为20000天。另外通过keypass 可以设置数字证书私钥的密码; 通过keysize 可设置算法的位长,默认为1024bit, 推荐2048bit及更长; 通过 storepass可设置数字证书的密码。
数字证书生成后,即可用它来进行应用程序的签名了,方法如下:
#jarsigner -verbose -keystore android.keystore demo.apk 证书别名
接下来,jarsigner会提示输入密钥库的口令和证书别名的口令,全部输入后,即可完成签名。
具有相同数字证书的应用程序可以彼此分享数据和执行调用。查看应用程序是否已经签名的方法如下:
#jarsigner -verify demo.apk
查看数字签名证书的更详细信息的方法如下:
#jarsigner -verify -verbose demo.apk
//"certs"选项可以显示"CN=",揭示谁创建了密钥
#jarsigner -verify -verbose -certs demo.apk
在进行数字证书签名后,可用 zipalign 工具来优化应用。
第十二章 android的调试,测试与性能优化
android中,提供了模拟器和目标端等两种场景下使用的调试和优化工具。
12.1 android调试
android中,提供ddms的工具组件供开发者使用。
当发生android ANR错误时,错误信息会保存在\data\anr\traces.txt中,有利于无法实时查看日志的情况下分析bug.
12.1.2 dmtracedump跟踪
dmtracedump是一个机遇图形界面的显示方法间调试关系的工具。在使用前,必须安装Graphviz。 在linux下安装Grapviz方法如下:
#apt-get install graphviz
dmtracedump的用法如下:
dmtracedump [-ho] [-s sortable] [-d trace-base-name] [-g outfile]
实际操作中,为了分析函数间的调用关系,首先要在需要分析的方法中设置跟踪的起始点和结束点,方法如下:
//开始跟踪
Debug.startMethodTracing( "loadEvents" ); //输出文件为loadEvents.trace
//结束跟踪
Debug.stopMethodTracing();
程序结束后,即可在\sdcard下看到
loadEvents.trace 文件。将loadEvents.trace 导出到ubuntu下,通过traceview loadEvents.trace 即可分析时间关系和方法调用关系。这是文本界面的, 要通过图形界面来显示就要用到 dmtracedump,方法如下:
#dmtracedump -g out.png calc.trace
浏览out.png 接口看到相关函数的调用关系图,其节点的格式如下:
[ callname (, , )]
上述格式中,ref表示编号,callname表示方法名,inc-ms表示调用时间,exc-ms表示执行时间,numcalls表示执行次数。
12.1.5 内存调试
在android中,DDMS, Procrank, Dumpsys等可以获得系统运行期的内存信息。
1. DDMS内存调试
直接启动DDMS工具,然后在Sysinfo 下,可以看到内存的使用情况
2. Dumpsys内存调试
分析内
存的方法为 adb shell dumpsys meminfo 。 通过Dumpsys观察应用的使用情况,实例如下:
3. Procrank内存调试
另外 ,通过 adb shell procrank 也可查看进程占用内存的情况,其中Uss(Unique Set Size)的大小代表属于本进程正在使用的内存大小,这些内存在该进程被撤销后,会被完全回收,Uss也是进行内存泄露观察是的重点; Vss(Virtual Set Size)和Rss(Resident Set Size)表示共享库的内存使用,但由于共享库的资源一般占用比重较大,因此会是进程自身创建引起的内存波动所占比例减小; 而 Pss(Proportional Set Size)则按照比例进行共享内存分割。
通过间隔性地运行Procrank来观察进程占用Uss内存的变化,可以分析应用是否存在内存泄露。 Procrank的代码位于system\extras\procrank文件夹下。用法:
procrank [-W] [-v | -r | -p | -u | -h]
下面是一个方便间隔运行Procrank观察内存变化的脚本:
#!/system/bin/sh
while true; do
date >> ./procrank.log
procrank >> ./procrank.log
echo >> ./procrank.log
sleep 5
done
假设脚本名为 procrank.sh ,其运行方法如下:
#chmod 777 procrank.sh
#./procrank .sh &
考虑到android是基于Dalvik虚拟机的,垃圾回收并非实时的,故通过单个界面的单次启动,关闭是无法确定内存是否泄露。一个好的策略是,重复执行某个界面的启动和关闭,如果发现应用占用的内存不断上升,则可以判断该界面存在内存泄露。
4. Eclipse 插件内存调试
在eclipse中,插件 MAT 可以帮助分析java层的内存泄露。 MAT 分析的是 hprof文件,该文件存放了进程的内存快照。下面是从终端获取hprof文件的方法:
#adb shell
#ps //查看进程号
#chmod 777 /data/misc
#kill -10 PID //PID即进程号
这样即可在\data\misc目录下生成一个带当前时间的hprof文件,但这个文件不能直接被MAT读取,需要借助 hprof-conv 将 hprof转换为MAT可以读取的格式,然后,才可用MAT进行分析。 hprof-conv 的用法:
hprof-conv
12.2 android布局优化
android提供了两个android布局优化工具, 即 Layoutopt 和Hierarchyviewer。 其中
Layoutopt 可以优化布局,帮助开发者减少冗余信息,Hierarchyviewer可直接调试用户界面
1. Layoutopt 优化
可帮助开发者分析采用的布局是否合理,并给出修改意见。其用法:
layoutopt
具体方法如下:
layoutopt res/layout/land
layoutopt res/layout/main.xml
2. Hierarchyviewer 优化
可清晰地看到当前设备的UI界面的实际布局和控件属性,其仅能优化debug模式的应用。
12.3 android测试
分为Monkey压力测试和CTS兼容性测试
12.3.1 Monkey压力测试
Monkey 工具可以模拟各种按键,触屏,轨迹球,导航,activity等时间。工具用法:
adb shell monkey [options] //特定事件
adb shell monkey -p your.package.name -v 50000 //50000次随机事件
monkey测试仅能模拟系统事件监测应用中存在的语法Bug,对深层次的语义bug无能为力,而且对于网络功能,monkey也无法有效的测试。
由于monkey是通过加载特定的Activity(category属性需为android.intent.category.LAUNCHER或android.intent.category.MOKEY)作为程序入口来进行测试的,故在进行Monkey测试时,容易形成孤岛的Activity,为了全面的测试,需要为其增加intent 过滤器。相应的参考:
注意,如果只针对单个应用进行压力测试,monkey会阻止对其他包的调用,当然针对单个应用的压力测试无法检测到应用间交互可能存在的bug。为了测试系统内应用交互与冲突带来的问题,可针对系统进行monkey测试,方法如下
#adb shell monkey -p -v 50000
在进行压力测试过程中,monkey会因为应用奔溃,网络超时以及一些无法处理的异常而停止测试。建议在对网络进行压力测试时,忽略网络茶超时的情况,方法如下:
#adb shell monkey -p your.package.name -v 50000 --ignore-timeouts
如果希望忽略应用奔溃的情况,可以执行如下方法:
#adb shell monkey -p your.package.name -v 50000 --ignore-crashes
12.3.2 JUnit 回归测试
即白盒测试,常用于单元测试场景中,如非图形化界面的接口测试,这在开发框架性代码时非常有用。android仅支持JUnit3.
在android中,android JUnit 还支持对图形界面如Activity,View等的测试,甚至还支持对图形界面接口的功能压力测试。android JUnit的内容主要分布在android.test中。
根据约束的不同,测试可分为 AndroidOnly(表示测试项仅适用于android); SideEffect(表示测试具有副作用);UiThreadTest(表示测试项在UI主线程中运行); BrokenTest(表示测试想需要修复); Smoke(表示测试项为冒烟测试); Suppress(表示该测试项不应出现在测试用例中)。
为了进行JUnit回归测试,需要在eclipse中创建Android Test Project,可通过执行file -> new -> other -> android -> android Test Project命令完成,运行的方法为右击工程项,在弹出的菜单中执行 run as -> android JUnit Test命令。 对于具有多个InstrumentationTestRunner的测试工程,在执行测试时,应该先在工程的Run Configurations 中,指明 instrumentationTestRunner。测试完成后,会自动给出测试结论。
JUnit测试框架和测试步骤。
1. Junit测试的框架
Junit 测试主要包括 TestCase, Instrumentation 等。为了运行测试用例,必须将测试用例添加到TestSuite中,通过Instrumentation来管理。
2. JUnit测试的实现
步骤:
》构建AndroidManifest.xml配置文件。
》制定InstrumentationTestRunner文件
》构建具体测试代码
》如果是基于源代码进行的测试,那么还需要构建Android.mk文件。
下面以Calculator应用为例:
(1)
构建AndroidManifest.xml配置文件
需要构建AndoridManifest.xml配置文件;但和普通的工程不同的是,JUnit回归测试工程没有图形界面,
必须声明要用到”android.test.runner“ JAR包
,
声明相应的InstrumentationTestRunner
。 在通过SDK创建测试工程时,AndroidManifest.xml会自动生成,默认的InstrumentationTestRunner 为 android.test.InstrumentationTestRunner。在某些情况下,开发者需要自定义 InstrumentationTestRunner。 下面是JUnit回归测试的AndroidManifest.xml 文件实现:
package="com.android.calculator2.texts">
//仅在源代码下可用
android:targetPackage="com.android.calculator2"
android:label="Calculator Launch Performance">
//加载InstrumentationTestRunner时,需指定报名
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.android.calculator2"
android:label="Calculator Functional Testset">
(2)制定InstrumentationTestRunner文件
InstrumentationTestRunner文件为 JUnit测试的入口文件,在某些情况下,需要自定义InstrumentationTestRunner类,最重要的是addTestSuite()方法。 它将 TestCase 纳入TestSuite流程,调用响应的方法,过程如下:
public class MusicPlayerFunctionalTestRunner extends InstrumentationTestRunner{
public TestSuite getAllTests(){
TestSuite suite = new InstrumentationTestRunner(this);
suite.addTestSuite(TestSongs.class);
suite.addTestSuite(TestPlaylist.class);
suite.addTestSuite(MusicPlayerStability.class);
return suite;
}
public ClassLoader getLoader(){
return MusicPlayerFunctionalTestRunner.class.getClassLoader();
}
}
(3)构建具体的测试代码
需要根据组件的类型构建响应的测试用例,其中有两个关键的方法:setUp()方法用来构建测试环境,如打开网络链接等; tearDown()方法可以确保在进入下一个测试用例前所有资源被销毁并被回收。对于不同的测试,应该配置不同的测试类型。
测试Activity 的用例实现如下:
public class SpinnerTest extends ActivityInstrumentationTestRunner2{
private Context mTargetContext;
public SpinnerTest(){
super( "com.android.cts.stub", RelativeLayoutStubActivity.class );
}
protected void setUp() throws Exception{
super.setUp();
mTargetContext = getInstrumentation().getTargetContext();
}
protected void tearDown() throws Exception{
super.tearDown();
}
@UiThreadTest //配置测试类型
public void testGetBaseline(){//测试项方法必须以test开头
。。。。。。。。。。。。
}
}
(4)构建Android.mk文件
和普通的android.mk文件不同,其需要制定 LOCAL_MODULE_TARGS 为 tests, 通过 LOCAL_JAVA_LIBRARIES变量加载android.test.runner的JAR包,通过 LOCAL_INSTRUMENTATION_FOR 指定对那个包进行回归测试。下面是一个测试用例
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_JAVA_LIBRARIES := android.test.runner
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CalculatorTests
LOCAL_INSTRUMENTATION_FOR := Calculator
include $(BUILD_PACKAGE)
12.2.2 CTS兼容性测试
为了防止OEM厂商对android的定制导致平台的不兼容问题,google在发布android版本时会发布相关的CTS测试,编译CTS和启动交互CTS控制台的方法如下:
cd /path/to/android/root
make cts
cts
执行CTS测试并设置测试参数的方法如下:
cts start -plan CTS -p android.os.cts.BuildVersionTest
第十三章 android编译
1. android快捷方式:
croot 用于改变当前路径到android根目录
m 用于从android根目录开始编译
mm 用于编译当前目录下的所有模块
mmm 用于编译特定目录下的所有模块
cgrep 用于在C/C++文件中查找
jgrep 用于在java文件中查找
resgrep 用于在资源文件中查找
godir 用于跳转到某个目录
2. 主要脚本
android中的脚本类文件主要用来配置产品,目标板,以及根据开发者的Host 和Target来选择相应的工具并设定编译选项。编译系统的主要脚本包括 envsetup.sh , config.ml, envsetup.mk product_config.ml, BoardConfig.mk version_defaults.ml product.mk, build_id.mk. AndroidProducts.ml Makefile等。下图为android执行编译所设计的主要脚本之间的调用关系:
上图中,AndroidProducts.mk 包含了具体的应用配置脚本; product_config.mk 主要定义AAPT, 产品制造商,wifi,OTA等相关信息; product.mk定义了产品的一些变量信息。
对模块编译进行控制,主要是通过 core.mk, generic.mk, sdk.mk 等脚本及特定目标环境的脚本进行的;对单个模块进行控制,主要是通过Android.mk, 和CleanSpec.mk等脚本进行的。
下面详细介绍:
(1)
envsetup.sh
脚本主要功能包括定义环境变量信息,加载系统配置信息(软件信息,硬件配置),定义编译快捷方式,调试,冒烟测试,GDB调试等。
若编译代码,就涉及代码的编译工具,
目前android支持的原生代码编译工具链位于prebuilt目录下,包
括交叉编译工具链和普通编译工具链。目前
交叉编译工具链为 arm-eabi。 所谓的EABI, 即应用程序二进制接口,针对ARM架构的CPU的,支持软件浮点和硬件浮点功能混用,效率高。
普通编译工具链包括 i686-linux-glibc207-4.4.3, i686-unknown-linux-gnu-4.2.1 和 sh-4.3.3等。设置交叉编译链的过程如下:
export ANDROID_EABI_TOOLCHAIN=
$prebuiltdir/toolchain/arm-eabi-4.4.3/bin
export ANDROID_TOOLCHAIN=$ANDROID_EABI_TOOLCHAIN
export ANDROID_QTOOLS=$T/development/emulator/qtools
设置java编译工具的方法如下 :
function set_java_home(){
if [ ! "JAVA_HOME" ]; then
case 'uname -s' in Darwin)
export JAVA_HOME=/System/Library/Fromeworks/JavaVM.framework/Versions/1.6/Home )
; ;
)
export JAVA_HOME=/usr/lib/jvm/java-6-sun
; ;
esac
fi
}
目前android的Makefile文件名为Android.mk,与标准相同。
(2)config.mk
config.mk 用于定义系统相关的配置信息和编译变量等。下面是config.mk中对输出包后缀的设置:
COMMON_PACKAGE_SUFFIX := .zip
COMMON_JAVA_PACKAGE_SUFFIX := .jar
CONNON_ANDROID_PACKAGE_SUFFIX := .apk
下面是config.mk中加载目标环境的过程:
board_config_mk := \
$(strip $(wildcard \
$(SRC_TARGET_DIR) /board/$(TARGET_DEVICE)/BoardConfig.mk \
device/*/$(TARGET_DEVICE) /BoardConfig.mk \
vendor/*/$(TARGET_DEVICE)/BoardConfig.mk \
))
默认情况下,目标环境信息位于SRC_TARGET_DIR, device, vendor 下。开发者可以将自定义的目标环境文件夹放在这些目录下。
(3)envsetup.mk
envsetup.mk 主要用于判断驻留的操作系统环境,设置环境变量。比较重要的环境变量有 TARGET_PRODUCT, TARGET_BUILD_VARIANT,
HOST_OS, BUILD_OS, BUILD_ARCH, HOST_BUILD_TYPE, OUT_DIR等。
》 TARGET_PRODUCT 表示编译的目标环境。TARGET_PRODUCT的定义具体由OEM厂商决定。对应于特定的目标环境,需要在build\target 或 device目录下存在特定的目标环境配置文件夹,比如对应的generic目标环境,在 build\target\board\generic下定义了generic目标环境的具体配置(如系统属性,键盘配置等)。而对于 Samsung的 crespo目标环境,配置文件目录为 device\Samsung\crespo.
》TARGET_BUILD_VARIANT 表示目标编译变量,说明有哪些文件被纳入编译控制。目前TARGET_BUILD_VARIANT的值为 eng, user, debug, tests 等。
》HOST_OS用于设置驻留的操作系统。目前android支持的值有linux,Darwin(用于Darwin,Mac等操作系统),windows等。判断操作系统的方法如下:
UNAME :=$(shell uname-sm) //UNAME包含了操作系统和CPU结构的信息
ifneq ( , $(findstring Linux, $(UNAME)))
HOST_OS :=linux
endif
》BUILD_OS 表示真正执行编译的操作系统,目前与HOST_OS相同。
》BUILD_ARCH 表示驻留处理器架构,目前android仅支持x86 和PPC两种架构
》HOST_BUILD_TYPE 表示编译的类型,目前仅支持 release 和debug两种类型,debug用于调试
》OUT_DIR 表示输出文件的路径。目前输出文件位于out 目录下,主要由 target, host 和 common 3部分,下面是定以OUT_DIR的实现:
ifeq ( , $(strip $(OUT_DIR)))
OUT_DIR := $(TOPDIR)out
endif
(4)BoradConfig.mk
用于设置硬件相关的信息,是构建目标环境配置的重要文本。目前android定义的目标环境包括 Emulator, Generic, Generic_x86, Sim。 除了以上目标环境外,android源码还包含了HTC的passion与dream, 以及 Samsung 的 crespo等目标环境,可以作为驱动开发人员进行目标环境配置的参考。
build\target\board\generic\目录下BoardConfig.ml的实现如下,其中定义了引导器,内核,编译器,驱动,分区配置等方面的信息。
TARGET_NO_BOOTLOADER := true
TARGET_NO_KERNEL := true
TARGET_CPU_ABI := armeabi
HAVE_HTC_AUDIO_DEIVER := true
BOARD_USED_GENERIC_AUDIO :=true
TARGET_SHELL :=mksh
crespo目标环境在BoardConfig.mk中对分区配置的定义如下:
在目标环境中,另一个重要的脚本为 AndroidBoard.mk, 通常用于定义按键布局等信息,与键盘有关的信息定义在 tuttle2.kl 和 tuttle2.kcm 等文件中。 build\target\board\generic\目录下
AndroidBoard.mk实现如下:
(5)version_defaults.mk
version_defaults.mk用于处理各种编译版本信息,如PLATFORM_VERSION, PLATFORM_SKD_VERSION, BUILD_ID, BUILD_NUMBER等。
对于主分支的源码,其PLATFORM_VERSION的值为 AOSP, PLATFORM_SKD_VERSION的值为数字,BUILD_ID的值为OPENMASTER, BUILD_NUMBER的值是根据编译日期生成的。
(6)build_id.mk
build_id.mk用于定义版本分支信息,其应用方法如下:
BUILD_ID :=OPENMASTER //编译分支
DISPLAY_BUILD_NUMBER := true //是否显示版本号
(7)Makefile
定义各种映像文件的配置,包括boot.img, ramdisk.img, userdata.img, system.img, recovery.img 等。
下面是定义System.img包含内容的过程:
生成system.img的过程:
其他映像文件的生成方法和system.img类似
(8)core.mk
定义了产品的一些基本信息和核心包。基本信息主要包括产品的BRAND, DEIVCE 以及产品属性(如提示音,振铃声,多媒体框架配置等)。
(9)generic.mk
定义了generic目标环境的应用编译控制脚本,主要侧重将哪些应用加入到编译系统中。如果开发者希望将实现的某一应用加入到编译系统中,应在generic.mk中添加相应的配置,是应用的LOCAL_PACKAGE_NAME纳入到 PRODUCT_PACKAGES变量的控制中。
例如,如果希望将Calendar应用加入到编译系统,通过查看packages\apps\Calendar\目录下 Android.mk中的相关配置,得知 LOCAL_PACKAGE_NAME 为 Calendar, 将 Calendar应用加入到编译系统的方法如下:
PRODUCT_PACKAGE :=\
Calendar \
(10)sdk.mk
如果在编译SDK,则应注意 sdk.mk脚本,其和generic.mk一样具有编译控制功能。除了对普通应用进行控制外,根据SDK的特点,sdk.mk工具,资源相关的信息纳入到编译系统。
(11)Android.mk
对于单个工程,android是通过Android.mk来进行编译控制的。实例如下:
注意:清空本地环境变量实际上是调用 build\core\目录下的Clear_vars.mk完成的; LOCAL_MODULE_TAGS 的可选值包括 samples, optional, eng, debug, cts, tests, user等,其默认值为 optional。
如果应用代码中包含AIDL文件,那么将AIDL文件添加到源代码树中的方法如下:
a. 加载共享库
android.mk还支持加载java库和原生库,执行多个编译任务等。以Calculator为例介绍。Calculator的Android.mk执行了两个编译任务,出列编译应用外,还编译了一个JAR库,相关实例如下:
为了加载JAR库,需要重点了解 LOCAL_STATIC_JAVA_LIBRARIES 和 LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES 两个本地环境变量。前者为应用加载的库名,后者用于设置与库名相对应的具体的JAR库。如果希望加载多个JAR库,可按下面方式设置本地环境变量:
除了JAR库外,部分应用可能还包含了基于JNI的原生代码实现,这些代码通常被编译成共享库的形式。假设生成的共享库为 libnative.so, 那么应用的 Android.mk文件中加载共享库的方法入下:
LOCAL_JNI_SHARED_LIBRARIES := libnative
如果在SKD下进行开发,那么对JAR库和原生共享库不需要进行配置,通常将JAR库放置在应用的根目录或libs目录下,将原生共享库放置在libs\armeabi目录下。
b. 应用权限
要进行限制级的操作,如查看电话薄,拨打电话,就涉及权限问题。android.mk支持设置本地证书,方法如下:
LOCAL_CERTIFICATE := platform
目前
LOCAL_CERTIFICATE的可选值包括,platform, shared, media, testkey, cts/tests/appsecurity-tests/certs/cts-testkey1, cts/tests/appsecurity-tests/certs/cts-testkey2等。其中 platform 表示系统证书,通常和在AndroidManifest.xml中加入的 android:sharedUserId="android.uid.system" 属性结合使用。
c. 混淆器设置
在android中加载 proguard混淆器的方法如下:
LOCAL_PROGUARD_FLAG_FILES := proguard.flags
以launcher2为例,其proguard.flags实现如下:
如不希望进行java混淆,则可进行如下设置:
LOCAL_PROGUARD_ENABLED := disabled
d. 安装到特定目录下
设置变量值 LOCAL_MODULE_CLASS 可选择器安装的目录,可选值有 EXECUTABLES, ETC, DATA, STATIC_LIBRARIES, JAVA_LIBRARIES, SHARED_LIBRARIES等。
(12)CleanSpec.mk
用于编译时,清除遗留的中间文件和数据文件,通常不需要进行设置。
13.1.4 环境变量
android编译系统中,有几个重要的环境变量需要注意,下面做简单的介绍。
(1)ANDROID_TOOLCHAIN
ANDROID_TOOLCHAIN主要用于设置交叉编译工具链,目前android的交叉编译工具链为arm-eabi-4.4.3。需要注意看,工具链包括ar, as, c++, g++, gcc, ld, nm, objcopy, objdump, ranlib, strip 等。查看模块间的依赖关系的工具ldd并不在其中。
(2)ANDROID_PRODUCT_OUT
ANDROID_PRODUCT_OUT定义编译目标环境输出的绝对路径。对于generic目录环境而言,其ANDROID_PRODUCT_OUT的值为ANDROIDROOT/out/target/product/generic, 由 TARGET_PRODUCT_OUT_ROOT和 TARGET_DEVICE组成。
(3)TARGET_PRODUCT
TARGET_PRODUCT表示编译的目标环境,这是android编译系统中最重要的环境变量。目前android提供了多个目标环境,包括sdk, sim, full , full_x86, generic, full_crespo等。需要注意:generic表示最低配置; full表示集成所有语言,应用,输入法的配置;
(4)TARGET_BUILD_VARIANT
TARGET_BUILD_VARIANT定义了编译的变量,目前支持的有: eng, user, debug , tests 等。
(5)TARGET_BUILD_TYPE
表示编译的类型,指定的是release 还是debug类型。
(6)TARGET_SIMULATOR
为一个布尔型的变量,常用于判断输出目标环境的真实的设备还是模拟器。
13.1.5 目标环境
目标环境由 TARGET_PRODUCT 和 TARGET_BUILD_VARIANT共同定义,目前android自带的编译模式有: full_eng, full_x86-eng 和 simulator等。
在进行源码编译时,通过如下方式可执行目标环境:
#lunch " TARGET_PRODUCT"-"TARGET_BUILD_VARIANT"
接着直接执行make即可执行相应的目标环境编译。
13.4 应用程序编译
13.4.1 本地环境变量
LOCAL_MODULE: 表示本地模块名,常用于编译原生代码的模块,在应用层开发汇中,多出现在JNI场景中,用于编译动态库,静态库。
LOCAL_PATH : 表示本地路径,通常在编译模块时表示当前编译过程中的根路径。其实现多维当前路径,
LOCAL_PATH:=$(call my-dir)
LOCAL_MODLUE_TAGS: 表示编译的标签,可选值optional, eng, debug, tests, samples, user等。
LOCAL_MODULE_PATH: 表示编译输出文件放置的位置。
LOCAL_MODULE_CLASS: 表示模块的类型,可选值 STATIC_LIBRARIES, EXECUTABLES, JAVA_LIBRARIES, ETC, SHARED_LIBRARIES等。
LOCAL_SRC_FILES: 表示编译的源文件,是最基本的编译变量
LOCAL_SDK_VERSION 表示编译模块的SDK版本,默认值为current。 如果希望在源代码下编译特定SDK版本的模块,需显式声明SDK版本。
eg: 要在Gingerbread上编译Froyo版本,可进行配置:LOCAL_SDK_VERSION :=8
LOCAL_PACKAGE_NAME : 表示编译的模块名,在源码下编译应用时,应注意其为编译开关的角色。
LOCAL_CERTIFICATE: 表示采用的系统证书。LOCAL_CERTIFICATE通常与AndroidManifest.xml中的android:sharedUserId属性同时使用。
LOCAL_SHEARED_LIBRARIES: 表示需要加载的动态库、
LOCAL_PRELINK_MODULE: 如果希望将模块链接到系统中,则应设置LOCAL_PRELINK_MODULE为true, 否则为false.