关于Service你需要知道这些

什么是service

Service是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。

一般的我们用两种方法与service进行联系:startService()和bindService()。

方法 启动方式 停止方式 与启动它的组件之间的通信方式 生命周期
startService 在其他组件中调用startService()方法后,服务即处于启动状态 service中调用stopSelf()方法,或者其他组件调用stopService()方法后,service将停止运行 没有提供默认的通信方式,启动service后该service就处于独立运行状态 一旦启动,service即可在后台无限期运行,即使启动service的组件已被销毁也不受其影响,直到其被停止
bindService() 在其他组件中调用bindService()方法后,服务即处于启动状态 所有与service绑定的组件都被销毁,或者它们都调用了unbindService()方法后,service将停止运行 可以通过 ServiceConnection进行通信,组件可以与service进行交互、发送请求、获取结果,甚至是利用IPC跨进程执行这些操作 当所有与其绑定的组件都取消绑定(可能是组件被销毁也有可能是其调用了unbindService()方法)后,service将停止

生命周期


image.png
  • 启动的服务

startService()->onCreate()->onStartCommand()->running->stopService()/stopSelf()->onDestroy()->stopped 其中,服务未运行时会调用一次onCreate(),运行时不调用。

  • 绑定的服务

bindService()->onCreate()->onBind()->running->onUnbind()->onDestroy()->stopped

如何创建service

  1. 创建一个类继承自Service(或它的子类,如IntentService),重写里面的一些关键的回调方法,如onStartCommand(),onBind()等
  2. 在Manifest文件里面为其声明,并根据需要配置一些其他属性。

在Manifest里面声明注意:

<service android:enabled="true"/"false" 
         android:exported="true"/"false" 
         android:icon="drawable resource"
         android:isolatedProcess="true"/"false" 
         android:label="string resource" 
         android:name="string" 
         android:permission="string" 
         android:process="string" > 
</service>
  • android:enabled : 如果为true,则这个service可以被系统实例化,如果为false,则不行。默认为true
  • android:exported : 如果为true,则其他应用的组件也可以调用这个service并且可以与它进行互动,如果为false,则只有与service同一个应用或者相同user ID的应用可以开启或绑定此service。它的默认值取决于service是否有intent filters。如果一个filter都没有,就意味着只有指定了service的准确的类名才能调用,也就是说这个service只能应用内部使用,其他的应用不知道它的类名。这种情况下exported的默认值就为false。反之,只要有了一个filter,就意味着service是考虑到外界使用的情况的,这时exported的默认值就为true
  • android:icon : 一个象征着这个service的icon
  • android:isolatedProcess : 如果设置为true,这个service将运行在一个从系统中其他部分分离出来的特殊进程中,我们只能通过Service API来与它进行交流。默认为false。
  • android:label : 显示给用户的这个service的名字。如果不设置,将会默认使用<application>的label属性。
  • android:name : 这个service的路径名,例如“com.lypeer.demo.MyService”。这个属性是唯一一个必须填的属性。
  • android:permission : 其他组件必须具有所填的权限才能启动这个service。
  • android:process : service运行的进程的name。默认启动的service是运行在主进程中的。(注意:如android:process=":ramote" 一定要带冒号不然会跑不起来)
startService

当一个service通过这种方式启动之后,它的生命周期就已经不受启动它的组件影响了,只要service自身没有调用stopSelf()并且其他的组件没有调用针对它的stopService(),它可以在后台无限期的运行下去。另外,如果确定了使用这种方式启动service并且不希望这个service被绑定的话,除了传统的创建一个类继承service之外我们有一个更好的选择——继承IntentService。

如果是扩建Service类的话,通常情况下我们需要新建一个用于执行工作的新线程,因为默认情况下service将工作于应用的主线程,而这将会降低所有正在运行的Activity的性能。而IntentService就不同了。它是Service的子类,它使用工作线程来注意的处理所有的startService请求。如果不要求这个service要同时处理多个请求,那么继承这个类显然要比直接继承Service好很多。

IntentService做了以下这些事:因此我们只需要实现onHandleIntent()方法来完成具体的功能逻辑就可以了。

  1. 创建默认的工作线程,用于在应用的主线程外执行传递给 onStartCommand() 的所有 Intent
  2. 创建工作队列,用于将一个 Intent 逐一传递给 onHandleIntent() 实现,这样的话就永远不必担心多线程问题了
  3. 在处理完所有启动请求后停止服务,从此妈妈再也不用担心我忘记调用 stopSelf() 了
  4. 提供 onBind() 的默认实现(返回 null)
  5. 提供 onStartCommand() 的默认实现,可将 Intent 依次发送到工作队列和 onHandleIntent() 实现
public class HelloIntentService extends IntentService {  
        /**   
          * A constructor is required, and must call the super IntentService(String) 
          * constructor with a name for the worker thread.    */  

    public HelloIntentService() {
        //构造方法      
        super("HelloIntentService");
    }
    /**    
     * The IntentService calls this method from the default worker thread with    
     * the intent that started the service. When this method returns, IntentService    
     * stops the service, as appropriate.    
     */ 

    @Override
    protected void onHandleIntent(Intent intent) {
        // Normally we would do some work here, like download a file.
        // For our sample, we just sleep for 5 seconds. 
        // 这里根据Intent进行操作       
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // Restore interrupt status. 
            Thread.currentThread().interrupt();
        }
    }
}

注意:如果需要重写其他的方法,比如onDestroy()方法,一定不要删掉它的超类实现!因为它的超类实现里面也许包括了对工作线程还有工作队列的初始化以及销毁等操作

下面是一个官网的例子,提供了service 类实现的代码示例,该类执行的工作与上述使用的IntentService示例完全相同。也就是说,对于每个启动请求,它均使用工作线程执行作业,且每次仅处理一个请求。

public class HelloService extends Service {
    private Looper mServiceLooper;
    private ServiceHandler mServiceHandler;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            long endTime = System.currentTimeMillis() + 5 * 1000;
            while (System.currentTimeMillis() < endTime) {
                synchronized (this) {
                    try {
                        wait(endTime - System.currentTimeMillis());
                    } catch (Exception e) {
                    }
                }
            }
            stopSelf(msg.arg1);
        }
    }

    @Override
    public void onCreate() {
        HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        mServiceHandler.sendMessage(msg);
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {
        Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
    }
}

如果让service同时处理多个请求的需求,这个时候就只能去继承Service了。这个时候就要自己去处理工作线程那些事。上面示例并未这样做,但如果希望如此,则可为每个请求创建一个新线程,然后立即运行这些线程(而不是等待上一个请求完成)。

注意:onStartCommand()的返回值是用来指定系统对当前线程的行为的。它的返回值必须是以下常量之一:

  • START_NOT_STICKY : 如果系统在 onStartCommand() 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
  • START_STICKY : 如果系统在 onStartCommand() 返回后终止服务,则会重建服务并调用 onStartCommand(),但不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 onStartCommand()。这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。
  • START_REDELIVER_INTENT : 如果系统在 onStartCommand() 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand()。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。

bindService

如需与 Activity 和其他应用组件中的服务进行交互,或者需要通过进程间通信 (IPC) 向其他应用公开某些应用功能,则应创建绑定服务。要创建绑定服务,必须实现 onBind() 回调方法以返回IBinder,用于定义与服务通信的接口。然后,其他应用组件可以调用bindService()来检索该接口,并开始对服务调用方法。服务只用于与其绑定的应用组件,因此如果没有组件绑定到服务,则系统会销毁服务(您不必按通过onStartCommand()启动的服务那样来停止绑定服务)。

一般来讲,我们有三种方式可以获得IBinder的对象:继承Binder类,使用Messenger类,使用AIDL。

继承Binder类

如果你的服务仅供本地应用使用,不需要跨进程工作,则可以实现自有Binder类,让你的客户端通过该类直接访问服务中的公共方法。

注意:此方法只有在客户端和服务位于同一应用和进程内这一最常见的情况下方才有效。

  • 在service类中,创建一个满足以下任一要求的Binder实例:
  • 包含客户端可调用的公共方法
  • 返回当前Service实例,其中包含客户端可调用的公共方法
  • 返回由当前service承载的其他类的实例,其中包含客户端可调用的公共方法
  • 在onBind()方法中返回这个Binder实例
  • 在客户端中通过onServiceDisconnected()方法接收传过去的Binder实例,并通过它提供的方法进行后续操作

引用官网例子:

public class LocalService extends Service {
    // Binder given to clients     
    private final IBinder mBinder = new LocalBinder(); 
    // Random number generator    
    private final Random mGenerator = new Random(); 
            /**      
             * Class used for the client Binder.  Because we know this service always      
             * runs in the same process as its clients, we don't need to deal with IPC.      
             */ 

    public class LocalBinder extends Binder {
        LocalService getService() {
            // Return this instance of LocalService so clients can call public methods             
            return LocalService.this;
        }
    } 
    @Override
    public IBinder onBind(Intent intent) { return mBinder;}
            /** method for clients */ 

    public int getRandomNumber() {return mGenerator.nextInt(100)}
}

LocalBinder 为客户端提供 getService() 方法,以检索 LocalService 的当前实例。这样,客户端便可调用服务中的公共方法。 例如,客户端可调用服务中的 getRandomNumber():

public class BindingActivity extends Activity {
    LocalService mService;
    boolean mBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Bind to LocalService        
        Intent intent = new Intent(this, LocalService.class);
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // Unbind from the service        
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }

    /**
     * Called when a button is clicked (the button in the layout file attaches to      
     * this method with the android:onClick attribute)
     */
    public void onButtonClick(View v) {
        if (mBound) {
            // Call a method from the LocalService.
            // However, if this call were something that might hang, then this request should
            // occur in a separate thread to avoid slowing down the activity performance.
            int num = mService.getRandomNumber();
            Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * Defines callbacks for service binding, passed to bindService()
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            // We've bound to LocalService, cast the IBinder and get LocalService instance
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName arg0) {
            mBound = false;
        }

    };
}
使用Messenger类

如需让服务与远程进程通信,则可使用 Messenger 为你的服务提供接口。利用此方法,你无需使用 AIDL 便可执行进程间通信 (IPC)。

方法步骤如下:

  • 服务端实现一个Handler,由其接受来自客户端的每个调用的回调
  • 使用实现的Handler创建Messenger对象
  • 通过Messenger得到一个IBinder对象,并将其通过onBind()返回给客户端
  • 客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用后者将 Message 对象发送给服务
  • 服务端在其 Handler 中(具体地讲,是在 handleMessage() 方法中)接收每个 Message

用这种方式,客户端并没有像扩展Binder类那样直接调用服务端的方法,而是采用了用Message来传递信息的方式达到交互的目的。

下面之所以建两个工程是为了通过Messenger实现IPC,当然你也可以写在一个工程

新建一个工程,作为服务端。

public class MessagerService extends Service {
    public static final int MSG = 0x0001;
    private Messenger mMessenger = new Messenger(new Ihandler());

    public class Ihandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == MSG) {
                Toast.makeText(getApplicationContext(), "服务开启了", Toast.LENGTH_SHORT).show();
            }
            super.handleMessage(msg);
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}

服务端就一个Service,可以看到代码相当的简单,只需要去声明一个Messenger对象,然后在onBind方法返回mMessenger.getBinder();

<service
            android:name=".service.MessagerService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.hw.playandroid.messenger"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>

说明:上述的 <action android:name="com.hw.playandroid.messenger" />是为了能让其他apk隐式bindService,通过隐式调用的方式来调起activity或者service,需要把category设为default,这是因为,隐式调用的时候,intent中的category默认会被设置为default。

主要是因为在这里要跨进程通信,所以在另外一个进程里面并没有我们的service的实例,此时必须要给其他的进程一个标志,这样才能让其他的进程找到我们的service。其实这里的android:exported属性不设置也可以的,因为在有intent-filter的情况下这个属性默认就是true,

再新建一个工程,作为客户端

public class MessagerActivity extends AppCompatActivity {
    Messenger mService;
    boolean isBound;
    ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            isBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
            isBound = false;
        }
    };

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messager);
        ButterKnife.bind(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent();
        intent.setAction("com.hw.playandroid.messenger");
        intent.setPackage("com.hw.playandroid");
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (isBound) {
            unbindService(mConnection);
            isBound = false;
        }
    }

    @OnClick(R.id.tv)
    public void onViewClicked() {
        if (isBound) {
            Message message = Message.obtain(null, MessagerService.MSG, 0, 0);
            try {
                mService.send(message);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
}

(注意:intent.setPackage("com.hw.playandroid");这句一定要加上不然会奔溃)

首先bindService,然后在onServiceConnected中拿到回调的service(IBinder)对象,通过service对象去构造一个mService =new Messenger(service);然后就可以使用mService.send(msg)发给服务端了。上面的例子只有客户端对服务端单方面的通信,而要实现双向通信其实也很简单,只要客户端里也创建一个Handler实例,让它接收来自服务端的信息,同时让服务端在客户端给它发的请求完成了之后再给客户端发送一条信息即可。Message在其中起到了一个信使的作用,通过它客户端与服务端的信息得以互通。

Messenger为什么能进行IPC呢?
其实Messenger的内部实现的,实际上也是依赖于aidl文件实现的。
服务端的onBind()中返回了mMessenger.getBinder()

public IBinder getBinder() { return mTarget.asBinder(); }

可以看到返回的是mTarget.asBinder();而mTarget在两处被赋值,这两处分别是Messenger的两个构造方法,而上面使用的构造mMessenger对象的代码:new Messenger(new Handler());可以确定其调用的构造方法

public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }

由此可以知道是Handler返回的,我们继续跟进Handler,全局搜索getIMessenger()

final IMessenger getIMessenger() {
    synchronized (mQueue) {
        if (mMessenger != null) {
            return mMessenger;
        }
        mMessenger = new MessengerImpl(); return mMessenger;
    }
}

看到这里应该就知道了,mTarget是一个MessengerImpl对象,那么asBinder实际上是返回this,也就是MessengerImpl对象;
这是个内部类,继承自IMessenger.Stub,然后实现了一个send方法,该方法就是将接收到的消息通过 Handler.this.sendMessage(msg);发送到handleMessage。
实际上,Messenger就是依赖IMessenger该aidl文件生成的类,继承了IMessenger.Stub类,实现了send方法,send方法中参数会通过客户端传递过来,最终发送给handler进行处理。

使用AIDL(Android 接口定义语言)

使用场景:引用一段官网原话
只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL。 如果您不需要执行跨越不同应用的并发 IPC,就应该通过实现一个Binder 创建接口;或者,如果您想执行 IPC,但根本不需要处理多线程,则使用Messenger类来实现接口。无论如何,在实现 AIDL 之前,请您务必理解绑定服务。

AIDL 支持下列数据类型:

  • Java 的基本数据类型(如 int、long、char、boolean 等等)
  • String 类型。
  • CharSequence类型。
  • List、Map(元素必须是 AIDL 支持的数据类型,Server 端具体的类里则必须是 ArrayList 或者 HashMap)
  • 其他类型必须使用import导入,即使它们可能在同一个包里。

如何使用

  1. 新建一个工程作为服务端,然后再在这个工程的main文件夹下aidl包用来存放.aidl文件,一般来说, aidl 包里默认有着和 java 包里的包结构。


    image2.png
  2. 创建一个实体类并使其实现Parcelable接口,实现序列化和反序列化。

public class Person implements Parcelable {
    String name;

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    public Person() {}

    protected Person(Parcel in) { name = in.readString(); }
    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @Override
        public Person createFromParcel(Parcel in) { return new Person( in ); }

        @Override
        public Person[] newArray(int size) { return new Person[size]; }
    };

    @Override
    public int describeContents() { return 0; }

    @Override
    public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); }
}
  1. 在里面新建IXxxAidl.aidl文件和实体类的映射.aidl文件

(注意在创建IXxxAidl.aidl时,除了基本类型(int,long,char,boolean等),String,CharSequence,List,Map,其他类型必须使用import导入,即使它们可能在同一个包里,比如下面的Person,就是import进来的。另外,接口中方法的参数除了aidl支持的类型,其他类型必须标识其方向:到底是输入还是输出或者是输入输出,用in,out或者inout来表示)

package com.example.com.test;
import com.example.com.test.bean.Person;
interface IMyAidl {
    Person getPerson();
}

一般的,都把实体类和映射文件放到一个包下,这样做的原因是方便移植。实体类和映射文件也可以不再在同一个包里面,也可以把实体类放到java包下,但是注意这时候,实体类Person的这个 Person.aidl映射文件的包名要和实体类包名一致,但是移植的时候就不那么方便了,需要把这个实体类Person单独进行移植。

package com.example.com.test.bean; 
parcelable Person;
  1. Make Project构建IXxxAidl.java文件。


    image3.png

注意在这一步可能会有一个坑等着你来踩,因为Android Studio 是使用 Gradle 来构建 Android 项目的,而 Gradle 在构建项目的时候会通过 sourceSets 来配置不同文件的访问路径,从而加快查找速度。Gradle 默认是将 java 代码的访问路径设置在 java 包下的,这样一来,如果 java 文件是放在 aidl 包下的话那么系统是找不到这个 java 文件的。有两种方法可以解决

  • 修改 build.gradle 文件:在 android{} 中间加上
sourceSets { main { java.srcDirs = ['src/main/java', 'src/main/aidl'] } }
  • 把 java 文件放到 java 包下去,上面已经介绍了
  1. 创建service,编写服务端代码,在其中创建上面生成的Binder对象实例,并在onBind方法里返回
public class AidlService extends Service {
    private IBinder mIBinder = new IMyAidl.Stub() {
        @Override
        public Person getPerson() throws RemoteException {
            Random random = new Random();
            Person mPerson = new Person();
            mPerson.setName("大家好,我的名字叫" + random.nextInt(10));
            return mPerson;
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mIBinder;
    }
}

记住在Manifest里面注册

<service android:name=".service.AidlService"
         android:enabled="true" 
         android:exported="true">
      <intent-filter>
             <action android:name="com.example.com.test.service.AidlService"/> 
             <category android:name="android.intent.category.DEFAULT"/>                                                       
      </intent-filter>
 </service>

  1. 新建一个工程作为客户端,然后将服务端的aidl整个包复制到客户端的main文件夹下,然后Rebuild一下


    image4.png
  2. 编写客户端代码,这里需要注意两点。

  • 当客户端在onServiceConnected回调中收到IBinder时,它必须调用IXxxAidl.Stub.asInterface(service) 以将返回的参数转换成 IXxxAidl 类型
  • 绑定服务时,必须要调用 intent.setPackage(xxx.xxx.xxx);其中包名为IXxxAidl的包名
public class AidlActivity extends Activity {

    private IMyAidl mAidl;
    private boolean isBound;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mAidl = IMyAidl.Stub.asInterface(service);
            isBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mAidl = null;
            isBound = false;
        }
    };


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
        ButterKnife.bind(this);
    }

    @OnClick(R.id.tv)
    public void onViewClicked() {
        if (isBound){
            try {
                String name = mAidl.getPerson().getName();
                Toast.makeText(this,name,Toast.LENGTH_SHORT).show();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        Intent intent = new Intent();
        intent.setAction("com.example.com.test.service.AidlService");
        intent.setPackage(IMyAidl.class.getPackage().getName());
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (isBound) {
            unbindService(mConnection);
            isBound = false;
        }
    }
}
  1. 运行结果显示


    AIDL显示效果图.gif
前台服务

上面说到的service都是运行在后台的,而这些运行在后台的service的系统优先级相对较低,有可能会被杀死,比如系统内存不足就会回收掉正在后台运行的service,想要service一直处在运行状态的话就可以将service设置为前台服务。

什么是前台服务

前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 前台服务必须为状态栏提供通知,放在“正在进行”标题下方,这意味着除非服务停止或从前台移除,否则不能清除通知。

如何创建前台服务

很简单和上面一样先创建服务,再定义一个方法,在方法里构建Notification,然后通过startForeground(110, notification);方法将服务设置到前台这个方法里的第一个参数不可以为0,停止的话用stopForeground(true);当然你可以通过startService()直接开启,或通过bindService()获取service来直接调方法。开启服务后我们会收到一条通知,这条通知就是前台服务提供的。注意在android8.0后 需要给otification设置一个channelId

注意开启允许通知哦,不然收不到消息的

public class ForeGroundService extends Service {

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //在android8.0后 需要给notification设置一个channelId,不然会报错:
        // Bad notification for startForeground: java.lang.RuntimeException: invalid channel for service notification
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel();
        } else {
            showNotification();
        }
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        stopForeground(true);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    public void showNotification() {
        Intent clickIntent = new Intent(this, NotificationClickReceiver.class); //点击通知之后要发送的广播
        int id = (int) (System.currentTimeMillis() / 1000);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, id, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        Notification.Builder builder = new Notification.Builder(this)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("今天是公元10000年1月1日")
                .setContentText("天气晴,温度零下170度")
                .setWhen(System.currentTimeMillis())
                .setContentIntent(pendingIntent);
        Notification notification = builder.build();
        startForeground(1, notification);
    }

    @RequiresApi(Build.VERSION_CODES.O)
    private void createNotificationChannel() {
        String NOTIFICATION_CHANNEL_ID = "foreground_service";
        String channelName = "ForeGroundService";
        NotificationChannel chan = new NotificationChannel(NOTIFICATION_CHANNEL_ID, channelName, NotificationManager.IMPORTANCE_NONE);
        chan.setLightColor(Color.BLUE);
        chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        if (manager != null) {
            manager.createNotificationChannel(chan);
        } else {
            stopSelf();
        }

        Intent clickIntent = new Intent(this, NotificationClickReceiver.class); //点击通知之后要发送的广播
        int id = (int) (System.currentTimeMillis() / 1000);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(this, id, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("今天是公元10000年1月1日")
                .setContentText("天气晴,温度零下170度")
                .setWhen(System.currentTimeMillis())
                .setContentIntent(pendingIntent);
        Notification notification = builder.build();
        startForeground(2, notification);
    }
}

通过这几句代码可以实现点击通知之后要发送的广播。

Intent clickIntent = new Intent(this, NotificationClickReceiver.class); //点击通知之后要发送的广播 
int id = (int) (System.currentTimeMillis() / 1000);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this.getApplicationContext(), id, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT);

通过广播进行一些操作,如跳转页面,关闭服务等等。

public class NotificationClickReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"我是点击通知过来的,我要关闭服务了",Toast.LENGTH_SHORT).show();
        context.stopService(new Intent(context, ForeGroundService.class));
        context.startActivity(new Intent(context, NotificationActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    }
}

运行结果如下


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

推荐阅读更多精彩内容