Android 开发最佳实践

Android 开发最佳实践

构建系统

默认编译环境应该是Gradle. Ant 有很多限制,用起来也很不方便,并且现在Github上的开源项目都是采用的Gradle。使用Gradle,完成以下工作会很方便:

  • 构建APP不同版本的变种,方便于向不同市场发布App
  • 很方便的管理和下载依赖,这个是最显而易见的,依赖第三方的库一句话搞定
  • 制作简单类似脚本的任务
  • 自定义秘钥

同时,Android Gradle是Google推荐的作为新标准的构建系统,并还在不断的完善中。

工程结构

现阶段有两种流行的结构:老的Ant & Eclipse ADT 工程结构,和新的Gradle & Android Studio 工程结构,我们开发推荐的应选择新的工程结构,并且会发现将我们的工程从老的结构移植到新的结构后,我们的开发效率和质量都将会得到质的提升。下面是两种结构:

老的结构:

old-structure
├─ assets
├─ libs
├─ res
├─ src
│  └─ com/futurice/project
├─ AndroidManifest.xml
├─ build.gradle
├─ project.properties
└─ proguard-rules.pro

新的结构

new-structure
├─ library-foobar
├─ app
│  ├─ libs
│  ├─ src
│  │  ├─ androidTest
│  │  │  └─ java
│  │  │     └─ com/futurice/project
│  │  └─ main
│  │     ├─ java
│  │     │  └─ com/futurice/project
│  │     ├─ res
│  │     └─ AndroidManifest.xml
│  ├─ build.gradle
│  └─ proguard-rules.pro
├─ build.gradle
└─ settings.gradle

这两种结构主要的区别在于,新的结构明确的分开了'source sets' (main,androidTest),这是Gradle的一个理念。我们的项目引用第三方项目库时(例如,library-foobar),拥有一个顶级包名app从第三方库项目区分你的应用程序是非常有用的。然后settings.gradle不断引用这些库项目,其中app/build.gradle可以引用。

Gradle 配置

常用结构 参考Google's guide on Gradle for Android

密码 在做版本release时我们app的 build.gradle需要定义 signingConfigs.此时应该避免以下内容:

不要做这个 . 这会出现在版本控制中。

signingConfigs {
	release {
		storeFile file("myapp.keystore")
		storePassword "password123"
		keyAlias "thekey"
		keyPassword "password789"
	}
}

而是,建立一个不加入版本控制系统的gradle.properties文件。

KEYSTORE_PASSWORD=password123
KEY_PASSWORD=password789

那个文件是gradle自动引入的,你可以在buld.gradle文件中使用,例如:

signingConfigs {
	release {
		try {
			storeFile file("myapp.keystore")
			storePassword KEYSTORE_PASSWORD
			keyAlias "thekey"
			keyPassword KEY_PASSWORD
		}
		catch (ex) {
			throw new InvalidUserDataException("You should define KEYSTORE_PASSWORD and KEY_PASSWORD in gradle.properties.")
		}
	}
}

使用 Maven 依赖方案代替使用导入jar包方案 如果在我们的项目依赖工程使用的是jar文件,那么它们可能成为永久的版本,如2.1.1.下载jar包更新他们是很繁琐的,这个问题Maven很好的解决了,这也是Android Gradle构建中推荐的方法。我们可以指定版本的一个范围,如2.1.+,然后Maven会自动升级到制定的最新版本,例如:

dependencies {
	compile 'com.netflix.rxjava:rxjava-core:0.19.+'
	compile 'com.netflix.rxjava:rxjava-android:0.19.+'
	compile 'com.fasterxml.jackson.core:jackson-databind:2.4.+'
	compile 'com.fasterxml.jackson.core:jackson-core:2.4.+'
	compile 'com.fasterxml.jackson.core:jackson-annotations:2.4.+'
	compile 'com.squareup.okhttp:okhttp:2.0.+'
	compile 'com.squareup.okhttp:okhttp-urlconnection:2.0.+'
}

IDE集成开发环境和文本编辑器

无论使用什么编辑器,一定要构建一个良好的工程结构 当下首推Android Studio,因为他是由谷歌开发,最接近Gradle,默认使用最新的工程结构,已经到1.3的稳定版本,它就是为Android开发定制的。

我们也可以使用Eclipse ADT ,但是需要对它进行配置,因为它使用了旧的工程结构和Ant作为构建系统

无论使用何种开发工具,只要确保Gradle和新的项目结构保持官方的方式构建应用程序,避免编辑器配置文件加入到版本控制。例如,避免加入Ant build.xml文件。 特别如果需要改变Ant的配置,不要忘记保持build.gradle是最新和起作用的。同时,善待其他开发者,不要强制改变他们的开发工具和偏好。

开源类库推荐

Jackson 是一个将java对象转换成JSON与JSON转化java类的类库。Gson是解决这个问题的流行方案,然而Jackson更高效,因为它支持替代的方法处理JSON:流、内存树模型,和传统JSON-POJO数据绑定。不过,Jsonkson库比起GSON更大,所以根据我们的情况选择,你可能选择GSON来避免APP 65k个方法限制。其它选择: Json-smart and Boon JSON

网络请求,缓存,图片 执行请求后端服务器,有几种交互的解决方案,我们可以使用Volley或者Retrofit来实现自己的网络客户端。Volley同时提供图片缓存类。若果我们选择使用Retrofit,那么可以考虑使用Picasso来加载图片和缓存,同时使用OkHttp作为高效的网络请求。Retrofit,Picasso和OkHttp都是有同一家公司开发(注:是由Square公司开发),所以它们能很好的在一起运行。OkHttp 同样可以和Volley在一起使用 Volley.

当心dex方法数限制,同时避免使用过多的类库 Android apps,当打包成一个dex文件时,有一个65535个应用方法强硬限制 [1] https://medium.com/@rotxed/dex-skys-the-limit-no-65k-methods-is-28e6cb40cf71 [2] http://blog.persistent.info/2014/05/per-package-method-counts-for-androids.html [3] http://jakewharton.com/play-services-is-a-monolith/。 当我们突破65k限制之后又会看到一个致命错误。因此,使用一个正常范围的类库文件,同时使用dex-method-counts工具来决定哪些类库可以再65k限制之下使用,特别的避免使用Guava类库,因为它包含超过13k个方法。

Activities and Fragments

Fragments应该作为你实现UI界面默认选择。可以重复使用Fragments接口来组合成我们的应用。并强烈推荐使用Fragments而不是activity来呈现UI界面,理由如下:

  • 提供多窗格布局解决方案 Fragments 的引入主要将手机应用延伸到平板电脑,所以在平板电脑上你可能有A、B两个窗格,但是在手机应用上A、B可能分别充满整个屏幕。如果你的应用在最初就使用了fragments,那么以后将你的应用适配到其他不同尺寸屏幕就会非常简单。

  • 屏幕间数据通信 从一个Activity发送复杂数据(例如Java对象)到另外一个Activity,Android的API并没有提供合适的方法。不过使用Fragment,你可以使用一个activity实例作为这个activity子fragments的通信通道。即使这样比Activity与Activity间的通信好,当然也可以使用Event Bus架构,不过这个不推荐过多的使用,因为这个过多的使用会造成很高的维护成本。

  • Fragments 一般通用的不只有UI 可以有一个没有界面的fragment作为Activity提供后台工作。进一步可以使用这个特性来创建一个fragment包含改变其它fragment的逻辑而不是把这个逻辑放在activity中。

  • 甚至ActionBar 都可以使用内部fragment来管理 可以选择使用一个没有UI界面的fragment来专门管理ActionBar,或者你可以选择使用在每个Fragment中添加它自己的actionBar 来作为父Activity的ActionBar.参考.

主要事项:不建议广泛的使用嵌套的fragments,因为有时会引起matryoshkabugs。我们只有当它有意义(例如,在水平滑动的ViewPager在像屏幕一样fragment中)或者他的确是一个明智的选择的时候才广泛的使用fragment。

在一个架构级别,APP应该有一个顶级的activity来包含绝大部分业务相关的fragment。我们也可能还有一些辅助的activity ,这些辅助的activity与主activity通信一般采用这两种方法Intent.setData()Intent.setAction()或类似的方法。

Java 包结构

Android 应用程序在架构上大致是Java中的Model-View-Controller结构。在Android 中Fragment和Activity通常上是控制器类(http://www.informit.com/articles/article.aspx?p=2126865).换句话说,他们是用户接口的部分,同样也是Views视图的部分。

正是因为如此,才很难严格的将fragments (或者 activities) 划分成控制器controlloers还是视图 views。最好还是将它们放在自己单独的 fragments 包中。只要遵循之前提到的建议,Activities 则可以放在顶级目录下。若果规划有2到3个以上的activity,那么还是同样新建一个activities包吧。

这种架构可以看做是另一种形式的MVC,包含要被解析API响应的JSON数据,来填充的POJO的models包中。 和一个views包来包含自定义视图、通知、导航视图,widgets等等。适配器Adapter是在数据和视图之间。然而他们通常需要通过getView()方法来导出一些视图,所以可以将adapters包放在views包里面。

一些控制器角色的类是应用程序级别的,同时是接近系统的。这些类放在managers包下面。 一些繁杂的数据处理类,比如说"DateUtils",放在utils包下面。 与后端交互负责网络处理类,放在network包下面。

总而言之,以最接近用户而不是最接近后端去安排他们。

com.futurice.project
├─ network
├─ models
├─ managers
├─ utils
├─ fragments
└─ views
   ├─ adapters
   ├─ actionbar
   ├─ widgets
   └─ notifications

资源文件 Resources

  • 命名 遵循前缀表明类型的习惯,形如type_foo_bar.xml。例如:fragment_contact_details.xml,view_primary_button.xml,activity_main.xml.

组织布局文件 若不确定如何排版好一个布局文件,遵循统一的规则会很好的提高效率。

  • 每一个属性一行,缩进4个空格
  • android:id 总是作为第一个属性
  • android:layout_**** 属性在上边
  • style 属性在底部
  • 关闭标签/>单独起一行,有助于调整和添加新的属性
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:tools="http://schemas.android.com/tools"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="vertical"
	>

	<TextView
		android:id="@+id/name"
		android:layout_width="match_parent"
		android:layout_height="wrap_content"
		android:layout_alignParentRight="true"
		android:text="@string/name"
		style="@style/FancyText"
		/>

	<include layout="@layout/reusable_part" />

</LinearLayout>

作为一个经验法则,android:layout_****属性应该在 layout XML 中定义,同时其它属性android:**** 应放在 styler XML中。这个规则也有例外,不过大部分情况下都是很好的。这个思想整体是保持layout属性(positioning, margin, sizing) 和content属性在布局文件中,同时将所有的外观细节属性(colors, padding, font)放在style文件中。

例外有以下这些:

  • android:id 明显应该在layout文件中
  • layout文件中android:orientation对于一个LinearLayout布局通常更有意义
  • android:text 由于是定义内容,应该放在layout文件中
  • 有时候将android:layout_widthandroid:layout_height属性放到一个style中作为一个通用的风格中更有意义,但是默认情况下这些应该放到layout文件中。

使用styles 几乎每个项目都需要适当的使用style文件,因为对于一个视图来说有一个重复的外观是很常见的。 在应用中对于大多数文本内容,最起码应该有一个通用的style文件,例如:

<style name="ContentText">
	<item name="android:textSize">@dimen/font_normal</item>
	<item name="android:textColor">@color/basic_black</item>
</style>

应用到TextView 中:

<TextView
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:text="@string/price"
	style="@style/ContentText"
	/>

其实在项目中不仅仅是对于文本需要通用的style,对于一组相关的和重复android:****的属性都应该放到一个通用的style中。

将一个大的style文件分割成多个文件 为了不去维护一个很大的styles.xml,我们可以有多个styles.xml 文件。需要注意的是styles这个文件名称并没有作用,起作用的是在文件里xml的<style>标签。因此我们项目中可以有多个style文件styles.xml,style_home.xml,style_item_details.xml,styles_forms.xml

colors.xml是一个调色板 在我们的colors.xml文件中应该只是映射颜色的名称一个RGBA值,而没有其它的。不要使用它为不同的按钮来定义RGBA值。

不要这样做

<resources>
	<color name="button_foreground">#FFFFFF</color>
	<color name="button_background">#2A91BD</color>
	<color name="comment_background_inactive">#5F5F5F</color>
	<color name="comment_background_active">#939393</color>
	<color name="comment_foreground">#FFFFFF</color>
	<color name="comment_foreground_important">#FF9D2F</color>
	...
	<color name="comment_shadow">#323232</color>

使用这种格式,会很容易的造成重复定义RGBA值的情况,这样如果需要改变一些基本色就会变的很复杂。同时,这些定义是跟一些环境关联起来的,如button或者comment,但这些属性应该放到一个按钮风格中,而不是在color.xml文件中。

相反,应该这样做:

<resources>

	<!-- grayscale -->
	<color name="white"     >#FFFFFF</color>
	<color name="gray_light">#DBDBDB</color>
	<color name="gray"      >#939393</color>
	<color name="gray_dark" >#5F5F5F</color>
	<color name="black"     >#323232</color>

	<!-- basic colors -->
	<color name="green">#27D34D</color>
	<color name="blue">#2A91BD</color>
	<color name="orange">#FF9D2F</color>
	<color name="red">#FF432F</color>

</resources>

调色板可以从美工那里得到,名称不需要跟"green", "blue"等等相同。"brand_primary", "brand_secondary", "brand_negative" 这样的名字也是完全可以接受的。像这样规范的颜色很容易修改或重构,会使应用一共使用了多少种不同的颜色变得非常清晰。通常一个具有审美价值的UI来说,减少使用颜色的种类是非常重要的。

像对待colors.xml一样对待dimens.xml文件 与定义颜色调色板一样,我们同时也应该定义一个空隙间隔和字体大小的统一的文件。 如下所示:

<resources>

	<!-- font sizes -->
	<dimen name="font_larger">22sp</dimen>
	<dimen name="font_large">18sp</dimen>
	<dimen name="font_normal">15sp</dimen>
	<dimen name="font_small">12sp</dimen>

	<!-- typical spacing between two views -->
	<dimen name="spacing_huge">40dp</dimen>
	<dimen name="spacing_large">24dp</dimen>
	<dimen name="spacing_normal">14dp</dimen>
	<dimen name="spacing_small">10dp</dimen>
	<dimen name="spacing_tiny">4dp</dimen>

	<!-- typical sizes of views -->
	<dimen name="button_height_tall">60dp</dimen>
	<dimen name="button_height_normal">40dp</dimen>
	<dimen name="button_height_short">32dp</dimen>

</resources>

布局时在写 margins 和 paddings时,应该使用spacing_****尺寸格式来布局,而不是像对待String字符串一样直接写值。这样写会非常有感觉,会使组织和改变风格或布局是非常容易。

避免深层次的视图结构 有时候为了摆放一个视图,我们可能尝试添加另一个LinearLayout。也可能使用这种方法解决:

<LinearLayout
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:orientation="vertical"
	>

	<RelativeLayout
		...
		>

		<LinearLayout
			...
			>

			<LinearLayout
				...
				>

				<LinearLayout
					...
					>
				</LinearLayout>

			</LinearLayout>

		</LinearLayout>

	</RelativeLayout>

</LinearLayout>

这种多层次的嵌套结构就很可能会导致一系列的问题。最主要的会是遇到性能问题,因为这就需要处理一个复杂的UI树结构,系统在测量和绘制界面就会花费很长的时间,如果这一系列的操作(网络请求,界面测量,布局,绘制等等)不能在16ms中完成,最终结果就会出现界面掉帧及卡顿的情况,这也是系统卡顿出现的最主要的原因之一。因此我们尽量是用RelativeLayout来替代LinearLayout,维护一个简洁的视图tree

小心关于WebViews的问题. 如果必须显示一个web视图,比如说对于一个新闻文章,要避免做客户端处理HTML的工作,最好让后端工程师协助,让他返回一个 "" HTML。WebViews 也能导致内存泄露,当使用简单的文字或按钮时,应避免使用WebView,这时使用TextView或Buttons更好。

测试框架

Android SDK的测试框架还处于初级阶段,特别是关于UI测试方面。Android Gradle 目前实现了一个叫connectedAndroidTest的测试,它使用一个JUnit 为Android提供的扩展插件 extension of JUnit with helpers for Android.可以跑生成的JUnit测试,

模拟器

毫无疑问的会推荐Genymotion 模拟器,他的运行速度比起典型的AVD模拟器会有质的提升。他可以高质量的模拟网络连接,GPS位置,等等。它同时还有理想的连接测试。若涉及适配使用很多不同的设备,买一个Genymotion版权是比买很多真设备便宜多的。

注意:Genymotion模拟器没有装载所有的Google服务,如Google Play Store和Maps(这个可以通过第三方的方法来给我们的模拟器装上Google的GSM)。

混淆配置

ProGuard是一个在Android项目中广泛使用的压缩和混淆打包的源码的工具。

是否使用ProGuard取决我们项目的配置,当构建一个release版本的apk时,通常应该配置gradle文件。

buildTypes {
	debug {
		minifyEnabled false
	}
	release {
		signingConfig signingConfigs.release
		minifyEnabled true
		proguardFiles 'proguard-rules.pro'
	}
}

Android framework 使用一个默认的配置文件,可以在SDK_HOME/tools/proguard/proguard-android.txt目录下找到。自定义的工程在project-specific中指定混淆规则,如在my-project/app/proguard-rules.pro中定义,会被添加到默认的配置中。

关于 ProGuard 一个普遍的问题,是看应用程序是否崩溃并报ClassNotFoundException或者NoSuchFieldException 或类似的异常,即使编译是没有警告并运行成功。这意味着以下两种可能:

  1. ProGuard 已经移除了类,枚举,方法,成员变量或注解。
  2. ProGuard 混淆了类,枚举,成员变量的名称,但是这些名字又被原始名称使用了,比如通过Java的反射。

检查app/build/outputs/proguard/release/usage.txt文件看有问题的对象是否被移除了。 检查 app/build/outputs/proguard/release/mapping.txt 文件看有问题的对象是否被混淆了。

以防 ProGuard 剥离 需要的类和类成员,添加一个 keep选项在proguard 配置文件中:

-keep class com.futurice.project.MyClass { *; }

防止 ProGuard 混淆 一些类和成员,添加 keepnames:

-keepnames class com.futurice.project.MyClass { *; }

可以查看this template's ProGuard config 中的一些例子。 更多例子请参考Proguard

在构建项目之初,发布一个版本 来检查ProGuard规则是否正确的保持了重要的部分。同时无论何时当添加了新的类库,做一个发布版本,同时apk在设备上跑起来测试一下。不要等到app要发布"1.0"版本了才做版本发布,那时候可能会碰到好多意想不到的异常,需要一些时间去修复他们。

转载于:https://my.oschina.net/crazy261/blog/498241

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值