前言

从我入门 Android 开发至今已经两年有余,过程中有了一些经验和积累,所以在此将我对于入门者的建议记录下来。

Java

学习 Android 开发,首先要有扎实的 Java 基础。如果不理解一门语言的设计思想,编写代码时将常常感觉思维与语法发生冲突,同时也无法针对这门语言扬长避短,最终代码将会难以实现和维护。

对于尚无 Java 基础的同学,我推荐 《Thinking in Java》 这本书(中文译本为《Java 编程思想》),因为它对 Java 语法和设计的讲解较为精确详细,同时对设计模式和最佳实践也有合适的涉猎。这本书除了最后一章关于图形界面的部分可以跳过,之前所有的章节,都应当仔细地阅读和理解一遍。我个人在阅读时使用了两周大约 40 小时的时间。

此外,初学者请格外重视代码风格,始终遵循 Java 的编码规范,因为这可以使代码风格统一、结构有序、逻辑清晰和易于维护。IntelliJ Idea(Android Studio)的默认代码风格正与之符合。

框架入门

在拥有了 Java 语法基础之后,就可以开始学习 Android 的框架了。

Android API 和最佳实践的更新较为迅速,所以几乎没有可靠的 Android 开发书籍,不推荐购买书籍入门。而 Android 官方网站的文档 是保持与框架同步的,同时也有很多关于最佳实践的介绍。

所以我的建议是,从官方文档的 Training 开始,读完最基础的部分后,自己想好要做的一个小型项目,再按照需求查找 GuideReference,结合 StackOverflow 等资源,在实践中完成第一个自己的 Android 应用,掌握一些 Android 开发的基础知识。这个过程我使用了另外两周的时间。

此外作为补充,Code Path 的教程 也可以参考,虽然并非必须;大多数最佳实践和陷阱都需要开发者自己通过搜索多方资料和阅读源代码积累。

开发环境

关于开发时使用的环境,Eclipse 早已经是日薄西山,而构建于 IntelliJ Idea 之上的 Android Studio 才是 Google 和社区支持的重心,也是在官方文档中建议使用的开发环境。

同时,Android Studio 的 Refactor,Generate,Lint Check 等功能也十分强大,能够显著地提高开发体验和效率。此外 Emacs 键绑定在调节个别设置之后就完全可用。

Android Studio 的连击 Shift 全局搜索功能在想要打开特定文件时也十分好用,默认在工程范围内搜索,再次连击可以将框架代码也纳入搜索范围。

在工具的选择上还是应该以功能性和主流选择为依据,因此我建议即使之前熟悉 Eclipse 的同学,也开始使用 Android Studio 进行 Android 开发。

官方文档中有关于 Android Studio 和 Android SDK 配置的详细说明。Android Studio 的更新动态可以在 Anroid Tools Project Site 上查看,也提供了 RSS 订阅。

问题解决

在开发的过程中如果遇到问题,首先可以尝试 Google 搜索。搜索结果中,又以 Android 官方的教程结果为好,StackOverflow 辅之,某些编写精良的国外博客可以参考。

此外,如果是框架或者支持库的 Bug,Google Code Issues 和 StackOverflow 上也可能有人贴出 Workaround;如果暂时没有,自己在 Google Code 上提交 issue 也有一定可能收到关注(和 Chris Banes 搭话玩)

对于 CSDN、博客园等国内博客,则应该尽量避免,即使读时,应抱有将信将疑的态度,因为国内博客的总体质量偏低,常常代码风格混乱、不遵循最佳实践、只以达成效果为目的,甚至可能有明显的错误。当然少数排版良好、编写细致的博客除外。

至于 Android 开发 QQ 群、微信群等,由于质量和氛围,在总体上个人极其不建议新手加入。

阅读框架代码

如果问题没有直接的解答,则可以尝试查看 Android 的 JavaDoc 和源代码,来理解框架的具体行为。

在这个过程中,Android Studio 是很好的工具。通过按住 Ctrl + 单击,可以跳转到源代码所在的位置,或者资源文件的定义;点击类或方法左侧行号旁边的标记,可以访问继承或重载当前片段的代码,查看框架的细节行为,或是内部的使用范例;这些功能使得查看框架代码十分方便快捷。

此外,GrepCodeGitHubGoogle Source 可以在线查看框架代码的变更,以及未进入 SDK 的代码,使用时 Google“类名 网站名”即可。三家各有千秋。GrepCode 类名或方法名旁的向下箭头,类似 Android Studio,可以点击查看使用情况或者继承、覆盖者等,便于快速定位问题、理解架构;GitHub 和 Google Source 则有完整的 git 记录,可以查看 diff 和 blame(GoogleSource 是真正的版本库所在地,GitHub 会自动拉取 GoogleSource 上的更新);个人感觉 GitHub 的界面和功能相对更加友好一些。

顺带一提,涉及到 IPC 的 IBinder 之类代码,具体实现可以在 com.android.server 这个包下找到。

向下兼容

虽然 Android 的 Training 和 Guide 中大部分是以原生框架 API 来教学的,但实际上有一些功能是在后续版本的 Android 中才引入的,直接使用这些功能会导致在旧的 Android 上找不到相应方法而发生崩溃。

在 Android 开发的早期,问题不是如此明显,开发者经常自己条件执行与版本相关的代码。久而久之,也有人将部分方面的兼容性代码封装成库提供给开发者使用,直到后来 Android 官方终于发布了 Support Library 来全面地完成这项工作。

我曾经对支持库有所保留,有时还是偏向于使用原生实现而非支持库实现。但后来我慢慢感受到,Android 官方的支持重点还是使用支持库的方式,并且这样也能够不断带来更好的兼容性。因此,我还是建议新手在使用新 API 时,选择支持库而非原生实现。

目前支持库主要向下支持至 V7,而官方建议开发者向下支持至 V14(Android 4.0) 就可以了,这样覆盖了 95% 左右的用户。关于 Android 版本的统计数据可以在 这里 查看。

开源第三方库

Android 中界面、网络、数据存储等许多高级功能的实现可能较为复杂,这时开源库就变得十分方便。现在大多数 Android 的开源库都托管在 GitHub 上,并支持在 gradle 中一行引入。但是,GitHub 并非专注于 Android,搜索功能也还有待提升,因此以下介绍其他一些常用的技巧。

建议:

  • Google:直接搜索你的关键字即可。
  • Android Arsenal:目前最好用的 Android 开源库索引,提供搜索和分类查询。

了解:

在选择开源库时,建议考察它的 Star/Fork 数、文档、代码风格、作者回复 Issue 的活跃程度,谨慎作出决定。一旦使用,建议 Watch 此项目,关注更新。

建议使用的部分开源第三方库:

  • ButterKnife:自动视图注入,替代冗长的 findViewById()
  • Volley:短时网络请求。已进入 AOSP,链接中是第三方附加了一些 Tweak 的镜像。
  • Gson:JSON 与 Java 对象自动转换,在进行数据模型复杂的应用开发时极其有用,与 Volley 搭配和谐。
  • Glide:为可回收视图容器优化的图片加载,可与 Volley 整合。被 Chris Banes 在 cheesesquare 中使用。
  • ThreetenABP:Java 8 日期时间 API,优异的设计和实现,从此日期时间编程不再困难。

另外提一下其他几个项目,且并非针对作者个人。在我个人看来,虽然很多很全,但 android-commonandroid-demo 两个项目的代码质量并不算很高,也无法确定是否始终遵循着 Android 最新的变更和最佳实践。因此,我个人极其反对直接复制粘贴其中的代码(或者其他任何代码),也不推荐直接参考其中的代码而不查阅其他来源;相应地,应该始终使用 Google 搜索解决方案,这样才能保持最新。

界面设计

作为一名合格的 Android 开发者,也应该拥有 Android 相关的设计素养,而不是设计师给了一个不标准(或者像 iOS)的设计,却不知道提出意见或修正。

Android 现有的设计规范是 Material Design,已经初步成熟,但有时还是会有小的补充,因此建议始终阅读这个文档。在入门 Android 开发后,建议抽出时间,将这份文档通读一遍,以了解符合 Android 平台的设计。另外,我见过的几个中文版设计文档翻译质量一般,排版较差,还有一些时效性问题,因此不是十分建议阅读。

Material Design 的实现主要依靠支持库中的 AppCompat 和 Design 库,目前文档不算十分完善,可以参考 BlogSpot 上的相关介绍Chris Banes 的 CheeseSquare,辅助以 StackOverflow、Google Code Issues 和部分国外博客。

为了精确地实现界面布局,可以使用开发者选项中的“显示视图边界”来确定各个视图的大小和位置,此外 KeylinePushing 是一款十分好用的工具应用,可以在屏幕上显示关键线和网格,帮助检视视图布局。

关于设计资源。Material Icons 是 Google 的开源 Material Design 图标库,内容十分丰富,也符合设计规范。Android Asset Studio 曾经十分流行,但由于 Material Design 的引入开始有些过时,不过启动器图标和设备预览图的生成器依然可以使用。

信息来源

Android 的更新发展较为快速,为了跟进 Android 最新的动态,可以了解、关注或订阅以下的人或内容。其中,Google Plus 可以作为一个干净的技术社交平台(屏蔽几个热门推荐就好),有很多活跃的 Android 开发者和社群,十分建议加入。

建议:

  • +Android Developers:Android 官方帐号,新闻、ProTip、工具更新等,十分值得关注。
  • +Chris Banes:Andorid Design 支持库开发者,Android 相关动态更新频繁。

推荐:

  • +Roman Nurik:Android 视图组件开发者,同时也是一位优秀的摄影师,你或许不经意间也见过他的作品。
  • +Dannie Hackborn:Android 框架开发者。
  • +Chet Haase:Android 框架开发者,写作此文时正在开发 Data Binding 支持库。
  • Android Developers Blog:Android Developers 的博客,可 RSS 订阅,不过 +Android Developers 在它更新时一般会发动态推荐。
  • Android Tools Project Site – Recent Changes:Android Studio、Gradle 插件、SDK 等工具更新,可 RSS 订阅,不过 +Android Developers 在它更新时一般会发动态推荐。
  • Android Developers on YouTube:Android 开发相关视频,可关注,不过 +Android Developers 在它更新时一般会发动态推荐。
  • Android Developers Backstage:Android 开发相关 PodCast,可选择关注。
  • Jake Wharton’s Blog,ButterKnife、ActionBarSherlock,Andorid-PullToRefresh 等知名开源项目作者,就职于 Square,博客更新已不太频繁,但有深度。
  • CommonsWare’s Blog:StackOverflow 知名答主,CWAC 系列组件作者,某本 Android 开发书籍的作者,博客内容大多优质,但十分喜欢卖书,在 Google+ 上主要推销他的书……

结语

学习和开发是一个循序渐进的过程,通过一个实际项目,不断接触 Android 的方方面面,例如网络、数据库、视图、资源系统、组件、支持库等等,最终才可以对 Android 开发拥有深入的了解。祝各位学习顺利。

由来

这是我在作为学生组织的面试官时,对熟练掌握 C/C++ 的面试者提出几个的问题,以及对于这几个问题我自己的答案。

语法规则和特定技巧很大程度上只是记忆的问题,而在几个事实上知晓与否并不能断定面试者的高下。C 语言是一门十分接近底层实现的语言,许多的设计决定与实现直接相关,如果想要合适地运用,也要求程序员对于 C 的实现有透彻的理解。因此,这几个问题主要考察的是面试者在 C 原理与设计层面上的理解。

问题

  1. 为什么以下的语句不会导致非法内存访问?

    1
    2
    3
    4
    5
    struct {
    int i;
    float f;
    } *s = NULL;
    printf("%p", &(*s).f);
  2. C++ 中的类实现了继承。请简单阐述如何在 C 中实现 struct 的继承。

  3. 在 C 中 void * 类型可以自动转换成其他指针类型,但在 C++ 中需要显式转换。请谈一谈 C 和 C++ 为什么采取了这两种设计。

  4. (请简述 C 语言中变量自动初始化的规则。)

    在 C 语言中,全局变量和静态局部变量可以自动初始化为全零,但局部变量不会进行自动初始化。请谈一谈 C 为什么采取了这样的设计。

我的答案

  1. C 语言中类型只存在于编译前,struct只是对偏移量计算等操作的简化而已,因此,计算 &(*s).f,不过是计算s + sizeof(s.i) 而已,并不会像其他语言那样导致空指针异常。

  2. 因为 C 中的 struct 不过是对偏移量计算的简化,可以通过在子类的头部直接放置一个父类成员,之后进行类型转换,就能实现数据成员的继承,示例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    typedef struct {
    int i;
    } base_t;

    typedef struct {
    base_t base;
    float f;
    } derived_t

    derived_t *derived = malloc(sizeof(derived_t));
    base_t *base = (base_t *) derived;

    为了实现函数的继承和多态,则需要函数指针,this指针参数,构造器等实现,此处不再赘述。

  3. 在 C 中,void *指向的是未知类型的内存区域,无论按照什么类型理解,都是合法的;并且 malloc 这样的常用调用返回类型只能是void *,允许自动转换可以大大减少麻烦。

    在 C++ 中,类型可能是类,而为了实现面向对象编程的特性,在类中保存了一些元信息,因此将一块未知的内存区域作为一个类来理解,这个操作不一定合法;为了保持类和非类指针在语法上的统一,选择了禁止 void * 的自动转换。

  4. 由于很多全局变量的初始值都需要是零,而在可执行文件中存储这么多零并没有意义,因此 C 语言规定全局变量初值为零,由此可以在生成的可执行文件中只记录全局变量所需空间的大小,而省略为零的值。至于静态局部变量,它的生命周期要求与全局变量相同,只是在编译时赋予了不同作用域的限制而已。为了实现这个特性,在大多数操作系统中,全局变量和静态局部变量被存放在一个全局的数据存储位置(BSS 段),在程序开始执行前由系统进行分配和清零。

    局部变量与函数调用相关,在系统调用栈上动态分配,自动初始化将会在运行时不断带来额外负担,因此 C 语言将初始化交给程序员处理,不进行自动初始化。

结语

理解了 C 的实现与设计之后,使用 C 就得心应手了--你了解语言这样设计的原因和目的,也知道自己的一行代码会被翻译成怎样的指令,由此,豁然开朗。

当然,C 有适合的任务,也有不适合的任务,任何语言都是如此,所以复杂性上的欠缺并不影响我对 C 语言的评价。

大道至简,C 的许多设计与 Unix 哲学若合一契。也是因此我对 C/C++ 这种说法不太喜欢。