Android 怎么自定义共享库

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 怎么自定义共享库相关的知识,希望对你有一定的参考价值。

  在源码根目录下有个 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 := $(call my-dir)
  include $(CLEAR_VARS) # 源码所在目录,all-subdir-java-files 表示所有了目录中的 Java 文件。
  LOCAL_SRC_FILES := \\
  $(call all-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 := $(call all-subdir-java-files) $(call all-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 # 编译为 Java API。
  include $(BUILD_DROIDDOC)

  c、编写 com.example.android.platform_library.xml,内容如下:
  < xml version="1.0" encoding="utf-8" >
  <permissions>
  <!-- 库的名称及对应的 Jar 文件位置 -->
  <library name="com.example.android.platform_library"
  file="/system/framework/com.example.android.platform_library.jar"/>
  </permissions> 现在基本的库我们已经编写完成,现在需要对框架中的其它文件进行配置。
  d、编写 sample/frameworks/Android.mk, 内容如下:

  # 包含子目录中的所有 make 文件 include $(call all-subdir-makefiles) 该文件与 sample/Android.mk 文件相同。
  e、编写 sample/sdk_addon/manifest.ini,内容如下: # 该模块的名称、供应商及描述
  name=Sample Add-On
  vendor=Android Open Source Project
  description=sample add-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;Sample optional plaform library 该文件还可包括该模块的其它定义,如皮肤等,为了保持该文档清晰易懂的初衷,这里不做介绍,需要了解可以给我邮件。
  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 # 这个扩展继承系统扩展。 $(call inherit-product, $(SRC_TARGET_DIR)/product/sdk.mk) # 这个扩展的真实名字。这个名字会用于编译。
  # 用 \'make PRODUCT-<PRODUCT_NAME>-sdk_addon\' 的形式来编译此扩展。
  PRODUCT_NAME := sample_addon

  g、编写 sample/products/AndroidProducts.mk,内容如下:
  PRODUCT_MAKEFILES := \\
  $(LOCAL_DIR)/sample_addon.mk h、最后运行make -j8 PRODUCT-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 调用接口,代码如下 : package com.example.android.platform_library; import android.util.Config;
  import android.util.Log; public final class PlatformLibrary
  static / Load the library. If it\'s already loaded, this does nothing. System.loadLibrary("platform_library_jni");
  private int mJniInt = -1;   public PlatformLibrary()      / Test native methods.   public int getInt(boolean bad)
  // this alters mJniInt //
  int result = getJniInt(bad); // reverse a string, for no very good reason //
  String reverse = reverseString("Android!"); Log.i("PlatformLibrary", "getInt: " + result + ", \'" + reverse + "\'"); return mJniInt; //
  / Simple method, called from native code.   private static void yodel(String msg)
  Log.d("PlatformLibrary", "yodel: " + msg); //
  / Trivial native method call. If "bad" is true, this will throw an
  / exception.   native private int getJniInt(boolean bad);     / Native method that returns a new string that is the reverse of
  / the original. This also calls yodel().   native private static String reverseString(String str);
  

  c、在 frameworks/PlatformLibrary/jni/PlatformLibrary.cpp 中编写 PlatformLibrary.java 中规定本地调用的具体实现。
  d、编写 frameworks/PlatformLibrary/jni/Android.mk,内容如下:

  LOCAL_PATH:= $(call my-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 $(call all-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:= $(call my-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_library include $(BUILD_PACKAGE)

  b、在 AndroidManifest.xml 中添加一句:
  <uses-library android: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。
参考技术A LOCAL_PATH := $(call my-dir)//标准mk语句,指编译路径,所有mk文件第一句都是这个
/**这个模块表示引用了一个本地的静态库
include $(CLEAR_VARS) //清除各种变量,因为这些变量是静态全局的,如果清除,下次编译时又会用到这些变量造成出错
LOCAL_MODULE := libopencore-amrnb //本地静态库模块的名字,这个名字在下面编译jni时需要引用
LOCAL_SRC_FILES := lib/libopencore-amrnb.a //本地静态库文件地址
include $(PREBUILT_STATIC_LIBRARY) //PREBUILT_STATIC_LIBRARY表示先build一个静态库
**/
/**这个模块表示引用了一个本地的动态链接库
include $(CLEAR_VARS) //同上 www.2cto.com
LOCAL_MODULE := libswscale //本地动态库模块的名字
LOCAL_SRC_FILES := lib/libswscale.so //本地动态库文件位置
include $(PREBUILT_SHARED_LIBRARY) //表示先build这个动态链接库

http://blog.csdn.net/smfwuxiao/article/details/6591927
NDK r5 开始支持预编译共享库。预编译共享库就是从其他地方获得源码编译出的共享库,而不是Android系统自带的。方法如下:
1、声明共享库模块
把共享库声明为一个独立模块。假如 libfoo.so 与 Android.mk 位于同一目录。则 Android.mk 应该这样写:
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := foo-prebuilt # 模块名
LOCAL_SRC_FILES := libfoo.so # 模块的文件路径(相对于 LOCAL_PATH)

include $(PREBUILT_SHARED_LIBRARY) # 注意这里不是 BUILD_SHARED_LIBRARY
这个共享库将被拷贝到 $PROJECT/obj/local 和 $PROJECT/libs/<abi> (strip过的)
2、在其他模块中引用这个共享库
在 Android.mk 中,将这个共享库的模块名加入 LOCAL_STATIC_LIBRARIES (静态库)或 LOCAL_SHARED_LIBRARIES (动态库)
例如, 使用 libfoo.so 的方法:
include $(CLEAR_VARS)
LOCAL_MODULE := foo-user
LOCAL_SRC_FILES := foo-user.c
LOCAL_SHARED_LIBRARY := foo-prebuilt
include $(BUILD_SHARED_LIBRARY)
3、为共享库导出头文件
这个共享库一般有相应的头文件,比如 libfoo.so 就有 foo.h。
一个简单方法(在Android.mk中写):
include $(CLEAR_VARS)
LOCAL_MODULE := foo-prebuilt
LOCAL_SRC_FILES := libfoo.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
这样,使用该共享库的模块就会在它的 LOCAL_C_INCLUDES 变量加入该头文件搜索路径。
4、调试共享库
建议你的共享库保留调试信息。 $PROJECT/libs/<abi> 目录下的共享库都是 strip 之后的(没有调试信息)。有调试信息的版本可被ndk-gdb使用。

5、共享库 ABI
你的共享库与目标系统ABI的兼容性很重要。
请检查 TARGET_ARCH_ABI,有以下值:
armeabi => ARMv5TE 以上
armeabi-v7a => ARMv7 以上
x86 => x86
建议: armeabi ABI 可以运行在所有 ARM CPU 上。

以上是关于Android 怎么自定义共享库的主要内容,如果未能解决你的问题,请参考以下文章

即使定义了搜索路径,也无法找到自定义共享库

通过库共享时未注册 NestJS 自定义 PassportStrategy

如何使用 gcc 创建和使用自定义共享库?

带有自定义共享库的 Qt 项目交叉编译 Qt

如何与内部 svn 存储库共享自定义 Grails 插件?

具有共享库支持的 Grub 引导加载程序