`
hgfghwq10
  • 浏览: 44374 次
  • 性别: Icon_minigender_1
  • 来自: 上海
最近访客 更多访客>>
社区版块
存档分类
最新评论

RSS Reader实例开发之使用Service组件

 
阅读更多

  到目前为止,我们已经实现了RSS Reader的基本功能,在这个OPhone应用程序中,我们使用Activity作为UI界面,使用SQLite数据库并封装为ContentProvider实现数据存储和查询。为了更进一步地优化RSS Reader应用程序的设计,我们将使用OPhone系统提供的另一种重要的组件--Service来封装RSS Reader的逻辑,使应用程序的结构更加清晰。
  使用Service组件
  Service组件是OPhone系统中定义的一类没有界面,在后台运行并提供服务的组件。例如,音乐播放器就使用了Service组件在后台播放音乐,这样,即使用户关闭了前台的Activity,也可以继续播放音乐。
  使用Service组件的另一个好处是将应用程序的逻辑全部移到Service组件中,这样,Activity只需要把注意力放在UI逻辑上,通过调用Service组件,Activity不必关心业务逻辑。
  下面,我们就把RSS Reader的联网、XML解析、数据存取等复杂逻辑从Activity移到Service里。
  要编写一个Service组件相当容易,从android.app.Service派生一个实现类即可:
  01.public class ReadingService extends Service { 02. ... 03.}  Service组件是由系统或Activity启动的,其生命周期主要对应onCreate()、onStart()和onDestroy()三个方法。Service组件被创建时,onCreate()方法被调用,这里可以编写初始化代码,每当Activity请求启动一个Service组件时,onStart()方法被调用,最后,当系统销毁Service组件时,onDestroy()方法被调用,这里可以编写清理资源的代码。
  需要注意的是,onStart()方法可能被多次调用,因此,只需初始化一次的代码需要放到onCreate()而不是onStart()方法。然后,我们就可以向ReadingService中添加若干公共方法: 01.public BriefSubscription addSubscription(String url) { ... } 02.public int getPreferenceOfExpires() { ... } 03.public int getPreferenceOfFreq() { ... } 04.public boolean getPreferenceOfUnreadOnly() { ... } 05.public void markRead(long item_id) { ... } 06.public void markUnread(long item_id) { ... } 07.public List queryBriefItems(long sub_id, boolean unreadOnly) { ... } 08.public List queryBriefSubscriptions() { ... } 09.public void removeSubscription(String sub_id) { ... } 10.public void storePreferences(boolean unreadOnly, int freq, int expires) { ... }  把Activity中的相关逻辑代码移至相应的方法中即可。
  此外,Service组件也支持消息处理,因此,多线程和任务调度相关的逻辑也从MainActivity中移至ReadingService中,并添加删除过期Item的逻辑: 01.private final Handler handler = new Handler() { 02. @Override 03. public void handleMessage(Message msg) { 04. switch (msg.what) { 05. case MSG_TIMER: 06. log.info("Message: MSG_TIMER"); 07. removeExpires(); 08. refreshFeeds(); 09. break; 10. } 11. } 12.};   现在,应用程序的逻辑已经完全移至ReadingService中。下一步,我们需要在AndroidManifest.xml中添加ReadingService的声明: 注意:Service的Class全名由AndroidManifest.xml中声明的package名称"org.expressme.wireless.reader"和Service的android:name组合而成。
  启动Service
  Service的启动是通过Activity的startService()方法实现的,同样需要一个Intent实例: 01.@Override 02.public void onCreate(Bundle savedInstanceState) { 03. super.onCreate(savedInstanceState); 04. ... 05. Intent intent = new Intent(this, ReadingService.class); 06. ComponentName service = startService(intent); 07.}  Activity无需知道Service当前是否已经启动。如果Service还没有启动,OPhone系统会创建Service,调用其onCreate()方法,再调用其onStart()方法。如果Service已经正在运行,OPhone系统会调用其onStart()方法,由于onStart()方法可能被多次调用,因此,Service组件要维护自己的内部状态,防止在onStart()方法中多次初始化。
  停止Service
  停止Service与启动Service类似,也需要构造一个Intent实例,然后,通过stopService()方法停止Service: 01.@Override 02.protected void onDestroy() { 03. super.onDestroy(); 04. Intent intent = new Intent(this, service.getClass()); 05. stopService(intent); 06.}   停止Service的方法一般由生命周期最长的Activity在其onDestroy()方法中调用,这样,Activity被销毁时,Service就停止了,能够及时释放系统资源。
  与Activity通信
  仅仅是启动和停止Service还远远不够。细心的读者可能发现了,启动ReadingService时,返回的不是ReadingService类的引用,而是ComponentName的实例。那么,我们在ReadingService中定义了若干个public方法,如何才能在Activity中调用呢?
  在OPhone系统中,要调用Service的public方法,需要通过Binder机制来实现,首先,Service组件本身要实现Binder机制,然后,Activity才能通过Binder连接到Service组件,并调用其public方法。
  因此,第一步是给ReadingService添加Binder支持。在ReadingService内部添加一个ReadingBinder的内部类声明,添加getService()方法并返回ReadingService的当前实例,然后,实例化并持有一个Binder的引用:
  01.public class ReadingBinder extends Binder { 02. public ReadingService getService() { 03. return ReadingService.this; 04. } 05.} 06.private final IBinder binder = new ReadingBinder();  下一步,覆写onBind()方法,返回binder实例:  现在,ReadingService组件就实现了Binder机制,下面,我们需要在Activity中添加一点代码,通过bindService()方法来绑定ReadingService的实例: // bind service:   
  Intent bindIntent = new Intent(this, ReadingService.class);   
  bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);  
  // bind service: Intent bindIntent = new Intent(this, ReadingService.class); bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
  没错!绑定一个Service也是通过Intent完成的,同时需要提供一个ServiceConnection回调接口,用于接收Bind事件: 01.private ServiceConnection serviceConnection = new ServiceConnection() { 02. public void onServiceConnected(ComponentName className, IBinder service) { 03. serviceBinder = ((ReadingService.ReadingBinder)service).getService (); 04. init(); 05.; 06. } 07. public void onServiceDisconnected(ComponentName className) { 08. serviceBinder = null; 09. } 10.};    ServiceConnection回调接口用于接收Connected和Disconnected事件,请注意,bindService()方法是异步执行的,即bindService()返回后,并不能立刻获取到Service的实例,必须响应onServiceConnected()事件,在这个事件中获取Service的实例,然后执行一些初始化方法。
  绑定Service后,Activity就获得了Service实例的引用,我们将其保存在成员变量中,然后,在Activity的生命周期中,就可以随时调用Service的public业务方法了。
  Activity结束时,还必须及时取消对Service的绑定,通过unbindService()方法实现:        对Service的绑定和取消应该分别对应Activity的onCreate()和onDestroy()事件,这样,能够保证Activity正确释放引用的资源。
  使用Broadcast广播
  在Activity中调用Service的public方法很容易,例如,我们通过调用refresh()方法就可以请求ReadingService组件在后台开启新的异步任务来获取最新的RSS。当ReadingService获得了最新的RSS内容并写入数据库后,如何通知前台的MainActivity刷新当前显示的ListView呢?
  直接调用MainActivity的某个notifyChanged()方法可不好,因为ReadingService很难获得MainActivity的引用,即使获得了,ReadingService不是运行在系统API层的,无法掌控MainActivity的状态,如果MainActivity已经处于销毁状态,则刷新UI可能引发应用程序崩溃。
  此外,直接调用还导致两个组件的紧密耦合,将来如果有其他Activity也需要得到该通知的话,则还需添加更多的代码,导致更紧密的耦合。
  理想状态下,ReadingService应该只负责发出通知,不知道也不关心谁会接收到该消息,而MainActivity则应该只负责接收该通知,不知道也不关心谁发出的消息,这样,通过典型的Observer模式实现的广播,就可以让各个组件保持松耦合,还可以动态地加入接收者。
  发送广播
  OPhone系统已经提供了Observer模式的实现,即使用Broadcast广播一个Intent。下面,我们通过Broadcast机制来实现ReadingService和MainActivity之间的异步消息发送和接收的功能。
  首先,我们需要定义ReadingService能够发出的消息类型,目前,RSS Reader应用一共支持以下3种消息类型: 01.// 有新的RSS项: 02.public static final String NOTIFY_NEW_ITEMS = ReadingService.class.getName() + ".NOTIFY_NEW_ITEMS"; 03.// 用户设置已更改: 04.public static final String NOTIFY_PREF_CHANGED = ReadingService.class.getName() + ".NOTIFY_PREF_CHANGED"; 05.// 用户删除了一个订阅: 06.public static final String NOTIFY_SUB_REMOVED = ReadingService.class.getName() + ".NOTIFY_SUB_REMOVED";   注意到消息类型是String类型,因此,为了确保全局唯一,我们使用ReadingService的完整类名+自定义消息名称。
  现在,ReadingService可以在合适的时候发出通知消息。例如,当用户修改了设置后,ReadingService将首先保存用户设置,然后,发出NOTIFY_PERF_CHANGED消息:
  使用sendBroadcast()方法就可以发出广播消息,该方法定义在android.content.Context接口中,Service和Activity均继承并实现了该方法。
  如果我们希望能在消息中再附带一点数据,则需要将需要携带的数据放入Intent中,通过Intent的putExtra()方法可以放入String、int、boolean等常见数据类型,例如,当发现新的RSS项后,ReadingService将发送NOTIFY_NEW_ITEMS消息,并同时附上Subscription的ID值: 接收广播
  现在,ReadingService已经能够发出广播了,下一步需要做的,就是让MainActivity能够接收广播。
  要接收一个广播,首先需要创建一个BroadcastReceiver的实例,并覆写onReceive()方法用于处理广播:      建议将BroadcastReceiver的实例定义为final类型。
  然后,在Activity的onCreate()方法中注册BroadcastReceiver的实例,以便能够接收到广播消息: 01.@Override 02.public void onCreate(Bundle savedInstanceState) { 03. super.onCreate(savedInstanceState); 04. ... 05. // 注册: 06. IntentFilter filter = new IntentFilter(ReadingService.NOTIFY_NEW_ITEMS); 07. registerReceiver(this.newItemsReceiver, filter); 08.}  注意到registerReceiver方法除了传入BroadcastReceiver的实例外,还需要一个IntentFilter。顾名思义,IntentFilter就是根据消息类型来过滤接收到的Intent的。例如,上述代码指定的IntentFilter将过滤掉除NOTIFY_NEW_ITEMS之外的其他所有Intent,这样,该BroadcastReceiver接收到的广播消息就全部是NOTIFY_NEW_ITEMS,没有必要再根据Intent.getAction()来判断了。
  最后,不要忘记在Activity的onDestroy()方法中取消已注册的BroadcaseReceiver:
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics