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

Stella981
• 阅读 162

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

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

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

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

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

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

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

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

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

[html] view plain copy

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:orientation="vertical"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent">
  5. <Button
  6. android:layout_width="fill_parent"
  7. android:layout_height="wrap_content"
  8. android:text="start"
  9. android:onClick="start"/>
  10. <TextView
  11. android:id="@+id/text"
  12. android:layout_width="fill_parent"
  13. android:layout_height="wrap_content"
  14. android:gravity="center"/>
  15. </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. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:orientation="vertical"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent">
  5. <ProgressBar
  6. android:id="@+id/progress"
  7. style="?android:attr/progressBarStyleHorizontal"
  8. android:layout_width="fill_parent"
  9. android:layout_height="wrap_content"
  10. android:max="100"
  11. android:progress="0"/>
  12. <Button
  13. android:id="@+id/cancel"
  14. android:layout_width="fill_parent"
  15. android:layout_height="wrap_content"
  16. android:text="cancel"
  17. android:onClick="cancel"/>
  18. </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为主动方,让一个女人脚踏两只船的方式。

点赞
收藏
评论区
推荐文章
刚刚好 刚刚好
2个月前
css问题
1、 在IOS中图片不显示(给图片加了圆角或者img没有父级) <div<img src""/</div div {width: 20px; height: 20px; borderradius: 20px; overflow: h
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录 问题 用navicat导入数据时,报错: 原因这是因为当前的MySQL不支持datetime为0的情况。 解决修改sql\mode: sql\mode:SQL Mode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。 全局s
Karen110 Karen110
1年前
一篇文章教会你使用JS+CSS实现一个简单加载进度条的效果
大家好,我是前端进阶者,今天给大家来做个小项目,一起来看看吧一、前言我们经常在网页上 ,游戏界面加载时会看到加载进度条的效果,我们往往会以为这些加载进度条的效果,很难实现。今天教大家JS+CSS结合做简单一个加载进度条的效果。 二、项目准备软件:HBuilderX。 三、项目实现 1\. body 创建2个div,外部div添加id"progress"属
晴空闲云 晴空闲云
2个月前
css中box-sizing解放盒子实际宽高计算
我们知道传统的盒子模型,如果增加内边距padding和边框border,那么会撑大整个盒子,造成盒子的宽度不好计算,在实务中特别不方便。boxsizing可以设置盒模型的方式,可以很好的设置固定宽高的盒模型。 盒子宽高计算假如我们设置如下盒子:宽度和高度均为200px,那么这会这个盒子实际的宽高就都是200px。但是当我们设置这个盒子的边框和内间距的时候,那
艾木酱 艾木酱
1个月前
快速入门|使用MemFire Cloud构建React Native应用程序
> MemFire Cloud是一款提供云数据库,用户可以创建云数据库,并对数据库进行管理,还可以对数据库进行备份操作。它还提供后端即服务,用户可以在1分钟内新建一个应用,使用自动生成的API和SDK,访问云数据库、对象存储、用户认证与授权等功能,可专
Stella981 Stella981
1年前
Notification使用详解之四:由后台服务向Activity发送进度信息
上次讲到了如何在Activity中监听后台服务的进度信息,实现的方式是让Activity与后台服务绑定,通过中间对象Binder的实例操作 后台服务。从效果上来讲,这种方式是可行的,不过这种实现有个缺点,那就是Activity的任务太重了,为了监听服务的状态,我们不得不绑定服务,然后 还需不断地定时的获取最新的进度,我们为何不换一下形式呢,让Service主
Stella981 Stella981
1年前
Notification使用详解之二:可更新进度的通知
上次和大家分享了关于Notification的基础应用,包括简单的通知和自定义视图的通知。今天和大家分享一下如何实现一个可更新进度的通知。 我们将会模拟一个下载任务,先启动一个线程负责模拟下载工作,在这个过程中更新进度信息,然后下载线程把最新的进度信息以消息的形式,发送到UI线程的消息队列中,最后UI线程负责根据最新的进度信息来更新进度通知的UI界面。
Wesley13 Wesley13
1年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表: **时辰** **时间** **24时制** 子时 深夜 11:00 - 凌晨 01:00 23:00 - 01 :00 丑时 上午 01:00 - 上午 03:00 01:00 - 03 :00 寅时 上午 03:00 - 上午 0
helloworld_28799839 helloworld_28799839
2个月前
常用知识整理
# Javascript ## 判断对象是否为空 ```js Object.keys(myObject).length === 0 ``` ## 经常使用的三元运算 > 我们经常遇到处理表格列状态字段如 `status` 的时候可以用到 ``` vue
helloworld_34035044 helloworld_34035044
4个月前
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。 uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid() 或 uuid(sep)参数说明:sep 布尔值,生成的uuid中是否包含分隔符'',缺省为