Android组件之全面解析Service

前言

服务(Service)是Android中实现程序后台运行的解决方案,非常适合用于执行那些不需要和用户交互且长期运行的任务。

要点
  • 依赖于创建服务时所在的应用进程,不是运行在一个独立的进程当中。
  • 耗时操作需要在服务内部手动创建子线程,并在这里执行具体任务,否则就有可能会出现主线程被阻塞住的情况。

生命周期

生命周期
Service 有三种启动服务的方式:

  • 启动服务:startService()开启服务,stopService()关闭服务

startService() 回调 onCreate ——> onStartCommand
stopService() 回调 onDestroy

  • 绑定服务:bindService()绑定服务,unbindService()解绑服务。客户端通过一个IBinder接口与服务进行通信

bindService() 回调 onCreate ——> onBind
unbindService() 回调 onUnBind——>onDestory

  • 混合方式: 先开启服务,再绑定服务

startService() 回调 onCreate ——> onStartCommand; bindService() 回调 onBind,unbindService() 回调 onUnBind; stopService() 回调 onDestroy

要点

  • 无论开启service多少次,onCreate 方法仅会调用一次,并且系统只会创建Service的一个实例
  • 整个生命周期方法里,只有onStartCommand 方法可以多次调用,其他方法只能调用1次
  • onStartCommand() 调用次数= startService() 调用次数
  • 多个客户端可以绑定到同一个服务上,当所有的客户端都解除绑定后,系统会销毁服务

小结:

  • startService开启服务,但无法操作服务
  • bindService绑定服务,还能操作服务

Service分类

  1. 运行地点分为:本地服务和远程服务

本地服务

用于应用程序内部,实现一些耗时任务,最普通、最常用的后台服务Service。

实例

MyService 类

public class MyService extends Service {
     @Override
    public void onCreate() {
        super.onCreate();
        System.out.println("执行了onCreat()");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("执行了onStartCommand()");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        System.out.println("执行了onDestory()");
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    
    private MyBinder mBinder = new MyBinder();
    class MyBinder extends Binder {
        public String printServiceInfo() {
            return "Activity 和 Service 建立通信,传递数据";
        }
    }
}

在Activity 里构建Intent对象,并调用startService()启动Service、stopService停止服务,bindService绑定服务、unbindService解绑服务

@Override
    public void onClick(View view) {
        Intent i = new Intent(MainActivity.this, MyService.class);
        switch (view.getId()) {
            case R.id.btn_start_service:
                startService(i);
                break;
            case R.id.btn_stop_service:
                stopService(i);
                break;
            case R.id.btn_bind_service:
                bindService(i, connection, BIND_AUTO_CREATE);
                break;
            case R.id.btn_unbind_service:
                unbindService(connection);
                break;
   }
}
    private MyService.MyBinder myBinder;

    //创建ServiceConnection的匿名类
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //实例化Service的内部类myBinder
            //通过向下转型得到了MyBinder的实例,Binder实现了IBinder 接口
            myBinder = (MyService.MyBinder) iBinder;
            //在Activity调用Service类的方法
            String info = myBinder.printServiceInfo();
            System.out.println("---------->" + info);
        }
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
        }
    };

在AndroidManifest.xml里注册Service

<service android:name=".MyService" />

运行结果为
测试

远程服务

用于Android系统内部的应用程序之间,可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可。可以定义接口并把接口暴露出来,以便其他应用进行操作。客户端建立到服务对象的连接,并通过那个连接来调用服务。

步骤

服务器端:

  • 新建定义AIDL文件,并声明该服务需要向客户端提供的接口
  • 在Service子类中实现AIDL中定义的接口方法,并定义生命周期的方法
  • 在AndroidMainfest.xml中注册服务且声明为远程服务
服务器端实例

新建AIDL文件,在新建AIDL文件里定义Service需要与Activity进行通信的内容(方法),并进行编译(Make Project)
在这里插入图片描述

interface AIDLService {
    /**
     * //AIDL中支持以下的数据类型
       //1. 基本数据类型
       //2. String 和CharSequence
       //3. List 和 Map ,List和Map 对象的元素必须是AIDL支持的数据类型;
       //4. AIDL自动生成的接口(需要导入-import)
       //5. 实现android.os.Parcelable 接口的类(需要导入-import)
     */
    void aidlService();
}

编译
在Service子类中实现AIDL中定义的接口方法,并定义生命周期的方法

public class ServiceDemo extends Service {
    public ServiceDemo() {
    }
    @Override
    public IBinder onBind(Intent intent) {
        System.out.println("-------->onBind");
        return mBinder;
    }
    AIDLService.Stub mBinder = new AIDLService.Stub() {
        @Override
        public void aidlService() throws RemoteException {
            System.out.println("客户端通过AIDL与远程后台成功通信");
        }
    };
}

在AndroidMainfest.xml中注册服务 & 声明为远程服务

        <service
            android:name=".service.ServiceDemo"
            android:exported="true" //设置可被其他进程调用
            android:process=":remote">//将本地服务设置成远程服务
            <intent-filter>
                 // 此处Intent的action必须写成“服务器端包名.aidl文件名”
                <action android:name="com.xf.AIDLService"/>
            </intent-filter>
        </service>

客户端:

  • 拷贝服务端的AIDL文件到目录下
  • 使用Stub.asInterface接口获取服务器的Binder,根据需要调用服务提供的接口方法
  • 通过Intent指定服务端的服务名称和所在包,绑定远程Service
客户端实例

将服务端的AIDL文件所在的包复制到客户端目录下(Project/app/src/main),并进行编译
aidl
在Activity里,使用Stub.asInterface接口获取服务器的Binder;通过Intent指定服务端的服务名称和所在包,进行Service绑定;根据需要调用服务提供的接口方法。

  // 开启服务
  case R.id.btn_remote_service:
       Intent intent = new Intent("com.xf.AIDLService");
       intent.setPackage("com.xf");
       bindService(intent, reConn, BIND_AUTO_CREATE);
       break;

// 连接 远程服务
   private AIDLService mAidlService;
    
    private ServiceConnection reConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //使用asInterface()方法获取服务器端返回的IBinder对象
            //将IBinder对象传换成了AIDLService接口对象
            mAidlService = AIDLService.Stub.asInterface(iBinder);
            try {
                mAidlService.aidlService();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
        }
    };

测试结果
在这里插入图片描述
从上面测试结果可以看出,客户端调用了服务端Service的方法,即客户端和服务端进行了跨进程通信

本地服务与远程服务比较

在这里插入图片描述

  1. 按运行类型分为前台服务后台服务

前台服务

前台服务是指那些经常会被用户关注的服务,因此内存过低时它不会成为被杀的对象。 前台服务必须提供一个状态栏通知,并会置于“正在进行的”(“Ongoing”)组之下。这意味着只有在服务被终止或从前台移除之后,此通知才能被解除。

实例

在Service类的onCreate方法开启前台服务

@Override
    public void onCreate() {
        super.onCreate();
        System.out.println("------->onCreate");
        // 前台服务
        String channelID = "com.xf.serviceclientdemo";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelID, "前台服务", NotificationManager.IMPORTANCE_HIGH);
            NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            notificationManager.createNotificationChannel(channel);
        }
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

        Notification notification = new NotificationCompat.Builder(this, channelID)
                .setContentTitle("前台服务通知的标题")
                .setContentText("前台服务通知的内容")
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentIntent(pendingIntent)
                .build();

        startForeground(1, notification);//让Service变成前台Service,并在系统的状态栏显示出来
    }

要从前台移除服务,请调用stopForeground()方法,这个方法接受个布尔参数,表示是否同时移除状态栏通知

测试结果
ss

前台服务与后台服务比较

在这里插入图片描述

Service相关知识

  1. bindService() 在进行服务绑定的时,其flags有:
  • Context.BIND_AUTO_CREATE
    表示收到绑定请求的时候,如果服务尚未创建,则即刻创建; 在系统内存不足需要先摧毁优先级组件来释放内存,且只有驻留该服务的进程成为被摧毁对象时,服务才被摧。
  • Context.BIND_DEBUG_UNBIND  
    通常用于调试场景中判断绑定的服务是否正确,但容易引起内存泄漏,因此非调试目的的时候不建议使用
  • Context.BIND_NOT_FOREGROUND
    表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行
  1. Service 元素的属性
  • android:name :服务类名
  • android:label :服务的名字,如果此项不设置,那么默认显示的服务名则为类名
  • android:icon:服务的图标
  • android:permission:申明此服务的权限,这意味着只有提供了该权限的应用才能控制或连接此服务
  • android:process:表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字
  • android:enabled :如果此项设置为 true,那么 Service 将会默认被系统启动,不设置默认此项为 false
  • android:exported:表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false
  1. onStartCommand()方法必须返回一个整数。这个整数是描述系统在杀死服务之后应该如何继续运行
  • START_STICKY

如果系统在onStartCommand()返回后杀死了服务,则将重建服务并调用onStartCommand(),但不会再次送入上一个intent, 而是用null intent来调用onStartCommand() 。除非还有启动服务的intent未发送完,那么这些剩下的intent会继续发送。 这适用于媒体播放器(或类似服务),它们不执行命令,但需要一直运行并随时待命。

  • START_NOT_STICKY

如果系统在onStartCommand()返回后杀死了服务,则不会重建服务了,除非还存在未发送的intent。 当服务不再是必需的,并且应用程序能够简单地重启那些未完成的工作时,这是避免服务运行的最安全的选项。

  • START_REDELIVER_INTENT

如果系统在onStartCommand()返回后杀死了服务,则将重建服务并用上一个已送过的intent调用onStartCommand()。任何未发送完的intent也都会依次送入。这适用于那些需要立即恢复工作的活跃服务,比如下载文件。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值