【网学网提醒】:网学会员编辑为广大网友搜集整理了:Android服务Service 详解绩等信息,祝愿广大网友取得需要的信息,参考学习。
第8章Android服务
?8.1Service起步
?8.1.2绑定Activity和Service?8.2.1获得系统服务?8.3.1计时器:Chronometer?8.3.4在线程中更新GUI组件?8.3.5全局定时器AlarmManager(2)?8.4.1什么是AIDL服务
?8.4.2建立AIDL服务的步骤(2)?8.5本章小结
?8.1.1Service的生命周期
?8.1.3在BroadcastReceiver中启动Ser..?8.2.2在模拟器上模拟重力感应?8.3.2预约时间Handler
?8.3.5全局定时器AlarmManager(1)?8.3.5全局定时器AlarmManager(3)?8.4.2建立AIDL服务的步骤(1)?8.4.2建立AIDL服务的步骤(3)
第8章Android服务
服务(Service)是Android系统中4个应用
程序组件之一(其他的组件详见3.2节的内容)。服务主要用于两个目的:后台运行和跨进程访问。通过启动一个服务,可以在不显示界面的前提下在后台运行指定的任务,这样可以不影响用户做其他事情。通过AIDL服务可以实现不同进程之间的通信,这也是服务的重要用途之一。
本章内容
Service的生命周期绑定Activity和Service
在BroadcastReceiver中启动Service系统服务时间服务
在线程中更新GUI组件AIDL服务
在AIDL服务中传递复杂的数据8.1Service起步
Service并没有实际界面,而是一直在Android系统的后台运行。一般使用Service为应用
程序提供一些服务,或不需要界面的功能,例如,从Internet
下载文件、控制Video播放器等。本节主要介绍Service的启动和结束过程(Service的生命周期)以及启动Service的各种方法。
8.1.1Service的生命周期
本节的例子代码所在的工程目录是src\ch08\ch08_servicelifecycle
Service与Activity一样,也有一个从启动到销毁的过程,但Service的这个过程比Activity简单得多。Service启动到销毁的过程只会经历如下3个阶段:
创建服务开始服务销毁服务
一个服务实际上是一个继承android.app.Service的类,当服务经历上面3个阶段后,会分别调用Service类中的3个事件方法进行交互,这3个事件方法如下:
1.2.3.
publicvoidonCreate();//创建服务
publicvoidonStart(Intentintent,intstartId);//开始服务publicvoidonDestroy();//销毁服务
一个服务只会创建一次,销毁一次,但可以开始多次,因此,onCreate和onDestroy方法只会被调用一次,而onStart方法会被调用多次。
下面编写一个服务类,具体看一下服务的生命周期由开始到销毁的过程。
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.
packagenet.blogjava.mobile.service;importandroid.app.Service;importandroid.content.Intent;importandroid.os.IBinder;importandroid.util.Log;
//MyService是一个服务类,该类必须从android.app.Service类继承publicclassMyServiceextendsService{
@Override
publicIBinderonBind(Intentintent){
returnnull;}
//当服务第1次创建时调用该方法@Override
publicvoidonCreate(){
Log.d("MyService","onCreate");super.onCreate();}
//当服务销毁时调用该方法@Override
publicvoidonDestroy(){
Log.d("MyService","onDestroy");super.onDestroy();}
//当开始服务时调用该方法@Override
publicvoidonStart(Intentintent,intstartId){
Log.d("MyService","onStart");super.onStart(intent,startId);}
36.}
在MyService中覆盖了Service类中3个生命周期方法,并在这些方法中输出了相应的日志信息,以便更容易地观察事件方法的调用情况。
读者在编写Android的应用组件时要注意,不管是编写什么组件(例如,Activity、Service等),都需要在AndroidManifest.xml文件中进行配置。MyService类也不例子。配置这个服务类很简单,只需要在AndroidManifest.xml文件的
标签中添加如下代码即可:
1.
其中android:enabled属性的值为true,表示MyService服务处于激活状态。虽然目前MyService是激活的,但系统仍然不会启动MyService,要想启动这个服务。必须显式地调用startService方法。如果想停止服务,需要显式地调用stopService方法,代码如下:
1.2.3.4.5.6.
publicvoidonClick(Viewview){
switch(view.getId()){
caseR.id.btnStartService:
startService(serviceIntent);//单击【StartService】按钮启动服务
7.
8.9.
break;
caseR.id.btnStopService:
stopService(serviceIntent);//单击【StopService】按钮停止服务10.11.12.
break;}}
其中serviceIntent是一个Intent对象,用于指定MyService服务,创建该对象的代码如下:
1.
serviceIntent=newIntent(this,MyService.class);
然后单击【StopService】按钮,会在Message列中输出如下信息:
1.onDestroy
在讨论完服务的生命周期后,再来总结一下创建和开始服务的步骤。创建和开始一个服务需要如下3步:
(1)编写一个服务类,该类必须从android.app.Service继承。Service类涉及到3个生命周期方法,但这3个方法并不一定在子类中覆盖,读者可根据不同需求来决定使用哪些生命周期方法。在Service类中有一个onBind方法,该方法是一个抽象方法,在Service的子类中必须覆盖。这个方法当Activity与Service绑定时被调用(将在8.1.3节详细介绍)。
(2)在AndroidManifest.xml文件中使用标签来配置服务,一般需要将标签的android:enabled属性值设为true,并使用android:name属性指定在第1步建立的服务类名。
(3)如果要开始一个服务,使用startService方法,停止一个服务要使用stopService方法。8.1.2绑定Activity和Service
本节的例子代码所在的工程目录是src\ch08\ch08_serviceactivity
如果使用8.1.1节介绍的方法启动服务,并且未调用stopService来停止服务,这个服务就会随着Android系统的启动而启动,随着Android系统的关闭而关闭。也就是服务会在Android系统启动后一直在后台运行,直到Android系统关闭后服务才停止。但有时我们希望在启动服务的Activity关闭后服务自动关闭,这就需要将Activity和Service绑定。
通过bindService方法可以将Activity和Service绑定。bindService方法的定义如下:
1.
publicbooleanbindService(Intentservice,ServiceConnectionconn,intflags)
该方法的第1个参数表示与服务类相关联的Intent对象,第2个参数是一个ServiceConnection类型的变量,负责连接Intent对象指定的服务。通过ServiceConnection对象可以获得连接成功或失败的状态,并可以获得连接后的服务对象。第3个参数是一个标志位,一般设为Context.BIND_AUTO_CREATE。
下面重新编写8.1.1节的MyService类,在该类中增加了几个与绑定相关的事件方法。
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.
packagenet.blogjava.mobile.service;
importandroid.app.Service;importandroid.content.Intent;importandroid.os.Binder;importandroid.os.IBinder;importandroid.util.Log;
publicclassMyServiceextendsService{
privateMyBindermyBinder=newMyBinder();//成功绑定后调用该方法@Override
publicIBinderonBind(Intentintent){
Log.d("MyService","onBind");returnmyBinder;}
//重新绑定时调用该方法@Override
publicvoidonRebind(Intentintent){
Log.d("MyService","onRebind");super.onRebind(intent);}
//解除绑定时调用该方法@Override
publicbooleanonUnbind(Intentintent){
Log.d("MyService","onUnbind");returnsuper.onUnbind(intent);}
@Override
publicvoidonCreate(){
Log.d("MyService","onCreate");super.onCreate();}
@Override
40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.
publicvoidonDestroy(){
Log.d("MyService","onDestroy");super.onDestroy();}
@Override
publicvoidonStart(Intentintent,intstartId){
Log.d("MyService","onStart");super.onStart(intent,startId);}
publicclassMyBinderextendsBinder{
MyServicegetService(){
returnMyService.this;}}}
现在定义一个MyService变量和一个ServiceConnection变量,代码如下:
1.2.3.4.5.6.7.8.9.
privateMyServicemyService;
privateServiceConnectionserviceConnection=newServiceConnection(){
//连接服务失败后,该方法被调用@Override
publicvoidonServiceDisconnected(ComponentNamename){
myService=null;
Toast.makeText(Main.this,"ServiceFailed.",Toast.LENGTH_LONG).show();10.11.12.13.14.15.16.17.
}
//成功连接服务后,该方法被调用。在该方法中可以获得MyService对象@Override
publicvoidonServiceConnected(ComponentNamename,IBinderservice){
//获得MyService对象
myService=((MyService.MyBinder)service).getService();Toast.makeText(Main.this,"ServiceConnected.",Toast.LENGTH_LONG).show();18.19.
}};
最后使用bindService方法来绑定Activity和Service,代码如下:
1.
bindService(serviceIntent,serviceConnection,Context.BIND_AUTO_CREATE);
如果想解除绑定,可以使用下面的代码:
1.
unbindService(serviceConnection);
8.1.3在BroadcastReceiver中启动Service
本节的例子代码所在的工程目录是src\ch08\ch08_startupservice
在8.1.1节和8.1.2节都是先启动了一个Activity,然后在Activity中启动服务。如果是这样,在启动服务时必须要先启动一个Activity。在很多时候这样做有些多余,阅读完第7章的内容,会发现实例43可以利用BroadcastReceiver在Android系统启动时运行一个Activity。也许我们会从中得到一些启发,既然可以在BroadcastReceiver中启动Activity,为什么不能启动Service呢?说做就做,现在让我们来验证一下这个想法。
先编写一个服务类,这个服务类没什么特别的,仍然使用前面两节编写的MyService类即可。在AndroidManifest.xml文件中配置MyService类的代码也相同。
下面来完成最关键的一步,就是建立一个BroadcastReceiver,代码如下:
1.2.3.4.5.6.7.8.
9.10.11.12.13.14.15.16.17.18.19.20.
packagenet.blogjava.mobile.startupservice;
importandroid.content.BroadcastReceiver;importandroid.content.Context;importandroid.content.Intent;
publicclassStartupReceiverextendsBroadcastReceiver{
@Override
publicvoidonReceive(Contextcontext,Intentintent){
//启动一个Service
IntentserviceIntent=newIntent(context,MyService.class);context.startService(serviceIntent);
IntentactivityIntent=newIntent(context,MessageActivity.class);//要想在Service中启动Activity,必须设置如下标志
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);context.startActivity(activityIntent);
}}
如果安装本例后,在重新启动模拟器后并未出现如图8.4所示的信息提示框,最大的可能是没有在AndroidManifest.xml文件中配置BroadcastReceiver和Service,下面来看一下AndroidManifest.xml文件的完整代码。
1.2.3.
android:versionName="1.0">
6.
7.8.
9.10.11.12.13.
14.
15.16.17.18.19.20.
21.
现在运行本例,然后重启一下模拟器,看看LogCat视图中是否输出了相应的日志信息。8.2系统服务
在Android系统中有很多内置的软件,例如,当手机接到来电时,会显示对方的电话号。也可以根据周围的环境将手机设置成震动或静音。如果想把这些功能加到自己的软件中应该怎么办呢?
答案就是"系统服务"。在Android系统中提供了很多这种服务,通过这些服务,就可以像Android系统的内置软件一样随心所欲地控制Android系统了。本节将介绍几种常用的系统服务来帮助读者理解和使用这些技术。
8.2.1获得系统服务
系统服务实际上可以看作是一个对象,通过Activity类的getSystemService方法可以获得指定的对象(系统服务)。getSystemService方法只有一个String类型的参数,表示系统服务的ID,这个ID在整个Android系统中是唯一的。例如,audio表示音频服务,window表示窗口服务,notification表示通知服务。
为了便于记忆和管理,AndroidSDK在android.content.Context类中定义了这些ID,例如,下面的代码是一些ID的定义。
1.
publicstaticfinalStringAUDIO_SERVICE="audio";//定义音频服务的ID2.
publicstaticfinalStringWINDOW_SERVICE="window";//定义窗口服务的ID3.
publicstaticfinalStringNOTIFICATION_SERVICE="notification";//定义通知服务的ID
下面的代码获得了剪贴板服务(android.text.ClipboardManager对象)。
1.2.3.
//获得ClipboardManager对象
android.text.ClipboardManagerclipboardManager=(android.text.ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);4.
clipboardManager.setText("设置剪贴版中的内容");
在调用ClipboardManager.setText方法设置文本后,在Android系统中所有的文本输入框都可以从这个剪贴板对象中获得这段文本,读者不妨自己试一试!
窗口服务(WindowManager对象)是最常用的系统服务之一,通过这个服务,可以获得很多与窗口相关的信息,例如,窗口的长度和宽度,如下面的代码所示:
1.2.3.4.5.6.7.8.9.
//获得WindowManager对象
android.view.WindowManagerwindowManager=(android.view.WindowManager)
getSystemService(Context.WINDOW_SERVICE);
//在窗口的标题栏输出当前窗口的宽度和高度,例如,320*480
setTitle(String.valueOf(windowManager.getDefaultDisplay().getWidth())+"*"+String.valueOf(windowManager.getDefaultDisplay().getHeight()));
本节简单介绍了如何获得系统服务以及两个常用的系统服务的使用方法,在接下来的实例47和实例48中将给出两个完整的关于获得和使用系统服务的例子以供读者参考。
实例47:监听手机来电
工程目录:src\ch08\ch08_phonestate
当来电话时,手机会显示对方的电话号,当接听电话时,会显示当前的通话状态。在这期间存在两个状态:来电状态和接听状态。如果在应用
程序中要监听这两个状态,并进行一些其他处理,就需要使用电话服务(TelephonyManager对象)。
本例通过TelephonyManager对象监听来电状态和接听状态,并在相应的状态显示一个Toast提示信息框。如果是来电状态,会显示对方的电话号,如果是通话状态,会显示"正在通话..."信息。下面先来看看来电和接听时的效果,如图8.5和图8.6所示。
其中MyPhoneCallListener类是一个电话状态监听器,该类是PhoneStateListener的子类,代码如下:
1.2.
publicclassMyPhoneCallListenerextendsPhoneStateListener{
3.4.5.6.7.8.9.10.
@Override
publicvoidonCallStateChanged(intstate,StringincomingNumber){
switch(state){
//通话状态
caseTelephonyManager.CALL_STATE_OFFHOOK:Toast.makeText(Main.this,"正在通话...",Toast.LENGTH_SHORT).show();
11.
12.13.14.
break;//来电状态
caseTelephonyManager.CALL_STATE_RINGING:Toast.makeText(Main.this,incomingNumber,Toast.LENGTH_SHORT).show();
15.16.17.18.19.
break;}
super.onCallStateChanged(state,incomingNumber);}}
实例48:来电黑名单
工程目录:src\ch08\ch08_phoneblacklist
虽然手机为我们带来了方便,但有时实在不想接听某人的电话,但又不好直接挂断电话,怎么办呢?很简单,如果发现是某人来的电话,直接将手机设成静音,这样就可以不予理睬了。
本例与实例47类似,也就是说,仍然需要获得TelephonyManager对象,并监听手机的来电状态。为了可以将手机静音,还需要获得一个音频服务(AudioManager对象)。本例需要修改实例47中的手机接听状态方法onCallStateChanged中的代码,修改后的结果如下:
1.2.3.4.5.6.7.
publicclassMyPhoneCallListenerextendsPhoneStateListener{
@Override
publicvoidonCallStateChanged(intstate,StringincomingNumber){
//获得音频服务(AudioManager对象)
AudioManageraudioManager=(AudioManager)getSystemService(Context.AUDIO_SERVICE);8.9.10.11.12.
switch(state){
caseTelephonyManager.CALL_STATE_IDLE:
//在手机空闲状态时,将手机音频设为正常状态audioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);13.14.15.
break;
caseTelephonyManager.CALL_STATE_RINGING://在来电状态时,判断打进来的是否为要静音的电话号,如果是,则静音16.17.18.19.
if("12345678".equals(incomingNumber)){
//将电话静音
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);20.21.22.23.24.25.
}break;}
super.onCallStateChanged(state,incomingNumber);}}
在上面的代码中,只设置了"12345678"为静音电话号,读者可以采用实例47的方法使用"12345678"打入电话,再使用其他的电话号打入,看看模拟器是否会响铃。
8.2.2在模拟器上模拟重力感应
众所周知,Android系统支持重力感应,通过这种技术,可以利用手机的移动、翻转来实现更为有趣的程序。但遗憾的是,在Android模拟器上是无法进行重力感应测试的。既然Android系统支持重力感应,但又在模拟器上无法测试,该怎么办呢?别着急,天无绝人之路,有一些第三方的工具可以帮助我们完成这个工作,本节将介绍一种在模拟器上模拟重力感应的工具(sensorsimulator)。这个工具分为服务端和客户端两部分。服务端是一个在PC上运行的JavaSwingGUI程序,客户端是一个手机程序(apk文件),在运行时需要通过客户端程序连接到服务端
程序上才可以在模拟器上模拟重力感应。
读者可以从下面的地址
下载这个工具:
1.
code.google/p/openintents/downloads/list
下面来测试一下SensorSimulator自带的一个demo,在这个demo中输出了通过模拟重力感应获得的数据。
在实例49中将给出一个完整的例子来演示如何利用重力感应的功能来实现手机翻转静音的效果。实例49:手机翻转静音
1.
工程目录:src\ch08\ch08_phonereversal
与手机来电一样,手机翻转状态(重力感应)也由系统服务提供。重力感应服务(android.hardware.SensorManager对象)可以通过如下代码获得:
1.
SensorManagersensorManager=(SensorManager)getSystemService(Context.SENSOR_SERVICE);
本例需要在模拟器上模拟重力感应,因此,在本例中使用SensorSimulator中的一个类
(SensorManagerSimulator)来获得重力感应服务,这个类封装了SensorManager对象,并负责与服务端进行通信,监听重力感应事件也需要一个监听器,该监听器需要实现SensorListener接口,并通过该接口的onSensorChanged事件方法获得重力感应数据。本例完整的代码如下:
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.
packagenet.blogjava.mobile;
importorg.openintents.sensorsimulator.hardware.SensorManagerSimulator;importandroid.app.Activity;importandroid.content.Context;
importandroid.hardware.SensorListener;importandroid.hardware.SensorManager;importandroid.media.AudioManager;importandroid.os.Bundle;importandroid.widget.TextView;
publicclassMainextendsActivityimplementsSensorListener{
privateTextViewtvSensorState;
privateSensorManagerSimulatorsensorManager;@Override
publicvoidonAccuracyChanged(intsensor,intaccuracy){}
@Override
publicvoidonSensorChanged(intsensor,float[]values){
switch(sensor){
caseSensorManager.SENSOR_ORIENTATION://获得声音服务
AudioManageraudioManager=(AudioManager)getSystemService(Context.AUDIO_SERVICE);29.
//在这里规定翻转角度小于-120度时静音,values[2]表示翻转角度,也可以设置其他角度30.31.32.
if(values[2]<-120){
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
33.34.35.36.
}else{
audioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
37.38.
}
tvSensorState.setText("角度:"+String.valueOf(values[2]));
39.40.41.42.43.44.45.46.
break;}}
@Override
protectedvoidonResume(){
//
注册重力感应监听事件
sensorManager.registerListener(this,SensorManager.SENSOR_ORIENTATION);
47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.
super.onResume();}
@Override
protectedvoidonStop(){
//取消对重力感应的监听
sensorManager.unregisterListener(this);super.onStop();}
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);setContentView(R.layout.main);
//通过SensorManagerSimulator对象获得重力感应服务
sensorManager=(SensorManagerSimulator)SensorManagerSimulator.getSystemService(this,Context.SENSOR_SERVICE);//连接到服务端
程序(必须执行下面的代码)sensorManager.connectSimulator();}}
在上面的代码中使用了一个SensorManagerSimulator类,该类在SensorSimulator工具包带的
sensorsimulator-lib.jar文件中,可以在lib目录中找到这个jar文件。在使用SensorManagerSimulator类之前,必须在相应的Eclipse工程中引用这个jar文件。
现在运行本例,并通过服务端主界面右侧的【Roll】滑动杆移动到指定的角度,例如,-74.0和-142.0,这时设置的角度会显示在屏幕上,如图8.14和图8.15所示。
读者可以在如图8.14和图8.15所示的翻转状态下拨入电话,会发现翻转角度在-74.0度时来电仍然会响铃,而翻转角度在-142.0度时就不再响铃了。
由于SensorSimulator目前不支持AndroidSDK1.5及以上版本,因此,只能使用AndroidSDK1.1中的SensorListener接口来监听重力感应事件。在AndroidSDK1.5及以上版本并不建议继续使用这个接口,代替它的是android.hardware.SensorEventListener接口。
8.3.1计时器:Chronometer
8.3时间服务
在AndroidSDK中提供了多种时间服务。这些时间服务主要处理在一定时间间隔或未来某一时间发生的任务。Android系统中的时间服务的作用域既可以是应用
程序本身,也可以是整个Android系统。本节将详细介绍这些时间服务的使用方法,并给出大量的实例供读者学习。
8.3.1计时器:Chronometer
本节的例子代码所在的工程目录是src\ch08\ch08_chronometer
Chronometer是TextView的子类,也是一个Android组件。这个组件可以用1秒的时间间隔进行计时,并显示出计时结果。
Chronometer类有3个重要的方法:start、stop和setBase,其中start方法表示开始计时;stop方法表示停止计时;setBase方法表示重新计时。start和stop方法没有任何参数,setBase方法有一个参数,表示开始计时的基准时间。如果要从当前时刻重新计时,可以将该参数值设为SystemClock.elapsedRealtime()。
还可以对Chronometer组件做进一步设置。在默认情况下,Chronometer组件只输出MM:SS或H:MM:SS的时间格式。例如,当计时到1分20秒时,Chronometer组件会显示01:20。如果想改变显示的信息内容,可以使用Chronometer类的setFormat方法。该方法需要一个String变量,并使用"%s"表示计时信息。例如,使用setFormat("计时信息:%s")设置显示信息,Chronometer组件会显示如下计时信息:
计时信息:10:20
Chronometer组件还可以通过onChronometerTick事件方法来捕捉计时动作。该方法1秒调用一次。要想使用onChronometerTick事件方法,必须实现如下接口:
1.
android.widget.Chronometer.OnChronometerTickListener
在本例中有3个按钮,分别用来开始、停止和重置计时器,并通过onChronometerTick事件方法显示当前时间,代码如下:
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.
packagenet.blogjava.mobile;
importjava.text.SimpleDateFormat;importjava.util.Date;importandroid.app.Activity;importandroid.os.Bundle;importandroid.os.SystemClock;importandroid.view.View;
importandroid.view.View.OnClickListener;importandroid.widget.Button;importandroid.widget.Chronometer;importandroid.widget.TextView;
importandroid.widget.Chronometer.OnChronometerTickListener;
publicclassMainextendsActivityimplementsOnClickListener,OnChronometerTickListener16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.
{
privateChronometerchronometer;privateTextViewtvTime;@Override
publicvoidonClick(Viewview){
switch(view.getId()){
caseR.id.btnStart://开始计时器
chronometer.start();break;
caseR.id.btnStop://停止计时器chronometer.stop();break;
caseR.id.btnReset//重置计时器:
chronometer.setBase(SystemClock.elapsedRealtime());break;}}
38.39.40.41.42.
43.44.45.46.47.48.49.50.51.52.53.
54.55.56.57.58.59.60.61.62.63.
@Override
publicvoidonChronometerTick(Chronometerchronometer){
SimpleDateFormatsdf=newSimpleDateFormat("HH:mm:ss");
//将当前时间显示在TextView组件中
tvTime.setText("当前时间:"+sdf.format(newDate()));}
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);setContentView(R.layout.main);
tvTime=(TextView)findViewById(R.id.tvTime);
ButtonbtnStart=(Button)findViewById(R.id.btnStart);ButtonbtnStop=(Button)findViewById(R.id.btnStop);ButtonbtnReset=(Button)findViewById(R.id.btnReset);chronometer=(Chronometer)findViewById(R.id.chronometer);btnStart.setOnClickListener(this);btnStop.setOnClickListener(this);btnReset.setOnClickListener(this);//设置计时监听事件
chronometer.setOnChronometerTickListener(this);//设置计时信息的格式
chronometer.setFormat("计时器:%s");}}
8.3.2预约时间Handler
本节的例子代码所在的工程目录是src\ch08\ch08_handler
android.os.Handler是AndroidSDK中处理定时操作的核心类。通过Handler类,可以提交和处理一个Runnable对象。这个对象的run方法可以立刻执行,也可以在指定时间后执行(也可称为预约执行)。
Handler类主要可以使用如下3个方法来设置执行Runnable对象的时间:
1.2.3.4.5.6.
//立即执行Runnable对象
publicfinalbooleanpost(Runnabler);
//在指定的时间(uptimeMillis)执行Runnable对象
publicfinalbooleanpostAtTime(Runnabler,longuptimeMillis);//在指定的时间间隔(delayMillis)执行Runnable对象
publicfinalbooleanpostDelayed(Runnabler,longdelayMillis);
从上面3个方法可以看出,第1个参数的类型都是Runnable,因此,在调用这3个方法之前,需要有一个实现Runnable接口的类,Runnable接口的代码如下:
1.2.3.4.
publicinterfaceRunnable{
publicvoidrun();//线程要执行的方法}
在Runnable接口中只有一个run方法,该方法为线程执行方法。在本例中Main类实现了Runnable接口。可以使用如下代码指定在5秒后调用run方法:
1.2.
Handlerhandler=newHandler();handler.postDelayed(this,5000);
如果想在5秒内停止计时,可以使用如下代码:
1.
handler.removeCallbacks(this);
除此之外,还可以使用postAtTime方法指定未来的某一个精确时间来执行Runnable对象,代码如下:
1.2.3.4.
Handlerhandler=newHandler();handler.postAtTime(newRunToast(this){
},android.os.SystemClock.uptimeMillis()+15*1000);//在15秒后执行Runnable对象
其中RunToast是一个实现Runnable接口的类,代码如下:
1.2.3.4.5.6.7.8.9.10.
classRunToastimplementsRunnable{
privateContextcontext;
publicRunToast(Contextcontext){
this.context=context;}
@Override
publicvoidrun(){
11.Toast.makeText(context,"15秒后显示Toast提示信息",Toast.LENGTH_LONG).show();
12.13.
}}
postAtTime的第2个参数表示一个精确时间的毫秒数,如果从当前时间算起,需要使用android.os.SystemClock.uptimeMillis()获得基准时间。
要注意的是,不管使用哪个方法来执行Runnable对象,都只能运行一次。如果想循环执行,必须在执行完后再次调用post、postAtTime或postDelayed方法。例如,在Main类的run方法中再次调用了postDelayed方法,代码如下:
1.2.3.4.
publicvoidrun(){
tvCount.setText("Count:"+String.valueOf(++count));//再次调用postDelayed方法,5秒后run方法仍被
调用,然后再一次调用postDelayed方法,这样就形成了5.6.7.
//循环调用
handler.postDelayed(this,5000);}
8.3.4在线程中更新GUI组件
本节的例子代码所在的工程目录是src\ch08\ch08_thread
除了前面介绍的时间服务可以执行定时任务外,也可以采用线程的方式在后台执行任务。在Android系统中创建和启动线程的方法与传统的Java
程序相同,首先要创建一个Thread对象,然后使用Thread类的start方法开始一个线程。线程在启动后,就会执行Runnable接口的run方法。
本例中启动了两个线程,分别用来更新两个进度条组件。在8.3.3节曾介绍过,在线程中更新GUI组件需要使用Handler类,当然,直接利用线程作为后台服务也不例外。下面先来看看本例的完整源代码。
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.
packagenet.blogjava.mobile;
importandroid.app.Activity;importandroid.os.Bundle;importandroid.os.Handler;
importandroid.widget.ProgressBar;
publicclassMainextendsActivity{
privateProgressBarprogressBar1;privateProgressBarprogressBar2;
privateHandlerhandler=newHandler();privateintcount1=0;privateintcount2=0;
privateRunnabledoUpdateProgressBar1=newRunnable(){
@Override
publicvoidrun(){
for(count1=0;count1<=progressBar1.getMax();count1++){
//使用post方法立即执行Runnable接口的run方法handler.post(newRunnable(){
@Override
publicvoidrun(){
progressBar1.setProgress(count1);}});}}};
privateRunnabledoUpdateProgressBar2=newRunnable(){
@Override
publicvoidrun(){
39.40.41.42.43.44.45.
46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.
for(count2=0;count2<=progressBar2.getMax();count2++){
//使用post方法立即执行Runnable接口的run方法handler.post(newRunnable(){
@Override
publicvoidrun(){
progressBar2.setProgress(count2);}});}}};
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);setContentView(R.layout.main);
progressBar1
=(ProgressBar)findViewById(R.id.progressbar1);progressBar2=(ProgressBar)findViewById(R.id.progressbar2);Threadthread1=newThread(doUpdateProgressBar1,"thread1");//启动第1个线程thread1.start();
Threadthread2=newThread(doUpdateProgressBar2,"thread2");//启动第2个线程thread2.start();}}
8.3.5全局定时器AlarmManager(1)
8.3.5全局定时器AlarmManager(1)
本节的例子代码所在的工程目录是src\ch08\ch08_alarm
前面介绍的时间服务的作用域都是应用程序,也就是说,将当前的应用程序关闭后,时间服务就会停止。但在很多时候,需要时间服务不依赖应用程序而存在。也就是说,虽然是应用程序启动的服务,但即使将应用
程序关闭,服务仍然可以正常运行。
为了达到服务与应用
程序独立的目的,需要获得AlarmManager对象。该对象需要通过如下代码获得:
1.
AlarmManageralarmManager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
AlarmManager类的一个非常重要的方法是setRepeating,通过该方法,可以设置执行时间间隔和相应的动作。setRepeating方法的定义如下:
1.
publicvoidsetRepeating(inttype,longtriggerAtTime,longinterval,PendingIntentoperation);
setRepeating方法有4个参数,这些参数的含义如下:
type:表示警报类型,一般可以取的值是AlarmManager.RTC和AlarmManager.RTC_WAKEUP。如果将type参数值设为AlarmManager.RTC,表示是一个正常的定时器,如果将type参数值设为AlarmManager.RTC_WAKEUP,除了有定时器的功能外,还会发出警报声(例如,响铃、震动)。
triggerAtTime:第1次运行时要等待的时间,也就是执行延迟时间,单位是毫秒。interval:表示执行的时间间隔,单位是毫秒。
operation:一个PendingIntent对象,表示到时间后要执行的操作。PendingIntent与Intent类似,可以封装Activity、BroadcastReceiver和Service。但与Intent不同的是,PendingIntent可以脱离应用
程序而存在。
从setRepeating方法的4个参数可以看出,使用setRepeating方法最重要的就是创建PendingIntent对象。例如,在下面的代码中用PendingIntent指定了一个Activity。
1.2.
Intentintent=newIntent(this,MyActivity.class);PendingIntentpendingActivityIntent=PendingIntent.getActivity(this,0,intent,0);
在创建完PendingIntent对象后,就可以使用setRepeating方法设置定时器了,代码如下:
1.
AlarmManageralarmManager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);2.
alarmManager.setRepeating(AlarmManager.RTC,0,5000,pendingActivityIntent);
执行上面的代码,即使应用程序关闭后,每隔5秒,系统仍然会显示MyActivity。如果要取消定时器,可以使用如下代码:
1.
alarmManager.cancel(pendingActivityIntent);
4.5.6.7.8.9.10.11.12.
importandroid.app.Service;importandroid.content.Intent;importandroid.os.IBinder;
publicclassChangeWallpaperServiceextendsService{
privatestaticintindex=0;//保存res\raw目录中图像资源的ID
privateint[]resIds=newint[]{R.raw.wp1,R.raw.wp2,R.raw.wp3,R.raw.wp4,R.raw.wp5};
13.14.15.16.17.18.19.
@Override
publicvoidonStart(Intentintent,intstartId){
if(index==5)index=0;
//获得res\raw目录中图像资源的InputStream对象InputStreaminputStream=getResources().openRawResource(resIds[index++]);
20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.
try{
//更换壁纸
setWallpaper(inputStream);}
catch(Exceptione){}
super.onStart(intent,startId);}
@Override
publicvoidonCreate(){
super.onCreate();}
@Override
publicIBinderonBind(Intentintent){
returnnull;}}
8.3.5全局定时器AlarmManager(2)
8.3.5全局定时器AlarmManager(2)
在编写ChangeWallpaperService类时应注意如下3点:
为了通过InputStream获得图像资源,需要将图像文件放在res\raw目录中,而不是res\drawable目录中。本例采用了循环更换壁纸的方法。也就是说,共有5个图像文件,系统会从第1个图像文件开始更换,更换完第5个文件后,又从第1个文件开始更换。
更换壁纸需要使用Context.setWallpaper方法,该方法需要一个描述图像的InputStream对象。该对象通过getResources().openRawResource(...)方法获得。
在AndroidManifest.xml文件中配置ChangeWallpaperService类,代码如下:
1.
最后来看一下本例的主程序(Main类),代码如下:
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.
packagenet.blogjava.mobile;
importandroid.app.Activity;importandroid.app.AlarmManager;importandroid.app.PendingIntent;importandroid.content.Context;importandroid.content.Intent;importandroid.os.Bundle;importandroid.view.View;
importandroid.view.View.OnClickListener;importandroid.widget.Button;
publicclassMainextendsActivityimplementsOnClickListener{
privateButtonbtnStart;privateButtonbtnStop;@Override
publicvoidonClick(Viewview){
AlarmManageralarmManager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);21.22.23.24.25.26.27.28.
//指定ChangeWallpaperService的PendingIntent对象
PendingIntentpendingIntent=PendingIntent.getService(this,0,newIntent(this,ChangeWallpaperService.class),0);switch(view.getId()){
caseR.id.btnStart:
//开始每5秒更换一次壁纸
alarmManager.setRepeating(AlarmManager.RTC,0,5000,pendingIntent);29.30.31.32.
btnStart.setEnabled(false);btnStop.setEnabled(true);break;
caseR.id.btnStop:
33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.
//停止更换一次壁纸
alarmManager.cancel(pendingIntent);btnStart.setEnabled(true);btnStop.setEnabled(false);break;}}
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);setContentView(R.layout.main);
btnStart=(Button)findViewById(R.id.btnStart);btnStop=(Button)findViewById(R.id.btnStop);btnStop.setEnabled(false);
btnStart.setOnClickListener(this);btnStop.setOnClickListener(this);}}
在编写上面代码时应注意如下3点:
在创建PendingIntent对象时指定了ChangeWallpaperService.class,这说明这个PendingIntent对象与ChangeWallpaperService绑定。AlarmManager在执行任务时会执行ChangeWallpaperService类中的onStart方法。
不要将任务代码写在onCreate方法中,因为onCreate方法只会执行一次,一旦服务被创建,该方法就不会被执行了,而onStart方法在每次访问服务时都会被调用。
获得指定Service的PendingIntent对象需要使用getService方法。在8.3.5节介绍过获得指定Activity的PendingIntent对象应使用getActivity方法。在实例51中将介绍使用getBroadcast方法获得指定BroadcastReceiver的PendingIntent对象。
实例51:多次定时提醒
工程目录:src\ch08\ch08_multialarm
在很多软件中都支持定时提醒功能,也就是说,事先设置未来的某个时间,当到这个时间后,系统会发出声音或进行其他的工作。本例中将实现这个功能。本例不仅可以设置定时提醒功能,而且支持设置多个时间点。运行本例后,单击【添加提醒时间】按钮,会弹出设置时间点的对话框,如图8.22所示。当设置完一系列的时间点后(如图8.23所示),如果到了某个时间点,系统就会播放一个声音文件以提醒用户。
下面先介绍一下定时提醒的原理。在添加时间点后,需要将所添加的时间点保存在文件或数据库中。本例使用SharedPreferences来保存时间点,key和value都是时间点。然后使用AlarmManager每隔1分钟扫描一次,在扫描过程中从文件获得当前时间(时:分)的value。如果成功获得value,则说明当前时间为时间点,需要播放声音文件,否则继续扫描。
8.3.5全局定时器AlarmManager(2)
在编写ChangeWallpaperService类时应注意如下3点:
为了通过InputStream获得图像资源,需要将图像文件放在res\raw目录中,而不是res\drawable目录中。本例采用了循环更换壁纸的方法。也就是说,共有5个图像文件,系统会从第1个图像文件开始更换,更换完第5个文件后,又从第1个文件开始更换。
更换壁纸需要使用Context.setWallpaper方法,该方法需要一个描述图像的InputStream对象。该对象通过getResources().openRawResource(...)方法获得。
在AndroidManifest.xml文件中配置ChangeWallpaperService类,代码如下:
1.
最后来看一下本例的主程序(Main类),代码如下:
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.
packagenet.blogjava.mobile;
importandroid.app.Activity;importandroid.app.AlarmManager;importandroid.app.PendingIntent;importandroid.content.Context;importandroid.content.Intent;importandroid.os.Bundle;importandroid.view.View;
importandroid.view.View.OnClickListener;importandroid.widget.Button;
publicclassMainextendsActivityimplementsOnClickListener{
privateButtonbtnStart;privateButtonbtnStop;@Override
publicvoidonClick(Viewview){
AlarmManageralarmManager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);21.22.23.24.25.26.27.28.
//指定ChangeWallpaperService的PendingIntent对象
PendingIntentpendingIntent=PendingIntent.getService(this,0,newIntent(this,ChangeWallpaperService.class),0);switch(view.getId()){
caseR.id.btnStart:
//开始每5秒更换一次壁纸
alarmManager.setRepeating(AlarmManager.RTC,0,5000,pendingIntent);29.30.31.32.
btnStart.setEnabled(false);btnStop.setEnabled(true);break;
caseR.id.btnStop:
33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.
//停止更换一次壁纸
alarmManager.cancel(pendingIntent);btnStart.setEnabled(true);btnStop.setEnabled(false);break;}}
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);setContentView(R.layout.main);
btnStart=(Button)findViewById(R.id.btnStart);btnStop=(Button)findViewById(R.id.btnStop);btnStop.setEnabled(false);
btnStart.setOnClickListener(this);btnStop.setOnClickListener(this);}}
在编写上面代码时应注意如下3点:
在创建PendingIntent对象时指定了ChangeWallpaperService.class,这说明这个PendingIntent对象与ChangeWallpaperService绑定。AlarmManager在执行任务时会执行ChangeWallpaperService类中的onStart方法。
不要将任务代码写在onCreate方法中,因为onCreate方法只会执行一次,一旦服务被创建,该方法就不会被执行了,而onStart方法在每次访问服务时都会被调用。
获得指定Service的PendingIntent对象需要使用getService方法。在8.3.5节介绍过获得指定Activity的PendingIntent对象应使用getActivity方法。在实例51中将介绍使用getBroadcast方法获得指定BroadcastReceiver的PendingIntent对象。
实例51:多次定时提醒
工程目录:src\ch08\ch08_multialarm
在很多软件中都支持定时提醒功能,也就是说,事先设置未来的某个时间,当到这个时间后,系统会发出声音或进行其他的工作。本例中将实现这个功能。本例不仅可以设置定时提醒功能,而且支持设置多个时间点。运行本例后,单击【添加提醒时间】按钮,会弹出设置时间点的对话框,如图8.22所示。当设置完一系列的时间点后(如图8.23所示),如果到了某个时间点,系统就会播放一个声音文件以提醒用户。
下面先介绍一下定时提醒的原理。在添加时间点后,需要将所添加的时间点保存在文件或数据库中。本例使用SharedPreferences来保存时间点,key和value都是时间点。然后使用AlarmManager每隔1分钟扫描一次,在扫描过程中从文件获得当前时间(时:分)的value。如果成功获得value,则说明当前时间为时间点,需要播放声音文件,否则继续扫描。
8.3.5全局定时器AlarmManager(3)
本例使用BroadcastReceiver来处理定时提醒任务。BroadcastReceiver类的代码如下:
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.
packagenet.blogjava.mobile;
importjava.util.Calendar;importandroid.app.Activity;
importandroid.content.BroadcastReceiver;importandroid.content.Context;importandroid.content.Intent;
importandroid.content.SharedPreferences;importandroid.media.MediaPlayer;
publicclassAlarmReceiverextendsBroadcastReceiver{
@Override
publicvoidonReceive(Contextcontext,Intentintent){
SharedPreferencessharedPreferences=context.getSharedPreferences(
17.18.
"alarm_record",Activity.MODE_PRIVATE);Stringhour=String.valueOf(Calendar.getInstance().get(Calendar.HOUR_OF_DAY));
19.Stringminute=String.valueOf(Calendar.getInstance().get(Calendar.MINUTE));
20.21.
//从XML文件中获得描述当前时间点的valueStringtime=sharedPreferences.getString(hour+":"+minute,null);
22.23.24.25.
if(time!=null){//播放声音
MediaPlayermediaPlayer=MediaPlayer.create(context,R.raw.ring);
26.27.28.29.
mediaPlayer.start();}}}
配置AlarmReceiver类的代码如下:
1.
在主程序中每添加一个时间点,就会在XML文件中保存所添加的时间点,代码如下:
1.2.3.4.5.6.
packagenet.blogjava.mobile;
importandroid.app.Activity;importandroid.app.AlarmManager;importandroid.app.AlertDialog;importandroid.app.PendingIntent;
7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.
importandroid.content.Context;
importandroid.content.DialogInterface;importandroid.content.Intent;
importandroid.content.SharedPreferences;importandroid.os.Bundle;importandroid.view.View;
importandroid.view.View.OnClickListener;importandroid.widget.Button;importandroid.widget.TextView;importandroid.widget.TimePicker;
publicclassMainextendsActivityimplementsOnClickListener{
privateTextViewtvAlarmRecord;
privateSharedPreferencessharedPreferences;@Override
publicvoidonClick(Viewv){
Viewview=getLayoutInflater().inflate(R.layout.alarm,null);finalTimePickertimePicker=(TimePicker)view.findViewById(R.id.timepicker);
27.28.29.30.
timePicker.setIs24HourView(true);//显示设置时间点的对话框
newAlertDialog.Builder(this).setTitle("设置提醒时间").setView(view).setPositiveButton("确定",newDialogInterface.OnClickListener()
31.32.33.34.35.36.37.
{
@Override
publicvoidonClick(DialogInterfacedialog,intwhich){
StringtimeStr=String.valueOf(timePicker.getCurrentHour())+":"+String.valueOf(timePicker.getCurrentMinute());
38.39.
//将时间点添加到TextView组件中tvAlarmRecord.setText
(tvAlarmRecord.getText().toString()+"\n"+timeStr);
40.41.
//保存时间点
sharedPreferences.edit().putString(timeStr,timeStr)mit();
42.43.44.45.
}
}).setNegativeButton("取消",null).show();}
@Override
46.47.48.49.50.51.52.53.54.55.
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);setContentView(R.layout.main);
ButtonbtnAddAlarm=(Button)findViewById(R.id.btnAddAlarm);tvAlarmRecord=(TextView)findViewById(R.id.tvAlarmRecord);btnAddAlarm.setOnClickListener(this);
sharedPreferences=getSharedPreferences("alarm_record",Activity.MODE_PRIVATE);
AlarmManageralarmManager=(AlarmManager)getSystemService(Context.ALARM_SERVICE);
56.57.58.
Intentintent=newIntent(this,AlarmReceiver.class);//创建封装BroadcastReceiver的pendingIntent对象PendingIntentpendingIntent=PendingIntent.getBroadcast(this,0,intent,0);
59.60.
//开始定时器,每1分钟执行一次
alarmManager.setRepeating(AlarmManager.RTC,0,60*1000,pendingIntent);
61.62.
}}
在使用本例添加若干个时间点后,会在alarm_record.xml文件中看到类似下面的内容:
1.2.3.4.5.6.7.8.9.10.11.
上面每个
元素都是一个时间点,定时器将每隔1分钟查一次alarm_record.xml文件。
8.4.1什么是AIDL服务
8.4跨进程访问(AIDL服务)
Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。第7章介绍的Activity和Broadcast都可以跨进程通信,除此之外,还可以使用ContentProvider(见6.6节的介绍)进行跨进程通信。现在我们已经了解了4个Android应用程序组件中的3个(Activity、Broadcast和ContentProvider)都可以进行跨进程访问,另外一个Android应用程序组件Service同样可以。这就是本节要介绍的AIDL服务。
8.4.1什么是AIDL服务
本章前面的部分介绍了开发人员如何定制自己的服务,但这些服务并不能被其他的应用程序访问。为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(RemoteProcedureCall,RPC)方式来实现。与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(InterfaceDefinitionLanguage,IDL)来公开服务的接口。因此,可以将这种可以跨进程访问的服务称为AIDL(AndroidInterfaceDefinitionLanguage)服务。
IMyService.aidl文件的内容与Java代码非常相似,但要注意,不能加修饰符(例如,public、private)、AIDL服务不支持的数据类型(例如,InputStream、OutputStream)等内容。
(2)如果IMyService.aidl文件中的内容输入正确,ADT会自动生成一个IMyService.java文件。读者一般并不需要关心这个文件的具体内容,也不需要维护这个文件。关于该文件的具体内容,读者可以查看本节提供的源代码。
(3)编写一个MyService类。MyService是Service的子类,在MyService类中定义了一个内嵌类(MySer, viceImpl),该类是IMyService.Stub的子类。MyService类的代码如下:
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.
packagenet.blogjava.mobile.aidl;
importandroid.app.Service;importandroid.content.Intent;importandroid.os.IBinder;
importandroid.os.RemoteException;
publicclassMyServiceextendsService{
publicclassMyServiceImplextendsIMyService.Stub{
@Override
publicStringgetValue()throwsRemoteException{
return"Android/OPhone开发讲义";}}
@Override
publicIBinderonBind(Intentintent){
returnnewMyServiceImpl();}}
在编写上面代码时要注意如下两点:
IMyService.Stub是根据IMyService.aidl文件自动生成的,一般并不需要管这个类的内容,只需要编写一个继承于IMyService.Stub类的子类(MyServiceImpl类)即可。
onBind方法必须返回MyServiceImpl类的对象实例,否则客户端无法获得服务对象。
(4)在AndroidManifest.xml文件中配置MyService类,代码如下:
1.2.3.4.5.
调用AIDL服务首先要绑定服务,然后才能获得服务对象,代码如下:
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.
packagenet.blogjava.mobile;
importnet.blogjava.mobile.aidl.IMyService;importandroid.app.Activity;
importandroid.content.ComponentName;importandroid.content.Context;importandroid.content.Intent;
importandroid.content.ServiceConnection;importandroid.os.Bundle;importandroid.os.IBinder;importandroid.view.View;
importandroid.view.View.OnClickListener;importandroid.widget.Button;importandroid.widget.TextView;
publicclassMainextendsActivityimplementsOnClickListener{
privateIMyServicemyService=null;privateButtonbtnInvokeAIDLService;privateButtonbtnBindAIDLService;
21.22.
privateTextViewtextView;
privateServiceConnectionserviceConnection=newServiceConnection()
23.24.25.
{
@Override
publicvoidonServiceConnected(ComponentNamename,IBinderservice)
26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.
{
//获得服务对象
myService=IMyService.Stub.asInterface(service);btnInvokeAIDLService.setEnabled(true);}
@Override
publicvoidonServiceDisconnected(ComponentNamename){}};
@Override
publicvoidonClick(Viewview){
switch(view.getId()){
caseR.id.btnBindAIDLService://绑定AIDL服务
bindService(newIntent("net.blogjava.mobile.aidl.IMyService"),
44.45.46.47.48.49.
serviceConnection,Context.BIND_AUTO_CREATE);break;
caseR.id.btnInvokeAIDLService:try{
textView.setText(myService.getValue());//调用服务端的getValue方法
50.51.52.53.54.55.56.57.58.59.60.
}
catch(Exceptione){}break;}}
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
61.62.
setContentView(R.layout.main);
btnInvokeAIDLService=(Button)findViewById(R.id.btnInvokeAIDLService);
63.btnBindAIDLService=(Button)findViewById(R.id.btnBindAIDLService);
64.65.66.67.68.69.
btnInvokeAIDLService.setEnabled(false);
textView=(TextView)findViewById(R.id.textview);btnInvokeAIDLService.setOnClickListener(this);btnBindAIDLService.setOnClickListener(this);}}
8.4.2建立AIDL服务的步骤(2)
实例53:传递复杂数据的AIDL服务
AIDL服务工程目录:src\ch08\ch08_complextypeaidl客户端程序工程目录:src\ch08\ch08_complextypeaidlclient
AIDL服务只支持有限的数据类型,因此,如果用AIDL服务传递一些复杂的数据就需要做更一步处理。AIDL服务支持的数据类型如下:
Java的简单类型(int、char、boolean等)。不需要导入(import)。String和CharSequence。不需要导入(import)。
List和Map。但要注意,List和Map对象的元素类型必须是AIDL服务支持的数据类型。不需要导入(import)。
AIDL自动生成的接口。需要导入(import)。
实现android.os.Parcelable接口的类。需要导入(import)。
其中后两种数据类型需要使用import进行导入,将在本章的后面详细介绍。
传递不需要import的数据类型的值的方式相同。传递一个需要import的数据类型的值(例如,实现android.os.Parcelable接口的类)的步骤略显复杂。除了要建立一个实现android.os.Parcelable接口的类外,还需要为这个类单独建立一个aidl文件,并使用parcelable关键字进行定义。具体的实现步骤如下:
(1)建立一个IMyService.aidl文件,并输入如下代码:
1.2.3.4.5.6.7.
packagenet.blogjava.mobileplex.type.aidl;
importnet.blogjava.mobileplex.type.aidl.Product;interfaceIMyService{
MapgetMap(inStringcountry,inProductproduct);ProductgetProduct();}
在编写上面代码时要注意如下两点:
Product是一个实现android.os.Parcelable接口的类,需要使用import导入这个类。
如果方法的类型是非简单类型,例如,String、List或自定义的类,需要使用in、out或inout修饰。其中in表示这个值被客户端设置;out表示这个值被服务端设置;inout表示这个值既被客户端设置,又被服务端设置。
(2)编写Product类。该类是用于传递的数据类型,代码如下:
1.2.3.4.5.6.7.8.9.10.11.
packagenet.blogjava.mobileplex.type.aidl;
importandroid.os.Parcel;importandroid.os.Parcelable;
publicclassProductimplementsParcelable{
privateintid;privateStringname;privatefloatprice;
publicstaticfinalParcelable.CreatorCREATOR=newParcelable.Creator()12.13.
{
publicProductcreateFromParcel(Parcelin)
14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.
{
returnnewProduct(in);}
publicProduct[]newArray(intsize){
returnnewProduct[size];}};
publicProduct(){}
privateProduct(Parcelin){
readFromParcel(in);}
@Override
publicintdescribeContents(){
return0;}
publicvoidreadFromParcel(Parcelin){
id=in.readInt();name=in.readString();price=in.readFloat();}
@Override
publicvoidwriteToParcel(Parceldest,intflags){
dest.writeInt(id);dest.writeString(name);dest.writeFloat(price);}
//此处省略了属性的getter和setter方法......}
在编写Product类时应注意如下3点:
Product类必须实现android.os.Parcelable接口。该接口用于序列化对象。在Android中之所以使用Pacelable接口序列化,而不是java.io.Serializable接口,是因为Google在开发Android时发现Serializable序列化的效率并不高,因此,特意提供了一个Parcelable接口来序列化对象。
在Product类中必须有一个静态常量,常量名必须是CREATOR,而且CREATOR常量的数据类型必须是Parcelable.Creator。
在writeToParcel方法中需要将要序列化的值写入Parcel对象。(3)建立一个Product.aidl文件,并输入如下内容:
1.
parcelableProduct;
8.4.2建立AIDL服务的步骤(3)
8.4.2建立AIDL服务的步骤(3)(4)编写一个MyService类,代码如下:
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.
packagenet.blogjava.mobileplex.type.aidl;
importjava.util.HashMap;importjava.util.Map;importandroid.app.Service;importandroid.content.Intent;importandroid.os.IBinder;
importandroid.os.RemoteException;//AIDL服务类
publicclassMyServiceextendsService{
publicclassMyServiceImplextendsIMyService.Stub{
@Override
publicProductgetProduct()throwsRemoteException{
Productproduct=newProduct();product.setId(1234);product.setName("汽车");product.setPrice(31000);returnproduct;}
@Override
publicMapgetMap(Stringcountry,Productproduct)throwsRemoteException25.26.27.28.29.30.31.32.33.34.
{
Mapmap=newHashMap();map.put("country",country);map.put("id",product.getId());map.put("name",product.getName());map.put("price",product.getPrice());map.put("product",product);returnmap;}}
35.36.37.38.39.40.
@Override
publicIBinderonBind(Intentintent){
returnnewMyServiceImpl();}}
(5)在AndroidManifest.xml文件中配置MyService类,代码如下:
1.2.3.
4.5.
在客户端调用AIDL服务的方法与实例52介绍的方法相同,首先将IMyService.java和Product.java文件复制到客户端工程(ch08_complextypeaidlclient),然后绑定AIDL服务,并获得AIDL服务对象,最后调用AIDL服务中的方法。完整的客户端代码如下:
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.
packagenet.blogjava.mobile;
importnet.blogjava.mobileplex.type.aidl.IMyService;importandroid.app.Activity;
importandroid.content.ComponentName;importandroid.content.Context;importandroid.content.Intent;
importandroid.content.ServiceConnection;importandroid.os.Bundle;importandroid.os.IBinder;importandroid.view.View;
importandroid.view.View.OnClickListener;importandroid.widget.Button;importandroid.widget.TextView;
publicclassMainextendsActivityimplementsOnClickListener{
privateIMyServicemyService=null;privateButtonbtnInvokeAIDLService;privateButtonbtnBindAIDLService;privateTextViewtextView;
privateServiceConnectionserviceConnection=newServiceConnection(){
@Override
publicvoidonServiceConnected(ComponentNamename,IBinderservice){
//获得AIDL服务对象
28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.
myService=IMyService.Stub.asInterface(service);btnInvokeAIDLService.setEnabled(true);}
@Override
publicvoidonServiceDisconnected(ComponentNamename){}};
@Override
publicvoidonClick(Viewview){
switch(view.getId()){
caseR.id.btnBindAIDLService://绑定AIDL服务
bindService(newIntent("net.blogjava.mobileplex.type.aidl.IMyService"),
44.45.46.47.48.49.50.51.
serviceConnection,Context.BIND_AUTO_CREATE);break;
caseR.id.btnInvokeAIDLService:try{
Strings="";
//调用AIDL服务中的方法s="Product.id="+myService.getProduct().getId()+"\n";
52.s+="Product.name="+myService.getProduct().getName()+"\n";
53.s+="Product.price="+myService.getProduct().getPrice()+"\n";
54.s+=myService.getMap("China",myService.getProduct()).toString();
55.56.57.58.59.60.61.62.63.64.65.66.
textView.setText(s);}
catch(Exceptione){}break;}}
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
67.68.69.70.71.
72.73.74.75.
setContentView(R.layout.main);
btnInvokeAIDLService=(Button)findViewById(R.id.btnInvokeAIDLService);btnBindAIDLService=(Button)findViewById(R.id.btnBindAIDLService);btnInvokeAIDLService.setEnabled(false);
textView=(TextView)findViewById(R.id.textview);btnInvokeAIDLService.setOnClickListener(this);btnBindAIDLService.setOnClickListener(this);}}
8.5本章小结
8.5本章小结
本章主要介绍了Android系统中的服务(Service)技术。Service是Android中4个应用程序组件之一。在Android系统内部提供了很多的系统服务,通过这些系统服务,可以实现更为复杂的功能,例如,监听来电、重力感应等。Android系统还允许开发人员自定义服务。自定义的服务可以用来在后台运行程序,也可以通过AIDL服务提供给其他的应用使用。除此之外,在Android系统中还有很多专用于时间的服务和组件,例如,Chronometer、Timer、Handler、AlarmManager等。通过这些服务,可以完成关于时间的定时、预约等操作。