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)# 使用当前版本的 SDK

LOCAL_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。

相关推荐