【Android】Service前台服务的使用

1.什么是前台服务

前台服务是那些被认为用户知道(用户所认可的)且在系统内存不足的时候不允许系统杀死的服务。前台服务必须给状态栏提供一个通知,它被放到正在运行(Ongoing)标题之下——这就意味着通知只有在这个服务被终止或从前台主动移除通知后才能被解除。
官方描述:
A foreground service(前台服务) is a service that's considered to be(被用户所认可的) something the user is actively aware of and thus not a candidate for(而不是一个候选的,可以在内存不足时,被系统杀死的) the system to kill when low on memory. A foreground service must provide a notification for the status bar(前台服务必须提供一个显示通知), which is placed under the "Ongoing" heading(它是不可以忽略的), which means that the notification cannot be dismissed unless the service is either stopped or removed from the foreground.(意思是通知信息不能被忽略,除非服务停止或主动移除,否则将一直显示。)


2.为什么要使用前台服务

在一般情况下,Service几乎都是在后台运行,一直默默地做着辛苦的工作。但这种情况下,后台运行的Service系统优先级相对较低,当系统内存不足时,在后台运行的Service就有可能被回收。
  那么,如果我们希望Service可以一直保持运行状态且不会在内存不足的情况下被回收时,可以选择将需要保持运行的Service设置为前台服务

例:
App中的音乐播放服务应被设置在前台运行(前台服务)——在App后台运行时,便于用户明确知道它的当前操作、在状态栏中指明当前歌曲信息、提供对应操作。


3.如何创建一个前台服务

  • 新建一个服务
public class MusicPlayerService extends Service {
  
  private static final String TAG = MusicPlayerService.class.getSimpleName();
  
  @Override
  public void onCreate() {
      super.onCreate();
      Log.d(TAG, "onCreate()");
  }
  
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    Log.d(TAG, "onStartCommand()");
    return super.onStartCommand(intent, flags, startId);
  }
  
  @Override
  public IBinder onBind(Intent intent) {
       Log.d(TAG, "onBind()");
       // TODO: Return the communication channel to the service.
    throw new UnsupportedOperationException("Not yet implemented");
  }
}
  • 构建通知消息
    ServiceonStartCommand中添加如下代码构建Notification:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
  Log.d(TAG, "onStartCommand()");
  // 在API11之后构建Notification的方式
  Notification.Builder builder = new Notification.Builder
    (this.getApplicationContext()); //获取一个Notification构造器
  Intent nfIntent = new Intent(this, MainActivity.class);
  
  builder.setContentIntent(PendingIntent.
    getActivity(this, 0, nfIntent, 0)) // 设置PendingIntent
    .setLargeIcon(BitmapFactory.decodeResource(this.getResources(),
      R.mipmap.ic_large)) // 设置下拉列表中的图标(大图标)
    .setContentTitle("下拉列表中的Title") // 设置下拉列表里的标题
    .setSmallIcon(R.mipmap.ic_launcher) // 设置状态栏内的小图标
    .setContentText("要显示的内容") // 设置上下文内容
    .setWhen(System.currentTimeMillis()); // 设置该通知发生的时间
  
  Notification notification = builder.build(); // 获取构建好的Notification
  notification.defaults = Notification.DEFAULT_SOUND; //设置为默认的声音

  return super.onStartCommand(intent, flags, startId);
}

其它Notification构建方式:
Notification(int icon, CharSequence tickerText, long when)
  This constructor was deprecated in API level 11. Use Notification.Builder instead.

// 已过时--在API11之后就被淘汰了,之后需要调用Notification.Builder()来构建Notification.
Notification notification = new Notification(R.mipmap.ic_launcher,
"前台服务测试",System.currentTimeMillis());

getNotification()
  This method was deprecated in API level 16. Use build() instead.

// 在API16之后,可以使用build()来进行Notification的构建 Notification
notification = new Notification.Builder(this.getApplicationContext())
  .setContentText("这是一个前台服务")
  .setSmallIcon(R.mipmap.ic_launcher)
  .setWhen(System.currentTimeMillis())
  .build();
  • 启动前台服务
    在完成Notification通知消息的构建后,在Service的onStartCommand中可以使用startForeground方法来让Android服务运行在前台。
// 参数一:唯一的通知标识;参数二:通知消息。
startForeground(110, notification);// 开始前台服务

注:当使用的通知ID一致时,只会更新当前Notification。

  • 停止前台服务
    在Service的onDestory中使用stopForeground方法来停止正在运行的前台服务。
@Override
public void onDestroy() {
  Log.d(TAG, "onDestroy()");
  stopForeground(true);// 停止前台服务--参数:表示是否移除之前的通知
  super.onDestroy();
}

4.自定义Notification布局并设置点击事件

  • 自定义Notification布局
     有时候我们需要个性化前台服务的通知内容,那么我么就需要通过自定义Notification布局的方式来达到想要的效果:
RemoteViews remoteViews = new RemoteViews(this.getPackageName(),
  R.layout.notification_layout);// 获取remoteViews(参数一:包名;参数二:布局资源)
builder = new Notification.Builder(this.getApplicationContext())
      .setContent(remoteViews);// 设置自定义的Notification内容
builder.setWhen(System.currentTimeMillis())
    .setSmallIcon(R.mipmap.ic_launcher);
  
Notification notification = builder.getNotification();// 获取构建好的通知--.build()最低要求在
                  // API16及以上版本上使用,低版本上可以使用.getNotification()。
Notificationnotification.defaults = Notification.DEFAULT_SOUND;//设置为默认的声音
  
startForeground(110, notification);// 开始前台服务
自定义效果.png
  • 为自定义通知内容上的控件绑定点击事件
      在通知(Notification)中为自定义布局上的控件绑定点击事件监听,我们需要通广播的形式来实现效果。
  • 注册广播
private static final int REQUEST_CODE = 100;
private static final String ACTION_PLAY = "play";
  
Intent intentPlay = new Intent(ACTION_PLAY);// 指定操作意图--设置对应的行为ACTION
PendingIntent pIntentPlay = PendingIntent.getBroadcast(this.getApplicationContext(),
REQUEST_CODE, intentPlay, PendingIntent.FLAG_UPDATE_CURRENT);// 取的一个PendingIntent,
                      // 它会发送一个广播,如同Context.sendBroadcast.
  • 绑定点击事件
remoteViews.setOnClickPendingIntent(R.id.iv_pause, pIntentPlay);// 绑定点击事件(参数一:
  // 控件id;参数二:对应触发的PendingIntent)
  • 注册广播监听器,监听对应广播
  • 动态注册
    • 在Service的onCreate中添加如下代码注册广播监听:
// 动态注册广播
@Override
public void onCreate() {
  super.onCreate();
  Log.d(TAG, "onCreate()");
  
  playerReceiver = new PlayerReceiver();
  IntentFilter mFilter = new IntentFilter();
  
  mFilter.addAction(ACTION_PLAY);
  mFilter.addAction(ACTION_PAUSE);
  mFilter.addAction(ACTION_LAST);
  mFilter.addAction(ACTION_NEXT);
  
  registerReceiver(playerReceiver, mFilter);
}
  • 在Service销毁时(OnDestory中)解除广播注册:
@Override
public void onDestroy() {
  Log.d(TAG, "onDestroy()");
  
  stopForeground(true);// 停止前台服务
  if (playerReceiver != null)
    unregisterReceiver(playerReceiver);// 解除广播注册
  super.onDestroy();
}
  • 静态注册
    在AndroidManifest.xml的receiver标签内添加需要过滤的内容,如:
<receiver
 android:name=".PlayerReceiver"
 android:exported="true">
 <intent-filter>
  <action android:name="play"/>
  <action android:name="pause"/>
  <action android:name="last"/>
  <action android:name="next"/>
 </intent-filter>
</receiver>
  • BroadcastReceiver代码如下:
public class PlayerReceiver extends BroadcastReceiver {
  
  private static final String TAG = PlayerReceiver.class.getSimpleName();
  
  public PlayerReceiver() {
  }
  
  @Override
  public void onReceive(Context context, Intent intent) {
    // TODO: This method is called when the BroadcastReceiver is receiving
    String action = intent.getAction();// 获取对应Action
    Log.d(TAG,"action:"+action);
  
    if(action.equals(MusicPlayerService.ACTION_PLAY)){
      // 进行对应操作
    } else if(action.equals(MusicPlayerService.ACTION_PAUSE)){
    } else if(action.equals(MusicPlayerService.ACTION_LAST)){
    } else if(action.equals(MusicPlayerService.ACTION_NEXT)){
    } 
  
  }
}
点击后监听到的动作.png

1.建立对应的Intent(意图)--即指定对应操作的行为Action;
2.PendingIntent来处理意图,(Pending译为“待定的”、“延迟”,PendingIntent类提供了一种创建可由其它应用程序在稍晚时间触发的Intent的机制--推荐阅读
解析Android延迟消息(PendingIntent)处理

3.通过RemoteViews.setOnClickPendingIntent(int viewId,PendingIntent pendingIntent)方法来为指定的控件绑定对应的意图;
4.在Service中注册广播,监听对应操作。


5.修改自定义通知(Notification)上的显示内容

在自定义通知布局后,我们在有些场景下需要修改一些控件的显示内容(如修改TextView显示文字、ImageView图片、ProgressBar进度等),那么我们可以通过如Notification.contentViewsetTextViewText、setImageViewBitmap、setProgressBar等方法打成效果,示例代码如下:

notification.contentView.setTextViewText(R.id.title_tv, "标题");

注:方法的差异不大,都需要传递控件的id,需要设置的内容、属性等参数。


6.前台服务与普通服务的区别

  • 前台Service的系统优先级更高、不易被回收;
  • 前台Service会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。

7.Service系列拓展阅读

【Android】Service那点事儿
【Android】远程服务(Remote Service)的使用

用到的图片资源(看不清没关系,右击保存即可...):

last_img.png
play_img.png
pause_img.png
next_img.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容