原文地址::http://www.ophonesdn.com/article/show/122

到目前为止,我们已经实现了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派生一个实现类即可:

view plain copy to clipboard print ?
  1. public class ReadingService extends Service {
  2. ...
  3. }
public class ReadingService extends Service { ... }

Service组件是由系统或Activity启动的,其生命周期主要对应onCreate()、onStart()和onDestroy()三个方法。Service组件被创建时,onCreate()方法被调用,这里可以编写初始化代码,每当Activity请求启动一个Service组件时,onStart()方法被调用,最后,当系统销毁Service组件时,onDestroy()方法被调用,这里可以编写清理资源的代码。

需要注意的是,onStart()方法可能被多次调用,因此,只需初始化一次的代码需要放到onCreate()而不是onStart()方法。然后,我们就可以向ReadingService中添加若干公共方法:

view plain copy to clipboard print ?
  1. public BriefSubscription addSubscription(String url) { ... }
  2. public int getPreferenceOfExpires() { ... }
  3. public int getPreferenceOfFreq() { ... }
  4. public boolean getPreferenceOfUnreadOnly() { ... }
  5. public void markRead(long item_id) { ... }
  6. public void markUnread(long item_id) { ... }
  7. public List<BriefItem> queryBriefItems(long sub_id, boolean unreadOnly) { ... }
  8. public List<BriefSubscription> queryBriefSubscriptions() { ... }
  9. public void removeSubscription(String sub_id) { ... }
  10. public void storePreferences(boolean unreadOnly, int freq, int expires) { ... }
public BriefSubscription addSubscription(String url) { ... } public int getPreferenceOfExpires() { ... } public int getPreferenceOfFreq() { ... } public boolean getPreferenceOfUnreadOnly() { ... } public void markRead(long item_id) { ... } public void markUnread(long item_id) { ... } public List<BriefItem> queryBriefItems(long sub_id, boolean unreadOnly) { ... } public List<BriefSubscription> queryBriefSubscriptions() { ... } public void removeSubscription(String sub_id) { ... } public void storePreferences(boolean unreadOnly, int freq, int expires) { ... }

把Activity中的相关逻辑代码移至相应的方法中即可。

此外,Service组件也支持消息处理,因此,多线程和任务调度相关的逻辑也从MainActivity中移至ReadingService中,并添加删除过期Item的逻辑:

view plain copy to clipboard print ?
  1. private final Handler handler = new Handler() {
  2. @Override
  3. public void handleMessage(Message msg) {
  4. switch (msg.what) {
  5. case MSG_TIMER:
  6. log.info("Message: MSG_TIMER");
  7. removeExpires();
  8. refreshFeeds();
  9. break;
  10. }
  11. }
  12. };
private final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_TIMER: log.info("Message: MSG_TIMER"); removeExpires(); refreshFeeds(); break; } } };

现在,应用程序的逻辑已经完全移至ReadingService中。下一步,我们需要在AndroidManifest.xml中添加ReadingService的声明:

view plain copy to clipboard print ?
  1. <service android:enabled="true" android:name=".service.ReadingService">
  2. </service>
<service android:enabled="true" android:name=".service.ReadingService"> </service>

注意:Service的Class全名由AndroidManifest.xml中声明的package名称“org.expressme.wireless.reader”和Service的android:name组合而成。

启动Service

Service的启动是通过Activity的startService()方法实现的,同样需要一个Intent实例:

view plain copy to clipboard print ?
  1. @Override
  2. public void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. ...
  5. Intent intent = new Intent(this, ReadingService.class);
  6. ComponentName service = startService(intent);
  7. }
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... Intent intent = new Intent(this, ReadingService.class); ComponentName service = startService(intent); }

Activity无需知道Service当前是否已经启动。如果Service还没有启动,OPhone系统会创建Service,调用其onCreate()方法,再调用其onStart()方法。如果Service已经正在运行,OPhone系统会调用其onStart()方法,由于onStart()方法可能被多次调用,因此,Service组件要维护自己的内部状态,防止在onStart()方法中多次初始化。

停止Service
         停止Service与启动Service类似,也需要构造一个Intent实例,然后,通过stopService()方法停止Service:

view plain copy to clipboard print ?
  1. @Override
  2. protected void onDestroy() {
  3. super.onDestroy();
  4. Intent intent = new Intent(this, service.getClass());
  5. stopService(intent);
  6. }
@Override protected void onDestroy() { super.onDestroy(); Intent intent = new Intent(this, service.getClass()); stopService(intent); }

停止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的引用:

view plain copy to clipboard print ?
  1. public class ReadingBinder extends Binder {
  2. public ReadingService getService() {
  3. return ReadingService.this;
  4. }
  5. }
  6. private final IBinder binder = new ReadingBinder();
 public class ReadingBinder extends Binder { public ReadingService getService() { return ReadingService.this; } } private final IBinder binder = new ReadingBinder();

下一步,覆写onBind()方法,返回binder实例:

view plain copy to clipboard print ?
  1. @Override
  2. public IBinder onBind(Intent intent) {
  3. return binder;
  4. }
@Override public IBinder onBind(Intent intent) { return binder; }

现在,ReadingService组件就实现了Binder机制,下面,我们需要在Activity中添加一点代码,通过bindService()方法来绑定ReadingService的实例:

view plain copy to clipboard print ?
  1. // bind service:
  2. Intent bindIntent = new Intent(this, ReadingService.class);
  3. 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事件:

view plain copy to clipboard print ?
  1. private ServiceConnection serviceConnection = new ServiceConnection() {
  2. public void onServiceConnected(ComponentName className, IBinder service) {
  3. serviceBinder = ((ReadingService.ReadingBinder)service).getService();
  4. init();
  5. ;
  6. }
  7. public void onServiceDisconnected(ComponentName className) {
  8. serviceBinder = null;
  9. }
  10. };
private ServiceConnection serviceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { serviceBinder = ((ReadingService.ReadingBinder)service).getService(); init(); ; } public void onServiceDisconnected(ComponentName className) { serviceBinder = null; } };

ServiceConnection回调接口用于接收Connected和Disconnected事件,请注意,bindService()方法是异步执行的,即bindService()返回后,并不能立刻获取到Service的实例,必须响应onServiceConnected()事件,在这个事件中获取Service的实例,然后执行一些初始化方法。

绑定Service后,Activity就获得了Service实例的引用,我们将其保存在成员变量中,然后,在Activity的生命周期中,就可以随时调用Service的public业务方法了。
Activity结束时,还必须及时取消对Service的绑定,通过unbindService()方法实现:

view plain copy to clipboard print ?
  1. unbindService(serviceConnection);
unbindService(serviceConnection);

对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种消息类型:

view plain copy to clipboard print ?
  1. // 有新的RSS项:
  2. public static final String NOTIFY_NEW_ITEMS = ReadingService.class.getName() + ".NOTIFY_NEW_ITEMS";
  3. // 用户设置已更改:
  4. public static final String NOTIFY_PREF_CHANGED = ReadingService.class.getName() + ".NOTIFY_PREF_CHANGED";
  5. // 用户删除了一个订阅:
  6. public static final String NOTIFY_SUB_REMOVED = ReadingService.class.getName() + ".NOTIFY_SUB_REMOVED";
// 有新的RSS项: public static final String NOTIFY_NEW_ITEMS = ReadingService.class.getName() + ".NOTIFY_NEW_ITEMS"; // 用户设置已更改: public static final String NOTIFY_PREF_CHANGED = ReadingService.class.getName() + ".NOTIFY_PREF_CHANGED"; // 用户删除了一个订阅: public static final String NOTIFY_SUB_REMOVED = ReadingService.class.getName() + ".NOTIFY_SUB_REMOVED";

注意到消息类型是String类型,因此,为了确保全局唯一,我们使用ReadingService的完整类名+自定义消息名称。

现在,ReadingService可以在合适的时候发出通知消息。例如,当用户修改了设置后,ReadingService将首先保存用户设置,然后,发出NOTIFY_PERF_CHANGED消息:

view plain copy to clipboard print ?
  1. Intent intent = new Intent(NOTIFY_PREF_CHANGED);
  2. sendBroadcast(intent);
Intent intent = new Intent(NOTIFY_PREF_CHANGED); sendBroadcast(intent);

使用sendBroadcast()方法就可以发出广播消息,该方法定义在android.content.Context接口中,Service和Activity均继承并实现了该方法。

如果我们希望能在消息中再附带一点数据,则需要将需要携带的数据放入Intent中,通过Intent的putExtra()方法可以放入String、int、boolean等常见数据类型,例如,当发现新的RSS项后,ReadingService将发送NOTIFY_NEW_ITEMS消息,并同时附上Subscription的ID值:

view plain copy to clipboard print ?
  1. Intent intent = new Intent(NOTIFY_NEW_ITEMS);
  2. intent.putExtra(SubscriptionColumns._ID, sub_id);
  3. sendBroadcast(intent);
Intent intent = new Intent(NOTIFY_NEW_ITEMS); intent.putExtra(SubscriptionColumns._ID, sub_id); sendBroadcast(intent);

接收广播
        现在,ReadingService已经能够发出广播了,下一步需要做的,就是让MainActivity能够接收广播。

要接收一个广播,首先需要创建一个BroadcastReceiver的实例,并覆写onReceive()方法用于处理广播:

view plain copy to clipboard print ?
  1. private final BroadcastReceiver newItemsReceiver = new BroadcastReceiver() {
  2. @Override
  3. public void onReceive(Context context, Intent intent) {
  4. // TODO...
  5. }
  6. };
private final BroadcastReceiver newItemsReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // TODO... } };

建议将BroadcastReceiver的实例定义为final类型。

然后,在Activity的onCreate()方法中注册BroadcastReceiver的实例,以便能够接收到广播消息:

view plain copy to clipboard print ?
  1. @Override
  2. public void onCreate(Bundle savedInstanceState) {
  3. super.onCreate(savedInstanceState);
  4. ...
  5. // 注册:
  6. IntentFilter filter = new IntentFilter(ReadingService.NOTIFY_NEW_ITEMS);
  7. registerReceiver(this.newItemsReceiver, filter);
  8. }
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... // 注册: IntentFilter filter = new IntentFilter(ReadingService.NOTIFY_NEW_ITEMS); registerReceiver(this.newItemsReceiver, filter); }

注意到registerReceiver方法除了传入BroadcastReceiver的实例外,还需要一个IntentFilter。顾名思义,IntentFilter就是根据消息类型来过滤接收到的Intent的。例如,上述代码指定的IntentFilter将过滤掉除NOTIFY_NEW_ITEMS之外的其他所有Intent,这样,该BroadcastReceiver接收到的广播消息就全部是NOTIFY_NEW_ITEMS,没有必要再根据Intent.getAction()来判断了。

最后,不要忘记在Activity的onDestroy()方法中取消已注册的BroadcaseReceiver:

view plain copy to clipboard print ?
  1. @Override
  2. protected void onDestroy() {
  3. super.onDestroy();
  4. unregisterReceiver(this.newItemsReceiver);
  5. }
@Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(this.newItemsReceiver); }

有了Broadcast机制,我们就最大限度地分离了Service和Activity之间的通信逻辑,现在,RSS Reader已经在ReadingService中实现了广播,而MainActivity则接收广播消息。当MainActivity接收到NOTIFY_NEW_ITEMS后,菜单项Refresh将被设置为enable状态,用户就可以点击Refresh选项菜单来刷新ListView。

关于作者
        廖雪峰,精通Java/Java EE/Java ME/Android/Python/C#/Visual Basic开发,对开源框架有深入研究,著有《Spring 2.0核心技术与最佳实践》一书,其官方博客是http://www.liaoxuefeng.com/和http://michael-liao.appspot.com/,可以通过askxuefeng@gmail.com与之联系。

RSS Reader实例开发之使用Service组件相关推荐

  1. RSS Reader实例开发之联网开发

    在OPhone应用程序中,如果我们要做一些费时的操作,例如,从网络获取数据,就不能将这些操作放在普通的处理UI的逻辑中,否则,主线程一旦执行耗时任务,将无法响应用户的任何操作,OPhone系统会判定该 ...

  2. RSS Reader实例开发之系统设计

    系统设计         基于OPhone的RSS Reader将以Google Reader的功能为参考,并充分考虑到手机屏幕的限制.RSS Reader将实现以下几个Activity: 1. Ma ...

  3. Native Rss Reader 的资料

    转:http://hi.baidu.com/mikyliang/blog/item/11d420d3135832013af3cf19.html RSS文档的构成 2007-05-04 14:58 RS ...

  4. Vue.js-Day02-PM【组件化开发(全局注册组件、局部注册组件、案例)、组件的配置选项、轮播图实例(左右切换按钮、底部导航栏、定时器、鼠标移入-图片静止)】

    Vue.js实训[基础理论(5天)+项目实战(5天)]博客汇总表[详细笔记] 目   录 4.组件化开发 4.1.组件的注册 全局注册 局部注册(只能在当前整个Vue实例的范围内才可以使用) 使用组件 ...

  5. .net开发安卓入门 - Service (服务)

    .net开发安卓入门 - Service Android Service 概述 Service VS Thread (服务和线程之间进行选择) 前台服务 代码 启动前台服务方法 运行效果 后台服务 代 ...

  6. RSS Reader for MAC Code

    RSS Reader for MAC Code 该项目代码来自于王志刚 <软件创富密码:iPhone应用程序开发攻略之深入浅出Objective-C 2.0>一书,这是一个图例很丰富的书, ...

  7. PhoneGap RSS Reader

    这是关于如何使用phoneGap来开发手机上的RSS阅读器的文章,原作者分别写了四个版本.如下所示: 原文链接:http://www.raymondcamden.com/index.cfm/2011/ ...

  8. WSE3.0构建Web服务安全(3):WSE3.0策略配置、证书、签名、与实例开发

    继WSE3.0构建Web服务安全(1):WSE3.0安全机制与实例开发和WSE3.0构建Web服务安全(2):非对称加密.公钥.密钥.证书.签名的区别和联系以及X.509 证书的获得和管理之后,今天我 ...

  9. Android插件化开发之解决OpenAtlas组件在宿主的注冊问题

    Android插件化开发之解决OpenAtlas组件在宿主的注冊问题 OpenAtlas有一个问题,就是四大组件必须在Manifest文件里进行注冊,那么就必定带来一个问题,插件中的组件都要反复在宿主 ...

最新文章

  1. 【敏捷开发】从需求文档出发聊敏捷
  2. 局域网下两台电脑ping不通
  3. 1:Hello world
  4. 更新wpscan_wpscan扫描工具
  5. linux的任务计划6,Linux计划任务
  6. Java开发中遇到具有挑战的事_170道Java工程师面试题,你敢挑战吗?
  7. 微服务升级_SpringCloud Alibaba工作笔记0017---Nacos之服务消费者注册和负载
  8. Atitit 未来趋势把控的书籍 attilax总结 v3
  9. 大象装企营销:装饰公司如何通过差异化营销传播口碑
  10. 利用DEEPLABV3-RESNET101获取人体蒙版
  11. matlab折线参数,matlab画含参数曲线族
  12. 为什么Android项目mainactivity中有一个变量R_教我兄弟学Android逆向12 编写xpose模块...
  13. TestBench 基本写法与框架
  14. 无缘无故,谷歌浏览器主页被篡改为360导航,如何解决?
  15. P58-前端基础HTML-表格入门介绍
  16. azkaban 与 java任务_任务调度工具oozie和azkaban的对比
  17. 【附源码】Java计算机毕业设计旅游管理系统(程序+LW+部署)
  18. 董路太有才了--春晚零点报时出错技术分析
  19. Plotly Express 详细使用指南,20组案例从入门到进阶(附源代码)
  20. 通用Excel数据导入功能模板

热门文章

  1. Windows下TortoiseGit和Git配置使用同一ssh私钥
  2. linux文件名长度限制6,linux和windows文件名长度限制问题
  3. 用matlab求解线性代数方程组,线性代数方程组数值解法与MATLAB实现综述
  4. FPGA学习笔记(八):ASK调制解调的仿真
  5. 2021年危险化学品经营单位主要负责人考试题库及危险化学品经营单位主要负责人考试技巧
  6. host文件修改与刷新
  7. 基于springboot的外星人官方笔记本商城(序言)
  8. Docker在官网下载Tomcat镜像里面没有ip addr等命令解决
  9. 【附源码】计算机毕业设计JAVA校园摄影爱好者交流网站
  10. Response status code does not indicate success: 401 (Unauthorized).