[干货]30条Android开发建议
There are two kinds of people :
those who learn the hard way and those who learn by taking someone’s advice.
当前Android开发领域最佳实践方案,覆盖了最新的技术、架构等各种细节。
本文主要用来收集Android开发中积累的一些宝贵经验,这些经验中有一些约定熟成且经过检验的建议,有一些结合最新技术的实践。无论是菜鸟还是大神,都应该学会阅读别人的经验,并结合自己的思考转化成对自己有用的知识,这才是最快的成长之路。另外,对于这些建议,我会尽量翔实的进行说明以确保能够顺利快速应用到实际开发中。
介绍
下面以这篇文章:Building Android Apps — 30 things that experience made me learn the hard way为核心,对其中提出的每一点建议进行较为深入的分析探究,最终整理成一篇完整的文章。当然,本文还在不断更新中。
1. 第三方库
在你添加每一个third party library之前,请认真考虑是否真的需要这个library。
2. OverDraw
如果用户看不到,那就不要进行绘制(draw),或者说,不要过度绘制 OverDraw
OverDraw
会导致GPU浪费,也会导致app的速度变慢。为了减少这种危害,我们可以利用Debug GPU Overdraw Tool
来观察app里的绘制情况,然后可以使用Hierarchy Viewer来进行优化。
3. 数据库
除非不得不,否则不要使用database
4. 65k methods limit
Dalvik 65K methods limit
你很快就会遇到的,不过放心,multidexing
会帮助你。
什么是Dalvik 65K methods limit
?我们知道,我们写完java code之后,dx tool会把java编译成Dalivik虚拟机能识别的DEX
文件,这个文件里最多能够索引65536个method
。关于这个有两点要注意:
- 这些method是指能够
索引(reference)
到的,而不是定义(define)
的。或者说,如果你定义了一个方法,但这个方法并没有被调用,那么就不算在内。 - 这些method不仅仅是开发人员自己写的,还包括所有第三方library里面的method。
所以,我们总共可以索引65536
个方法,包括自己写的和引入第三方库里的。
那么,我们如何能快速知道我们的app里已经有多少个method了呢?
- bash script: dex-method-counts。这个工具可以快速计算,并且提供一个清晰的视图来阅读。
- dex.sh by Jake Wharton。这个工具由于采用了递归算法,所以耗时比较长。(Jake大神还写了一篇有趣的分析文章Play Services 5.0 Is A Monolith Abomination,针对Play Services 5.0太大的问题进行了分析,有空时我再翻译下给各位。虽然Play Services 6.5已经模块化,更加轻量级了)。
现在,既然我们已经知道了自家app里的method数了,那么如何来处理这种情况呢?
- Multidex,官方提供的解决方案,这篇文章里有详细的使用方法,此不赘述。
-
ProGuard
ProGuard
可以把code里unnecessary的method移除,压缩apk,当然还有代码混淆
的奇效。 - 再创建一个
DEX File
。把app里可以独立的模块或code提取出来,放到一个独立的dex文件里,你可以使用Custom ClassLoader来加载这些类,然后使用接口
或反射
来调用这些方法。不过,这个过程还是比较麻烦的。
5. RxJava+RxAndroid+Retrolambda
使用前可以通过这篇gist来了解RxJava, RxAndroid & Retrolambda
的结合用法。这个组合可以优雅的在不同线程中处理事务,同时能够方便的实现数据流动和及时响应,而且Retrolambda能够精简你的code。其中,核心的两个概念是Observables
和Subscribers
,前者对外提供数据,后者监听并消费这些数据。
另外,这里有一个看起来不错的项目Learning RxJava for Android by example,等空闲时再去阅读下code。
6. Retrofit + RxJava
利用Retrofit与RxJava结合,为你的app提供网络请求服务。
你可以参考这个超赞的例子,让你快速感受二者结合使用方法。
7. 按feature
来分package,而不是按layer
这是这篇文章提出的一个点,文中认为分package就像公司安排座位,要按照team
来分而不是按照每个人的职位来分,即按照负责一个app的developer、designer、pm
坐在一起,而不是把所有developer
坐在一起,所有designer
坐在一起。所以,原文作者认为把一个feature相关的如Activity
adapter
等都放在一起。
不过,我认为按feature也有坏处,那就是复用
,拿adapter来讲,一个app里很多adapter是类似且可以复用的,如果我们把各个adapter拆倒各个角落里,就很难提取其中的关联来创建一个BaseAdapter
了。而且,不同feature之间也有很多公用的东西
,比如一个自定义view,那就很难界定应该放在哪个feature包里了。相反,我们把所有自定义view放在一起,这样也有助于我们发现某些自定义view的区别,然后在refactor时可以提取公用的东西来复用
。
关于这点,欢迎读者给出自己意见。
8. 加速Gradle
9. 架构:使用clean Architecture
这里有两篇优质文章:Architecting Android…The clean way?和Architecting Android…The evolution 分别介绍并用code实现了一个Clean架构。后面我也会专门分析下这种架构,因为对于任何一个project而言,最初的好的架构是非常重要的!所以,如果你想提高自己,那么架构
这一关是必经之路。
10. 测试你的app
虽然做测试需要花费你不少时间,但一旦你完成了这一步,以后的开发会更加快速,app也会更加稳定。
这里有个哥们,对unit test
进行了细致的点评。
11. 使用依赖注入
神器Dagger
如果你不知道什么是依赖注入
,你可以先读一下这篇文章Dependency injection on Android: Dagger (Part 1),或者这篇依赖注入。简单来说,依赖注入替代了传统创建对象的new
操作,当需要创建一个class的实例时,使用依赖注入从外部直接获取一个实例,具体这个实例是如何创建的不需要关心,由一个对象库统一管理每个对象的创建过程,并直接对外提供对象。这样做的好处是我们不用管实例是怎么创建的,这种抽象可以使得每个对象的创建过程变得可扩展性,只要在对象库里修改一次,那么所有用到这个实例的地方都随之变更。例如在测试时,我们希望某个mock某个对象的数据,就可以修改注入的对象。
依赖注入有不少工具,不过Dagger2使用的是编译时代码生成(code generation)
方式而不是反射(reflection)
,所以它的性能比较出众。这篇文章有对Dagger2的实践和分析。
12. 对EditText使用合适的输入类型
13. 关注新的开源library
你可以通过Android Arsenal来保持对开源项目的关注,同时利用这个工具dryrun来快速将开源项目跑在genymotion以看到实际效果。
14. Service
如果你创建了Service,那么一旦这个Service完成了自己的使命,就应该立即清理掉它
15. 使用AccountManager来统一管理用户的帐号密码。
16. 使用持续集成CI(Continuous Integration)
来编译并发布你的beta和release build
持续集成可以帮助你方便的编译并发布项目,不过,不要去搭建你们自己的CI服务器,因为你需要花费太多的时间来处理硬盘空间、安全问题和预防SSL攻击等问题。你可以尝试Jenkins、circleci、 travis 或者 shippable,这些价格并不贵,而且能帮你省很多事情。
17. 自动发布到Play Store
你可以使用这个工具gradle-play-publisher来帮助你自动上传apk到Play Store上
18. 开始考虑用svg替代png
理由很简单,android developer应该很熟悉每次导入一张图片时都需要生成四五种不同大小的png图片并放入到对应文件夹。与其维护这么多图片,显然使用一张svg图片更加方便。而且,google也在不断提供相关的支持,除了基本的Vector Drawable,从最新的Support Library我们也能看到google也在鼓励developer们使用svg。
不过,svg也有自己的限制,比如它比较适合小icon,因为它最终会生成bitmap加载以供显示,所以这需要一定的cpu支持。当然总体来讲svg还是更优的,至少大家可以不用再维护四五张不同尺寸的图片了。
19. 将一些library的类进行抽象,从而方便后期替换library
例如,一旦我们打log会使用Log.i()
,但是,如果后面我们突然想换成Timber.i()
就会很麻烦,需要一个个log找到来替换。但是,如果我们抽象出一个AppLogger
来,全部调用AppLogger.i()
来记log,那么我们只要简单的在AppLogger
内部替换掉具体实现就可以了。
20. 监控网络连接类型,区分移动数据流量
和Wi-Fi
;同样,可以监控电量
和充电状态
对于不同网络类型,我们可以动态改变我们的UI,比如大图可以选择在Wi-Fi
下才加载,而在移动数据流量
则不加载。对于电量
也是类似的逻辑。用户一定会很感谢app做的这种自适应的。
21. User Interface is like a joke.If you have to explain it, it's not that good.
22. 先写slow但是right的代码,再去进行优化
小结
本文长期更新,会保持跟踪最新的技术和研究实践经验,为大家提供有效有用的经验,少走坑。
谢谢!
wingjay
wingjay