声明:本文CSDN作者原创投稿文章,未经许可禁止任何形式的转载。
作者:徐鸿福,多年后端及移动端开发经验,现任MaxLeap UX团队成员,主要从事于Android相关开发,目前对Kotlin和Ruby有浓厚兴趣。
责编: 钱曙光,关注架构和算法领域,寻求报道或者投稿请发邮件[email protected],另有「CSDN 高级架构师群」,内有诸多知名互联网公司的大牛架构师,欢迎架构师加微信ysle-0313申请入群,备注姓名+公司+职位。
随着环境问题越来越严重,人们越来越重视低碳环保的生活方式。作为码农的我们自然也应该为环保做出应有的贡献。那么什么是低碳环保,简而言之就是就是低能量、低消耗、低开支的生活方式,映射到我们的工作中就是以最低的消耗的来完成组织交给我们的任务。
以下就以 Android 开发为例从库和语言两方面来讨论如何实现低碳环保的编程方式。
充分利用现存资源,尽可能不重复造轮子。从以往来看,如果你对现存的轮子有各种不满试图从头写,那么最终结局中可能性最大的就是只写了部分后直接放弃转而成为某个轮子的支持者,写完且比现有轮子要好的可能性还不如转行去大学城门口卖炒面。当然如果你写的是就是 SDK 之类的基础工具,那还是尽量减少依赖为好。
那么该如何挑选第三方库呢?Android 的应用层开发虽然使用 Java 语言,但并不是 Java 上的库都适合 Android 开发。Android 使用的不是 Oracle JDK 也不是 Open JDK,而是 Google 改写过的 Apache Harmony JDK,很多 Oracle JDK 自带的类(特别是 javax 包下的)在 Android 中并不存在,所以使用这些方法的库 Android 不能使用。
此外 Android 存在 65536 问题,这个坑体现在以下两点:
1)Android 机器在应用的安装过程中,系统会运行 dexopt
工具,将 .dex
文件优化为 .odex
文件,其中 dexopt
工具使用了固定的缓冲区大小来保存方法的元信息,低版本的 Android 机器上该缓冲区非常小,所以一旦方法数过多会直接导致dexopt
崩溃,应用无法运行。
2)Dalvik 指令集对于一个 .dex
文件只能保存 65536 个方法的索引,所以一个 .dex
文件即使可以拥有很多方法,但是那些多余的方法也都是无法运行的。详细信息可以阅读官方的 dalvik-bytecode 的invoke-kind {vC, vD, vE, vF, vG}, meth@BBBB
条目。
因此选择 Android 的第三库需要严格注意控制方法的总数量。
以下我总结了一些 J2EE 和 Android 上的常用库的对比以供参考,使用这些库可以有效提供编程效率,减少能量消耗:
功能 | J2EE | Android | 备注 |
---|---|---|---|
JSON 解析 | Jackson | Gson | Jackson 功能全面,但太大;Gson 速度一般但胜在体积 |
Restful | Jersey | Retrofit | Jersey 面向服务端,符合 JAX-RS 标准, Retrofit 面向客户端,不符合 JAX-RS 标准 |
依赖注入 | Guice | Dagger | Guice 使用反射,Dagger 使用预编译,效率不是一个等级的 |
NoSQL | MongoDB | Realm | Realm 兼容 Android,iOS,ReactNative,比起 Sqlite3 快得多 |
单元测试 | JUnit | Robolectric +Espresso | JUnit 只能运行在 JVM 上,Robolectric 使 Android 代码可以运行在 JVM 上,Espresso 简化 UI 测试流程(虽然很多情况下 UI 测试没什么用) |
异步调用 | CompletableFuture/RxJava | RxJava +RxAndroid +RxLifecycle | 三合一基本是标配,除此之外还可以加上RxBinding,RxPermissions,RxAndroidAudio 等等组成完整的 Rx 大家族 |
网络请求 | - | OkHttp/Volley | OkHttp 功能强大,Fluent API 可以写出更优秀的代码;Volley 代码量小容易扩展,有非常优秀的队列机制 |
时间处理 | Java 8/Joda-Time | - | Java 原生时间处理 API 非常糟糕,因此 Java 8 直接加入了 Joda-Time。Joda-Time 虽然易用,但一个只是处理时间的库有 5000 多方法对于 Android 显然不实际,尽管有 joda-time-android 库但通常 Android 端需要处理时间代码不多,建议还是直接调用难用的原生 API |
构建良好的 Android 架构,尽量将 Context 相关的一切和业务逻辑进行分离,使业务逻辑能够脱离于 UI 组件进行本地测试。MVC,MVP,MVVM,Flux 之类的只有适合自己才是最好的。也可以参考下 Google 最近开始编写的 Android 架构的示例代码,不得不说 Google 这一步做得是实在有点晚。
引入 Fragment 后 Android 应用的生命周期 过于恐怖,所以尽管 Google 提倡使用 Fragment,但还是能少用就尽量少用。
使用 Timber + Hugo 记录 Log 信息而不是使用原生的 Log 工具,这样无需再自己拼接类名,方法名,参数名和参数值,也不用为了使 Log 更容易被识别加上一堆 =========afafaf=============
或者 ~~~~~~~~~~~~~~~~~ 这样的提示符。
使用 Android DataBinding,尽管你不一定喜欢它的数据绑定方式。但是使用了 DataBinding 后你无需调用findViewById
后再强制进行类型转换,也不用使用 Butter Knife 之类的库编写各种注解。
对于图像加载注重质量和包大小可以使用 Picasso,注重加载速度或者需要支持 GIF 类型和大图片可以使用 Google 人出品的 Glide。除此之外还有老牌的 Universal Image Loader 和相对较新的 Facebook 出品的 Fresco(Fresco 在这里面是重量级选手,无论是功能还是体积,刚推出时坑不少,还有非常严重的内存泄露,目前该库已经作为 React Native 的图片加载库,不知道这些问题都解决了么)
使用 IDEA Live Template 保存常用的类或方法的模板,这是我最常使用的方式,这样有时甚至可以减少近一半的工作量。
强制竖屏,Android 上除了视频播放和游戏大部分情况下竖屏足以,根据 2-8 法则很多时候专门适配横屏是很大的资源浪费
以上的方法在实现低碳环保的功能上还是非常有限的,所以还有种方法就是直接从最根本的语言层面进行着手。
使用 Lambda 表达式可以省去不少代码,可惜直到 Java 8 才支持,索性的是 Android 上有一些解决方法。
Retrolambda Gradle 插件可以在编译时通过字节码转换使 Android 能够使用 Lambda 表达式。
在介绍 Jack 与 Jill 之前先来看看 Android 那坑爹的构建系统,以下图片来自 Google 官方文档,请注意这只是一个大纲,而且有些过时,实际更复杂,想想如果没有 IDE 自己手动敲的痛苦吧:
而 Jack 与 Jill 就是在 Android M 时 Google 为了简化以上流程而推出的构建工具。
以前的主要流程
为了减小 I/O 读取的次数,dx 工具将所有 .class
文件合并成 .dex
文件
javac (.java --> .class) --> dx (.class --> .dex)
使用 Jack 与 Jill
Jill 将第三方 .class
文件和 .jar
文件转换为 .jayce
文件。
Jack 将 .java
和 .jayce
文件合并后转换为优化过的 .dex
文件。
Jack (.java --> .jack --> .dex)
Jill (.class --> .jayce)
当然 Jack 实际是一个工具链,除了以上功能 Jack 还包含了 multidex, proguard 等大量功能,直接替代了原来构建过程中的很多工具。
使用 Jack 与 Jill 还有一个吸引人的特点是可以在今年将发布的 Android N 平台使用上大部分 Java 8 的功能。感兴趣的人可以现在就去尝试一下,但是需要注意的是 IDE 必须大于 Android Studio 2.1 (preview),SDK 平台必须为 Android N Preview SDK。
此外由于 Java 8 提供的 Lambda 表达式实际就是通过函数式接口实现的,所以在使用 Jack 与 Jill 后这一功能也可以直接使用在 Android N 以前的平台,而不用使用 Retrolambda 这些第三方工具(当然其它的 Java 8 功能都不支持)
目前来说 Jack 与 Jill 有一个很大的缺点就是速度较慢,不支持 Instant Run。此外由于不生成中间状态的字节码文件,所以开启 Jack 与 Jill 后基于字节码的各种工具(如 JaCoCo, Mockito)都将无法使用。(注:Gradle 插件在 1.5 版本提供了 Transform API,可以让我们直接对生成的 dex 文件进行处理,但是目前 Jack 与 Jill 不支持,所以使用该种方式 JaCoCo 仍然无法使用)
相比较 Lambda 表达式也许换种语言是种更有效的方法。以下就介绍一下适用于 Android 开发的其它语言,当然这并不是说真的需要在实际工作中应用。更多得是因为如果你只会用锤子,那你眼里的所有东西都是钉子,换种语言是为了开拓思路,了解在其它语言中是如何实现同样的功能的。
该方案以 Java 代码为主,以少量 Native 代码为辅。主要有两种实现方式:
Java + C/C++
很明显这种方法非常不环保,基于 NDK 的开发非常复杂,出了错误也不好调试,目前这种方式主要用于 Cocos2D-X 这种游戏引擎。
Java + Go
Go 从 1.5 版本开始同时支持 Android 和 iOS 开发。由于 Go 是 Google 亲儿子所以 1.5 出来时大家对未来都非常期待。不过至今为止实际发展非常缓慢,文档非常稀少。相比较使用 C/C++ 方式使用 Go 有这么几个特点:
优点:
.so
文件的 .aar
库,导入 Android 工程后就像写原生 Java 代码一样直接使用即可。缺点:
该方案以 Native 代码为主,以少量 Java 代码为辅。最大的特点就是提倡以同一种语言为不同平台编写不同代码,而不是一套代码到处运行。主要代表有以下几种:
RubyMotion
RubyMotion 由 MacRuby 的开发者发明,可以通过 Ruby 代码编写 iOS 和 Android 代码。
优点:
缺点:
ReactNative
ReactNative 由 Facebook 在去年发布,一经发布瞬间成为 Github 上的网红。可以使用 JavaScript 进行代码的编写以及动态更新是其最大特点。然而相比较 iOS 来说 Android 版本并没有发布多少时间,目前还有不少问题。由于最近在看 ReactNative 所以这里写得稍微详细点:
优点:
缺点:
嵌套
的情况,不适合写复杂的布局。相比较以上方案,使用其它 JVM 语言的最大特点就是可以和 Java 进行无缝切换,开发方式上没有什么变更,很容易让人接受。
Groovy 官方从 2.4 开始就支持了 Android 开发。Groovy 本身是动态语言,效率较低。但是可以通过开启静态编译来提高效率。由于 Groovy 在处理 XML 方面是一绝,所以如果你的应用服务器是基于 SOAP 的话,那么使用 Groovy 替换 Java 无疑是更好的选择,原生的 DOM,SAX,PULL 方式使用起来都太痛苦了。
替换成 Groovy 后最大的优点就是上手没有任何什么难度, 毕竟所有 Android 开发者都写过 Groovy (build.gradle
实际就是 Groovy 源文件),而缺点就是编译速度会更慢。
Android 应用中常常需要将上下文传来传去,所以很多人都会在每个 Activity 写上 mContext = this
这样没营养的代码。而使用了 Scala 后借助隐式参数 + 隐式转换 + 隐式类这三兄弟就不用写这些没有营养的代码。除此之外借助 Scala 各种 FP 特性代码量也可以得到大量减少。
但是替换成 Scala 后有一个致命的弱点就是主流的 Scala 的 2.11.x 版本核心方法多达 5w。这意味着加上 Android 的原生代码,即使你一行代码没写,仅仅是集成 Scala 运行环境后就超过了 65536 的限制。即使开启了 proguard 删除掉那些不用的代码这个问题也没办法根本解决,所以或许将 Scala 搬到 Android 平台并不是一个好的解决方案。
Kotlin 是 JetBrain 研发的一门运行在 JVM 上的语言,官方支持 Android 开发,语法和其之后发布的 Swift 非常相似,所以也有人开发出了 Kotlin 2Swift 的工具,详细对比可以见Swift is like Kotlin。Kotlin 的语法可以看做是 Scala++–,其语法借鉴了 Scala,但是也去除了 Scala 中大量复杂的概念。
在所有 JVM 语言中,目前个人最推荐使用 Kotlin 进行 Android 开发。有以下几个原因:
就我个人开发中常使用的 Kotlin 功能有这么几种:
最后附上一段简单的 kotlin 代码
relativeLayout {
textView {
id = android.R.id.text1
text = "Loading..."
}.lparams {
centerInParent()
}
editText {
id = android.R.id.edit
hint = "Page Count for retain"
inputType = InputType.TYPE_NUMBER_VARIATION_NORMAL
}.lparams {
below(android.R.id.text1)
centerInParent()
}
button("click") {
}.lparams {
below(android.R.id.edit)
centerInParent()
}.onClick {
println("hello world")
}
}.style {
when(it){
is TextView -> it.textSize = 20f
}
}
以上 Go 和 Ruby 编写 Android 应用的示例可以见 AndroidDemoInOtherLanguages。其它 JVM 语言编写 Android 应用的示例可以见 AndroidDemoIn4Languages。
扯了这么多可能有很多人觉得就这些怎么可能实现低碳环保的编程。没错,你想的很对,要实现低碳环保的编程方式说到底只有唯一一个有效的方法就是说服你的项目经理或者其他有话语权的人取消或修改掉那些不环保的需要,仅此而已。本次扯淡到此为止。
欢迎加入我们的技术活动群:571126915,我们将不定期做技术分享活动。