美文网首页Android Classandroidandroid基础知识点
【Android】Service前台服务的使用

【Android】Service前台服务的使用

作者: 紫豪 | 来源:发表于2016-07-28 14:58 被阅读21042次

    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

    相关文章

      网友评论

      • 掬水月在手_0748:动态注册mFilter.addAction(ACTION_PLAY)真的有效果?不是只有ConnectivityManager中的常量才有作用吗
      • 面向星辰大海的程序员:没看懂多少 startForeground(110, notification);有了这行代码就是前台服务了么
      • Pyro:onStartCommand 少了 return super.onStartCommand(intent, flags, startId); ?
        紫豪:是少了,谢谢提醒。
      • fe82d5d97c55:你好博主,我这边有一个这样的需求:当别人对我发起视频时,不管我在APP的那个界面,都会弹出一个接听和拒接的界面,类似微信视频。这个用服务要怎么实现呢
        紫豪:@深雨燕南飞 关于IM通信视频/语音呼入状态的监听,我了解过环信、融云第三方IM,它们是通过注册广播就可以了。当然,如果完全是自己搭的话,用Service轮询也可以。。。
        通话界面的话,App内弹出,个人觉得一个独立的界面或弹窗就ok。
        fe82d5d97c55:@紫豪 是的
        紫豪:请问你是想用服务去监听是否有发起视频通话的请求么?
      • 95b81146c820:为什么我修改自定义的内容点了后没反应袄?
        Intent intentplay = new Intent(ACTION_PLAY);
        RemoteViews view=new RemoteViews(this.getPackageName(),R.layout.notificationlayout);
        Notification.Builder builder = new Notification.Builder(this).setContent(view);
        builder.setSmallIcon(R.mipmap.icon);
        builder.setContentText("Pshop Box运行中...");
        builder.setContentTitle("PShop Box");
        Intent intent1 = new Intent("action");
        PendingIntent pendingIntent = PendingIntent.getBroadcast
        (this.getApplicationContext(), 0, intent1, PendingIntent.FLAG_UPDATE_CURRENT);
        builder.setContentIntent(pendingIntent);
        view.setOnClickPendingIntent(R.id.text,pendingIntent);
        NotificationManager manager=(NotificationManager)
        getSystemService(getApplicationContext().NOTIFICATION_SERVICE);
        Notification notification = builder.build();
        if(Utils.getCache("flag").equals("开")){
        notification.contentView.setTextViewText(R.id.text,"已关闭");
        Utils.putCache("flag","关");
        }else{
        notification.contentView.setTextViewText(R.id.text,"已开启");
        Utils.putCache("flag","开");
        }
        startForeground(1, notification);
        95b81146c820:把notification.contentView改成我里面的view也不行
      • a6b2ff637cf5:请问andoid6.0 以后的休眠模式下,前台服务还能正常运行吗
        紫豪:@frankenste_6509 目前我个人了解的情况是:休眠模式还是会有影响的,待机模式没影响。休眠模式的情况下,可以考虑设置白名单。最近几天做个实验具体看下两种模式下包含前台服务的app的反应。
      • 三七七七七七七七:你好我想请教一下 我启动service 的时候在onStartCommand 里面启动一个前天service startForeground(1, notification); notification 通知什么都没有就是个对象,我在onDestroy 的时候直接用stopForeground(true) ,会异常 ,class name is null 我没懂 能帮我看看吗?
        紫豪:可以私聊发具体的Log信息,我看看能不能帮到你
      • Anonymous_NO1:厉害了 我的老铁 big fundamental
        紫豪:@Anonymous_NO1 :sunglasses::sunglasses::sunglasses:
      • 代码咖啡:笔者,想问您一下,QQ的聊天服务是前台服务还是后台服务。从有Notification角度讲,它像是前台服务;但是在没有新消息时,它一直是没有Notification的。感觉这个服务是一直在后台轮训的,然后在有新消息触发时才来到前台。
        紫豪:@代码咖啡 不用谢,互相帮助:relaxed:
        代码咖啡:@紫豪 好的,学习了,谢谢您
        紫豪:Notification可以主动推出来,从聊天的角度来说,它不应该是一个前台服务,因为它没必要常驻通知栏。
      • Mr_Tao先先生:原来前台Service就是Service绑定了一个Notification
      • 辣条超人:写的不错
      • Duke丶Lee:你好,我发现更改前台通知的View的内容,比如我要更改前台通知中的TextView的text内容,发现调用了notification.content.setTextViewText()方法后并未改变该TextView的内容。还有很多方法是deprecated的
      • 8b79c4cbcae4:完整的看下来跟我使用的基本一致,不过我用的前台服务就是发发心跳而已,没点击事件,也没什么广播,不过写的挺好的
        紫豪:@走在岸上的鱼 一般情况下不会有那么复杂的使用,谢谢支持。
      • 闫鹏飞写字的地方:你这怎么写的,这么整洁
        紫豪:@EddieYan markdown编辑模式

      本文标题:【Android】Service前台服务的使用

      本文链接:https://www.haomeiwen.com/subject/jnrojttx.html