领先一步
VMware提供培训和认证,以加速您的进步。
了解更多假设你某天早上醒来,心想:“今天我要做一个Android应用。”首先,这是一个不错的选择!截至六月底,每天有50万台Android设备被激活,甚至超过了iPhone。这意味着你的应用拥有庞大的潜在用户群体。此外,Android是用Java构建的。这看起来似乎并不重要,但我已经在Objective-C平台上从事iOS平台开发多年,虽然我现在已经相当熟悉它了,但iOS SDK的学习曲线比我使用Android时要陡峭得多。当我第一次开始使用Android SDK时,Android感觉更容易上手。也就是说,它与你过去构建的任何其他Java应用程序之间存在一些明显的区别,我将在第一部分中介绍其中的一些区别。
所以,随着时间的推移,你已经完成了你的第一个应用程序,并将其提交到Android Market。祝贺你,你的朋友们都在下载你的应用程序并在推特上谈论它。现在是时候开始你的第二个应用程序了。你花了几天才突然意识到你开始重用第一个应用程序中的代码,这本身并不是一件坏事。代码重用很有价值。但是你注意到有很多样板代码经常重复出现,这会分散你对业务逻辑的注意力。幸运的是,有一些方法可以改进这一点。
在这篇博文中,我将概述Android和应用程序生命周期,并讨论框架强加的一些限制。我还将回顾一些可以帮助你清理Android代码并专注于你想要通过应用程序实现的目标的技术和第三方项目。
让我们简要概述一下Android的工作原理。Android应用程序(应用)使用Java构建,并编译成class文件。然后将class文件编译成Dalvik可执行文件 (DEX) 格式,以便它们可以在Android使用的Dalvik虚拟机上运行。转换为DEX格式后,class文件会被压缩到Android包 (APK) 中,以便分发到设备。由于使用了DEX格式,Dalvik VM并不是一个真正的Java虚拟机,因为它不运行Java字节码。此外,Dalvik VM的核心类库基于Apache Harmony项目的子集。这意味着你习惯于在Java SE中使用的许多类和方法是可用的,但肯定不是全部。我发现API 参考在Android开发者网站上是一个宝贵的资源,可以用来回顾这些差异。
默认情况下,每个Android应用程序都被Android操作系统分配一个唯一的Linux用户ID。当系统启动时,应用程序在其自己的Linux进程中,在其自己的虚拟机 (VM) 中运行。系统根据需要管理此进程的启动和关闭。正如你所猜到的,这意味着每个应用程序都与其他正在运行的应用程序隔离运行。安装后,应用程序可以请求权限来访问硬件功能或与其他应用程序交互。用户可以选择授予应用程序这些权限或不安装它。应用程序所需或请求的权限在每个应用程序的Android清单文件中定义。这是一个XML文件,列出了应用程序的所有组件以及这些组件的任何设置。四种类型的应用程序组件是活动、服务、内容提供者和广播接收器。出于本文的目的,我将重点介绍活动。
活动基本上代表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方法。当然,活动现在必须“知道”应用程序,这似乎是一个缺点。但请记住,活动已经知道它的应用程序。该方法是内置的。
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(面向方面编程)支持,另一种是没有。在内部,标准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);
}
}
每个活动都必须继承自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在编译时生成样板代码,因此使用它不会产生运行时开销。它通过为每个活动生成一个子类,并用标准的样板代码替换注解来实现这一点。这种方法的一个小缺点是你必须在清单文件中的每个活动的名称后附加一个下划线。例如,如果我创建一个MyActivity类,Android Annotations将生成一个相应的MyActivity_类。此外,startActivity(…)中的任何引用都必须更新为使用新类。
在这里你可以看到一些与RoboGuice的相似之处。这是前面RoboGuice示例的Android Annotations版本。请注意,在类上使用@EActivity注解允许你设置活动的布局。另一个不错的功能是,如果你省略注解中的资源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(模型-视图-视图模型)框架,旨在将活动与直接操作用户界面分开。为此,几乎所有代码都从Android活动类中移出,并放置在一个ViewModel对象中,该对象旨在帮助进行可测试性。ViewModel对象处理视图(布局)的所有事件和数据,但它与视图没有紧密耦合。在视图中发生的事件作为命令发送到ViewModel。然后,这些命令执行一些业务逻辑,并可能与模型交互,并更新ViewModel的属性,以便视图可以绑定到它们。如果你曾经使用过.Net,那么数据绑定的概念对你来说会非常熟悉,而Android Binding努力将该概念应用于Android。Android Binding是由Andy Tsui发起并维护的。它仍然很年轻,缺乏文档,但具有一些不错的潜力。
下面的示例显示一个继承自BindingActivity的EmailActivity。当你继承自BindingActivity时,你使用setAndBindRootView(…)方法为视图设置模型。
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
}
}
};
}
这个库的功能远不止我这里包含的内容。它还支持通过使用一组注解进行模型验证。有一些内置规则,你可以使用正则表达式模式匹配,以及实现你自己的自定义规则。最新版本还增加了对绑定到选项菜单的支持。
与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插件有价值吗?