关于Service你须要知道这些

什么是service

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

通常的咱们用两种方法与service进行联系:startService()和bindService()。android

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

生命周期 api

image.png

  • 启动的服务

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

  • 绑定的服务

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的名字。若是不设置,将会默认使用的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好不少。app

IntentService作了如下这些事:所以咱们只须要实现onHandleIntent()方法来完成具体的功能逻辑就能够了。dom

  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()方法,必定不要删掉它的超类实现!由于它的超类实现里面也许包括了对工做线程还有工做队列的初始化以及销毁等操做ide

下面是一个官网的例子,提供了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后,服务被异常kill掉,系统不会自动重启该服务。
  • START_STICKY : 若是service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试从新建立service,因为服务状态为开始状态,因此建立服务后必定会调用onStartCommand(Intent,int,int)方法。若是在此期间没有任何启动命令被传递到service,那么参数Intent将为null。
  • START_REDELIVER_INTENT:使用这个返回值时,若是在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将以前的Intent的值传入。
  • START_STICKY_COMPATIBILITY:这个实际上是用来兼容api5一下的,这个的做用和START_STICK同样,可是这个返回值不能保证系统必定会从新建立service。

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>
复制代码

说明:上述的 是为了能让其余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 Ihandler());能够肯定其调用的构造方法

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);其中包名为服务端的包名
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