关于Service的一些难点

1、前言

Service作为安卓四大组件之一,在开发中也是经常遇到的,顾明思议我们称为后台服务。当我们的业务不需要前台页面的时候,我们可以使用Service来实现。

2、关于Service的启动方式以及生命周期

Activity的生命周期我们也许背的滚瓜烂熟了,但是Service的生命周期我们可能往往很容易忽略。和Activity不一样的是Service的启动分为两种:startService和bindService。下面我们分别介绍:

2.1、startService

我们通过startService的方式来启动一个service,然后观察生命周期的变化
1、创建MySevice类

package com.example.administrator.workmanager;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {
    private static final String TAG ="MyService" ;

    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind: ..." );
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: ..." );
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand: ..." );
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.e(TAG, "onDestroy: ..." );
        super.onDestroy();
    }
}


2、配置AndroidManifest.xml

   

3、startService和stopService

   final Intent intent = new Intent(MyActivity.this,MyService.class);
        ivOne.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                startService(intent);
            }
        });
        ivTwo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                stopService(intent);
            }
        });

点击startService启动一个service,我们看打印结果:

E/MyService: onCreate: ...
E/MyService: onStartCommand: ...

我们看到首次启动service的时候,首先会调用onCreate方法,然后在调用onStartCommand方法。那如果启动一个servcie之后再启动呢?我们在启动看看:

E/MyService: onStartCommand: ...

我们看到再次启动的时候,调用了onStartCommand方法,也就是说onCreate在第一次启动的时候会调用。接下来我们来看下通过stopService停止一个service。

   stopService(intent);
E/MyService: onDestroy: ...

通过stopService停止一个service。则会调用onDestroy方法。这个时候当我们再次startService,由于之前的service已经被销毁了,则会重新启动一个service

E/MyService: onCreate: ...
E/MyService: onStartCommand: ...

那么就会重新调用onCreate和onStartCommand方法。

2.2、Activity的生命周期会影响service的生命周期吗?

我们看到我们是在Activity中通过startService的方式启动一个service,那么Activity的生命周期和service有关联吗?我们在做一个测试。
我们启动service之后,分别点击Activity进行跳转,回到原来的Activity,关闭Activity等等。发现即使Activity被关闭了调用onDestroy方法。service的生命周期也不会有影响,也就是说service依然是存活在后台中的。因此我们得出结论,通过startService方式启动一个service,那么service的生命周期和Activity的生命周期是没有关系的。他们之间真的没关系吗?答案不是绝对的,下面我们介绍通过bindService的方式启动一个service。

2.3、bindService

首先我们观察到在MyServcie类中是不是有一个onBind方法。在上面我们没有提到,那么其实这个方法和bindService有关的。

   @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind: ..." );
        return null;
    }

下面我们通过bindService的方式来启动service

   //第一个参数:Intent意图
        //第二个参数:是一个接口,通过这个接口接收服务开启或者停止的消息,并且这个参数不能为null
        //第三个参数:开启服务时的操作,BIND_AUTO_CREATE代表自动创建service
        final Intent intent = new Intent(MyActivity.this,MyService.class);
        final  MyConnection conn = new MyConnection();
        ivOne.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                bindService(intent,conn,BIND_AUTO_CREATE);
            }
        });
        ivTwo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(conn);
            }
        });


 private class MyConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //只有当我们自己写的MyService的onBind方法返回值不为null时,才会被调用
            Log.e("call","onServiceConnected");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //这个方法只有出现异常时才会调用,服务器正常退出不会调用。
            Log.e("call","onServiceDisconnected");
        }
    }

我们来看打印结果:分别是onCreate、onBind。

 E/MyService: onCreate: ...
 E/MyService: onBind: ...

当我们再次调用bindService的时候,不会有任何打印。然后我们来看下通过unbindService停止一个service。我们可以打印结果调用了onUnbind然后调用onDestroy方法

 E/MyService: onUnbind: ...
 E/MyService: onDestroy: ...

我们得出结论:通过bindService的方式启动一个service首先会调用onCreate方法,然后在调用onBind方法,这种启动方式我们一般叫做绑定服务,也就是service和activity 绑定。当我们调用bindService服务之后,我们在再次调用,不会调用任何生命周期方法。我们通过unbindService方式停止解绑一个服务,分别会调用onUnbind和onDestroy方法。那这种方式启动的service和activity有没有关联呢?我们通过bindService启动一个service之后,然后退出当前activity,我们来看下面的生命周期的打印结果:分别调用了Activity的onPause和onDestroy方法,然后在调用了service的onUnbind和onDestroy方法

E/MyService: Activity onPause: ....
 E/MyService: Activity onDestroy: ....
 E/MyService: onUnbind: ...
E/MyService: onDestroy: ...

我们得出结论就是:通过bindService方式启动一个service其实是和当前activity绑定的,当activity被销毁的时候,那么service也会相应的解绑和销毁。以上我们分析是在onBind返回值是null的情况下,那么如果onBind的返回值不为null呢?

2.4、onBind返回值不为null情况下service生命周期的变化。

我们对MyService和MyConnection进行修改。

package com.example.administrator.workmanager;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {
    private static final String TAG ="MyService" ;

    @Override
    public IBinder onBind(Intent intent) {
        Log.e(TAG, "onBind: ..." );
        return new MyBinder();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e(TAG, "onCreate: ..." );
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e(TAG, "onStartCommand: ..." );
        return super.onStartCommand(intent, flags, startId);
    }
    @Override
    public void onDestroy() {
        Log.e(TAG, "onDestroy: ..." );
        super.onDestroy();
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.e(TAG, "onUnbind: ..." );
        return super.onUnbind(intent);

    }
    public class MyBinder extends Binder {
        public void test(){
            serviceTest();
        }
    }
    private  void serviceTest(){
        Log.e(TAG, "serviceTest: 调用服务中的方法" );
    }
}

   private class MyConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //只有当我们自己写的MyService的onBind方法返回值不为null时,才会被调用
            Log.e(TAG,"onServiceConnected");
            //调用服务中的方法
            ((MyService.MyBinder)service).test();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //这个方法只有出现异常时才会调用,服务器正常退出不会调用。
            Log.e(TAG,"onServiceDisconnected");
        }
    }

我们bindService启动然后看打印结果

 E/MyService: onCreate: ...
 E/MyService: onBind: ...
 E/MyService: onServiceConnected
 E/MyService: serviceTest: 调用服务中的方法

然后在通过unbindeService解绑

E/MyService: onUnbind: ...
 E/MyService: onDestroy: ...

得出结论是:unbindeService解绑,生命周期的调用顺序还是一样的,但是bindService的时候,在MyConnection中会调用onServiceConnected方法。我们在onServiceConnected可以获取到IBinder对象,其实就是service中onBind返回的MyBiner对象,我们拿到IBinder对象,可以调用MyBiner中的方法,并可以在MyBiner中的方法中调用service的方法,这样也就完成了一次Activity和Service的通信,这种机制我们叫做binder机制。通过binder机制我们可以跨进程通信,假如这个service和我们的Activity不在同一个进程中,那么不就是完成了一次跨进程的通信吗?在启动一个安卓应用时,我们也许以为只是启动了一个应用进程,其实不是的,在内部还启动了很多个进程,我们来看一张图:

关于Service的一些难点_第1张图片
image.png

从架构图上我们可以看到,安卓系统中主要包含了这几个进程:Init进程、Zygite进程、System Server进程、和应用进程。那么进程之间的通信主要就是以Binder机制为主。

3、小结

我们针对以上的知识点进行总结:
1、service的方式有startService和bindService
2、startService方法启动,生命周期:onCreate,onStartCommand.
停止用stopService方法,生命周期:onDestroy
3、bindService方法启动,生命周期: onCreate, onBind.
unbindService解绑,onUnbind,onDestroy。
Activity被销毁的时候,
Activity onPause
Activity onDestroy
Service onUnbind
Service onDestroy
4、onBind返回值不为null情况下,我们可以通过MyConnection中onServiceConnected方法获取onBind的返回值,调用服务service中的方法,实现Activity和Service的通信。

注意

以上提到的service都是在ui线程中执行的,我们之前的文章有提到,如果想在子线程中执行可以使用IntentService。

4、关于Android O(8.0)后台service限制

Android的每次平台更新,都一直在努力收紧应用权限。8.0也不例外,这次是限制应用后台启动service的权限。
对于所有应用可以使用的解决方法。
1). 平台区分,8.0以下,爱咋咋地,以前怎样还怎样。
2). 8.0以上,统一一个常驻service,转为前台服务。方法为:将startService改成startForegroundService,并在对应的service创建的时候,使用startForeground注册自己的notification。
3). 对,这个方法是带notification的,也就是说,如果想用常驻service,就得让用户知道。

5、startForegroundService的使用

我们知道startForegroundService启动的是作为一个前台服务,我们来看下具体的使用方式
首先创建一个服务,和上面一样

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()");
  }
  
  @Override
  public IBinder onBind(Intent intent) {
       Log.d(TAG, "onBind()");
       // TODO: Return the communication channel to the service.
    throw new UnsupportedOperationException("Not yet implemented");
  }
}

然后创建Notification:
在Service的onStartCommand中添加如下代码:

@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; //设置为默认的声音
}

在完成Notification通知消息的构建后,在Service的onStartCommand中可以使用startForeground方法来让Android服务运行在前台。

// 参数一:唯一的通知标识;参数二:通知消息。
startForeground(110, notification);// 开始前台服务

如果需要停止前台服务,可以使用stopForeground来停止正在运行的前台服务。

@Override
public void onDestroy() {
  Log.d(TAG, "onDestroy()");
  stopForeground(true);// 停止前台服务--参数:表示是否移除之前的通知
  super.onDestroy();
}

到此为止,我们基本上就学会了如何使用前台服务了。

5.1、前台服务与普通服务的区别

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

你可能感兴趣的:(关于Service的一些难点)