内容摘要
本节开始讲解Android开发的技术细节,我们首先讲解界面展示部分,这主要是为了满足产品经理做原型的需要,我相信不管多么强大的产品设计软件,都没法把你的想法落实到手机上执行,并且能够控制界面的按钮和跳转逻辑。
但是如果掌握Android开发中的两大控件, 足以满足你初期的产品原型开发,把界面逻辑和原型制作成一个简易APP,展示效果会非常不一样。
Android控件--Activity
Android控件-- Service
Activity
首先来感性认识一下Activity。
这是我手机上《今日头条》的两个界面,这两个界面就是通过Activity展示的,在Activity里面你可以添加图片,图片的组件叫做ImageView,所有内容罗列在一个表单中,表单的组件叫做ListView。这些叫做View的组件可以互相嵌套布局,他们最后都展示在Activity的布局中。
这些组件名称都是Android环境下的名称,在IOS环境中也有相应的组件,只是名称有所不同,用法也稍有差异,比如列表在IOS被称作TableView,而用法上,Android利用XML的布局文件说明ListView的样式或者直接用代码实现ListView的样式,而IOS则用StoryBoard直接编辑布局或者用代码实现,
最新的Android Studio虽说也可以直接图形化编辑页面布局,但是现在的阶段还不够成熟,建议首先使用XML文件编辑布局,或许再过几个版本的迭代,Android Studio也可以像IOS的Xcode那样直接图形化编辑页面布局。
实际上这2个界面还用到了另一个比较有名的组件: Fragments。这个组件其实不是所有的Android版本都支持,这是在Android 3.0之后引入的, Fragments被用作多个组件的组织单元,是各种小组件的一个集合管理对象。
Fragments相当于轻量级的Activity,他内嵌在Activity的布局中,管理具有相同业务逻辑意义的组件合集。 Fragments与Activity有很多相似的地方。
我们在讲解完Activity之后会再讲解 Fragments,之所以没有单独把它单独列出来讲,是因为这2个控件几乎有相同的用法,我们只关注他们的不同点。
Tasks and Back Stack
Activity需要重点讲解的一个知识点是Tasks and Back Stack。所有的APP是由一个或者多个Activity构成的,当你在某个页面(Activity A)上点击某个按钮并跳转到下一个页面(Activity B)的时候,系统需要保持A的状态,这样当我们按回退按钮的时候,以便能够再次显示A的内容和数据。这个过程中系统要把A的信息和状态保持到一个数据结构中,这个结构就叫做Back Stack 而为了完成某个功能,Activity A和Activity B这样一个组合就叫做一个Task。
上图描述了一个Task过程中Back Stack管理Activity的。接下来我们举个例子:
当我们点击 ”今日头条”-->“我的”-->“商城”按钮的时候,会弹出一个新的界面,此时“我的”就是示意图中Activity1,而弹出的“商城”界面就是Activity2,当我再次点击“淘宝特卖”的时候,会弹出“特卖频道”界面,这个界面就是上图中的Activity3,当我按“回退” 按钮的时候,Activity3就会从Back Stack中弹出并且销毁,Activity2就会处于激活状态,同理再次按下“回退”按钮,Activity2就会销毁而Activity1就会处在或者月状态。
因此Task就是在打开一个app并且点击各个界面时候,系统为了管理这一些列操作和状态而创建的管理对象。而Back Stack是为了管理各个节目的数据结构。实际上,一般情况下一个Task就是一个linux进程,而Back Stack就是这个进程管理APP用的数据结构。
Activity Lifecycle
Activity需要重点讲解的第二知识点就是 Lifecycle。我们讲过Activity3在用户按下“回退”键的时候,会销毁并且Activity2处于激活状态。这个过程中Activity3是被系统释放了资源的。Android有一套资源管理方式,为了在有限的手机内存上展示尽可能丰富的内容,Android会为每一个正在展示的界面创建多个状态,并为不同的状态给予不同的资源权限。
首先我首先看一下Activity的状态图,图片来自官方网站,希望大家养成阅读原版官方文档的资料,本书大部分内容是在官方文档基础上,进行的总结和解读。
总的来看Activity流程稍微有点复杂,但是如果结合上面的例子,你会对这些状态有清晰的认识,首先是“Created”,当你点击“商城”按钮之后会弹出“商城”界面,在这个界面显示之前,你需要首先解析Activity的布局文件,以便知道界面上有哪些元素需要显示,以及他们的位置,我们例子中,一个列表需要显示,列表中有三项内容:“今日特卖”,“今日电影”,“淘宝特卖”。这个解析布局文件并设置显示内容的过程就是”Create“的过程,结束之后Activity就处于“Created”。而这个过程是在onCreated()函数中执行的。
创建结束之后就要显示这个界面了,完成显示之后Activity就处于“Started”状态了,在创建之后,显示之前,如果你还要做些什么事情,那就调用onStart()函数。
界面显示之后,你还有机会做一些事情,这个动作可以在onResume()中进行,执行完成这个函数之后,Activity就会处于“Resumed”状态了。
接下来2个状态是“Paused”和“Stopped”,这两个状态一般情况下是连续同时出现的,如果从当前界面点击了一个按钮,按钮的功能是弹出一个新的Activity,那么当前的Actvity就会首先处于“Paused”的状态,进入此状态前会首先调用onPause()方法,然后再调用onStop()方法,进入Stopped的状态,而新的Activity要重新走一遍Created到Resumed的流程。
在这个过程中,如果点击按钮之后不是显示一个新的Activity,而是弹出一个确认对话框,那么当前的Activity只会进入Paused的状态,而不会进入Stopped,并且只有onPause()方法会被执行到。因为此时当前的Activity只是部分可见,而不是完全被覆盖掉。这个地方是容易被开发者混淆的知识点。
当确认按钮被点击并重新返回当前Activity之后,Activity又重新进入了Resumed状态。此时onResume()函数会被执行到。
如果当前的Activity不是被新的Activity覆盖掉,而是用户点击“回退”按钮,此时的Activity就会从Back Stack中弹出,然后它使用的资源会被释放掉,进入“Destroyed”状态,再此之前会先后进入Paused和Stopped状态,在进入Stopped状态之后,Destroy之前,系统会调用onDestroy()方法,你可以在此方法中做一些存储数据的动作,以便下次再显示次界面时使用。
在IOS中,也有类似的状态变迁,只不过状态发生变迁时调用的方法名称不相同而已,比如下列四个方法:
- (void)viewWillAppear:(BOOL)animated; --界面将要显示
- (void)viewDidAppear:(BOOL)animated; --界面已经显示
- (void)viewWillDisappear:(BOOL)animated;--界面将要消失
(void)viewDidDisappear:(BOOL)animated; --界面已经消失
以上四个函数只是状态变迁时,系统可以调用的函数的一部分,用户可以在界面状态将要或者已经发生变化时,做相应的逻辑处理。
不同点是Android在系统内存不够的情况下,会释放掉处于Stopped状态的Activity以腾出资源给当前正在显示的界面,而IOS则是先发送告警到用户的回调函数,由用户来处理这个问题。IOS处理内存告警的方法是:
-(void)didReceiveMemoryWarning;
Activity Layout
Layout 是需要重点说明的第三点。在Activity的onCreate()方法中,我们提到需要加载界面的布局文件,然后设置他们的属性。每个Activity都需要一个XML文件来描述起界面各个元素的位置和属性。这就是设计模式中的MVC。Activity就是MVC中的C即控制层,而XML描述文件就是V,即展示层,而M数据层我们会在后面的章节中讲解。在下一节的讲解中,我们会详细讲解布局文件。
Fragment
Activity就讲解这些,需要额外说明的是与Activity非常类似的一个控件: Fragment。我只讲解一下 Fragment与Activity的不同之处,相同地方不再赘述。
首先要说明的是 Fragment与Activity的关系就像操作系统中线程与进程的区别,如果你了解这方面的知识,那么这两大控件的区别就非常容易理解了。如果你熟悉面向对象编程,那么Fragment与Activity的关系就像子类与父类之间的关系,这个比喻不是很恰当,但是可以暂时这么理解。
再就是状态Fragment要比Activity多,但是它的状态依赖于Activity的状态,也就是说不存在Fragment处于Resumed而它所属的Activity却处于Stopped的这情况。具体的状态和状体依赖关系见图。这里不再详细解释,相信如果你看懂Activity的状态变迁,就能很好的理解Fragment的状态图。
其次是Fragment的布局文件可以是Activity的一部分,即Activity的xml文件中,有一个控件叫做Fragment。Fragment设计之初就是为了将复杂的Activity文件中的控件,分组进行管理和展示,这个分组就是Fragment。
Fragment还有一些其它特殊属性,不过最重要的特点就是,Fragment是为了在同一个Activity中展示复杂的界面并且节省了Activity在start和destroy的时候所消耗的CPU资源。
Service
我们同样首先对Service有个感性的认识,见下图:
通过手机 设置->应用程序管理->运行中,你可以看到很多在后台运行进程和服务,点击其中一个,你会进入一个更详细的界面,注意“NotifyService”这个服务,如果没有猜错的话,他是用来发送“PUSH”消息的,就是当我们的APP进程被杀死之后,依然能够收到该APP的推送提醒,比如有新的消息或者有新的内容可以查看。点击微信的图标,试着停止里面的服务,当有朋友发送消息给你的时候,看看有什么不同。
关于Service,我们主要记住的就是它的作用,当我们的APP没有处于激活状态的时候,我们需要APP与我们的业务服务器进行一些交互功能,此时需要一个不同于Activity的控件来完成此功能,Service就是为此而设计的。
关于Service我们需要讲解几个要点。
Service有2种类型,Started和Bound。
这2种类型的的Service从创建到销毁以及用途都有所不同,对于Started类型的Service,如果要启动一个Service对象来执行特定的任务,需要通过 startService()调用,当任务执行完成之后,你需要用代码来明确的停止它,并且它启动之后就不在与启动它的控件(Activity)交互。
对于Bound类型的Service,你需要通过调用 bindService()来启动它,并且需要定义一个接口,来说明Service是如何与其他组件进行信息交互的。多个控件可以同时绑定同一个Service,如果一个Service没有控件绑定,类似指针引用计数为0,则系统会停止这个Service并释放其使用的资源。
Service是在当前APP的main thread中运行的。
Service是在当前绑定的它的进程的主线程中运行的。相信你的Android手机曾经提醒过你,某个APP长时间没有响应,请问是否关闭该APP。这个就是因为主线程太长时间没有响应用户的UI操作。因此如果Service执行太繁重的任务(一般情况下都会这样),那么我们必须单独创建一个线程来执行这个任务,而不要在主线程中执行这个繁重的任务。
有2个类可以用来实现Service控件:Service和IntentService
Service和IntentService是Android里面的两个Service控件的实现类,他们的不同是IntentService会自动创建一个新的线程,所有的业务逻辑只要在onHandleIntent()这个函数实现就可以了,这个函数自动在主线程之外的线程中执行,避免了上面所说的APP无响应的问题。同时系统会自动实现一个队列,所有对IntentService的多线程调用都是先把调用请求放入队列,然后依据队列顺序的调用onHandleIntent(),这就避免了多线程下调用IntentService可能产生的问题。
而Service类需要自己管理线程,不过它的优势就是你可以根据自己的需要,创建足够多得线程去处理每一个发送到Service请求,也就是说Service类有更大的灵活性。
Service也可以在前台运行,比如音乐播放器的Service
有些Service是可以在前台运行的,这些Service需要与用户进行频繁的交互,同时又需要与服务器后台进行交互, 因此可以把这类Service设置为前台Service,比较典型的就是播放音乐的Service即使在系统资源紧张的时候,也不会被系统回收资源。
Service的生命周期
Service的生命周期要比Activity的简单很多,只是需要注意的是Service运行时间越久,当系统在资源紧张的情况下,被系统杀死并回收资源的概率越大。
当Service被系统杀死并回收资源之后,系统会在资源充足的情况下试图恢复该Service,当然要看该Service在启动时候的配置,这个配置分为3类:
START_NOT_STICKY:不需要重新启动该Service。
START_STICKY:需要重建该Service,但是忽略之前通过Intent传入的参数。
START_REDELIVER_INTENT:需要重新启动该Service,并且重新输入之前传入的参数。
这3种类型是Service在创建的时候,调用onStartCommand() 时的返回值。
本部分内容我们讲解了Service的几个重要属性,需要注意的是除此之外,Service的另一个重要的属性是,它可以被其它APP调用,我们可以在配置文件中配置一下改Service的属性,将该Service设置为仅为本APP可以调用。
本节主要从理论和经验方面,讲解了Android主要控件的主要特性,本书限于设计的知识面过于广泛,无法从所有的细节讲解所有的技术,希望大家通过本书的讲解,对于技术细节的学习有所有帮助,作为一个提纲挈领的工具书来使用,在下一节中我们将讲解本书工程中用到的技术的细节,同时我会把代码上传到代码库中,希望大家一边对照代码一边对照本书,进行仔细的学习。
欢迎关注公众号