startService和bindService两种使用方式, 隐式启动、显式启动两种启动方式及生命周期

本文详细介绍了Android中Service的使用,包括显式和隐式启动Service、前台服务的实现、服务的绑定与解绑。通过示例代码展示了不同API版本下的行为差异,并阐述了startService与bindService在生命周期和独立性上的区别。同时,演示了如何通过ADB命令与Service交互,提供了一种调试和测试服务的方法。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

一、Service的两种使用方式

本文介绍的是app A 启动 app B的服务的方式。本篇内容相关代码已经上传Github:BinderDemo

这是一篇很基础的帖子,本身是不想写的,我想写的是《Binder的原理》,但是我略微思索一下,发现事情不简单。要了解Binder,当然离不开Service,我干脆再说一下《Service启动原理》吧,既然已经说了这么多了,我干脆再简单说一下Service启动方式吧,于是就有了现在的这篇文章……上面的两篇我后面补充吧。

首先声明,startService和bindService中贴的log可能由于使用隐式/显式启动/绑定服务的方式不同 ,日志有所出入,具体看1.5版本差异。
首先声明,startService和bindService中贴的log可能由于使用隐式/显式启动/绑定服务的方式不同 ,日志有所出入,具体看1.5版本差异。
首先声明,startService和bindService中贴的log可能由于使用隐式/显式启动/绑定服务的方式不同 ,日志有所出入,具体看1.5版本差异。

1.1 首先,准备app B

  • 首先继承系统Service,实现自己的Service;
public class SnapService extends Service {
    private static final L.TAG TAG = new L.TAG("SnapServer");

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
    	L.i(TAG, "onBind: ");
        return null;
    }
 	@Override
    public boolean onUnbind(Intent intent) {
        L.i(TAG, "onUnbind: ");
        return super.onUnbind(intent);
    }
    @Override
    public void onCreate() {
        super.onCreate();
        L.i(TAG, "onCreate: ");
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand: ");
 	}
    @Override
    public void onDestroy() {
        super.onDestroy();
        L.i(TAG, "onDestroy: ");
    }
}
  • 2.将Service添加到Manifest;
<service
    android:name=".SnapService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.example.demoserver.action" />
	</intent-filter>
</service>

注意 exported属性为true,否则不允许外部启动; 添加action

1.2 准备app A

暂时只需要创建好并添加一个ClientMainActivity就够了,先准备以下代码:
	//app B的包名
    private final static String TARGET_APP_PACKAGE = "com.example.demoserver";
    //app B 目标Service的名字
    private final static String TARGET_SERVICE_NAME = TARGET_APP_PACKAGE + ".SnapService";
    private final static String TARGET_SERVICE_ACTION = TARGET_APP_PACKAGE + ".action";

接下来开始正文,我们在app A 中启动app B的服务

1.3 启动服务——startService

1. 显式启动
	private void startService(){
		  // --- 显式启动 ---
        Intent intent = new Intent();
//        intent.setClassName(TARGET_APP_PACKAGE, TARGET_SERVICE_NAME);
        intent.setComponent(new ComponentName(
                TARGET_APP_PACKAGE,  //这个参数是另外一个app的包名
                TARGET_SERVICE_NAME));//这个是要启动的Service的全路径名
                
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(intent);
        } else {
            startService(intent);
        }
}

2. 隐式启动
private void startService(){
        // --- 隐式启动 ---
        Intent intent = new Intent();
        intent.setPackage(TARGET_APP_PACKAGE);
        intent.setAction(TARGET_SERVICE_ACTION);
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForegroundService(intent);
        } else {
            startService(intent);
        }
3. 服务前台化

Android8.0之后,官方为了防止app后台造成的网络、cpu、内存等性能消耗影响用户体验所以做了限制,不允许启动后台服务,常规方式启动服务超过5s就会闪退。而我们需要做的就是服务前台化:

  • manifest申请服务前台化权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
  • Service启动后调用startForeground
	@Override
    public void onCreate() {
        super.onCreate();
        L.i(TAG, "onCreate: ");
         //服务前台化
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            sendNotification();
        }
    }

    private void sendNotification() {
        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID, CHANNEL_ID, NotificationManager.IMPORTANCE_HIGH);
        channel.enableLights(true);//设置提示灯
        channel.setLightColor(Color.BLUE);//设置提示灯颜色
        channel.setShowBadge(true);//显示logo
        channel.setDescription("test");//设置描述
        channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);//锁屏可见
        manager.createNotificationChannel(channel);

        Notification testNotification= new Notification.Builder(this)
                .setChannelId(CHANNEL_ID)
                .setContentText("服务前台化")
                .setContentText("服务已经至于前台了")
                .setWhen(System.currentTimeMillis())
                .setSmallIcon(R.mipmap.ic_launcher)
                .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
                .build();
        //id 不能是0
        startForeground(1, testNotification);
    }

客户端使用start或者bind方式启动服务后就可以看到通知栏有通知代表服务已经前台化,我就不再贴出结果了。
服务前台化具体解释可参见大神的帖子

调用方法后服务被启动:

2021-06-20 01:40:15.181 15231-15231/com.example.demoserver I/Server_SnapServer: onCreate: 
2021-06-20 01:40:15.182 15231-15231/com.example.demoserver I/Server_SnapServer: onStartCommand: action = com.example.demoserver.action
4. 停止服务
 	@Snap
    public void stopService() {
        Intent intent = new Intent();
        intent.setPackage(TARGET_APP_PACKAGE);
        intent.setAction(TARGET_SERVICE_ACTION);
        stopService(intent);
    }

日志输出:

2021-06-20 02:28:05.198 21142-21142/com.example.binderdemo D/Client_SnapUtil: - snapSnaped : stopService

2021-06-20 02:29:09.405 21289-21289/com.example.demoserver I/Server_SnapServer: onDestroy: 

1.4 绑定服务——bindService

准备代码:

	 private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            L.i(TAG, "onServiceConnected: " + name.toShortString());
            mMessageController = MessageController.Stub.asInterface(service);
            mIsServiceConnected = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            L.i(TAG, "onServiceDisconnected: " + name.toShortString());
            mIsServiceConnected = false;
        }
    };
1.显式绑定
		// --- 显式绑定 ---
        Intent serviceIntent = new Intent();
//        intent.setClassName(TARGET_APP_PACKAGE, TARGET_SERVICE_NAME);
        serviceIntent.setComponent(new ComponentName(
                TARGET_APP_PACKAGE,  //这个参数是另外一个app的包名
                TARGET_SERVICE_NAME));//这个是要启动的Service的全路径名
        boolean success = bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
        L.d(TAG, "bindService: success = " + success);
2.隐式绑定
		// --- 隐式启动 ---
        Intent serviceIntent = new Intent();
        serviceIntent.setPackage(TARGET_APP_PACKAGE);
        serviceIntent.setAction(TARGET_SERVICE_ACTION);
        boolean success = bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
        L.d(TAG, "bindService: success = " + success);

调用方法后:

2021-06-20 02:13:57.678 16983-16983/com.example.binderdemo D/Client_SnapUtil: - snapSnaped : bindService
2021-06-20 02:13:57.689 16983-16983/com.example.binderdemo D/Client_ClientMainActivity: bindService: success = true
2021-06-20 02:13:57.701 16983-16983/com.example.binderdemo I/Client_ClientMainActivity: onServiceConnected: {com.example.demoserver/com.example.demoserver.SnapService}
-----------------------------------
2021-06-20 02:13:57.695 15679-15679/com.example.demoserver I/Server_SnapServer: onCreate: 
2021-06-20 02:13:57.699 15679-15679/com.example.demoserver I/Server_SnapServer: onBind: 
3.解绑服务
	public void unBindService() {
        unbindService(mServiceConnection);
    }

日志输出:

2021-06-20 02:30:53.705 21428-21428/com.example.demoserver I/Server_SnapServer: onUnbind: 

1.5 版本差异

  • Android 7.1
  • 显式startService执行onCreate(),隐式startService还执行onStartCommand();
  • 直接bindService不成功,必须在显式/隐式startService之后才能通过显式bindService绑定service。
  • unBindService之后仅仅执行onUnBind();
  • startService和bindService一起执行后:如果先执行stopService,则不执行onDestroy,执行完unBindService之后一并执行onUnbind()和onDestroy();若先后执行unBindService和stopService,则onUnBind()和onDestroy()一并先后执行。
  • Android 8.0
  • 不申请权限,不进行服务前台化。显式startService之后仅仅执行onCreate()隐式startService(),执行onCreate()->onStartCommand();显式bindService 隐式bindService 之后正常执行onCreate()->onBind();
  • 申请权限,服务前台化。显式/隐式startService/startForeGroundService,显式/隐式bindService,看起来能启动Service,但是模拟器它SystemUI崩溃了,app都没有报错,杀掉竟然启动不起来。

奇了怪了,很可能通知创建的有问题,或者模拟器有问题,我后续换台模拟器再试试。

  • Andorid 8.1, Android 9.0,Android 10
  • 显式startForeGroundService仅仅执行onCreate() ,隐式startForeGroundService()之后还会执行onStartCommand();
  • 显式/隐式bindService,unBindService执行onCreate()->onBind()->onUnbind()->onDestroy();
  • startService和bindService一起执行后:如果先执行stopService,则不执行onDestroy,执行完unBindService之后一并执行onUnbind()和onDestroy();若先后执行unBindService和stopService,则onUnBind()和onDestroy()一并先后执行。
  • Android 11
  • 此版本很特殊,只能通过显式bindService方式启动服务,但是却没有执行相关生命周期方法
  • 无法stopService和unBindService;

ps:我不知道是代码问题还是我打开方式不对,不论是模拟器还是真机都不能从外部直接启动app B 的Service,我看google文档好像没有看到这块儿有变更,麻烦知道的大佬烦请告知一下。

二、startService和bindService异同

2.1 生命周期不同
  • startService方式:
  • 如上1.3StartService 所讲 Service被启动和停止后日志输出可以看到,该方式Service的生命周期为onCreate() -> onStartCommandonDestroy
  • tartService可以多次调用,但是onCreate()只执行一遍,也就是Service只会创建一次,每次调用onStartCommand都会被调用。
  • bindService方式:
  • 如上1.4 bindService Service 的bind和unBind日志输出可以看到,这种方式Service的生命周期为:onCreate()->onBind()onUnBind
  • bindService只有第一次调用会创建Service,执行onCreate()和onBind(),连续多次调用并不会执行onCreate()和onBind();
2.2 Service独立性
  • startService方式:

Service一旦被启动,在调用者没有调用stopService()的前提下,其生命周期就和调用者的生命周期不再关联。即使调用者被杀掉,Service也可以独立生存。

  • bindService方式:

与startService方式不同的是,这种方式,service的生命周期是和调用者关联在一起的,如果调用者进程被终结后,服务便会终止。

2.3 调用服务的方式
  • startService之后可以发送action与服务交互。
  • bindService之后,除了发送action还 可以获得服务的完整方法(Binder的实现类),可以直接交互;

三、使用ADB向Service发送命

我们平时肯定会有这样的情况,项目里有一些方便我们平时调试或测试同学测试使用的功能,这些功能可能需要做个UI或者手动开启比较麻烦,这个时候,Service是不是也可以派上用场(和测试小姐姐装x)。

  • 准备action和参数
public interface SnapAction {
    String ACTION_SWITCH_THEME = "ACTION_SWITCH_THEME";
    String EXTRA_INT_THEME = "theme";
    /**
     * 0或不传值为打开,1为关闭
     */
    String ACTION_ENABLE_LOG = "ACTION_ENABLE_LOG";
    String EXTRA_INT_ENABLE_LOG = "enable_log";
}
  • Service内重写onStartCommand()
 @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        final String action = intent.getAction();
        if (!TextUtils.isEmpty(action)) {
            L.i(TAG, "onStartCommand: action = " + action);
            switch (action) {
                case SnapAction.ACTION_ENABLE_LOG:
                    int enableLog = intent.getIntExtra(SnapAction.EXTRA_INT_ENABLE_LOG, 0);
                    SnapConfig.LOG_ENABLE = enableLog == 0;
                    L.i(TAG, "enable_log = " + enableLog);
                    break;
                case SnapAction.ACTION_SWITCH_THEME:
                    int theme = intent.getIntExtra(SnapAction.EXTRA_INT_THEME, 0);
                    L.d(TAG, "switch_theme: theme = " + theme);
                    break;
                default:
                    break;
            }
        }
        return super.onStartCommand(intent, flags, startId);
    }
  • 终端通过ADB发送命令:

后面是包名/service完整路径名,–ei表示int类型的参数,后面是参数键值对;

adb shell am startservice -n com.example.demoserver/com.example.demoserver.SnapService -a ACTION_ENABLE_LOG --ei enable_log 0

服务端收到命令:

2021-06-21 19:11:51.523 22476-22476/com.example.demoserver I/Server_SnapServer: onStartCommand: action = ACTION_ENABLE_LOG
2021-06-21 19:11:51.524 22476-22476/com.example.demoserver I/Server_SnapServer: enable_log = 0

好了,以上就是service的简单使用方式,本篇内容相关代码已经上传[Github:BinderDemo],如有不对的地方,欢迎指正。

后续我一定会不上Service启动原理,以及Binder使用方式及原理,敬请期待

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值