1. Service 是什么?

服务(Service)Android 的四大组件之一,是 Android 中完成程序后台运转的解决方案,合适去履行那些不需求和用户交互并且还要求长期运转的使命。

Service 的运转不依赖于任何用户界面,即使程序被切换到后台,或者用户打开了另外一个应用程序,Service 依然能够保持正常运转。

Service 并不是运转在一个独立的进程当中的 ,而是依赖于创立服务时地点的应用程序进程。当某个应用程序进程被杀掉时,一切依赖于该进程的服务也会中止运转。

Service 并不会自动敞开线程,一切代码都是默许运转在主线程中。咱们需求在服务内部手动创立子线程,并在这儿履行详细的使命。

2. Service 的根本用法

2.1 界说一个 Service

如下图,如同创立 Activity 一样创立一个 Service:

Android Service 运用详解

Android Service 运用详解

有两个属性:

  • Exported:表明是否将这个 Service 露出给外部其他程序拜访。
  • Enabled:表明是否启用这个 Service。

创立之后的 MyService 承继 Service

// Android API 34
public class MyService extends Service {  
    public MyService() {  
    }  
    @Override  
    public IBinder onBind(Intent intent) {  
        // TODO: Return the communication channel to the service.  
        throw new UnsupportedOperationException("Not yet implemented");  
    }  
}

onBind()Service 类中仅有的笼统办法:

// Service.java
@Nullable  
public abstract IBinder onBind(Intent intent);

界说一个 Service 不仅完成 onBind() 办法,一般还会完成 onCreate()onStartCommend()onDestroy()

public class MyService extends Service {
    public MyService() {
    }
    // 服务第一次创立时调用。
    @Override
    public void onCreate() {
        super.onCreate();
    }
    // 每次发动服务时调用。服务一旦发动就立即去履行某个操作的代码逻辑现在这儿
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }
    // 服务销毁时调用。收回不再运用的资源
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mBinder;
    }
}
  1. onCreate() :服务第一次创立时调用。
  2. onStartCommend() :每次发动服务时调用。服务一旦发动就立即去履行某个操作的代码逻辑现在这儿。
  3. onDestroy() :服务销毁时调用。收回不再运用的资源。

Android 四大组件在 AndroidManifest.xml 中进行注册才能收效:

// AndroidManifest.xml
<service  
    android:name=".MyService"  
    android:enabled="true"  
    android:exported="true" />

2.2 startService() 显式敞开服务

主要经过 Intent 去完结服务的发动和中止。

敞开 Service 的两种办法:显现敞开绑定敞开

Activity 中能够直接调用 startService()stopService() 这两个办法显现敞开和中止服务:

if (id == R.id.start_service) {
    Intent startIntent = new Intent(this, MyService.class);  
    startService(startIntent);// 经过 startService() 办法发动 MyService 服务  
} else if (id == R.id.stop_service) {  
    Intent stopIntent = new Intent(this, MyService.class);  
    stopService(stopIntent);// 由 Activity 来决定服务何时中止。若想服务自己中止,在 MyService 调用stopSelf()办法  
}

点击敞开服务按钮敞开服务之后,在手机设置中正在运转的服务中会看见咱们敞开的服务,也能够经过 log 看看服务是否敞开,中止服务同理:

Android Service 运用详解

这儿,完全是 Activity 决定服务何时中止的,假如没有点击按钮,Service 会一向处于运转状态。

假如想让 Service 自己停下来,只需求在 MyService 任何一个位置调用 stopSelf()

显现敞开服务流程:

Android Service 运用详解

2.3 绑定敞开

经过 startService() 办法显现敞开 Service 后,调用者(Activity)就和 service 没有关联了。

这儿一个很重要的问题:Activity 无法拿到 Service引证

举个例子,如安在 MyService 中供给一个下载功用,并且在 Activity 中能够决定何时开端下载,以及检查下载进展呢?

经过 bindService() 办法绑定开启服务,能够借助 onBind() 办法,创立一个专门的 Binder 目标来对下载功用进行办理。

新建 DownloadBinder 承继自 Binder,并在其内部供给开端下载和检查进展办法:

public class MyService extends Service {
    private DownloadBinder mDownloadBinder = new DownloadBinder();
    public class DownloadBinder extends Binder{
        public void startDownload(){
            Log.d("MyService", "Start");
        }
        public int getProgress(){
            Log.d("MyService","getProgress");
            return 0;
        }
        public void stopDownload(){
            Log.d("MyService", "Stop");
        }
    }
    public MyService() {}
    @Override
    public void onCreate() {  super.onCreate(); }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) { return super.onStartCommand(intent, flags, startId);  }
    @Override
    public void onDestroy() { super.onDestroy();  }
    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        return mDownloadBinder;
    }
}

经过 startService() 敞开 Service 时,咱们重写了 onBind(),直接回来的是null,此刻该办法并没有调用。

第31~34行,当经过 bindService() 敞开 Service 时,需求回来 IBiner 的引证给绑定者运用。

回来的是 DownloadBinder 目标的引证,该目标持有了 MyService 引证。绑定者又是在哪里接纳 IBinder 的引证呢?

在 Activity 中界说的 ServiceConnection 匿名内部类:

创立一个 ServiceConnection 的匿名类,重写了 onServiceConnected()onServiceDisconnected(),这两个办法分别在 Activity 和 Service 成功绑定以及连接断开不时调用:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private MyService.DownloadBinder downloadBinder;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
    }
    @Override
    public void onClick(View v) {
        int id = v.getId();
        if (id == R.id.start_service) {
           ...
        } else if (id == R.id.stop_service) {
            ...
        } else if (id == R.id.bind_service) {
            Intent bindIntent = new Intent(this, MyService.class);
            // service 和 activity 绑定后自动创立服务 BIND_AUTO_CREATE,这会使得 MyService 中 OnCreate() 办法得到履行
            bindService(bindIntent,connection,BIND_AUTO_CREATE);
        } else if (id == R.id.unbind_service) {
            unbindService(connection);
        }
    }
    private ServiceConnection connection = new ServiceConnection() {
        // service 和 activity 绑定时调用
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // service 就是从 onBind() 办法回来的
            // 向下转型得到 DownloadBinder 的实例
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        // Service 被销毁时调用,内存不足等。
        }
    };
}

有了 ServiceConnection 引证,接着就需求和 Service 树立联络,树立联络的进程便是绑定敞开 Service 的进程:

private void bindService() {
    Intent intent = new Intent(this, MyService.class);
    bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}

从上面能够看出,绑定敞开的流程:

Android Service 运用详解

  1. 新建 DownloadBinder 类持有 Service 目标。
  2. 在 Service 的 onBind() 办法中回来 IBinder 引证(mDownloadBinder),该引证持有 Service 引证。
  3. Activity 调用 bindService() 办法成功后,会调用 ServiceConnection 的 onServiceConnected() 办法,Service 和 ServiceConnection 也会树立联络,onServiceConnected() 办法会回来 IBinder 引证(mDownloadBinder)。
  4. Activity 经过 IBinder 引证(mDownloadBinder)拿到 Service 引证,从而操作 Service。

以上回答了上面的问题:绑定者(Activity)怎么拿到 Service 引证。

那么怎么进行解绑呢?

手动调用 unbindService(serviceConnection) 办法进行解绑,解绑时也需求传入 ServiceConnection 引证,不然无法确定解绑哪个 Service。

手动调用该办法即可解绑 Service。 当 Activity 经过 bindService() 绑定敞开 Service 后,若是 Activity 销毁了,那么相应的 Service 也会被销毁掉。

值得注意的是:

  1. 若是 startService() 敞开 Service,则无法用 unbindService() 办法封闭 Service。
  2. 若是 bindService() 敞开 Service,则无法用 stopService() 封闭 Service。
  3. 既调用了 startService() 办法,又调用了 bindService() 办法的 Service ,这种情况下要同时调用 stopService()unbindService() 办法,onDestroy() 办法才会履行。

绑定敞开服务的流程:

Android Service 运用详解

3. 前台服务

体系内存不足时可能会收回服务。若想要服务一向保持运转状态,能够考虑运用前台服务,防止服务被收回。

前台服务会一向有一个正在运转的图标在体系的状态栏显现,类似于告诉的效果。相当于 Service 以这种办法在前台运转。

Android 9.0开端,需进行权限声明:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

// MyService.java
@Override
public void onCreate() {
    super.onCreate();
    Log.d("MyService","onCreate executed");
    NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
        NotificationChannel notificationChannel = new NotificationChannel("my_service","前台Service告诉 ", NotificationManager.IMPORTANCE_DEFAULT);
        notificationManager.createNotificationChannel(notificationChannel);
    }
    Intent intent = new Intent(this, MyServiceActivity.class);
    PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
    Notification notification =  new NotificationCompat.Builder(this,"my_service")
            .setContentTitle("This is content title")
            .setContentText("真实的平静,不是避开车马喧嚣,而是在心中修篱种菊。尽管如流往事,每一天都涛声仍旧,只需咱们消除执念,便可幽静安定。愿每个人,在纷呈世相中不会迷失荒径,能够端坐磐石上,醉倒落花前。")
            .setWhen(System.currentTimeMillis())
            .setSmallIcon(R.drawable.a)
            .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.drawable.a))
            .setContentIntent(pi)
            .build();
    startForeground(3,notification);
}

第5行,NotificationManager 类用于向用户展示告诉,对告诉进行办理。

第7行,NotificationChannel 类用于构建一个告诉途径,并运用 NotificationManager 的 createNotificationChannel() 办法进行创立。

告诉途径(Notification Channels),也被称为告诉类别,是Android 8.0(API 级别 26)引入的一个功用,它为开发者供给了更好的办理告诉的办法,也运用户能够有更好地控制他们想要接纳的告诉类型。

NotificationChannel 类和 createNotificationChannel() 办法都是 Android 8.0 体系中新增的 API,因此咱们在运用的时候进行版本判别。

创立一个告诉途径至少需求途径ID途径名称以及重要等级这三个参数:

  • 途径ID:能够随便界说,只需确保大局仅有性就能够。
  • 途径名称:给用户看的,需清楚地表达这个途径的用途。
  • 重要等级:主要有 IMPORTANCE_HIGHIMPORTANCE_DEFAULTIMPORTANCE_LOWIMPORTANCE_MIN。用户能够随时手动更改某个告诉途径的重要等级。

第12行,运用 NotificationCompat Builder 对 Notification 进行构造。由于告诉 API 的不稳定性,运用 NotificationCompat 类对告诉相关操作进行兼容完成。

NotificationCompat.Builder(this,"my_service") 第二个参数为途径ID,需求与创立时的 ID 保持一致。

第11行,PendingIntent 能够在某个合适的时机履行某个动作,能够理解为延迟履行的Intent。经过第18行设置,在点击状态栏中的告诉时会进行跳转。

运转程序,点击 START SERVICE 按钮,就会在状态栏中看到相关告诉:

Android Service 运用详解

若没有呈现,可能告诉权限还未敞开:

Android Service 运用详解