7.3 Android组件
7.3.1 Activity组件
Activity组件作为Android4大组件之一,可以表示应用的一个界面,在Android系统中起着十分重要的作用。简单地说,我们在应用中看到的每一个界面,都可以认为是一个Activity。Ac-tivity也可以理解为“活动”,一个Activity从创建到销毁的过程称为Activity的生命周期,即一个“活动”开始,代表Activity组件启动;“活动”结束,代表Activity的生命周期结束。
1.Activity的生命周期
一个Activity的对象的生命周期可类比于人的生命周期,不同阶段需要做对应的事。这就使得一个Activity从创建到销毁的生命周期中,存在4种不同的状态,7个生命周期函数和3个生存期。
(1)Activity的4种状态
1)Resumed:Activity对象处于运行状态,置于屏幕的最前端。
2)Paused:另一个Activity位于前端,该Activity被覆盖但是可见。此时它依然与窗口管理器保持连接,系统继续维护其内部状态,所以它仍然可见,但它已经失去了焦点故不可与用户交互。
3)Stopped:另一个Activity位于前端,完全遮挡之前的Activity。
4)Killed:Activity被系统杀死回收或者没有被启动。
(2)Activity的7个生命周期函数(见表7-2)
表7-2 Activity生命周期函数
以上7个方法中除了onRestart之外,其他都是两两相对的,从而又可以将活动分为三种生存期。
1)完整生存期。活动在onCreate方法和onDestroy方法之间所经历的过程,就是完整生存期。一般情况下,一个活动会在onCreate方法中完成初始化操作,而在onDestroy方法中结束活动并销毁,从而释放内存。
图7-32 Activity生命周期图
2)可见生存期。活动在onStart方法和onStop方法之间所经历的过程,就是可见生存期。在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。
3)前台生存期。活动在onResume方法和onPause方法之间所经历的过程,就是前台生存期。在前台生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行相互的,我们平时看到和接触最多的也在这个状态下的活动。
为了更好地理解Activity的生命周期,可以认真研读活动生命周期示意图,如图7-32所示。
activity启动时:onCreate→onStart→on-Resume。
为了方便说明,这里把当前activity称为Activity1,其他的activity称为Activity2、Activity3,等等。
从图7-32可以看出,activity从run-ning状态跳转到onPause状态的原因是:有另外一个actvity被启动并运行,比如Activity1通过startActivity启动了Activity2,那么Activity2就处于UI视图的最顶层,而Activity1不再是最顶层的activity,此时就会onPause,此时Activity1已经站在他人之后。
当Activity1完全被Activity2挡住,完全看不见时,Activity1就会onStop。从图7-32中看到,从onPause到onStop的原因是:此时Activity1完全不可见,可以理解为按下home键时,当前activity就会处于onStop的状态。
从图7-32中可以看出activity是可以停留在onPause和onStop这两个状态上,可以相应的恢复。
当在Activity1退出时,Activity1就会走到onDestory。
一旦Activity走到onDestory,若恢复只能onCreate→onStart→onResume。
2.界面的切换
Activity的界面切换是应用中必不可缺的,是实现从一个界面跳转到另一个界面的过程。按照实现的方法不同,大致上可以分为两种:启动新的Activity实现界面切换和改变XML文件实现界面切换。
1)启动新的Activity实现界面切换。对于这种方法分三步:第一步:创建Intent对象;第二步:通过setClass()方法指定当前界面和要跳转到的界面,第一个参数为当前界面,第二个参数是要跳转到的界面;第三步:用当前的Activity调用starActivity()实现界面的切换。
首先了解Intent,Intent可以理解为“意图”,是Android程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent一般可被用于启动活动、启动服务以及发送广播等场景。Intent有多个构造函数的重载,其中一个是Intent(Context packageContext,Class<?>cls)。这个构造函数接收两个参数,第一个参数Context要求提供一个启动活动的上下文,第二个参数Class则是指定想要启动的目标活动,通过这个构造函数就可以构建Intent的“意图”。Activity类中提供了一个startActivity()方法,这个方法是专门用于启动活动的,它接收一个Intent参数,这里将构建Intent传入startActivity()方法就可以启动目标活动。
下面对启动新的Activity实现界面切换举简单的例子进行分析:
先新建一个second_layout.xml布局文件,定义一个按钮,按钮显示Button2。代码如下:
再次,在AndroidManifest.xml中对SecondActivity进行注册,代码如下:
最后,对FirstActivity中按钮的单击事件进行修改。首先需要构建一个Intent,传入FirstAc-tivity.this作为承接,传入SecondActivity.class作为目标活动,这样的“意图”就非常明显,即在FirstActivity活动的基础上打开SecondActivity活动,然后通过startActivity()方法来执行Intent。代码如下:
就这样,已经成功启动SecondActivity活动,实现了界面的切换。如图7-33所示。
图7-33 界面切换图
2)改变XML文件实现界面切换。改变XML文件实现界面切换,可以把它看作重新设置界面的布局文件。这个方法的实质就是在监听事件实现的函数,重新调用setContentView方法,来改变界面的布局——比如,界面上有一个按钮,要实现用户单击这个按钮,就改变一下界面布局,来达到界面切换的效果。
可以分两步骤来实现这种方法:
1)创建一个xml布局文件。
2)通过触发某一个控件(如Button),该控件已经加载监听器,监听器通过setContentView函数切换界面布局。
这样实现整个过程都是在一个Activity上实现,因此所有变量都可
以在Activity状态中获得。
先定义两个按键,通过单击按键1跳转到按键2。
按键1:
这样,就可以通过按下按键1,跳转到按键2的界面,实现了界面的切换。如图7-34所示。
7.3.2 BroadcastReceiver组件
图7-34 界面切换图
本节介绍Android4大组件之一的BroadcastReceiver。先了解以下一些必要的概念。
1)Broadcast(广播) 是一种广泛运用在应用程序之间用来传输信息的一种机制。
2)BroadcastReceiver(广播接收者) 是对发送出来的广播进行过滤接收并响应的一类组件,它的作用就是用来接收来自系统或者应用中的广播。
实际中,广播及广播接收者的用途很多,例如当手机开机完成后,系统会产生一条广播。一些开机启动的程序,一旦接收了这条广播,都会自动启动,从而实现了一些开机自启动的功能;当网络状态改变时,系统也会产生一条广播,当收到这条广播后,就可以对程序进行保存数据或者其他相应的处理。
注意:BroadcastReceiver的生命周期只有10s左右,因此在BroadcastReceiver里不能做一些比较耗时的操作;不能使用子线程。
1.介绍广播的使用方法
1)发送:将信息装入一个Intent对象(如Action、Category),通过调用相应的方法sendBro-dacast()、sendOrderBrodacast()或sendStickyBrodacast(),将Intent对象以广播的方式发送出去。
2)接收:当Intent发送出去以后,所有已经注册的BroadcastReceiver会检查注册时的Intent-Filter是否与发送的Intent相匹配。一旦匹配,就会调用类BroadcastReceiver中的onReceive()方法。
例如,先准定义一个按钮准备用来发送广播:
2.介绍接收广播和发送广播
(1)接收广播
在onReceive方法内,可以获取随广播传送过来的Intent中的数据,就像无线电一样,包含很多有用的信息。下面先创建一个名为MyReceiver广播接收者:
在创建自己的BroadcastReceiver对象时,需要继承android.content.BroadcastReceiver,并实现其onReceive方法,可以通过以上程序实现。在创建BroadcastReceiver之后,并不能够使它马上进入工作状态,原因在于还需要为它注册一个指定的广播地址。值得注意的是,注册广播地址可分为静态注册和代码注册(也称动态注册)。
1)静态注册。静态注册是在AndroidManifest.xml文件中配置的。为MyReceiver注册一个广播地址:
<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="android.intent.action.MY_BROADCAST"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
注册完成后,只要是android.intent.action.MY_BROADCAST这个地址的广播,MyReceiver都可以接收到。并且,这种方式的注册是常驻型的,即当应用关闭后,如果有广播信息传来,MyReceiver也会被系统调用而自动运行。
2)代码注册:代码注册需要在代码中动态的指定广播地址并注册,通常是在Activity或Service注册一个广播。下面就来看一下注册的代码:
Activity和Service都继承了ContextWrapper,而registerReceiver是android.content.Context-Wrapper类中的方法,所以可以直接进行调用。
在Activity或Service中使用代码注册BroadcastReceiver时,需要注意的是当Activity或Serv-ice被销毁时要进行解除注册。如果没有解除注册,通常系统会提示一个异常,所以要记得在组件结束的地方进行解除注册操作,代码如下:
这种注册方式与静态注册不同,不是常驻型的,这样注册的广播会跟随程序的结束而结束。
最后,单击发送按钮,执行send方法,控制台打印如下:
(2)发送广播
广播接收器接收系统广播后,如何在应用程序中发送广播?广播主要分为两种类型:标准广播(又称无序广播)和有序广播,下面将通过实践的方式区别这两种广播。
1)标准广播的特点 同级别接收时间是随机的;级别低的迟一点收到广播;接收器不能截断广播的继续传播,也不能对广播进行处理;同级别的动态注册高于静态注册。
对于标准广播来说,最大的特点就是接收器不能截断广播的继续传播,同时也不能对广播进行处理。下面简单地验证一下。我们新建3个BroadcastReceiver,演示这个过程,FirstReceiver、SecondReceiver和ThirdReceiver。具体代码如下:
①FirstReceiver代码
②SecondReceiver代码
③ThirdReceiver代码
然后像之前单击发送按钮,系统会发送一条广播,控制台打印如下:
可以清楚的看到3个都接收到这条广播,接下来对代码进行略微的修改,试图终止广播。我们在onReceive方法的最后一行添加以下代码:
abortBroadcast();
最后,单击一下发送,再次观察控制台的信息:
控制台依然打印了三个接收器的日志,可以发现接收者并不能终止广播。
2)有序广播的特点。同级别接收顺序也是随机的;能截断广播的继续传播,高级别的接收器接收到广播后,可以控制该广播继续传播或者是将之截断;接收器能处理广播;同级别的动态注册高于静态注册。
对于有序广播来说,每次只发送给优先级高的接收器,然后再由优先级高的接收器选择传播到低优先级的接收器。
用实验的方法来理解这个过程。同样,先新建3个BroadcastReceiver,分别为FirstReceiver、SecondReceiver和ThirdReceiver。
①FirstReceiver代码
②SecondReceiver代码
③ThirdReceiver代码
在FirstReceiver和SecondReceiver中最后都使用了setResultExtras方法,将一个Bundle对象设置为结果集对象,传递到下一个接收者,这样优先级低的接收者可以用getResultExtras获取到最新的经过处理的信息集合。
接着,需要为这3个接收器注册广播地址,修改AndroidMainfest.xml文件:
<receiverandroid:name=".FirstReceiver">
<intent-filterandroid:priority="300">
<actionandroid:name="android.intent.action.MY_BROADCAST"/>
<categoryandroid:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
<receiverandroid:name=".SecondReceiver">
<intent-filterandroid:priority="200">
<actionandroid:name="android.intent.action.MY_BROADCAST"/>
<categoryandroid:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
<receiverandroid:name=".ThirdReceiver">
<intent-filterandroid:priority="100">
<actionandroid:name="android.intent.action.MY_BROADCAST"/>
<categoryandroid:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
通过AndroidMainfest.xml文件,可以发现FirstReceiver中的android:priority为300,Secon-dReceiver的为200,ThirdReceiver的为100。这个android:priority属性的范围:-1000到1000,数值越大,表示优先级越高。所以不难发现FirstReceiver的优先级高于SecondReceiver高于Thir-dReceiver。
然后,还需要修改发送广播的代码:
这段代码中使用了sendOrderedBroadcast方法发送有序广播时,需要一个权限参数。接收者若要接收此广播,需声明指定权限。
在AndroidMainfest.xml中定义一个权限:
<permissionandroid:protectionLevel="normal"
android:name="scott.permission.MY_BROADCAST_PERMISSION"/>
然后声明使用了此权限:
<uses-permissionandroid:name="scott.permission.MY_BROADCAST_PER-MISSION"/>
接着,我们单击发送按钮发送一条广播。控制台打印如下:
从控制台中,可以看到接收器接收是按顺序的,并且由高优先级向低优先级传递。通过实验再来看看高优先级能不能阻断广播的传播。打开FirstReceiver的代码,在onReceive的最后一行添加以下代码来试图阻止广播的传播:
abortBroadcast();
发送广播后,控制台打印如下:
这时,发现只有FirstReceiver接收到广播,其他两个接收器都没有执行,因为广播被FirstReceiver终止了。
7.3.3 Service组件
在前面章节已经学习了Android中最常见的组件Activity,Activity在一段程序里最多只可以被激活一次,而且激活之后所执行的时间也是有一定限制的。想象一下,如果想在听歌的时候还想继续和朋友聊天,这时多种工作同时进行的时候就需要用到Android中的另外一个组件——Service组件。
相对于Activity,Service同属于Android中的四大组件之一。但是Activity具有用户界面程序,而Service则是生命周期较长,但不具备用户界面的程序,这是两者之间最大的区别。
1.本地Service
本地服务(Localservice)是依附在主要进程的服务。使得节约了资源问题,此外,因为是位于同一个进程,所以不需要IPC和AIDL,但是它的缺点也是十分明显:程序结束后服务直接终止。
无论如何,服务都不可自己运行,需要通过调用startService()或bindService()方法启动服务。这两个方法都可以启动Service,但是它们也同样有所区别。
1)使用startService()方法启用服务,调用者与服务之间没有关连,即使调用者退出,服务仍然运行。
采用startService()方法启动服务,在服务未被创建时,系统会先调用服务的onCreate()方法,接着调用onStart()方法。
如果调用startService()方法前服务已经被创建,多次调用startService()方法并不会导致多次创建服务,但会导致多次调用onStart()方法。
采用startService()方法启动的服务,主要用于启动一个服务执行后台任务,不进行通信,停止服务使用stopService。
2)使用bindService()方法启用服务,调用者与服务绑定在一起,调用者一旦退出,服务也就终止。
onBind()只有采用bindService()方法启动服务时才会回调该方法。该方法在调用者与服务绑定时被调用,当调用者与服务已经绑定,多次调用Context.bindService()方法并不会导致该方法被多次调用。
采用bindService()方法,启动服务时只能调用onUnbind()方法解除调用者与服务解除,该方法启动的服务要进行通信,停止服务使用unbindService。
2.Service的生命周期
Service和Activity的生命周期是类似的,都是从它们创建的时候开始到最终销毁。Service相对于Activity整个过程则简单许多。Service有着多种启动方式,但是无论是哪种启动方式,当Service第一次被创建都会调用onCreate()方法,然后当Service被启动时调用onStart(),当停止Service时则执行onDestroy()方法,但是如果需要再一次启动Service时不需要再次执行onCreate()方法,而是直接执行onStart()方法。
当Service被Activity调用方法Context.startService启动后,该Service都在后台运行,不会自动关闭,不管对应程序的Activity是否在运行,直到被调用stopService,或自身的stopSelf方法或者系统关闭。不过当Service被某个Activity调用Context.bindService方法绑定启动,onCreate方法只会调用一次,onStart方法不会被调用。当连接建立之后,Service将会一直运行,除非调用Context.unbindService断开连接或者之前调用bindService的Context不存在(如Activity被finish的时候),系统将会自动停止Service,对应onDestroy将被调用。当一个Service被终止(①调用stopService;②调用stopSelf;③不再有绑定。)的连接(没有被启动)时,onDestroy方法将会被调用,这时应当做一些清除工作,如停止在Service中创建并运行的线程。
3.进程中的Service
Android系统中的各应用程序都运行在各自的进程中,进程之间通常是无法进行内存共享,所以就需要用一些方法使不同数据之间进行数据交换,AIDL(android interface define language)Android接口定义语言,提供了跨进程调用Service的功能。
AIDL用于约束两个进程之间的通信规则,它使得两个进程之间形成了一种通信协议并通过这个协议使进程之间通信进行了规范,当两个进程之间进行通信时,它们之间的通信信息首先会被转化成AIDL的协议信息,然后发送给对方,接收到信息后,再将这种AIDL信息转化成相应的对象,并且这种交流还是双向的。
建立AIDL文件和Java接口定义类似,此处不再阐述,只需注意以下几点:①定义接口的源代码必须.aidl,结尾接口名和文件名一样;②接口和方法前不可加修饰符;③AIDL文件中所有非Java参数必须加标记;④使用Java的基本类型时不用加IMPORT声明。
7.3.4 ContentProvider组件
人们的需求日益增多,手机的应用也就随之而增多,那么各个应用之间也是需要数据共享,这时Android为大家提供了一种统一的方法来实现不同应用程序的数据共享——ContentProvider。
1.ContentResolver
ContentResolver相当于一个客户端的存在,主要用于操作ContentProvider开放的数据。
它本属一个抽象类,主要提供了以下几个方法:
①Insert:向URI对应的ContentProvider中间插入Values对应的数据;
②Delete:删除URI对应的ContentProvider中符合条件的记录;
③Update:更新URI对应的ContentProvider中符合条件的记录;
④Query:查询URI对应的ContentProvider中符合条件的记录。
因为它是一个抽象类,所以在Android中用getContentResolver方法用于获取。
2.URI
当平台已经将数据公开的情况下,数据是以URI的形式向外公开。URI是通用资源标识符,用来定位任何远程或者本地的可用资源。URI可以表示Android系统中的图片、视频等资源,也可以表示ContentProvider中操作的数据。Content中使用的URI由4个字段构成。即
content://→通用的URI前缀,用于ContentProvider定位资源,无须修改。
authority→主机名用于唯一标识ContentProvider,外部调用者可以根据这个标识来找到它,用于确定具体由哪个ContentProvider提供资源。一般authority由包名和类名组成。
Path→路径(path)可以用来表示操作的数据,路径的构建应根据业务而定:①要操作peo-ple表中的ID为2的记录,就可以构建路径/people/2;②要操作people表中的所有记录,就可以构建路径:/people;③要操作people表中的ID为2的记录的number字段,就可以构建路径/people/2/number;④要操作###表中的记录,就可以构建路径:/###。
id→数据编号,用来唯一确定数据集中的一条记录,用来匹配数据集_ID字段的值。
3.ContentProvider
Android提供了ContentProvider,一个程序可以通过实现一个ContentProvider的抽象接口将自己的数据完全暴露出去,而且ContentProviders是以类似数据库中表的方式将数据暴露,也就是说ContentProvider就像一个“数据库”。那么外界获取其提供的数据,也就应该与从数据库中获取数据的操作基本一致,采用URI表示外界需要访问的“数据库”。
如果开发属于自己的ContentProvider主要经历有两步:
第一步:开发一个ContentProvider子类,该子类需要实现增删查改等方法;
第二步:在AndroidManifest.xml文件中配置该ContentProvider。