为什么要开发Android库?

简介:
本文讲的是 为什么要开发Android库, 不论是你要执行一个特定的任务,模块化你的代码,或者只是为了更优雅地重用你的代码,有些时候,作为开发者,通常会考虑开发库来实现。但开发库是个挺困难的事情。由 Bay Android Dev Group 主办,这次分享由我们的 Emanuele Zattin 分享一些他在开发 Java 和 C/C++ 库上的一些最佳实践。探讨下 API 设计,CI 技术,以及对于性能的看法,你会了解到对你工作很有帮助的一些工具。

为什么要开发Android库?

为什么要开发 Android 库?

为了追求更简洁的代码和更好的代码管理,我们通常需要把代码拆分成不同的逻辑单元,所以,开发 Android 库的第一个原因就是为了模块化。这也引申出我们开发库的第二个原因:代码重用。一旦你的代码模块化后,你可以将它用在很多不同的地方。相比高耦合度的代码,基于库的代码管理让你更容易的替换其中的代码,以适应不同的场景。还有一个原因,其实就是 “虚荣”,如果你有一个很不错的想法或者提出一种新的解决特定问题的方案,写个库会是一个将这个想法分享给全世界,并且让大家都能用到的一个好办法。

为什么是开发 Android 库而不是 Java 库呢?如果你的库要和 Android 的 UI,消息系统,设备传感器,或者原生代码打交道,那么你只能开发 Android 库,而不是 Java 库。

我们开始前,首先,打开你的 Android Studio,并且新建一个项目!Android Studio 并不支持直接新建一个新的库,这里有几种解决办法,如下:

方法一 - Hack 方案:

  • 创建 Application Project
  • 添加 Library Project
  • 删除 Application Module

简单粗暴效果好

方法二 - 使用命令行,Android 自带了很多很多的工具,创建 Android 库也是其中的一个命令。传入一个 ID 参数允许你指定特定的编译 版本,也可以通过传参指定项目包名,以及你想要运行的 Gradle 的版本。 Gradle 是一个极其有用和灵活的自动化构建工具,你可以非常容易地运行各种插件,来加速你的开发,比 Maven 和 Ant 不知道高到哪里去了。

第二步: 代码,代码,代码!

在开发库的时候,API 设计非常重要, Joshua Bloch 是这方面的专家,他的 Effective Java 尽管是基于 Java 1.5, 但仍然有很多有价值的技巧。他曾经做过一个 名叫 如何设计优秀的 API,为何如此重要 的精彩的演讲,因此我想分享一些他在 API 设计上的一些观点。

那么,一个好的 API 应该具备哪些特点?

简单易学,你并不想让开发者不断地去查看文档,因此你在设计时候的命名法、类名和参数名一定要尽可能的不言自明。

  • API 要少出错,所以要把 API 设计得坚如磐石。
  • API 一定要易读,易维护,毕竟你在很长一段时间内要和这些代码打交道,特别是它一旦流行开以后,你会收到很多的新功能的请求和漏洞报告。
  • API 的可扩展性也至关重要。想想 Jenkins :虽然不是一个 API,但是他是一个非常成功的开源项目,一部分原因是因为他易于扩展,以及为他开发插件。
  • 最后,你一定要明确你的受众,不论受众是你自己、你的团队、你工作所在的公司或是整个世界,你的 API 都要做到对你的受众友好。

测试很重要,对库而言更加重要,因为你无法预知你的受众将如何去使用它们。很幸运的是,在 Android 上测试库跟测试 App 差异并不大。你可以使用 Android TestCase ,也可以用一些其他的 Android 测试工具框架来做测试。

Android 的测试组件一个不好的地方就是大家通常会卡到 JUnit 3 上,尤其是你发现你不能用类似 Robolectric 这样不支持 Native 代码的工具。JUnit3 不支持一个对库开发很重要的测试特性:参数化测试。如果你想用一些列测试参数来测试的方法,你可以试试用 Square 开发的一个叫: Burst 的库。它很好地解决了这个问题。

自动化你的测试! Jenkins 是一个非常赞的工具来解决实现自动化测试。他提供了超过 1000 个插件,其中一些专为 Android 开发设计。我强烈推荐以下插件:

  • Job Config History 插件, 它可以在出状况的时候通过配置文件重新恢复现场。
  • Git 插件,以及与他类似的一些插件,比如: GitHub , GitHub pull request , GitLab , 等等。
  • 让执行任务和自动化变得简单的 Gradle 插件。Gradle 非常擅长自动化,你可以在 Jenkins 的 Gradle 里运行很多你自己的操作逻辑。
  • Android Emulator 插件,这个不仅仅是个模拟器。 当你想要测试不同屏幕分辨率以及内存使用的时候,这个插件非常有用。

另一个测试方案是写测试用的 App。这种方案在很多时候都很有用:它可以帮你验证你的测试用例,间接地测试,而且可以确保你的 App 不会崩溃或者无响应。想要做测试 App,推荐用 Gradle 的 这个插件 ,它同时支持发送指令给不同的 Android 设备。

在你准备要发布你的库的时候,你会作何选择?目前有两个选择:发布 Jar, 或者发布 Aar(Android 归档文件),如果你还不知道 Aar,我来做个简单介绍,Aar 是 Google 为了能让库文件包含 UI 元素而提出的一种文件格式,他可以让你不仅仅包含 Java 类,还可以存储数据和资源在里面。他在 Android 和 Android Studio 上非常有用,Ant 和 Eclipse 并不支持这种格式。

一个不好的事情是尽管使用本地的 Aar 是可行的,有两种方案来实现这个,但他们都很麻烦。尤其是你想用你刚刚生成的 Aar 来做 App 测试。所以,如何选择发布哪种格式,最后就归结为,你是否必须要支持 Eclipse,如果是,那么没得选,只能用 Jar。

问题产生的原因就是因为 Android Gradle 插件产生的是 Aar,而不是 Jar 文件,尽管如此,事实上 Jar 文件旧包含在 Aar 文件里,其实你只要复制出来 Jar 文件,然后重命名他就好了。下面的代码就是一个简单的 Gradle Demo 来做这件事情: 拷贝文件(通常会被命名为:’classes.jar’),然后重命名成你想要的名字。


 
 
  1. task generateJar(type: Copy) { 
  2.     group 'Build' 
  3.     description 'blah blah...' 
  4.     dependsOn assemble 
  5.     from 'build/intermediates/bundles/release/classes.jar' 
  6.     into 'build/libs' 
  7.     rename('classes.jar''awesome-library.jar'

下一个问题是:我们应该发布在哪?如果你计划开源它,那么想都不用想,就用酷炫的 Bintray 。Bintray 尽管需要你准备更多的东西,但依旧用起来非常简单。仅仅需要一个源码 Jar 文件,以及一个 Javadoc 的 Jar 文件,这两个都非常容易生成。这样一来,你基本上只要指定代码路径,就可以遍历所有的 Variant。


 
 
  1. // sources Jar 
  2. task androidSourcesJar(type: Jar) { 
  3.     from android.sourceSets.main.java.srcDirs  
  4.  
  5. // Javadoc Jar 
  6. android.libraryVariants.all { variant -> 
  7.     task("javadoc${variant.name.capitalize()}", type: Javadoc) { 
  8.         description "Generates Javadoc for $variant.name." 
  9.         group 'Docs' 
  10.         source = variant.javaCompile.source 
  11.         ext.androidJar = files(plugins 
  12.                                 .findPlugin("com.android.library"
  13.                                 .getBootClasspath()) 
  14.         classpath = files(variant.javaCompile.classpath.files) + 
  15.                     ext.androidJar 
  16.         exclude '**/BuildConfig.java' 
  17.         exclude '**/R.java' 
  18.     } 

Bintray 也为生产发布提供了一个很精美却不是那么好用的 Gradle 插件 ,尤其是你只是一个 Gradle 新手的话…… 另外,带 Web 界面的发布工具也很是有用。你只要上传这三个文件,再填一些信息,就可以了。一开始,你可以使用那些 web 方案,当你对 Gradle 插件的使用和自动化非常熟悉的时候,就让 Jenkins 去帮你做这些发布工作吧。

Advanced Topics

注解处理技术( Annotation processing technologies )现在已经非常流行了。在编译的时候,Javac 通常会找到你定义的注解并且在他们之上做操作,因此你能做的通常就是生成新的类和 Java 文件。你可以写你自己的注解处理器。虽然很不简单,但是可行。

注解技术非常擅长处理两件事情。一是减少重复代码(boilerplate),另一件拿手的就是优化运行时的反射(introspection)。在运行时执行反射操作是非常慢的,因此你最好能在编译时期就去优化你的程序,推荐一些比较流行的用注解来处理数据的库: Dagger , Butter Knife , AutoValue / Autoparcel , 以及 Realm 。

一个不好的消息是:Android API 不能给注解提供正确的包路径。解决这个问题的方法是创建两个子项目,一个用来指向注解,另一个指向注解处理器。注解同时需要注解处理器和你的代码。因此,你的 Android 库项目,将会有两个子项目。创建完两个子项目后,你需要将他们都打包在一起,最终打包出来的东西不仅仅是你的 jar 文件,同时还有注解和注解处理器。你还需要修改 javadoc 任务,把注解部分的文档也添加进去,以便让开发者能够读到所有的文档。


 
 
  1. // Jar 
  2. task androidJar(type: Jar) { 
  3.     dependsOn assemble 
  4.     group 'Build' 
  5.     description 'blah blah' 
  6.     from zipTree( 
  7.         'build/intermediates/bundles/release/classes.jar'
  8.     from zipTree( 
  9.         '../annotations-processor/build/libs/processor.jar'
  10.     from zipTree( 
  11.         '../annotations/build/libs/annotations.jar')  
  12.  
  13. // javadoc tasks 
  14. android.libraryVariants.all { variant -> 
  15.     task("javadoc${variant.name.capitalize()}", type: Javadoc) { 
  16.         description "Generates Javadoc for $variant.name." 
  17.         group 'Docs' 
  18.         source = variant.javaCompile.source 
  19.         source "../annotations/src/main/java" 
  20.         ext.androidJar = files(plugins 
  21.                                 .findPlugin("com.android.library"
  22.                                 .getBootClasspath()) 
  23.         classpath = files(variant.javaCompile.classpath.files) 
  24.                     + ext.androidJar 
  25.         exclude '**/BuildConfig.java' 
  26.         exclude '**/R.java' 
  27.     }  

Native Code 基本上都是和 NDK 打交道,整个工作流掌握起来很多很麻烦。如果你了解 C/C++,那最好不过,不然你要花大量的时间去学习新东西。Gradle 和 Android Studio 对 NDK 的支持并不好,当你尝试着去使用 NDK 模块的时候,他会警告你说:当前 NDK 已经不被支持了。Google 现在在尽可能的去让 Gradle 支持去支持 Native 插件。在此之前,我们还是必须得手动的搭建整个工具链,手动的编译以及把编译出来的文件拷贝到正确的位置。

遇到 NDK 不被支持的警告要做呢?一个方法是忽略警告,因为它依然可以工作。得留意的是,现在没有对 ldFlags 的定义,因此你不能为链接器指定 flag 参数。如果你需要这些参数,另一个方法是使用 Native 插件。这个方法可能很快就被废弃了,他需要你自己处理单独的工具链的搭建以及将生成文件从一个项目转移到对应的项目下。

如果你使用 jar 文件,如何才能包含基于 Native 实现的库呢?其实只用在 Building 的时候动动手脚就好了。如下所示,修改你的 “jar” 任务就可以了。


 
 
  1. task androidJar(type: Jar, dependsOn: ['assemble']) { 
  2.     group 'Build' 
  3.     description 'blah blah' 
  4.     from zipTree('build/intermediates/bundles/release/classes.jar'
  5.     from(file('src/main/jniLibs')) { 
  6.         into 'lib' 
  7.     } 
  • 拥抱 Gradle:可能需要花时间学习,但是非常值。
  • 探索 Gradle 插件!有数不清的插件,总有一款适合你。
  • 自动化你的测试。尽可能的用 Jenkins和自动化工具。
  • 如果你要开源你的炫酷组件,Bintray 是个好选择。

Q: Jenkins 上用 Gradle 的优势有哪些? Emanuele:优势就是 Gradle 有一些设置选项,包括你在你电脑上运行的 Gralde 或者 Gradle Wrapper 版本,插件能在出问题的时候让你通过 log 更容易的定位到问题所在。

Q: 你提到注解很有用,因为他们是在编译的时候处理问题,那么运行时的注解有没有什么特殊之处? Emanuele: 如果你想的话,你也可以在运行时调用注解,执行反射操作。但是相比于在编译时期处理慢很多。在 Android 上就更慢了,所以你用着用着就不想这么搞了。

Q: 你之前介绍了这么多有用的库,他们都会对 Android 性能产生很大影响么? Emanuele:不,我只是说你可以在编译时做反射优化。有的时候,只有运行时反射才能拿到一些你程序执行时候需要的信息,所以那种情况你也只能那样做了。

Q: 在处理注解的时候, Javapoet 是个不错的库,但依然要读取注解并且在类继承关系中寻找,你有没有处理这个的好的方案? Emanuele: 很不幸,没有。事实上,javapoet 只能在你生成新的类的时候帮到你。如果没有那么多的代码量,你也可以不用任何的库,只用模板。问题通常出在对象反射的限制上。你可以拿到注解的类,等等,但是往往无法反射内部的方法。为了实现这个,你需要操作字节码,听起来很可怕,但是是可行的。有一个叫 Morpheus 的库,可以帮你做这些。

Q: 我尝试过创建一个库,遇到了一个路径设置的问题,当时没有办法让 Gradle 明白我是在创建库而不是一个 App,你之前有见过这样的问题么? Emauele: 遇见过,你需要正确使用 Google Android 的 Gradle 插件,探索一下他在哪里存储你需要的信息,你需要对这个比较了解。

Q: 你更倾向用哪个 Jenkins 服务提供方?还是你通常在你本地运行? Emanule: 我通常自己跑他们。之所以这么做,是因为我搭建了一个没有任何 executro 的 Master 机器,同时搭建了一堆本地 Slave 机器当做 Excutor,Master 机器可以使非常小的机器,不用那么强劲,无须处理任何编译的事情。


本文作者:佚名

来源:51CTO

原文标题:为什么要开发Android库?
相关文章
|
10天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
15天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
1天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
17天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
17天前
|
存储 API 开发工具
探索安卓开发:从基础到进阶
【10月更文挑战第37天】在这篇文章中,我们将一起探索安卓开发的奥秘。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和建议。我们将从安卓开发的基础开始,逐步深入到更复杂的主题,如自定义组件、性能优化等。最后,我们将通过一个代码示例来展示如何实现一个简单的安卓应用。让我们一起开始吧!
|
18天前
|
存储 XML JSON
探索安卓开发:从新手到专家的旅程
【10月更文挑战第36天】在这篇文章中,我们将一起踏上一段激动人心的旅程,从零基础开始,逐步深入安卓开发的奥秘。无论你是编程新手,还是希望扩展技能的老手,这里都有适合你的知识宝藏等待发掘。通过实际的代码示例和深入浅出的解释,我们将解锁安卓开发的关键技能,让你能够构建自己的应用程序,甚至贡献于开源社区。准备好了吗?让我们开始吧!
26 2
|
19天前
|
Android开发
布谷语音软件开发:android端语音软件搭建开发教程
语音软件搭建android端语音软件开发教程!
|
7月前
|
存储 Java 开发工具
Android开发的技术与开发流程
Android开发的技术与开发流程
402 1
|
4月前
|
移动开发 搜索推荐 Android开发
安卓与iOS开发:一场跨平台的技术角逐
在移动开发的广阔舞台上,两大主角——安卓和iOS,持续上演着激烈的技术角逐。本文将深入浅出地探讨这两个平台的开发环境、工具和未来趋势,旨在为开发者揭示跨平台开发的秘密,同时激发读者对技术进步的思考和对未来的期待。
|
4月前
|
安全 Android开发 Swift
安卓与iOS开发:平台差异与技术选择
【8月更文挑战第26天】 在移动应用开发的广阔天地中,安卓和iOS两大平台各占一方。本文旨在探索这两个系统在开发过程中的不同之处,并分析开发者如何根据项目需求选择合适的技术栈。通过深入浅出的对比,我们将揭示各自平台的优势与挑战,帮助开发者做出更明智的决策。
74 5