Android的Service知识点总结(Service,IntentService)


更新2020年05月11日10:07:56:加入onStartCommand于2.2.1中。

前言

参考项目:https://github.com/wodongx123/ServiceDemo
正文的代码源码。

1. Service 简介

Service作为Android四大组件之一,最大的特点就是它不像Activity一样需要交互窗口,如果有需要在后台执行的任务的话,就可以用Service来进行。

  • Service的方法均由主线程来执行,所以不能执行太过耗时的操作。

2. Service的启动方式

在开始之前,我new了一个Project,并且自带一个 Empty Activity,然后不做任何操作。

2.1 startService()

正常的启动服务的形式,通过调用contextWrapper的startService的方法直接启动服务。 一般情况下我们只要在Activity中调用startService方法就好。

  1. new一个继承Service的类,然后重写其中的方法。

    public class MyService extends Service {
        private static final String TAG = "MyService";
        
        public MyService() {
        }
    
        @Override
        public void onCreate() {
            Log.i(TAG, "onCreate: " + toString());
            super.onCreate();
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            Log.i(TAG, "onStartCommand: " + toString());
            return super.onStartCommand(intent, flags, startId);
        }
    
        @Override
        public void onDestroy() {
            Log.i(TAG, "onDestroy: ");
            super.onDestroy();
        }
    
  2. 在Activity中调用Intent,然后加上Service,最后启动。

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    
        Button btnStartService;
        Button btnStopService;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
    		//这里为了方便,就随意的加了一些按钮来控制,xml文件也就这些按钮
            btnStartService = findViewById(R.id.btn_start);
            btnStopService = findViewById(R.id.btn_stop);
            btnStartService.setOnClickListener(this);
            btnStopService.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.btn_start:
                    Intent start = new Intent(this, MyService.class);
                    //startService是ContextWrapper的方法,但是Activity有继承它,可以直接调用
                    startService(start);
                    break;
                case R.id.btn_stop:
                    Intent stop = new Intent(this, MyService.class);
                    stopService(stop);
                    break;
            }
    
        }
    }
    
  3. 最后,如果你是通过new -> Service -> Service来创建Service的话就没必要,但是如果你是普通的new一个Java Class手写代码的话,你需要在AndroidManifest.xml中再添加我们的Service。

  • 如果哪天你发现你的代码都没问题,但是就是运行不了Service,就检查一下AndroidManifest.xml中添加了没有!

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.servicedemo">
    
        <application
            ......
            >
    
    		......
    
            <service
                android:name=".MyService"
                android:enabled="true"
                android:exported="true"></service>
                
        </application>
    
    </manifest>
    

    name指向我们的Service类,enable表示是否可以使用,exported表示是否可以被其他应用访问。

  1. 最后运行程序,多次点击startService按钮后,再点击stopService按钮,得到如下结果。
    startService结果
    可以发现,多次点击startService按钮后,onCreate方法没有重复执行,onStartCommand却会多次执行,且后面的toString所带的内存地址相同,表示是同一个对象。

2.2.1 onStartCommand()

在startService启动服务的方法中,主要逻辑都在onStartCommand方法中实现。接下来就分析onStartCommand这个方法。

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

传入参数:

  • intent:启动时附带的意图,可以在里面封装数据。
  • flags:表明启动请求时是否有附带内容,可选值为0,START_FLAG_REDELIVERY,START_FLAG_RETRY。
    • 0:一般情况下传入值为0。
    • START_FLAG_REDELIVERY:onStartCommand的返回值确定为START_REDELIVER_INTENT,且服务在被kill之前,会调用stopSelf()。
    • START_FLAG_RETRY:如果onStartCommand后一直没有返回值,会重新调用onStartCommand。
  • startId:指明当前服务的唯一id,与停止服务时的stopSelfResult(int startId)配合使用。

返回值,主要是用于判断服务在异常退出时是否重启,何时重启的问题,有以下四个取值:

  • START_STICKY:如果服务在启动后被killed(因为内存不足等各种原因),系统就会在稍后重启这个服务,但是重启的时候不保留intent,所以onStartCommand方法中intent为null。
  • START_NOT_STICKY:如果服务在启动后被killed,不会重启。
  • START_START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但是不能保证一定会重启。
  • START_REDELIVER_INTENT:START_STICKY的传入intent版本,intent为之前运行时的值。

2.2 bindService()

startService的缺点就是,Service和Activity之间的交互和通信太少,除了startService和stopService以外,不能通过其他的方法对Service进行操作。这个时候就要用到BindService。BindService通过Binder实现对Service的操作。

  1. 实现Service内部的onBind方法,为此,需要先定义一个Binder类。为了让这个Binder稍微有点现实意义,我让他做一个从0数到100的操作。
    public class MyService extends Service {
        private static final String TAG = "MyService";
        
        //把int定义在Service中
        int i=0;
    
        private CountBinder countBinder = new CountBinder();
        
        //省略若干方法
    	.....
    	
        @Override
        public IBinder onBind(Intent intent) {
            Log.i(TAG, "onBind: " + toString());
            return countBinder;
        }
    
    
    	@Override
        public boolean onUnbind(Intent intent) {
            Log.i(TAG, "onUnbind: ");
            return super.onUnbind(intent);
        }
    
        class CountBinder extends Binder{
    
    		//由于Service是由主线程来执行,所以要开一个子线程来计算
            Thread countThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (; i<100; i++){
                        Log.i(TAG, "startCount: " + Thread.currentThread() + "   " + i);
                        try {
                        	//数一下停一下,不然一下就数完了
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
    
            public void startCount(){
                countThread.start();
            }
            
        }
    
    }
    
  2. 在Activity中,创建一个ServiceConnection,这个就是BindService最重要的类,最后BindService。
    //省略了代码中一些无关内容
    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
        private static final String TAG = "MainActivity";
    
        Button btnBindService;
        Button btnUnbindService;
    	
        private  ServiceConnection conn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.i(TAG, "onServiceConnected: ");
                MyService.CountBinder binder = (MyService.CountBinder)service;
                binder.startCount();
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
    			Log.i(TAG, "onServiceDisconnected: ");
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
    		//我在xml文件中又加了两个按钮
            btnBindService = findViewById(R.id.btn_bind);
            btnUnbindService = findViewById(R.id.btn_unbind);
            btnBindService.setOnClickListener(this);
            btnUnbindService.setOnClickListener(this);
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.btn_bind:
                    Intent bind = new Intent(this, MyService.class);
                    //bindService需要传入ServiceConnection
                    bindService(bind, conn, BIND_AUTO_CREATE);
                    break;
                case R.id.btn_unbind:
                    unbindService(conn);
                    break;
            }
        }
    }
    
  3. 运行,点击一次BindService按钮后,再点击一次unBindService,结果如下:在这里插入图片描述
    虽然服务是关了,但是线程没有关掉,如果要关的话,就要写在onDestroy()里面,就可以关了,这里先不写。

2.3 Service的生命周期

根据不同的使用方法,Service有不同的生命周期。
Service生命周期

3. IntentService

刚刚有说到,Service的主要方法都是在主线程中执行,那么对于Service而言不能执行耗时过长的任务,这样就有点限制Service的可用性了。虽然也可以主动在Service内部new Thread来使用,不过Android提供了IntentService这个类。

IntentService继承Service,内部正好就封装了HandlerThread和Handler,可以将任务分发给子线程来完成。

  • IntentService内部有一个HandlerThread和Handler,这两个搭配将任务发给子线程完成。
  • onStartCommand方法被内部实现了,我们只需要将主逻辑实现在onHandleIntent()即可。
  • onBind方法内部返回是return null,如果我们手动实现功能的话,就不会执行多线程了。
  1. 还是以从0数到100为例,先实例化一个类继承IntentService。

    public class MyIntentService extends IntentService {
        private static final String TAG = "MyIntentService";
    
        int i;
    
        public MyIntentService() {
            super("111");
            //super()中的参数就是IntentService内部线程的名字
            Log.i(TAG, "MyIntentService: ");
        }
    
    	//主要逻辑都放在这里处理
        @Override
        protected void onHandleIntent(@Nullable Intent intent) {
            Log.i(TAG, "onHandleIntent: " + Thread.currentThread().getName());
            for (; i<100; ){
                i++;
                Log.i(TAG, "onHandleIntent: " + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        public void onDestroy() {
            Log.i(TAG, "onDestroy: ");
            super.onDestroy();
        }
    }
    
  2. 由于IntentService继承Service,调用方法也同Service。

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
        private static final String TAG = "MainActivity";
    
        Button btnStartIntentService;
        Button btnStopIntentService;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            btnStartIntentService = findViewById(R.id.btn_start_intentservice);
            btnStopIntentService = findViewById(R.id.btn_stop_intentservice);
            btnStartIntentService.setOnClickListener(this);
            btnStopIntentService.setOnClickListener(this);
    
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.btn_start_intentservice:
                    Intent startIntent = new Intent(this, MyIntentService.class);
                    startService(startIntent);
                    break;
                case R.id.btn_stop_intentservice:
                    Intent stopIntent = new Intent(this, MyIntentService.class);
                    stopService(stopIntent);
                    break;
            }
            
        }
    }
    
  3. 最后在AndroidManifest.xml中补上Service。

    <application
    		......
    		>
    
    		......
    
            <service
                android:name=".MyIntentService"
                android:enabled="true"
                android:exported="true"></service>
    
        </application>
    
  4. 先点击startService按钮,过一会点击stopService按钮,结果这样。
    在这里插入图片描述

其他

为什么说bindService比startService容易通信

在Java中,如果我们要访问一个对象,就必须要持有这个对象的引用,反过来说如果没有这个对象的引用,就不能访问这个对象。

Object o = new Object();
o.func();

但是对于startService而言,这是个void方法,没有返回Service的对象,所以你无法去访问Service的内部数据(除了onStartCommand以外)。

MyService myService = null;
Intent intent = new Intent(MainActivity.this, MyService.class);
//没法给myService赋值,无法持有引用
startService(intent);

但是bindService不同于startService的是,它使用了ServiceConnection这个类,看它其中的onServiceConnected方法。

private  ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.i(TAG, "onServiceConnected: ");
        MyService.CountBinder binder = (MyService.CountBinder)service;
        binder.startCount();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.i(TAG, "onServiceDisconnected: ");
    }
};

这个方法传入了一个IBinder对象,而这个IBinder对象就是Service内部的那个Binder对象。当他传入了这个对象时,我们就可以持有这个对象的引用。同时也可以访问其内部的方法。

我以正文的从0数到100为例,现在添加一个获取当前的数字的功能。

  1. 在Binder内部增加一个获取值的方法。

    public class MyService extends Service {
    
        int i=0;
    
        private CountBinder countBinder = new CountBinder();
        
        @Override
        public IBinder onBind(Intent intent) {
            return countBinder;
        }
    
    
        class CountBinder extends Binder{
    
    		......
    
            public int getI(){
                return i;
            }
            
        }
    
    }
    
  2. 在MainActivity中添加一个MyService.CountBinder的对象,在onServiceConnected的时候赋值。

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    	
    	MyService.CountBinder binder;
    	
        private  ServiceConnection conn = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                Log.i(TAG, "onServiceConnected: ");
                binder = (MyService.CountBinder)service;
                binder.startCount();
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.i(TAG, "onServiceDisconnected: ");
            }
        };
    }
    
  3. 最后在MainActivity中调用binder的getI方法。

    public class MainActivity extends AppCompatActivity implements View.OnClickListener {
        private static final String TAG = "MainActivity";
    
        Button btnGetI;
    
    	MyService.CountBinder binder;
    	
    	.......
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            btnGetI = findViewById(R.id.btn_get_i);
            btnGetI.setOnClickListener(this);
    
        }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()){
            	......
            	
    			case R.id.btn_get_i:
                    if (binder != null)
                        binder.getI();
                    break;
            }
    
        }
    }
    
  4. 通过这样的方法,就可以访问Service的内部方法和变量了,所以这是为什么BindService()比startService()容易通信的原因。

参考材料

Service由浅到深——AIDL的使用方式 - CNblog
https://www.cnblogs.com/huangjialin/p/7738104.html
Service 生命周期 - CNBlog
https://www.cnblogs.com/huihuizhang/p/7623760.html
Android IntentService使用介绍以及原理分析 - CSDN
https://www.jianshu.com/p/8c4181049564
Service-服务(二)onStartCommand()详解 - 简书
https://www.jianshu.com/p/d484e1ef3639

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值