为什么80%的码农都做不了架构师?>>>   

上次我们讲到如何实现一个可更新的进度通知,实现的方式是启动一个线程模拟一个下载任务,然后根据任务进度向UI线程消息队列发送进度消息,UI线 程根据进度消息更新通知的UI界面。可是在实际应用中,我们一般会将上传、下载等比较耗时的后台任务以服务的形式运行,更新进度通知也是交由后台服务来完 成的。 不过有的时候,除了在通知里面显示进度信息,我们也要在Activity中显示当前进度,很多下载系统都有这样的功能,例如Android自带浏览器的下 载系统、QQ浏览器的下载系统等等。那么如何实现这一功能呢?实现方式有很多,我们今天先来介绍其中的一种:在Activity中主动监听服务的进度。

具体的思路是:让Activity与后台服务绑定,通过中间对象Binder的实例操作后台服务,获取进度信息和服务的状态以及在必要的时候停止服务。

关于服务的生命周期,如果有些朋友们不太熟悉的话,可以去查阅相关资料;如果以后有时间,我可能也会总结一些与服务相关的知识。

为了让大家对这个过程更清晰一些,在上代码之前,我们先来看看几个截图:

整个过程如上图所示:在我们点击开始按钮后,下载任务开始运行,同事更新通知上的进度,当前Activity也从后台服务获取进度信息,显示到按钮下方;当我们点击通知后,跳转到下载管理界面,在这里我们也从后台服务获取进度,还可以做取消任务等操作。

了解了整个过程的情况后,我们就来分析一下具体的代码实现。

首先是/res/main.xml布局文件:

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent">
  6. <Button
  7. android:layout_width="fill_parent"
  8. android:layout_height="wrap_content"
  9. android:text="start"
  10. android:onClick="start"/>
  11. <TextView
  12. android:id="@+id/text"
  13. android:layout_width="fill_parent"
  14. android:layout_height="wrap_content"
  15. android:gravity="center"/>
  16. </LinearLayout>

其中Button是用来启动服务的,TextView是用来显示进度信息的。

然后再在看一下MainActivity.java的代码:

[java] view plain copy
  1. package com.scott.notification;
  2. import android.app.Activity;
  3. import android.content.ComponentName;
  4. import android.content.Context;
  5. import android.content.Intent;
  6. import android.content.ServiceConnection;
  7. import android.os.Bundle;
  8. import android.os.Handler;
  9. import android.os.IBinder;
  10. import android.os.Message;
  11. import android.view.View;
  12. import android.widget.TextView;
  13. public class MainActivity extends Activity {
  14. private DownloadService.DownloadBinder binder;
  15. private TextView text;
  16. private boolean binded;
  17. private Handler handler = new Handler() {
  18. public void handleMessage(android.os.Message msg) {
  19. int progress = msg.arg1;
  20. text.setText("downloading..." + progress + "%");
  21. };
  22. };
  23. private ServiceConnection conn = new ServiceConnection() {
  24. @Override
  25. public void onServiceConnected(ComponentName name, IBinder service) {
  26. binder = (DownloadService.DownloadBinder) service;
  27. binded = true;
  28. // 开始下载
  29. binder.start();
  30. // 监听进度信息
  31. listenProgress();
  32. }
  33. @Override
  34. public void onServiceDisconnected(ComponentName name) {
  35. }
  36. };
  37. @Override
  38. public void onCreate(Bundle savedInstanceState) {
  39. super.onCreate(savedInstanceState);
  40. setContentView(R.layout.main);
  41. text = (TextView) findViewById(R.id.text);
  42. }
  43. @Override
  44. protected void onDestroy() {
  45. super.onDestroy();
  46. if (binded) {
  47. unbindService(conn);
  48. }
  49. }
  50. public void start(View view) {
  51. if (binded) {
  52. binder.start();
  53. listenProgress();
  54. return;
  55. }
  56. Intent intent = new Intent(this, DownloadService.class);
  57. startService(intent);   //如果先调用startService,则在多个服务绑定对象调用unbindService后服务仍不会被销毁
  58. bindService(intent, conn, Context.BIND_AUTO_CREATE);
  59. }
  60. /**
  61. * 监听进度
  62. */
  63. private void listenProgress() {
  64. new Thread() {
  65. public void run() {
  66. while (!binder.isCancelled() && binder.getProgress() <= 100) {
  67. int progress = binder.getProgress();
  68. Message msg = handler.obtainMessage();
  69. msg.arg1 = progress;
  70. handler.sendMessage(msg);
  71. if (progress == 100) {
  72. break;
  73. }
  74. try {
  75. Thread.sleep(200);
  76. } catch (InterruptedException e) {
  77. e.printStackTrace();
  78. }
  79. }
  80. };
  81. }.start();
  82. }
  83. }

我们可以看到,当点击开始按钮后,以bindService的方式绑定服务,用获取到的DownloadService.DownloadBinder实例启动服务,并在Activity中启动一个线程监听服务的进度信息,及时的显示到按钮下方。

服务类DownloadService.java代码如下:

[java] view plain copy
  1. package com.scott.notification;
  2. import android.app.Notification;
  3. import android.app.NotificationManager;
  4. import android.app.PendingIntent;
  5. import android.app.Service;
  6. import android.content.Context;
  7. import android.content.Intent;
  8. import android.os.Binder;
  9. import android.os.Handler;
  10. import android.os.IBinder;
  11. import android.os.Message;
  12. import android.widget.RemoteViews;
  13. public class DownloadService extends Service {
  14. private static final int NOTIFY_ID = 0;
  15. private boolean cancelled;
  16. private int progress;
  17. private Context mContext = this;
  18. private NotificationManager mNotificationManager;
  19. private Notification mNotification;
  20. private DownloadBinder binder = new DownloadBinder();
  21. private Handler handler = new Handler() {
  22. public void handleMessage(android.os.Message msg) {
  23. switch (msg.what) {
  24. case 1:
  25. int rate = msg.arg1;
  26. if (rate < 100) {
  27. // 更新进度
  28. RemoteViews contentView = mNotification.contentView;
  29. contentView.setTextViewText(R.id.rate, rate + "%");
  30. contentView.setProgressBar(R.id.progress, 100, rate, false);
  31. } else {
  32. // 下载完毕后变换通知形式
  33. mNotification.flags = Notification.FLAG_AUTO_CANCEL;
  34. mNotification.contentView = null;
  35. Intent intent = new Intent(mContext, FileMgrActivity.class);
  36. // 告知已完成
  37. intent.putExtra("completed", "yes");
  38. //更新参数,注意flags要使用FLAG_UPDATE_CURRENT
  39. PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
  40. mNotification.setLatestEventInfo(mContext, "下载完成", "文件已下载完毕", contentIntent);
  41. stopSelf();//停掉服务自身
  42. }
  43. // 最后别忘了通知一下,否则不会更新
  44. mNotificationManager.notify(NOTIFY_ID, mNotification);
  45. break;
  46. case 0:
  47. // 取消通知
  48. mNotificationManager.cancel(NOTIFY_ID);
  49. break;
  50. }
  51. };
  52. };
  53. @Override
  54. public void onCreate() {
  55. super.onCreate();
  56. mNotificationManager = (NotificationManager) getSystemService(android.content.Context.NOTIFICATION_SERVICE);
  57. }
  58. @Override
  59. public IBinder onBind(Intent intent) {
  60. // 返回自定义的DownloadBinder实例
  61. return binder;
  62. }
  63. @Override
  64. public void onDestroy() {
  65. super.onDestroy();
  66. cancelled = true; // 取消下载线程
  67. }
  68. /**
  69. * 创建通知
  70. */
  71. private void setUpNotification() {
  72. int icon = R.drawable.down;
  73. CharSequence tickerText = "开始下载";
  74. long when = System.currentTimeMillis();
  75. mNotification = new Notification(icon, tickerText, when);
  76. // 放置在"正在运行"栏目中
  77. mNotification.flags = Notification.FLAG_ONGOING_EVENT;
  78. RemoteViews contentView = new RemoteViews(mContext.getPackageName(), R.layout.download_notification_layout);
  79. contentView.setTextViewText(R.id.fileName, "AngryBird.apk");
  80. // 指定个性化视图
  81. mNotification.contentView = contentView;
  82. Intent intent = new Intent(this, FileMgrActivity.class);
  83. PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
  84. // 指定内容意图
  85. mNotification.contentIntent = contentIntent;
  86. mNotificationManager.notify(NOTIFY_ID, mNotification);
  87. }
  88. /**
  89. * 下载模块
  90. */
  91. private void startDownload() {
  92. cancelled = false;
  93. int rate = 0;
  94. while (!cancelled && rate < 100) {
  95. try {
  96. // 模拟下载进度
  97. Thread.sleep(500);
  98. rate = rate + 5;
  99. } catch (InterruptedException e) {
  100. e.printStackTrace();
  101. }
  102. Message msg = handler.obtainMessage();
  103. msg.what = 1;
  104. msg.arg1 = rate;
  105. handler.sendMessage(msg);
  106. this.progress = rate;
  107. }
  108. if (cancelled) {
  109. Message msg = handler.obtainMessage();
  110. msg.what = 0;
  111. handler.sendMessage(msg);
  112. }
  113. }
  114. /**
  115. * DownloadBinder中定义了一些实用的方法
  116. *
  117. * @author user
  118. *
  119. */
  120. public class DownloadBinder extends Binder {
  121. /**
  122. * 开始下载
  123. */
  124. public void start() {
  125. //将进度归零
  126. progress = 0;
  127. //创建通知
  128. setUpNotification();
  129. new Thread() {
  130. public void run() {
  131. //下载
  132. startDownload();
  133. };
  134. }.start();
  135. }
  136. /**
  137. * 获取进度
  138. *
  139. * @return
  140. */
  141. public int getProgress() {
  142. return progress;
  143. }
  144. /**
  145. * 取消下载
  146. */
  147. public void cancel() {
  148. cancelled = true;
  149. }
  150. /**
  151. * 是否已被取消
  152. *
  153. * @return
  154. */
  155. public boolean isCancelled() {
  156. return cancelled;
  157. }
  158. }
  159. }

我们看到,在服务中有个DownloadBinder类,它继承自Binder,定义了一系列方法,获取服务状态以及操作当前服务,刚才我们在 MainActivity中获取的就是这个类的实例。最后,不要忘了在AndroidManifest.xml中配置该服务。关于进度通知的布局文件/res/layout/download_notification_layout.xml,在这里就不需贴出了,朋友们可以参考一下Notification使用详解之二中进度通知布局的具体代码。

下面我们来介绍一下FileMgrActivity,它就是点击通知之后跳转到的界面,布局文件/res/filemgr.xml如下:

[html] view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent">
  6. <ProgressBar
  7. android:id="@+id/progress"
  8. style="?android:attr/progressBarStyleHorizontal"
  9. android:layout_width="fill_parent"
  10. android:layout_height="wrap_content"
  11. android:max="100"
  12. android:progress="0"/>
  13. <Button
  14. android:id="@+id/cancel"
  15. android:layout_width="fill_parent"
  16. android:layout_height="wrap_content"
  17. android:text="cancel"
  18. android:onClick="cancel"/>
  19. </LinearLayout>

我们来看一下FileMgrActivity.java具体的代码:

[java] view plain copy
  1. package com.scott.notification;
  2. import android.app.Activity;
  3. import android.content.ComponentName;
  4. import android.content.Context;
  5. import android.content.Intent;
  6. import android.content.ServiceConnection;
  7. import android.os.Bundle;
  8. import android.os.Handler;
  9. import android.os.IBinder;
  10. import android.os.Message;
  11. import android.view.View;
  12. import android.widget.Button;
  13. import android.widget.ProgressBar;
  14. public class FileMgrActivity extends Activity {
  15. private DownloadService.DownloadBinder binder;
  16. private ProgressBar progressBar;
  17. private Button cancel;
  18. private boolean binded;
  19. private Handler handler = new Handler() {
  20. public void handleMessage(android.os.Message msg) {
  21. int progress = msg.arg1;
  22. progressBar.setProgress(progress);
  23. if (progress == 100) {
  24. cancel.setEnabled(false);
  25. }
  26. };
  27. };
  28. private ServiceConnection conn = new ServiceConnection() {
  29. @Override
  30. public void onServiceConnected(ComponentName name, IBinder service) {
  31. binder = (DownloadService.DownloadBinder) service;
  32. //监听进度信息
  33. listenProgress();
  34. }
  35. @Override
  36. public void onServiceDisconnected(ComponentName name) {
  37. }
  38. };
  39. @Override
  40. public void onCreate(Bundle savedInstanceState) {
  41. super.onCreate(savedInstanceState);
  42. setContentView(R.layout.filemgr);
  43. progressBar = (ProgressBar) findViewById(R.id.progress);
  44. cancel = (Button) findViewById(R.id.cancel);
  45. if ("yes".equals(getIntent().getStringExtra("completed"))) {
  46. //如果已完成,则不需再绑定service
  47. progressBar.setProgress(100);
  48. cancel.setEnabled(false);
  49. } else {
  50. //绑定service
  51. Intent intent = new Intent(this, DownloadService.class);
  52. bindService(intent, conn, Context.BIND_AUTO_CREATE);
  53. binded = true;
  54. }
  55. }
  56. @Override
  57. protected void onDestroy() {
  58. super.onDestroy();
  59. //如果是绑定状态,则取消绑定
  60. if (binded) {
  61. unbindService(conn);
  62. }
  63. }
  64. public void cancel(View view) {
  65. //取消下载
  66. binder.cancel();
  67. }
  68. /**
  69. * 监听进度信息
  70. */
  71. private void listenProgress() {
  72. new Thread() {
  73. public void run() {
  74. while (!binder.isCancelled() && binder.getProgress() <= 100) {
  75. int progress = binder.getProgress();
  76. Message msg = handler.obtainMessage();
  77. msg.arg1 = progress;
  78. handler.sendMessage(msg);
  79. try {
  80. Thread.sleep(200);
  81. } catch (InterruptedException e) {
  82. e.printStackTrace();
  83. }
  84. }
  85. };
  86. }.start();
  87. }
  88. }

我们发现,它和MainActivity实现方式很相似,恩,他们都是通过和服务绑定后获取到的Binder对象来跟服务通信的,都是主动和服务打招呼来获取信息和控制服务的。

这两个Activity和一个Service似乎像是复杂的男女关系,两个男人同时喜欢一个女人,都通过自己的手段试图从那个女人获取爱情,两个男人都很主动,那个女人显得很被动。

以上就是今天的全部内容,也许朋友们会有疑问,能不能让Service主动告知Activity当前的进度信息呢?答案是可以。下一次,我就会和大家分享一下,如何变Service为主动方,让一个女人脚踏两只船的方式。

转载于:https://my.oschina.net/mzhs/blog/179771

Notification使用详解之三:通过服务更新进度通知在Activity中监听服务进度相关推荐

  1. oracle监听服务无法启动不了,关于ORACLE监听服务无法启动的问题

    今天电脑拿回家,启动oracle数据库的监听服务时发现无法启动.给出的错误提示是说,这个服务启动了又停止了,不知道是为什么.因为在学校的时候用是没有问题的.我想应该是因为ip的是问题.在网上查了一下: ...

  2. ASP.NET Core真实管道详解[2]:Server是如何完成针对请求的监听、接收与响应的【上】

    Server是ASP .NET Core管道的第一个节点,负责完整请求的监听和接收,最终对请求的响应同样也由它完成.Server是我们对所有实现了IServer接口的所有类型以及对应对象的统称,如下面 ...

  3. Notification使用详解之四:由后台服务向Activity发送进度信息

    上次讲到了如何在Activity中监听后台服务的进度信息,实现的方式是让Activity与后台服务绑定,通过中间对象Binder的实例操作后台服务.从效果上来讲,这种方式是可行的,不过这种实现有个缺点 ...

  4. win7 oracle启动监听,win7系统oracle监听服务无法打开的解决方法

    无论谁在使用电脑的时候都可能会发现oracle监听服务无法打开的问题,oracle监听服务无法打开让用户们很苦恼,这是怎么回事呢,oracle监听服务无法打开有什么简便的处理方式呢,其实只要依照1.如 ...

  5. oracle 监听服务启动后停止

    发现Oracle 监听服务启动后刷新又停止了,导致这种问题的有几种可能情况: 1.listener服务不正常,因此需要重建这个服务 方法一 :修改注册表 1>进入dos窗口,手工启动listen ...

  6. 【Google Play】App Bundle 使用详解 ( 简介 | 应用内更新 | 即时更新 | 灵活更新 )

    Google Play 上架完整流程 系列文章目录 [Google Play]创建 Google 开发者账号 ( 注册邮箱账号 | 创建开发者账号 ) [Google Play]创建并设置应用 ( 访 ...

  7. Fragment详解之三——管理Fragment(1)

    前言:follow your heart,be your own king 相关文章: 1.<Fragment详解之一--概述> 2.<Fragment详解之二--基本使用方法> ...

  8. 一致性协议raft详解(四):raft在工程实践中的优化

    一致性协议raft详解(四):raft在工程实践中的优化 前言 性能优化 client对raft集群的读写 参考链接 前言 有关一致性协议的资料网上有很多,当然错误也有很多.笔者在学习的过程中走了不少 ...

  9. Vue watch监听详解,一篇文章彻底搞懂Vue2/Vue3中的watch监听

    watch监听在vue2和vue3中的用法详解(全) Vue2中的watch用法 Vue3中的watch用法 首先写一个vue页面 <template><div><h1& ...

最新文章

  1. Java 多线程概述
  2. TZOJ 4621 Grammar(STL模拟)
  3. POJ 2386 Lake Counting
  4. 快速排序和快速选择(quickSort and quickSelect)算法
  5. 获得BootstrapTable行号index
  6. c语言printf输出中文报错,printf输出中文后还带有数字是怎么回事?
  7. [Java] 蓝桥杯ALGO-145 算法训练 4-1打印下述图形
  8. 蒙特卡洛粒子滤波定位算法_序列蒙特卡洛(SMC)与粒子滤波
  9. C++中的void类型
  10. 【Axure原型分享】会议管理原型模板
  11. android应用图标在哪修改器,手机应用图标修改器app
  12. 阿里云P10技术专家褚霸:我是一个程序员
  13. App Extension应用扩展
  14. An exception has been thrown during the rendering of a template
  15. Win10系统中耳机插入前后面板均没有声音
  16. 学习Python的Django执行python manage.py startapp myApp创建应用出现的问题
  17. Application的启动流程
  18. 如何解决Error running ‘Tomcat 8.5.45‘: port out of range:-1
  19. SIM卡的密码PIN与PUK密码PIN
  20. Spring AOP动态代理

热门文章

  1. 微软高性能计算服务器pdf,微软高性能计算HPCserver2008技术概览.pdf
  2. 小小c语言贪吃蛇思路,【图片】C语言小游戏~贪吃蛇【c语言吧】_百度贴吧
  3. create报错不是函数_各种SQL中关于日期函数的那些事(建议收藏)
  4. html post 发送两次,jQuery Ajax发送两次第二次提交,发送三次三次
  5. mysql 备份如何使用_如何使用命令来备份和还原MySQL数据库
  6. Appfuse下载及安装步骤
  7. Idea导入maven工程,并运行
  8. 高数学习笔记:利用矩阵设置密码
  9. Python编程基础01:搭建Python开发环境
  10. 【BZOJ1146】网络管理,整体二分