Android四大组件之Service

Service

一、基础知识

1、定义

服务,属于Android中的计算型组件

2、作用

提供需要在后台长期运行的服务(如复杂计算、下载等等)

3、特点

  • 长生命周期的、没有用户界面、在后台运行、用户不手动关闭不会停止
  • 从Context派生出来的,也有getResources(),getContentResolver()等方法

二、相关方法

1、Context相关方法

  • startService(Intent intent) ComponentName 启动一个Service,访问者和Service之间没有关联,即使访问者退出了,Service仍然运行
  • stopService (Intent intent) boolean 之后会自动调用内部方法:onDestory()
  • bindService(Intent service, ServiceConnection conn, int flags) boolean 访问者与Service绑在了一起,访问者一旦退出,Service也将终止。conn:该参数是一个ServiceConnection(I)对象,用于监听访问者和Service之间的连接情况,若连接成功,将回调ServiceConnection对象的onServiceConnected(ComponentName name, IBinder service)方法,当异常终止连接时,回调ServiceConnection对象的onServiceDisconnected(ComponentName name)方法(若访问者主动调用unbindService(ServiceConnection conn) 断开连接,不会回调此方法),flags:指定绑定时若Service未创建是否自动创建,值:0或BIND_AUTO_CREATE
  • unbindService(ServiceConnection conn)

2、内部自动调用方法(生命周期方法)

  • onBind(Intent intent) IBinder 应用程序可以通过IBinder与Service组件进行通信,绑定该Service时回调该方法。

在绑定本地Service的情况下,onBind(Intent service)方法返回的IBinder对象会传给ServiceConnection对象的onServiceConnected(ComponentName name, IBinder service)的service参数,IBinder相当于一个代理人的角色,实现互相通信,所以onBind方法不应该返回一个null(一般返回一个继承Binder类的对象,可以操作Service中的内容,一般使用private class指定,里面有多个方法,要暴露什么方法,使用接口去定义),在onServiceConnected方法就可以使用该代理人。作用:暴露一些方法,改变服务的状态

  • onUnbind(Intent intent) boolean 当绑定在该Service上的所有客户端都断开连接时回调该方法
  • onStartCommand(Intent intent, int flags, int startId) int 调用startService(Intent)方法启动Service时回调该方法,会被调用多次
  • onCreate() void 第一次被创建时回调该方法,仅被调用一次
  • onDestroy() void
  • stopSelf()

3、onStartCommand(Intent intent, int flags, int startId)

  • intent :启动时,启动组件传递过来的Intent,如Activity可利用Intent封装所需要的参数并传递给Service

  • flags:表示启动请求时是否有额外数据。可选值有0,START_FLAG_REDELIVERY,START_FLAG_RETRY,0代表没有,它们具体含义如下:
    a)START_FLAG_REDELIVERY
    这个值代表返回值为START_REDELIVER_INTENT,而且在上一次服务被杀死前会去调用stopSelf()方法停止服务。
    b)START_FLAG_RETRY
    该flag代表当onStartCommand调用后一直没有返回值时,会尝试重新去调用onStartCommand()。

  • startId : 指明当前服务的唯一ID,与stopSelfResult (int startId)配合使用,stopSelfResult 可以更安全地根据ID停止服务。

实际上onStartCommand的返回值int类型才是最最值得注意的,它有三种可选值, START_STICKY,START_NOT_STICKY,START_REDELIVER_INTENT,它们具体含义如下:

  • START_STICKY
    当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand方法,但其中的Intent将是null,除非有挂起的Intent,如pendingintent,这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。

  • START_NOT_STICKY
    当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。除非程序中再次调用startService启动此Service,这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。

  • START_REDELIVER_INTENT
    当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起 Intent均依次传递。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务。

4、Service生命周期

4.1、单独调用
  • startService()->onCreate()->onStartCommand()->onStop()->onDestory()
  • bindService()->onCreate()->onBind()->onUnbind()->onDestory()
4.2、混合调用
  • onCreate()->onStartCommand()->onBind()->onUnbind()->onRebind()

说明:混合调用时,要两两对应,不要相互嵌套(类似于html标签)
服务只能解绑一次,多次会报错
建议在Activity的onDestroy方法中解绑掉服务
startService用于保证服务的后台运行,bindService用于调用服务的方法

三、Service分类

1、本地Service

这是最普通、最常用的后台服务Service。

1.1、使用步骤

步骤1:新建子类继承Service类
需重写父类的onCreate()、onStartCommand()、onDestroy()和onBind()方法
步骤2:构建用于启动Service的Intent对象
步骤3:调用startService()启动Service、调用stopService()停止服务
步骤4:在AndroidManifest.xml里注册Service

属性说明

  • android:name Service的类名
  • android:label Service的名字,若不设置,默认为Service类名
  • android:icon Service的图标
  • android:permission 声明此Service的权限,提供了该权限的应用才能控制或连接此服务
  • android:process 表示该服务是否在另一个进程中运行(远程服务) 不设置默认为本地服务;remote则设置成远程服务
  • android:enabled 是否可用即是否可以被系统实例化
  • android:exported 是否能被其他应用隐式调用。 默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。为false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用

2、可通信Service

实例Demo

步骤1:在新建子类继承Service类,并新建一个子类继承自Binder类、写入与Activity关联需要的方法、创建实例

public class MyService extends Service {

    private MyBinder mBinder = new MyBinder();

    @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) {
        System.out.println("执行了onBind()");
        //返回实例
        return mBinder;
    }


    @Override
    public boolean onUnbind(Intent intent) {
        System.out.println("执行了onUnbind()");
        return super.onUnbind(intent);
    }

    //新建一个子类继承自Binder类
    class MyBinder extends Binder {
        public void service_connect_Activity() {
            System.out.println("Service关联了Activity,并在Activity执行了Service的方法");
        }
    }
}

步骤2:在Activity通过调用MyBinder类中的public方法来实现Activity与Service的联系,即实现了Activity指挥Service干什么Service就去干什么的功能

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button startService;
    private Button stopService;
    private Button bindService;
    private Button unbindService;

    private MyService.MyBinder myBinder;


    //创建ServiceConnection的匿名类
    private ServiceConnection connection = new ServiceConnection() {

        //重写onServiceConnected()方法和onServiceDisconnected()方法
        //在Activity与Service建立关联和解除关联的时候调用
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }

        //在Activity与Service解除关联的时候调用
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //实例化Service的内部类myBinder
            //通过向下转型得到了MyBinder的实例
            myBinder = (MyService.MyBinder) service;
            //在Activity调用Service类的方法
            myBinder.service_connect_Activity();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startService = (Button) findViewById(R.id.startService);
        stopService = (Button) findViewById(R.id.stopService);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
        bindService = (Button) findViewById(R.id.bindService);
        unbindService = (Button) findViewById(R.id.unbindService);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            //点击启动Service
            case R.id.startService:
                //构建启动服务的Intent对象
                Intent startIntent = new Intent(this, MyService.class);
                //调用startService()方法-传入Intent对象,以此启动服务
                startService(startIntent);
                break;
            //点击停止Service
            case R.id.stopService:
                //构建停止服务的Intent对象
                Intent stopIntent = new Intent(this, MyService.class);
                //调用stopService()方法-传入Intent对象,以此停止服务
                stopService(stopIntent);
                break;
            //点击绑定Service
            case R.id.bindService:
                //构建绑定服务的Intent对象
                Intent bindIntent = new Intent(this, MyService.class);
                //调用bindService()方法,以此停止服务
                bindService(bindIntent,connection,BIND_AUTO_CREATE);
                //参数说明
                //第一个参数:Intent对象
                //第二个参数:上面创建的Serviceconnection实例
                //第三个参数:标志位
                //这里传入BIND_AUTO_CREATE表示在Activity和Service建立关联后自动创建Service
                //这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行
                break;
            //点击解绑Service
            case R.id.unbindService:
                //调用unbindService()解绑服务
                //参数是上面创建的Serviceconnection实例
                unbindService(connection);
                break;
                default:
                    break;
        }
    }
}

3、前台Service

前台Service和后台Service(普通)最大的区别就在于:

  • 前台Service在下拉通知栏有显示通知(如下图),但后台Service没有;

  • 前台Service优先级较高,不会由于系统内存不足而被回收;后台Service优先级较低,当系统出现内存不足情况时,很有可能会被回收

//用法很简单,只需要在原有的Service类对onCreate()方法进行稍微修改即可
@Override
public void onCreate() {
    super.onCreate();
    System.out.println("执行了onCreat()");

    //添加下列代码将后台Service变成前台Service
    //构建"点击通知后打开MainActivity"的Intent对象
    Intent notificationIntent = new Intent(this, MainActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

    //新建Builer对象
    Notification.Builder builer = new Notification.Builder(this);
    builer.setContentTitle("前台服务通知的标题");//设置通知的标题
    builer.setContentText("前台服务通知的内容");//设置通知的内容
    builer.setSmallIcon(R.mipmap.ic_launcher);//设置通知的图标
    builer.setContentIntent(pendingIntent);//设置点击通知后的操作

    Notification notification = builer.getNotification();//将Builder对象转变成普通的notification
    startForeground(1, notification);//让Service变成前台Service,并在系统的状态栏显示出来
}

4、使用场景

四、其他

1、Service和Thread的区别

Service和Thread之间没有任何关系,之所以有不少人会把它们联系起来,主要因为Service的后台概念

后台的定义:后台任务运行完全不依赖UI,即使Activity被销毁,或者程序被关闭,只要进程还在,后台任务就可以继续运行


一般来说,会将Service和Thread联合着用,即在Service中再创建一个子线程(工作线程)去处理耗时操作逻辑,如下:

@Override  
public int onStartCommand(Intent intent, int flags, int startId) {  
    //新建工作线程
    new Thread(new Runnable() {  
        @Override  
        public void run() {  
            // 开始执行后台任务  
        }  
    }).start();  
    return super.onStartCommand(intent, flags, startId);  
}  

class MyBinder extends Binder {  
    public void service_connect_Activity() {  
        //新建工作线程
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                // 执行具体的下载任务  
            }  
        }).start();  
    }  
}  

2. IntentService

2.1、定义

Android里的一个封装类,继承四大组件之一的Service

2.2、作用

处理异步请求 & 实现多线程

2.1、使用场景

线程任务需按顺序在后台执行

  • 最常见的场景:离线下载
  • 不符合多个数据同时请求的场景:所有的任务都在同一个Thread looper里执行
2.1、使用步骤

步骤1:定义 IntentService的子类

需传入线程名称、复写onHandleIntent()方法

public class myIntentService extends IntentService {

    /** 
    * 在构造函数中传入线程名字
    **/  
    public myIntentService() {
        // 调用父类的构造函数
        // 参数 = 工作线程的名字
        super("myIntentService");
    }

    /** 
     * 复写onHandleIntent()方法
     * 根据 Intent实现 耗时任务 操作
     **/  
    @Override
    protected void onHandleIntent(Intent intent) {

        // 根据 Intent的不同,进行不同的事务处理
        String taskName = intent.getExtras().getString("taskName");
        switch (taskName) {
            case "task1":
                Log.i("myIntentService", "do task1");
                break;
            case "task2":
                Log.i("myIntentService", "do task2");
                break;
            default:
                break;
        }
    }

    @Override
    public void onCreate() {
        Log.i("myIntentService", "onCreate");
        super.onCreate();
    }
    /** 
     * 复写onStartCommand()方法
     * 默认实现 = 将请求的Intent添加到工作队列里
     **/  
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("myIntentService", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i("myIntentService", "onDestroy");
        super.onDestroy();
    }
}

步骤2:在Manifest.xml中注册服务

<service android:name=".myIntentService">
    <intent-filter >
        <action android:name="cn.scu.finch"/>
    </intent-filter>
</service>

步骤3:在Activity中开启Service服务

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
            // 同一服务只会开启1个工作线程
            // 在onHandleIntent()函数里,依次处理传入的Intent请求
            // 将请求通过Bundle对象传入到Intent,再传入到服务里

            // 请求1
            Intent i = new Intent("cn.scu.finch");
            Bundle bundle = new Bundle();
            bundle.putString("taskName", "task1");
            i.putExtras(bundle);
            startService(i);

            // 请求2
            Intent i2 = new Intent("cn.scu.finch");
            Bundle bundle2 = new Bundle();
            bundle2.putString("taskName", "task2");
            i2.putExtras(bundle2);
            startService(i2);
            startService(i);  //多次启动
        }
    }

测试结果

3、进程优先级

  • 前台进程:某个Activity可见,获得焦点
  • 可见进程:某个Activity可见,但是没有焦点
  • 服务进程:有一个服务在后台运行
  • 后台进程:没有任何服务,打开一个Activity然后最小化(容易被回收)
  • 空进程:没有任何活动组件存在的进程(容易被回收)

4、AIDL

4.1、简介

为了实现跨进程通信(IPC),实现进程之间的数据交换

4.2、步骤

①:服务端创建.aidl文件
②:服务端创建Service,并在onBind中返回一个IBinder(接口对象(代理人))

IBinder binder = new IMyAidlInterface.Stub() {
    @Override
    public int add(int num1, int num2) throws RemoteException {
         return num1 + num2;
    }
};

③:客户端创建(复制)和服务端一样的.aidl文件(包名也必须一致)
④:客户端创建ServiceConnection的子类,并实现其onServiceConnected(ComponentName name, IBinder service),在方法中(service就是中间人)iMyAdil = IMyAidlInterface.Stub.asInterface(service),客户端可以使用服务端的方法了
⑤:客户端bindService

4.3、AIDL支持的数据类型(支持其实就是定义aidl的时候,参数可以使用的类型)

基本数据类型(除short),String,CharSequence,List(仅支持ArrayList),Map(仅支持HashMap),Parcelable

注意:
①:非基本数据类型,需要用in,out,inout指定数据的走向
②:复杂类型(如Book)必须实现Parcelable,且需要Book.aidl(内容parcelable Book;)—— 包名必须和Book.java相同,无论是否相同的包,都需要导入包
③:AIDL接口中只支持方法,不支持声明静态变量

参考文献

Android四大组件:Service史上最全面解析
Android 多线程 解析:IntentService(含源码解析)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260