Android 如何自定义共享库
一、开发者的难言之隐---讨厌的集成
在 Android实际开发过程中,每个供应商都会有自己专有的开发库如驱动程序、常用API的封装等。如何把这些用于开发的库无缝地集成到Android框架中成为了开发者最为头痛的事,每添加一个新的库就需要把 Android框架翻个遍,寻找合适的地方放置自己的代码,到最后把 Android的源码改得支离破碎、面目全非,调试 Bug或者查看代码得在若大的框架中翻来翻去,一片混乱。如果你以为这是最痛苦的事,那就错了,最痛苦的事莫过于 Android平台版本切换,由于 Android版本更新很快,隔三五几个月,就要把幸苦添加到框架中的代码找出来,重新添加到新的版本中去,费时费力,令开发人员苦不甚言。但是项目要继续,这些工作必须进行。如何添加自己的库又不影响 Android框架源码呢?如何让要集成的代码与框架耦合度最低,能在各个版本中便捷切换呢?谁能解救我们?
二、开发者的福音---自定义共享库
由于 Google自己也在开发第三方库如 Google Map API 等,加上合作伙伴的抱怨,Google也意识到了这个问题的严重性,于是提供了一套用于开发自定义共享库的轻量级框架——自定义共享库。通过使用该框架,不但可以添加驱动层到应用层的代码,还有其它一些有趣的功能。该框架把所有第三方源码整合到一个目录中,可以自由添加删除,便捷的移动,同时也保证了第三方代码的完整性。使用该框架可以完成的任务包括:
* 封装硬件驱动。
* 封装 Java API。
* 移植 C/C++代码到 Android 平台。
* 编写原生应用程序。
* 自定义模拟器皮肤。
三、开发者的实践---sample示例解读。
在源码根目录下有个 vendor(供应商) 目录,专门用于存放各个供应商的会有代码。其中有一个个 sample目录,这是 Google 用于示范如何编写自定义共享库的示例,它展示了自定义共享库、JNI调用、对库的使用方法及皮肤定制等功能。下面我们通过对该示例进行分析,让大家熟悉这个轻量级的框架。
1、首先看一下sample目录的结构:
sample
├──Android.mk
├──apps
│├──Android.mk
│├──client
│└──upgrade
├──frameworks
│├──Android.mk
│└──PlatformLibrary
├──MODULE_LICENSE_APACHE2
├──products
│├──AndroidProducts.mk
│└──sample_addon.mk
├──README.txt
├──sdk_addon
│├──hardware.ini
│└──manifest.ini
└──skins
└──WVGAMedDpi
Android.mk:该文件用于编写构建规则,默认继承Android的make框架。
frameworks:该目录在这里的意义等同于Android源码中的frameworks。
PlatformLibrary:该目录就自定义共享库。apps:该目录用于编写依赖该库的应用程序。经过测试也可以用来编写不依赖该库的程序,这有个好处,让开发商可以把自己特有的应用集成到框架中。
client与upgrade:这是两个依赖该库的应用程序示例。
products:该目录中的文件对包含该库与Android框架集成的信息,如模块名称等。
AndroidProducts.mk:指明该模块的make配置文件的在哪里。
sample_addon.mk:模块的配置信息。sdk_addon:该目录对该库的硬件需求进行定义。
hardware.ini:定义模块对硬件的需求。
manifest.ini:模块的说明文件。名称、供应商等。
skins:该目录用于存放自定义皮肤。
WVGAMedDpi:已经定义好的一套皮肤。2.如何封装 Java共享库?
PlatformLibrary为我们展示了封装Java共享库的方法。其目录结构如下:frameworks/PlatformLibrary
├──Android.mk
├──com.example.android.platform_library.xml
├──java
│└──com
│└──example
│└──android
│└──platform_library
│└──PlatformLibrary.java
└──README.txt
Android.mk:该文件说明如何构建该模块。
com.example.android.platform_library.xml:该文件是模块注册时需要的文件。该文件需要被放置到/system/etc/permissions目录下。
Java /*:Java 源码所在目录。具体步骤:a、编写 Java库,并将源码放到 java 目录下。这一步和编写普通 Java程序没有差别。b、编写 Android.mk,内容如下:
# 获得当前目录,清空环境变量
LOCAL_PATH:=$(callmy-dir)
include$(CLEAR_VARS)#源码所在目录,all-subdir-java-files表示所有了目录中的Java文件。
LOCAL_SRC_FILES:=\
$(callall-subdir-java-files)#该模块是可选的。
LOCAL_MODULE_TAGS:=optional#Java模块名称
LOCAL_MODULE:=com.example.android.platform_library#编译为Java库。最近以jar的形式而不是apk的形式存在。
include$(BUILD_JAVA_LIBRARY)#构建该库的API文档
include$(CLEAR_VARS)
LOCAL_SRC_FILES:=$(callall-subdir-java-files)$(callall-subdir-html-files)
LOCAL_MODULE:= platform_library# 文档对应的库LOCAL_DROIDDOC_OPTIONS :=com.example.android.platform_library
# 库的类型
LOCAL_MODULE_CLASS:=JAVA_LIBRARIES
LOCAL_DROIDDOC_USE_STANDARD_DOCLET:=true#编译为JavaAPI。
include$(BUILD_DROIDDOC)
c、编写com.example.android.platform_library.xml,内容如下:
<?xmlversion="1.0"encoding="utf-8"?>
<permissions>
<!--库的名称及对应的Jar文件位置-->
<libraryname="com.example.android.platform_library"
file="/system/framework/com.example.android.platform_library.jar"/>
</permissions>现在基本的库我们已经编写完成,现在需要对框架中的其它文件进行配置。
d、编写sample/frameworks/Android.mk,内容如下:
#包含子目录中的所有make文件include$(callall-subdir-makefiles)该文件与sample/Android.mk文件相同。
e、编写sample/sdk_addon/manifest.ini,内容如下:#该模块的名称、供应商及描述
name=SampleAdd-On
vendor=AndroidOpenSourceProject
description=sampleadd-on#构建该模块的Android平台代号
api=3#模块的版本号。必须为整数。
revision=1#该模块中包括的共享库列表
libraries=com.example.android.platform_library#对每个库的详细定义,格式如下:
#<library.name>=<name>.jar;<desc>#<library.name>:通过前面libraies定义的库的名称。
#<name>.jar:包含库API的jar文件。该文件放在libs/add-on下面。
com.example.android.platform_library=platform_library.jar;Sampleoptionalplaformlibrary该文件还可包括该模块的其它定义,如皮肤等,为了保持该文档清晰易懂的初衷,这里不做介绍,需要了解可以给我邮件。
f、编写sample/products/sample_addom.mk,内容如下:
#要植入系统镜像的应用及可选类库。可以包括Java库和本地库。这里我们只有Java库。
PRODUCT_PACKAGES:=\com.example.android.platform_library#把xml文件复制到系统镜像中相应的位置去。
PRODUCT_COPY_FILES:=\vendor/
sample/frameworks/PlatformLibrary/com.example.android.platform_library.xml:system/etc/permissions/
com.example.android.platform_library.xml#这个扩展的名称
PRODUCT_SDK_ADDON_NAME:=platform_library#把模块的manifest和硬件配置文件复制到系统镜像中相应的位置。PRODUCT_SDK_ADDON_COPY_FILES:=\
vendor/sample/sdk_addon/manifest.ini:manifest.ini\
vendor/sample/sdk_addon/hardware.ini:hardware.in#把库的Jar包复制到相应的位置。PRODUCT_SDK_ADDON_COPY_MODULES:=\
com.example.android.platform_library:libs/platform_library.jar#文档的名称。必须与。
#LOCAL_MODULE:=platform_library
PRODUCT_SDK_ADDON_DOC_MODULE:=platform_library#这个扩展继承系统扩展。$(callinherit-product,$(SRC_TARGET_DIR)/product/sdk.mk)#这个扩展的真实名字。这个名字会用于编译。
#用'makePRODUCT-<PRODUCT_NAME>-sdk_addon'的形式来编译此扩展。
PRODUCT_NAME:=sample_addon
g、编写sample/products/AndroidProducts.mk,内容如下:
PRODUCT_MAKEFILES:=\
$(LOCAL_DIR)/sample_addon.mkh、最后运行make-j8PRODUCT-sample_addon-sdk_addon,编译扩展。
至此,我们就完成了Java库的封装。
3、接下来我们再来看如何通过JNI的方式对C代码进行封装。
a、在sample/frameworks/PlatformLibrary目录下添加一个文件夹,用于放置JNI本地代码,目录结构如下:
frameworks/PlatformLibrary/jni
├──Android.mk
└──PlatformLibrary.cpp
b、把frameworks/PlatformLibrary/java/com/example/android/platform_library/PlatformLibrary.java
文件改写为JIN调用接口,代码如下:packagecom.example.android.platform_library;importandroid.util.Config;
importandroid.util.Log;publicfinalclassPlatformLibrary{
static{/Loadthelibrary.Ifit'salreadyloaded,thisdoesnothing.System.loadLibrary("platform_library_jni");
privateintmJniInt=-1;publicPlatformLibrary(){}/Testnativemethods.publicintgetInt(booleanbad){
//thisaltersmJniInt//
intresult=getJniInt(bad);//reverseastring,fornoverygoodreason//
Stringreverse=reverseString("Android!");Log.i("PlatformLibrary","getInt:"+result+",'"+reverse+"'");returnmJniInt;//
/Simplemethod,calledfromnativecode.privatestaticvoidyodel(Stringmsg){
Log.d("PlatformLibrary","yodel:"+msg);//
/Trivialnativemethodcall.If"bad"istrue,thiswillthrowan
/exception.nativeprivateintgetJniInt(booleanbad);/Nativemethodthatreturnsanewstringthatisthereverseof
/theoriginal.Thisalsocallsyodel().nativeprivatestaticStringreverseString(Stringstr);
}c、在 frameworks/PlatformLibrary/jni/PlatformLibrary.cpp中编写 PlatformLibrary.java 中规定本地调用的具体实现。
d、编写frameworks/PlatformLibrary/jni/Android.mk,内容如下:
LOCAL_PATH:=$(callmy-dir)
include$(CLEAR_VARS)LOCAL_MODULE_TAGS:=optional#JNI模块的名称
LOCAL_MODULE:=libplatform_library_jni#依赖的源代码文件
LOCAL_SRC_FILES:=\
PlatformLibrary.cpp#编译时需要的库
LOCAL_SHARED_LIBRARIES:=\
libandroid_runtime\
libnativehelper\
libcutils\
libutils#没有静态库
LOCAL_STATIC_LIBRARIES:=#包含必须的JNI头文件
LOCAL_C_INCLUDES+=\
$(JNI_H_INCLUDE)#编译器选项
LOCAL_CFLAGS+=#对该模块不进行预编译。使用预编译可以提高模块的性能。
LOCAL_PRELINK_MODULE:=false#把它编译成动态共享库
include$(BUILD_SHARED_LIBRARY)该文件主要定义了本地库的名字、依赖、编译选项及编译方式。
e、修改frameworks/PlatformLibrary/Android.mk,在末尾添加如下两行:
include$(CLEAR_VARS)#调用子目录中的make文件。
include$(callall-makefiles-under,$(LOCAL_PATH))
f、修改sdk_addon/sample_addon.mk,在PRODUCT_PACKAGES中添加该JNI本地库。
PRODUCT_PACKAGES:=\
com.example.android.platform_library\
libplatform_library_jni
g、编译即可。至此,添加JNI库完毕。
4、添加接下来我们再看看如何添加原生应用程序添加原生应用程序就很简单了,只需要把按照 Android应用开发的基本方法,写好一个应用,该应用可以依赖这个扩展,也可以不依赖。如 sample中的 client 应用,目录结构如下:apps/client/
├──AndroidManifest.xml
├──Android.mk
└──src
└──com
└──example
└──android
└──platform_library
└──client
└──Client.java
a、在应用根目录中添加一个Android.mk文件,内容如下:
LOCAL_PATH:=$(callmy-dir)
include$(CLEAR_VARS)LOCAL_MODULE_TAGS:=user#目标名称
LOCAL_PACKAGE_NAME:=PlatformLibraryClient#只编译这个apk包中的java文件
LOCAL_SRC_FILES := $(call all-java-files-under, src)# 使用当前版本的 SDKLOCAL_SDK_VERSION := current# 依赖使用刚才编写的扩展
LOCAL_JAVA_LIBRARIES:=com.example.android.platform_libraryinclude$(BUILD_PACKAGE)
b、在AndroidManifest.xml中添加一句:
<uses-libraryandroid:name="com.example.android.platform_library"/>
c、修改sdk_addon/sample_addon.mk,在PRODUCT_PACKAGES中添加该JNI本地库。
PRODUCT_PACKAGES:=\
com.example.android.platform_library\
libplatform_library_jni\
PlatformLibraryClient
d、编译即可。至此,添加JNI库完毕。
5、其他功能如添加皮肤等,这里就不一一示范了,请参考<sdk-src>/vendor/sample。