领先一步
VMware 提供培训和认证,助您加速进步。
了解更多假设你某天早上醒来,想:“嘿,今天我要做一个 Android 应用。” 首先,这是个绝妙的选择!截至六月底,每天有 500,000 台 Android 设备被激活,激活速度甚至超过了 iPhone。这意味着你的应用拥有庞大的潜在用户群体。此外,Android 是用 Java 构建的。这可能听起来没什么大不了,但我曾在 iOS 平台用Objective-C工作过几年,虽然我现在对此相当熟悉,但iOS SDK的学习曲线比我接触 Android 时要陡峭得多。iOS SDK。当我开始使用Android SDK时,Android 感觉更易于上手。话虽如此,它与你过去开发的任何其他 Java 应用都存在一些明显的区别,我将在第一部分介绍其中的一些。
再往前推移,您完成了第一个应用,并将其提交到了 Android Market。恭喜您,您的朋友们都在下载您的应用并在 Twitter 上讨论。现在该开始制作您的第二个应用了。您花了几天时间,突然意识到您开始重用第一个应用的代码,这本身并不是坏事。代码重用很有价值。但您会注意到有很多样板代码经常重复出现,这会分散您对业务逻辑的注意力。幸运的是,有一些方法可以改进这一点。
在这篇博文中,我将概述 Android 和应用程序生命周期,并讨论该框架施加的一些限制。我还将回顾一些可以帮助您清理 Android 代码、专注于应用目标的技术和第三方项目。
让我们从简要概述 Android 的工作原理开始。Android 应用程序(应用)使用 Java 构建,并编译为类文件。然后,类文件被编译为 Dalvik 可执行(DEX)格式,以便在 Android 使用的 Dalvik 虚拟机上运行。转换为 DEX 格式后,类文件会被打包成 Android 包(APK)分发到设备上。由于使用了 DEX 格式,Dalvik VM 并不是真正的 Java 虚拟机,因为它不操作 Java 字节码。此外,Dalvik VM 基于 Apache Harmony 项目的一个子集作为其核心类库。这意味着许多您在 Java SE 中习惯使用的类和方法是可用的,但肯定不是全部。我发现 Android 开发者网站上的 API 参考和Android 开发者网站对于回顾这些差异是非常宝贵的资源。
默认情况下,Android 操作系统会为每个 Android 应用分配一个唯一的 Linux 用户 ID。当系统启动时,一个应用会在自己的虚拟机 (VM) 中,以自己的 Linux 进程运行。系统会根据需要管理该进程的启动和关闭。正如你所料,这意味着每个应用都与其他正在运行的应用隔离开来。安装时,应用可以请求权限来访问硬件功能或与其他应用交互。用户可以选择授予这些权限给应用,否则可以选择不安装。应用所需或请求的权限在每个应用的 Android Manifest 文件中定义。这是一个 XML 文件,列出了应用的所有组件以及这些组件的任何设置。应用组件的四种类型是Activity、Service、ContentProvider和BroadcastReceiver。在本帖中,我将重点关注 Activity。
活动基本上代表 Android 应用程序的一个屏幕。例如,Twitter 应用可能有一个登录屏幕、一个显示推文列表的屏幕和一个撰写新推文的屏幕。这些屏幕中的每一个都代表了应用程序中的不同活动。作为开发人员,您永远不会自己实例化一个活动对象。活动是通过发送一个名为Intent 的异步消息来激活的,如下面的示例所示。
startActivity(new Intent(context, HomeActivity.class));
当调用startActivity(Intent intent) 时,系统会创建一个新实例或重用现有实例以向用户显示活动。关键点在于,系统控制着应用程序和每个活动的启动、停止、创建和销毁。如果您想与此过程交互,那么应用程序和活动类会提供不同生命周期事件的方法,您可以在子类中覆盖它们。
Spring Android 项目最近发布了其第四个里程碑版本。通过这次发布,我们继续改进了对 Android 的RestTemplate和Spring Social支持,这简化了进行 RESTful HTTP 请求和访问由OAuth保护的 REST API 的过程。虽然我们认为这些是对 Android 开发有价值的补充,但一些开发者询问了 Spring Android 中对依赖注入支持的可能性,因为你可能知道,Spring Framework已经提供了一个流行的控制反转 (IOC) 容器,用于在企业 Java 应用中实现依赖注入。在 Spring Android 的早期规划阶段,依赖注入支持就被确定为该项目潜在的纳入项。当时,尚不清楚这种支持将包含什么内容,以及如何实现。因此,我开始着手研究和调查在 Android 中进行依赖注入的可行方法和局限性。
那么,什么是依赖注入呢?如果您问两个不同的开发人员,您可能会得到两个不同的答案。您可能会听到 IOC、XML 文件、注解或其他一些实现细节。实际上,依赖注入只是一种通过将对象所需的东西直接交给它,而不是让对象主动去环境中获取,来减少耦合的技术。这听起来很简单,您可能会想,您可以通过类构造函数和 setter 方法来实现这一点,这是完全正确的。但是,请回想一下上面概述的部分,Android 系统驱动着应用程序的生命周期,所以我们做到这一点方式是有限的。
在不使用任何第三方库的情况下,将依赖项传递给 Activity 相当容易。如前所述,系统会创建应用程序实例。因此,通过扩展应用程序,您可以有效地创建一个单例依赖项实例,然后该应用程序中的任何活动都可以访问它。
public class MainApplication extends Application {
private MyService service;
@Override
public void onCreate() {
super.onCreate();
service = new MyServiceImpl();
}
public MyService getMyService() {
return this.service;
}
}
Activity 类有一个名为getApplication() 的方法,它返回一个对拥有该活动的应用程序对象的引用。我们只需将其强制转换为 MainApplication,就可以访问 MyService 的 getter 方法。当然,Activity 现在必须“知道”应用程序,这可能看起来是一个缺点。但请记住,Activity 已经知道它的应用程序。该方法是内置的。
public class MainActivity extends Activity {
private MyService service;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MainApplication app = (MainApplication) getApplication();
service = app.getMyService();
}
}
RoboGuice 项目利用 Google 的Guice库为 Android 添加依赖注入支持。Guice 本身有两种版本:带 AOP(面向切面编程)支持和不带 AOP 支持。内部而言,标准的 Guice 依赖于字节码生成来实现方法拦截。然而,Android 不支持运行时字节码生成,因此 RoboGuice 依赖于不带 AOP 的 Guice 版本。让我们看看如何使用 RoboGuice 实现之前的示例。要添加自定义绑定,你必须实现一个扩展自RoboApplication的 Application 对象。然后重写addApplicationModules(...)方法,并添加一个实例模块来绑定你的对象。
public class MainApplication extends RoboApplication {
@Override
protected void addApplicationModules(List<Module> modules) {
// add your module with custom bindings
modules.add(new MainModule());
}
@Override
public void onCreate() {
super.onCreate();
}
}
AbstractAndroidModule扩展自标准的 Guice AbstractModule。重写configure()方法来指定绑定。
public class MainModule extends AbstractAndroidModule {
@Override
protected void configure() {
bind(MyService.class).to(MyServiceImpl.class);
}
}
每个 Activity 都必须继承自RoboActivity,这样才能进行注入。
public class MainActivity extends RoboActivity {
@Inject
MyService service;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
这个例子并不十分令人印象深刻,因为它需要更多的代码来完成相似的任务。RoboGuice 的集成在由多个领域模块组成、封装了大量业务逻辑的更大型应用中更为有用。此外,它提供的注入视图、资源和系统服务的支持通常很有用。你可以在下面的示例中看到这一点。第一个示例说明了标准的 Android 方法,而第二个示例则使用了 RoboGuice。正如你所看到的,RoboGuice 允许你消除大量样板查找代码。
public class MyActivity extends Activity {
private TextView label;
private Drawable image;
private SearchManager searchManager;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.myactivity);
this.label = (TextView) findViewById(R.id.mylabel);
this.image = getResources().getDrawable(R.drawable.myimage);
this.searchManager = (SearchManager) getSystemService(Activity.SEARCH_SERVICE);
}
}
public class MyActivity extends RoboActivity {
@InjectView(R.id.mylabel)
TextView label;
@InjectResource(R.drawable.myimage)
Drawable image;
@Inject
SearchManager searchManager;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.myactivity);
}
}
这里还有一些关于编程模型的额外要点。以前我们扩展自标准的 Android 类,现在我们扩展自 RoboGuice 特定的变体。这让我们回到了关于 Android 架构的前一节。之所以需要子类化框架特定的类型,是因为第三方框架能够挂钩到 Android 应用生命周期的方式是有限的。此外,注入是在运行时执行的,这会带来性能和占用空间的成本。最后,值得注意的是,最新发布的版本基于 Guice 2.0。据我所知,Guice 3.0 的更新正在进行中。
我目前不确定 RoboGuice 的好处是否足够大,足以证明将应用程序与第三方框架紧密耦合是值得的。我通常发现实现自己的手动依赖注入技术(如前一节所述并在Greenhouse参考应用中演示的)已经足够了。此外,Guice 本身也不小,会增加大约 400 KB 的应用大小。RoboGuice 项目页面甚至讨论了这一点,在配置ProGuard以减小应用大小时。虽然配置 ProGuard 不一定容易,但对任何应用使用它也是一个好习惯,无论你是否使用 RoboGuice。
我们已经讨论了依赖注入和 RoboGuice,以及该项目方法的优缺点。本帖的标题是关于 Android 中的清晰代码以及如何减少冗余,所以现在让我们超越依赖注入,讨论一些其他技术。
Android Annotations 是一个由 Pierre-Yves Ricau 发起和维护的项目。通过使用注解,该项目的目标是专门帮助减少 Android 项目中的样板代码量。它并不是一个通用的依赖注入框架,事实上,它可以与 RoboGuice 并存。虽然存在一些重叠,因为 Android Annotations 提供了一些用于注入视图和资源的类似注解,但它还提供了许多其他有用的功能。主要区别在于 Android Annotations 在编译时生成样板代码,因此使用它不会带来运行时开销。它通过为每个 Activity 生成一个子类来实现这一点,并将注解替换为标准的样板代码。这种方法的一个小缺点是,你必须在清单文件中为每个 Activity 的名称附加一个下划线。例如,如果我创建一个 MyActivity 类,Android Annotations 将生成一个对应的 MyActivity_ 类。此外,startActivity(...) 中的任何引用都必须更新为使用新类。
在这里你可以看到与 RoboGuice 的一些相似之处。这是 RoboGuice 示例的 Android Annotations 版本。请注意,在类上使用 @EActivity 注解允许你设置 Activity 的布局。另一个不错的细节是,如果你省略注解中的资源 ID,Android Annotations 将会查找名称与变量匹配的资源 ID。
@EActivity(R.layout.myactivity) // Sets content view to R.layout.myactivity
public class MyActivity extends Activity {
@InjectView // Injects R.id.mylabel
TextView mylabel;
@DrawableRes(R.drawable.myimage)
Drawable image;
@SystemService
SearchManager searchManager;
}
注解库相当广泛。目前,它支持 SDK 的几个领域:
这里还有几个关于可能性的例子。我想重点介绍点击事件处理和线程支持,因为它们真正说明了该库的强大之处。
public class AnotherActivity extends Activity {
@Click // When R.id.runProcess button is clicked
void runProcess() {
runInBackground();
}
@Background // Executed in a background thread
void runInBackground() {
// perform some long running task
notifyUser();
}
@UiThread // Executed in the ui thread
void notifyUser() {
// display a notification that task is complete
}
}
一些开发者可能会因为使用代码生成而望而却步,但考虑到 Android 架构的局限性,Android Annotations 提供了一种优雅的方法来最大限度地减少样板代码的使用,帮助你专注于业务逻辑,同时又不增加运行时开销。
最后我想谈谈Android Binding项目。Android Binding 是一个MVVM(模型-视图-ViewModel)框架,旨在将 Activity 与直接操作用户界面分离。为此,几乎所有的代码都移出了 Android Activity 类,并放入 ViewModel 对象中,这旨在提高可测试性。ViewModel 对象处理视图(布局)的所有事件和数据,但它与视图并没有紧密耦合。视图中发生的事件作为命令发送到 ViewModel。这些命令然后执行一些业务逻辑,并可能与模型交互,更新 ViewModel 的属性,以便视图可以绑定到它们。如果你曾经使用过 .Net,那么数据绑定的概念对你来说应该很熟悉,而 Android Binding 致力于将这一概念应用于 Android。Android Binding 由Andy Tsui发起并维护。它还很年轻,缺乏文档,但有不错的潜力。
下面的示例显示了一个扩展自 BindingActivity 的 EmailActivity。当你扩展自 BindingActivity 时,你可以使用 setAndBindRootView(...) 方法为视图设置 Model。
public class EmailActivity extends BindingActivity {
private EmailViewModel model;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = new EmailViewModel();
setAndBindRootView(R.layout.main, model);
}
}
下一个示例片段显示了视图声明。对我来说,这是真正有趣的部分。看看这个布局文件,你可以看到添加了“binding”属性,以及自定义的“binding”xmlns 声明。这个例子展示了绑定两个文本字段和一个按钮,但该项目支持绑定到许多其他控件。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:binding="http://www.gueei.com/android-binding"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
android:id="@+id/subject"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
binding:text="subject"
/>
<EditText
android:id="@+id/message"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
binding:text="message"
/>
<Button
android:id="@+id/submit"
android:text="Submit"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
binding:onClick="submit"
/>
</LinearLayout>
最后,EmailViewModel 包含视图的数据和控件逻辑。下面的摘录显示了文本字段值的 Observables,以及当提交按钮被点击时执行的 Command。
public class EmailViewModel {
@Required
public StringObservable subject = new StringObservable();
@Required
public StringObservable message = new StringObservable();
public Command submit = new Command() {
public void Invoke(View arg0, Object... arg1) {
ValidationResult result = ModelValidator.ValidateModel(EmailViewModel.this);
if (result.isValid()){
// send email
} else {
// display validation error message
}
}
};
}
这个库还有比我在这里包含的更多的内容。它还支持通过使用一组注解进行模型验证。有一些内置规则,你可以使用 RegEx 模式匹配,也可以实现自己的自定义规则。最新版本还增加了对绑定到 Options menu的支持。
与 RoboGuice 一样,Android Binding 是基于运行时的,并且要求你继承自特定的 Activity 基类。这种对具体继承的依赖很可能使得在同一个应用程序中同时使用 RoboGuice 和 Binding 变得困难。我认为 Binding 真正出彩的地方在于大型业务应用程序,这些应用程序有数据录入和验证要求。声明式的XML 绑定语法相当不错。我对其他做类似事情的项目不太熟悉,它展现了对 Android 开发的新视角。
所有这些技术和项目都在努力在 Android 框架内实现已知的模式。这是一个有价值的目标,因为当正确应用时,这些模式可以帮助我们更好地构建应用程序。总的来说,当我接触一个新平台或语言时,我首先尝试使用 SDK 提供的功能。然后我才能决定是使用第三方库还是应用特定的设计模式。我的一些担忧是,有时在一个情况或技术中效果好的设计模式或想法,在另一个情况中却不那么适用。尽管 Android 架构存在局限性,但本文重点介绍的技术和项目在帮助你编写更清晰的代码方面是成功的。
在我讨论的所有方法中,我对 Android Annotations 最感兴趣。我喜欢它,因为它旨在保持代码的清晰易读,而不牺牲性能或使用额外的资源。因为它在编译时生成标准的 Android 代码,这意味着你可以调试你的应用程序,而无需深入第三方库。Spring Roo采用非常类似的方法,除了它的代码生成模型基于 AspectJ 而不是Java 注解处理。如果 Android Annotations 项目能够集成到 Spring Roo 插件中,那将是令人兴奋的。这将允许社区创建新的 Roo 命令来脚手架 Android 项目和类型等。此外,由于 Roo 使用 AspectJ 编译时织入,因此可以消除对使用下划线命名生成的独立类的需求。
总而言之,我展示了一些技术和第三方库的使用,以帮助消除样板代码并简化 Android 应用的开发过程。是的,在准备你的环境时需要一些设置。但是,收益可以抵消最初的时间投入。我建议花一些时间研究每一个项目和技术。它们都为 Android 开发提供了独特而宝贵的视角。
感谢阅读!我非常希望听到你的反馈。你对本文讨论的库有什么看法——你正在使用它们吗?是否有其他你认为必不可少的库?你认为一个基于 Android Annotations 项目的 Roo Android 插件会有价值吗?