5

理解Android命名规范

 3 years ago
source link: http://www.androidchina.net/5142.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
理解Android命名规范 – Android开发中文站

最近我一直在回答学生的以及StackOverflow上的问题,比如Activity里面应该写什么、如何在需要Context的时候获得它、如何在UI线程上进行异步任务以及为什么要用Fragment。这些问题归根结底都是在问:“我如何完成这些被Android搞得很麻烦的事情?”

不可避免的,大多数答案所提供的方法都是极具黑客色彩的,虽然技术上是可行的,但不应当遵从。这些问题表现出的是对Android框架中某些类的根本误解,正确的回答应该是:你误解了这个类的用途,本来这个类就不是干这个事儿的!

我甚至还看到过相关的开源框架,在解决某个因为框架误用才产生的问题。以Infograph for Robospice为例,Robospice的基础假设是:“AsyncTask有一个大问题:它和Activity的生命周期结合不够紧密.”这个假设无疑是正确的,但这本来就不是个问题,这是AsyncTask自身的特点。任何异步的任务都不应该绑定在某个Activity或其生命周期上。这个框架的存在会导致开发者误用Activity类。

我一直想对这些误用问题给出一个统一的回答,可是想不出来,直到这周我重新阅读了Robert Martin的《代码整洁之道》中有关命名规范的章节。当我读到“添加有意义的语境”部分时,我意识到,我们通过每个Android组件的名字就可以清晰地理解其功能。下面让我们探讨有关process(进程)、thread(线程)、application(应用)、activity(活动)、task(任务)、Fragment(碎片)与context(上下文)的命名。读完之后你将对这些组件的职责有更准确的理解,并能发现我们为什么在误用这些组件。

PROCESS

在书中“使用解决方案领域名称”部分里,Martin写到:

记住,只有程序员才会读你的代码。所以,尽管用那些计算机科学术语、算法名、模式名、数学术语吧。

Process(进程)的名字就是这么来的。在Android系统中,Process只不过是一个普通的计算进程,这是计算机所关心的东西,使用者自然不会关心它,事实上开发者也很少使用到这个概念。当我们谈论进程、多进程实现与进程间通信时,你会想到这些在计算机内是如何完成的。

在Android中,每个Application(应用)会在自己的Linux线程中开启,当系统需要回收内存时,最终会杀掉一个进程。进程的五个状态包括:forground(前台)、visible(课件)、service(服务)、background(后台)和empty(空)。所以简单来说,一个进程包括指令集和内存空间,而且是可被系统杀死的单元。

TASK

在书中“使用源自所涉问题领域的名称”部分中,Martin写到:

 如果不能用程序员熟悉的术语来给手头的工作命名,就采用从所涉问题领域而来的名称吧。至少,负责维护代码的程序员就能去请教领域专家了……与所涉问题领域更为贴近的代码,应当采用源自问题领域的名称。

Android设备所解决的最基本的问题是什么?它们能帮助人们完成任务。这看起来异常简单,但想想过去在智能手机刚被发明的时候人们是如何憧憬的。谁也不知道未来人们能用手机完成什么事情,但是开发者所开发的工具根本上都是为了帮助人们完成任务的,也就是task(任务)。任务是以人为中心的术语,它包含未知数目的步骤,但有一个核心的主题。在日常生活中有哪些任务实例呢?打扫房间、开车上班、逛超市,这些都是任务。人们在生活中可能要完成许多任务,但在同一时刻只能进行一项任务。人可以开始或停止做任务,也可以在任务间切换。

Android中也是这样。使用者在点击应用图标时开始一个task,可以在最近任务界面中看到最近的task,可以将task暂停或重新开始,甚至可以通过在最近任务界面中移除某task来完全销毁掉这个task。最重要的是,使用者只能同时与一个task进行交互。

ACTIVITY

就像task一样,activity是问题领域中的术语。每个task包含了一个或多个activity。当一个人在进行任务时,他需要完成许多活动。比如打扫房间时,一个人可能正在叠衣服,当他叠完衣服时,他要开始清理浴缸。在完成一件活动前,他可以暂停并跳转到当前任务下的另一个活动中。

当一个人在任务间切换时,他需要先暂停第一个任务中的一个活动,然后开始第二个任务中的某个活动。比如当一个人从打扫房间转到开车上班时,他需要先停止叠衣服的活动,并开始走向他的车。

虽然人可以在多个活动间切换,他同时只能进行一个活动。没有人可以同时叠衣服和擦马桶,如果有人尝试那么画面一定颇为娱乐。就算他在活动之间来回跳,也要线性地进行任务。

Android中Activity也类似,Activity是一个面向使用者的概念,它描述了使用者正在做的事情。一个Task包含了一个或多个Activity,但使用者只能同时与一个Activity交互,这个Activity通过占据整个屏幕来获得使用者所有的注意力。使用者在进行任务切换的同时会停止与启动Activity,每个Task都会记住用户正在使用哪个Activity,这样通过回退键回退时就会回到正确的Activity。

Activity的概念是基于使用者的,所以它负责向使用者展示数据并响应输入,也正因此,它不应该进行任何幕后操作,比如数据库读写、网络请求与大量计算。这些代码模块都应该与Activity解耦,并离开用户的视野。

只有当对使用者产生直接影响的变化发生时,计算机才会操作Activity生命周期,这些变化叫做配置信息改变,比如设备旋转。

这也是为什么我们不应该在设备旋转时让AsyncTask存货。Robospice看起来帮助了想在Activity中启动AsyncTask的开发者。其实,这个类库本不应这么做,因为Android中有更简洁的架构模式来处理这些情况。

Fragment

Activity大到一定程度后就需要分割成更小的组件,现在暂时我们还没有用于表示这个小组件的术语。这些Activity的部件是面向用户的,所以我们不能去找解决方案领域的名称。我们可能会想到“Sub Activity(子活动)”、“Component(组件)”、“Part(部分)”或是“Partial Activity(部分活动)”这些名称,但是这些不免有误导信息。Martin在“避免误导”部分中讲:

 程序员必须避免留下掩藏代码本意的错误线索。应当避免使用与本意相悖的词。

“sub Activity(子活动)”可能会被误解为Activity的子类。”Component(组件)”和”Part(部分)”都太模糊。“Partial Activity(部分活动)”隐含意是一个”部分活动”是不够的,我们需要多个这种组件来构建一个Activity。说实话,我猜当内部人员在设计框架时,对于这个组件的命名的争论最为激烈。开发者最终选择“Fragment(碎片)”这个词,我也想不出更好的词了。

就像Activity一样,Fragment的目的一样是完成任务,但是尺度则小了一些。比如“擦浴缸”与“打扫厨房”这两件活动,都需要以“准备清洁用具”这个碎片为开始。所以“准备清洁用具”这个碎片是可以在多个活动中使用的,而且不清楚整体的活动到底是擦浴缸还是打扫厨房。但比如“给花浇水”这类很小的活动,它本身就不需要再分碎片。

有些碎片很小很小,它们可以同时进行。比如“擦浴室瓷砖“和”擦浴室玻璃“是可以同时完成的,虽然它们是不同的碎片。

当使用者停止了一个任务或是任务中的一个活动时,自动就会停止一个碎片。一个人可以在多个碎片间切换,而不会停止活动。例如,一个人可以停止“准备清洁用具”并开始“往桶中灌水”,但他一直在进行”拖地”这项活动。

Android中的Fragment也是这个道理。一个Activity可以包含零个或多个Fragment,两个Activity可以包含同一个Fragment的不同实例。如果一个Fragment足够小,而且和另外的Fragment紧密相关,它们可以同时被显示,比如master/detail design pattern(主-从视图)。

使用者可以以任何顺序在Fragment间切换。开发者甚至可以让用户通过回退键在多个Fragment间回退。当使用者停止或暂停一个Activity时,Activity也会停止或暂停它的Fragment。

THREAD

Thread(线程),就像Process(进程)一样,也是解决方案领域的名称。就像Process一样,thread也只是一个计算机科学概念的基础实现。Android没有把事情搞得太复杂,直接使用了Java的thread。Android中的多数Thread都是Java Thread。Java Thread表现很好,Android只需要继承Thread一次就能实现HandlerThread。

就像Process一样,Thread是一个面向计算机的概念。使用Thread是为了同时完成多件事。就像计算机科学中线程的概念一样,多个Thread实例可以存在于一个Process中。

使用者永远不关心每个线程上正在发生什么,或是到底有多少个线程在运行。开发者使用Thread是为了告诉计算机干的再快一点。因为开发者是与计算机交谈,需要像计算机一样思考。所以涉及线程的问题一般都更难理解与debug,开发者需要考虑计算机的时间,而不是人类的时间,开发者需要思考有关内存访问的问题,以及要不要限制某些组件只能在规定时间访问规定内存空间。Thread之一,main thread又称UI Thread,负责监听使用者输入并与使用者交互。这个线程不应该进行任何后台操作。所有的Activity都存活在这个线程中,并完成其所有工作。每个应用开启时都只有一个线程,至于是否需要多个线程则是开发者所要考虑的问题。HandlerThread是进行后台任务的一个很好的选项。

APPLICATION

就像Process和Thread一样,Application(应用)也是解决方案领域的名称,也是一个基本的计算机科学概念。一个Application是一个帮助使用者完成(多个)任务的软件。但是,Application已经成为使用者与开发者所共同熟知的概念,所以有时它会带来一些面向使用者的问题。

银行和超市是物理意义上的Application(应用),它们被创建出来的目的就是帮助人们完成任务。超市的某些部分是直接与人交互的,但有些部分是在后台帮助超市实现功能,比如仓库和会计室。

许多应用可以组合成一个任务来帮助使用者完成更多的事情,即使这些应用是不同的人写的。如果使用者正在进行烤蛋糕的任务,那么他可以去超市来进行“购买原材料”的活动,然后回家继续烤蛋糕。只要有人在与一个应用交互,这个应用实例就存在。

开启一个应用可以说是开启一项任务的一部分。比如我们要开始做打扫房间这项任务,隐含意就是房间已经存在。当一个应用不再被使用时,它会被关闭掉。当所有的人都完成了超市中要完成的任务,超市就会关门。当一个人完成了要在家做的所有任务时,他就会把所有东西都关掉。

Android中的Application也是这个道理。启动一个Task,进而启动一个Activity的一部分就是启动一个Application。一般来说,开发者只需要创建一个Application实例就可以服务所有的Activity,当用户完成了所有的Activity,Android可能就会销毁Application。

在进行一项任务时,使用者可以转换到另一个Application来帮助他完成附加的活动,比如通过相机应用,使用者可以启动邮箱应用来发送有图片附件的邮箱。这两个应用是可以由不同的开发者开发的,但是都属于一个任务。

如果使用者在邮箱应用中强制停止当前任务,系统会停止邮箱应用中的Activity。如果使用者只是暂停并继续当前任务,系统会立即重新启动邮箱应用中的Activity。

如果使用者直接启动邮箱应用,系统会开启一个新的任务。相机任务中属于邮箱应用的Activity不受新创建的邮箱任务影响。也就是说,同一个Activity的不同实例可以在不同的任务中生存。

即使使用者没有与任何Activity进行交互,一个Application还是可以存在,比如当一个Application只在进行后台任务,没有Activity被展示,Application一样可以生存。

CONTEXT

有一篇来自Dave Smith的博客很好地解释了不同种类的Context(上下文)以及它们的功能。但什么是Context呢?很简单。Context就是Context(上下文)。

在我们的Android基础系列课程中,我将Context比作一个深入操作系统的钩子。因为Activity需要系统来启动,你需要使用Context来要求系统启动一个Activity)。因为系统可以填充布局,你需要通过Context来要求系统填充布局。因为系统可以发送广播,你可以通过Context来发送广播或是注册BroadcastReceiver(广播接收器)。因为系统提供系统服务,你可以通过Context来访问系统服务。

但是这些事情并不能随处完成。比如在Service中,要求系统展示对话框或是启动Activity是毫无意义的。再比如在Application中,系统甚至都不知道当前Activity的theme是什么,所以让系统填充布局是毫无意义的。所以当你要求系统来完成任务时,你需要告诉系统你是在哪种Context(上下文)里想要做这些事情。当系统认定你想要做的事情对于当前上下文是合理的。Context的名字就是这么来的。就像Task和Activity一样,Context属于问题领域的名称。

Context有许多子类,但我们主要会用到Application、Activity和Service。这几个组件都描述了不同的上下文,可以做不同的事情。Dave Smith的文章详细讲述了它们都能干什么。

Context是暂时的,随着时间变化会发生改变。当处理有关Context的问题时,我总是告诉自己:这个Context可以用于操作这个类,可是不一定能操作其他类。内存泄漏的一大来源就是到处传递Context并让其他对象持有其引用。如果这里的Context是一个Activity,那么Activity在生命周期结束后也不会被回收,所以到处传递Context并不是一个好主意,我们不应该这么做。

当你的确需要传递Context对象时,要确保这个Context对象适用范围越大越好,一般来说我们应该传递一个Application Context。当你传递Context时,不要假设传递过来的人考虑过你的需求,也不要假设传过来的就是正确的Context。

public void doSomething(Context context) {
// 不要假设传过来的就是Application
mContext = context;
// 永远要手动获取Application Context
mContext = context.getApplicationContext();
...
}

所以呢?

明白了这些名字是哪里来的又有什么用呢?我经常看到学生的问题是如下格式:我怎么让X完成Y?他们其实应该先问问自己:X应该完成Y吗?为了回答这个问题,他们需要问:X的单一职责是什么?对于所有的Android框架中的组件,其职责就体现在命名中。这个名称可以映射到问题领域或是解决方案领域的名臣很高。解决方案领域的名称,如Process、Thread和Application,是开发者所理解与熟知的。问题领域的术语如task、activity、context和fragment则只当我们在描述现实生活中的问题时才可以被理解。

所以当你下次想问如何在Activity中完成异步任务,或是如何通过Context对象获取系统组件,或是Process(进程)、Thread(线程)与Task(任务)之间的区别是什么时,看看名字就知道答案了。

这篇文章将在Android N推出时被推翻,因为多个Activity可以同时存在于一块屏幕上。

这么说好像很傻。

转载请注明:Android开发中文站 » 理解Android命名规范


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK