Android进阶之深入理解Service

1 概要

Service是一种计算型组件,主要用于在后台执行一系列计算任务,不直接与用户交互,执行任务的结果可以和外界进行通信。

由于Service组件工作在后台,因此用户无法直接感知到它的存在。Activity组件只有一种运行模式-启动状态,但是Service组件却有两种状态:启动状态和绑定状态。①通过startService启动的服务处于启动状态,它的内部可以执行一些后台计算,并且不需要和外界有直接的交互。②通过bindService启动的服务处于绑定状态,Service内部同样也可以执行后台计算,但是处于这种状态的Service可以通过ServiceConnection和外界进行通信。

1.1 Service的生命周期

在这里插入图片描述

1.2 什么时候使用Service

(1)播放多媒体的时候,用户启动了其他的Activity这个时候程序要在后台继续播放。
(2)后台service下载插件、更新包。
(3)检测SD卡上文件的变化。
(4)在后台记录你地理位置的改变等等。

1.3 Service分为本地服务(LocalService)和远程服务(RemoteService)

(1)默认情况下是在同一个主线程中。但可以通过配置android:process=”:remote” 属性让Service运行在不同的进程。
(2)本地服务依附在主进程上而不是独立的进程,不需要IPC,也不需要AIDL,主进程被Kill后,服务便会终止。
(3)远程服务为独立的进程,进程名格式为:包名 + android:process字符串。由于是独立的进程,因此在Activity所在进程被Kill的时候,该服务依然在运行,不受其他进程影响。

2 startService和bindService的区别

2.1 通过start方式开启服务

2.1.1 startService的步骤
1、定义一个类继承service
2、manifest.xml文件中配置service
3、使用context的startService(Intent)方法启动service
4、不使用时,调用stopService(Intent)方法停止服务
2.1.2 主要方法
// 开启服务
Intent service = new Intent(this, MyService.class);
startService(service);
// 结束服务
stopService(service);
2.1.3 生命周期

(1)完整生命周期

onCreate() -- > onStartCommand() -- > onDestory()

(2)调用一次startService,生命周期执行的方法依次是

TestService: onCreate---
TestService: onStartCommand---startId = 1 and intent = Intent { cmp=com.seniorlibs.lifecycle/.service.TestService }

(3)调用多次startService,onCreate只有第一次会被执行,而onStartCommand会执行多次

TestService: onCreate---
TestService: onStartCommand---startId = 1 and intent = Intent { cmp=com.seniorlibs.lifecycle/.service.TestService }
TestService: onStartCommand---startId = 2 and intent = Intent { cmp=com.seniorlibs.lifecycle/.service.TestService }
TestService: onStartCommand---startId = 3 and intent = Intent { cmp=com.seniorlibs.lifecycle/.service.TestService }

(4)结束服务时,调用stopService,执行onDestroy方法,并且多次调用stopService时,onDestroy只有第一次会被执行

TestService: onDestroy---
2.1.4 与activity之间的关系

(1)开启服务以后,与activity就没有关联,不受影响,独立运行。

2.2 采用bind的方式开启服务

2.2.1 bindService的步骤
1、定义一个类继承Service
2、在manifest.xml文件中注册service
3、使用context的bindService(Intent,ServiceConnection,int)方法启动service
4、不使用时,调用unbindService(ServiceConnection)方法停止该服务
2.2.2 主要方法
// 绑定服务
Intent bindIntent = new Intent(this, TestService.class);
// 参数1:Intent意图
// 参数2:是一个接口,通过这个接口接收服务开启或者停止的消息,并且这个参数不能为null
// 参数3:开启服务时的操作,BIND_AUTO_CREATE代表自动创建service
bindService(bindIntent, mConnection, BIND_AUTO_CREATE);

private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 只有当TestService的onBind方法返回值不为null时,才会被调用
            LogUtils.d(TAG, "onServiceConnected");
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            // 只有出现异常时才会调用,服务正常退出不会调用。
            LogUtils.e(TAG, "onServiceDisconnected");
        }
    };
// 结束服务
unbindService(conn);
2.2.3 生命周期

bindService开启服务时,根据生命周期里onBind方法的返回值是否为空,有两种情况

@Override
public IBinder onBind(Intent intent) {
	LogUtils.d(TAG, "onBind---");
	// 返回mBinder对象或null
	return mBinder; 
}
2.2.3.1 onBind返回值是null

(1)完整生命周期

onCreate() -- > onBind() -- > onDestory()

(2)调用一次bindService,生命周期执行的方法依次是

TestService: onCreate---
TestService: onBind---

(3)调用多次bindService,onCreate和onBind也只在第一次会被执行

TestService: onCreate---
TestService: onBind---

(4)调用unbindService结束服务,执行onDestroy方法,并且unbindService方法只能调用一次,多次调用应用会抛出异常

TestService: onDestroy---

(5)注意:调用unbindService一定要确保服务已经开启,以及避免多次调用unbindService,否则应用会抛出异常,如下:在这里插入图片描述

2.2.3.2 onBind返回值不为null

(1)完整生命周期

onCreate() -- > onBind() --> onServiceConnected() -- > onDestory()

(2)调用一次bindService,onServiceConnected方法也被调用了,生命周期执行的方法依次是

TestService: onCreate---
TestService: onBind---
TestService + TestActivity: onServiceConnected

(3)调用多次bindService,onCreate和onBind也只在第一次会被执行

TestService: onCreate---
TestService: onBind---
TestService + TestActivity: onServiceConnected

(4)调用unbindService结束服务,执行onDestroy方法,并且unbindService方法只能调用一次,多次调用应用会抛出异常

TestService: onDestroy---
2.2.4 与activity之间的关系

(1)bindService开启服务以后,与activity存在关联,退出activity时必须调用unbindService方法,否则会报ServiceConnection泄漏的错误。

2.3 同时采用start和bind的方式开启服务

2.3.1 采用start和bind的方式开启服务,没有先后顺序的要求,TestService的onCreate只会执行一次。

(1)先start再bind

TestService: onCreate---
TestService: onStartCommand---startId = 1 and intent = Intent { cmp=com.seniorlibs.lifecycle/.service.TestService }

TestService: onBind---
TestService + TestActivity: onServiceConnected

// 重复bind,没有回调;重复start,回调如下
TestService: onStartCommand---startId = 2 and intent = Intent { cmp=com.seniorlibs.lifecycle/.service.TestService }

(2)先bind再start

TestService: onCreate---
TestService: onBind---
TestService + TestActivity: onServiceConnected

TestService: onStartCommand---startId = 1 and intent = Intent { cmp=com.seniorlibs.lifecycle/.service.TestService }

// 重复bind,没有回调;重复start,回调如下
TestService: onStartCommand---startId = 2 and intent = Intent { cmp=com.seniorlibs.lifecycle/.service.TestService }
2.3.2 关闭服务需要stopService和unbindService都被调用,也没有先后顺序的影响,TestService的onDestroy也只执行一次

(1)如果只用一种方式关闭服务,不论是哪种关闭方式,onDestroy都不会被执行,服务也不会被关闭


(2)同时stopService和unbindService后执行,多次调用unbindService应用会抛出异常,如同2.2

TestService: onDestroy---

3 startService方式开启服务: onStartCommand诡异的返回值

3.1 源码分析

(1)源码实现方式

public int onStartCommand(Intent intent, int flags, int startId) {
	onStart(intent, startId);
	return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
 }

(2)默认实现方式

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

(3)默认实现方式返回的结果是START_STICKY

// 应用程序targetSdkVersion小于2.0就返回 START_STICKY_COMPATIBILITY,否则返回START_STICKY
mStartCompatibility = getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.ECLAIR; // 5 ,Android 2.0

(4)自定义的实现方式有4种

START_STICKY
START_NOT_STICKY
START_REDELIVER_INTENT
START_STICKY_COMPATIBILITY

3.2 TestService–>Runnable–>run制造异常,模拟系统杀死该进程

(1)START_STICKY(粘性的):服务被异常kill掉,当内存又存在的时候,service又被重新创建

TestService: onCreate---
TestService: onStartCommand---startId = 1 and intent = Intent { cmp=com.seniorlibs.lifecycle/.service.TestService }
TestService: 2s after---
// 异常:com.seniorlibs.lifecycle E/AndroidRuntime: FATAL EXCEPTION: main
TestService: onCreate---
TestService: onStartCommand---startId = 2 and intent = null
TestService: 2s after---

解释:如果Service所在的进程,在执行了onStartCommand方法后,被清理了,那么这个Service会被保留在已开始的状态,但是不保留传入的Intent,随后系统会尝试重新创建此Service,由于服务状态保留在已开始状态,所以重启服务后一定会调用onCreate和onStartCommand方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null,startId=2

(2)START_NOT_STICKY(非粘性的):服务被异常kill掉,系统不会自动重启该服务

TestService: onCreate---
TestService: onStartCommand---startId = 1 and intent = Intent { cmp=com.seniorlibs.lifecycle/.service.TestService }
TestService: 2s after---
// 异常:com.seniorlibs.lifecycle E/AndroidRuntime: FATAL EXCEPTION: main

解析:如果Service所在的进程,在执行了onStartCommand方法后,被清理了,则系统不会重新启动此Service。

(3)START_REDELIVER_INTENT(重新提交intent):服务被异常kill掉,当内存又存在的时候,系统会自动重启该服务,并将Intent的值传入

TestService: onCreate---
TestService: onStartCommand---startId = 1 and intent = Intent { cmp=com.seniorlibs.lifecycle/.service.TestService }
TestService: 2s after---
// 异常:com.seniorlibs.lifecycle E/AndroidRuntime: FATAL EXCEPTION: main
TestService: onCreate---
TestService: onStartCommand---startId = 1 and intent = Intent { cmp=com.seniorlibs.lifecycle/.service.TestService }
TestService: 2s after---

解析:如果Service所在的进程,在执行了onStartCommand方法后,被清理了,则结果和START_STICKY一样,也会重新创建此Service并调用onCreate和onStartCommand方法。接着系统会自动重启该服务,将Intent的值传入,并且startId=1。说明该int可以保留上次的startId与intent。

(4)START_STICKY_COMPATIBILITY(STICKY的兼容版本):不保证服务被kill后一定能重启

TestService: onCreate---
TestService: onStartCommand---startId = 1 and intent = Intent { cmp=com.seniorlibs.lifecycle/.service.TestService }
TestService: 2s after---
// 异常:com.seniorlibs.lifecycle E/AndroidRuntime: FATAL EXCEPTION: main
TestService: onCreate---
TestService: 2s after---

解析:如果Service所在的进程,在执行了onStartCommand方法后,被清理了,只调用onCreate方法,没有调用onStartCommand方法。

5 问题解答

5.1 Service与Activity怎么实现通信?

5.1.1 通过Binder实现通信(进程内通信)

(0)概要

(1)Activity调用bindService(Intent service, ServiceConnection conn, int flags)方法,绑定一个Service的继承类TestService;
(2)通过实例化ServiceConnection接口的onServiceConnected()方法,获取TestService中的Binder对象;
(3)通过此对象调用Binder继承类TestBinder中的方法将消息传给Service;
(4)如果想实现Service将消息传给Activity的,可以在TestBinder里面提供一个回调方法,在Activity中实现方法。

(1)在manifest.xml文件中注册service

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

(2)定义一个类继承Service

public class TestService extends Service {

    public static final String TAG = "TestService";
    private Handler sWork = new Handler();
    private TestBinder mBinder = new TestBinder();
    private OnTestListener mListener;

    @Override
    public void onCreate() {
        super.onCreate();
        LogUtils.d(TAG, "onCreate---");

        // 制造异常,模拟系统杀死该进程
//        sWork.postDelayed(new Runnable() {
//            @Override
//            public void run() {
//                LogUtils.d(TAG, 2000 / 1000 + "s after---");
//                // 故意制造异常,使该进程挂掉
//                Integer.parseInt("ok");
//            }
//        }, 5000);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtils.d(TAG, "onStartCommand---startId = " + startId + " and intent = " + intent);
        // 实验中,可轮换这几个值测试 :START_NOT_STICKY | START_STICKY | START_STICKY_COMPATIBILITY | START_REDELIVER_INTENT;
//        return super.onStartCommand(intent, flags, startId);
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        LogUtils.d(TAG, "onBind---");
        // 返回mBinder对象
        return mBinder;
    }

    @Override
    public void onDestroy() {
        LogUtils.d(TAG, "onDestroy---");
        super.onDestroy();
    }

    /**
     * 新建一个继承自Binder的类TestBinder
     */
    public class TestBinder extends Binder {
        // Activity通过Binder来调用TestBinder的方法将消息传给Service
        public void sendMsgToService(String msg) {
            LogUtils.d(TAG, "收到来自Activity的消息: " + msg);
            // 并回调mListener.onTest告诉Activity已收到消息
            if (mListener != null) {
                mListener.onTest("hi, activity");
            }
        }

        // TestBinder里面提供一个注册回调的方法
        public void setOnTestListener(OnTestListener listener) {
            mListener = listener;
        }
    }

    /**
     * 自定义一个回调接口
     */
    public interface OnTestListener {
        void onTest(String str);
    }
}

(3)使用context的bindService(Intent,ServiceConnection,int)方法启动service

public class TestActivity extends Activity implements View.OnClickListener {

    public static final String TAG = TestService.TAG + " + TestActivity";

    private Intent mStartIntent;
    private TestService.TestBinder mBinder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);

        initView();
    }

    private void initView() {
        findViewById(R.id.btn_start_service).setOnClickListener(this);
        findViewById(R.id.btn_bind_service).setOnClickListener(this);
        findViewById(R.id.btn_stop_service).setOnClickListener(this);
        findViewById(R.id.btn_unbind_service).setOnClickListener(this);
        findViewById(R.id.btn_send_msg_to_service).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_start_service:
                // 开启服务
                mStartIntent = new Intent(this, TestService.class);
                startService(mStartIntent);
                break;

            case R.id.btn_bind_service:
                // 绑定服务
                Intent bindIntent = new Intent(this, TestService.class);
                // 参数1:Intent意图
                // 参数2:是一个接口,通过这个接口接收服务开启或者停止的消息,并且这个参数不能为null
                // 参数3:开启服务时的操作,BIND_AUTO_CREATE代表自动创建service
                bindService(bindIntent, mConnection, BIND_AUTO_CREATE);
                break;

            case R.id.btn_stop_service:
                // 结束服务
                stopService(mStartIntent);
                break;
                
            case R.id.btn_unbind_service:
                // 结束连接服务
                unbindService(mConnection);
                break;

            case R.id.btn_send_msg_to_service:
                // 发送消息给Service
                if (mBinder != null) {
                    mBinder.sendMsgToService("hi, service");
                } else {
                    LogUtils.e(TAG, "请先绑定服务,再执行发送消息给Service");
                }
                break;

            default:
                break;
        }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 只有当TestService的onBind方法返回值不为null时,才会被调用
            LogUtils.d(TAG, "onServiceConnected");
            
            if (service != null) {
                // 取得Service里的binder对象
                mBinder = (TestService.TestBinder) service;
                // 注册自定义回调
                mBinder.setOnTestListener(new TestService.OnTestListener() {
                    @Override
                    public void onTest(String str) {
                        LogUtils.d(TAG, "收到来自Service的消息: "+str);
                    }
                });
            }
        }

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 结束连接服务
        unbindService(mConnection);
    }
}

(4)不使用时,调用unbindService(ServiceConnection)方法停止该服务

@Override
    protected void onDestroy() {
        super.onDestroy();
        // 结束连接服务
        unbindService(mConnection);
    }

(5)结果打印

TestService: 收到来自Activity的消息: hi, service
TestService + TestActivity: 收到来自Service的消息: hi, activity
5.1.2 通过Broadcast实现通信

(1)概要:Service向Activity发送消息,可以使用广播,当然Activity要注册相应的接收器。比如Service要向多个Activity发送同样的消息的话,用这种方法就更好。

(2)参考链接:Android Service与Activity之间通信的几种方式

5.2 Service重要的方法运行在哪个线程?

(1)Service的onCreate、onStartCommand、onDestory等全部生命周期方法都运行在UI线程
(2)ServiceConnection里面的回调方法也是运行在UI线程;
(3)bindService后,onServiceConnected调用时机是在onCreate和onBind之后被调用 ;
(4)千万不能在Service的生命周期方法中做非常耗时的操作,否则会引起主线程卡顿,严重时还会引起ANR
(5)参考链接:【源码解析】Service几个重要的方法运行在哪个线程

5.3 Service里面可以弹出dialog或Toast吗?

(1)Toast通常使用Activity和Application的context,也可以使用Service、ContentProvider和BroadcastReceiver的context。但是在IntentService的onHandleIntent()不能使用,因为其在子线程中。
(2)Dialog只能在Activity中使用,其他组件使用会抛出异常。也有方法可以实现,如下(加个权限,在manifest中添加此权限以弹出dialog)。

<uses-permission Android:name="android.permission.SYSTEM_ALERT_WINDOW" />

dialog.getWindow().setType((WindowManager.LayoutParams.TYPE_SYSTEM_ALERT));
Builder builder = new AlertDialog.Builder(this);
builder.setMessage("是否重启服务");
builder.setNegativeButton("取消", new OnClickListense() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
           // to do
    }
});
builder.setPositiveButton("确定", new OnClickListense() {
    @Override
    public void onClick(DialogInterface dialog, int which) {
           // to do
    }
});
final AlertDialog dialog = buidler.create();
//在dialog show前添加此代码,表示该dialog属于系统dialog。
dialog.getWindow().setType((WindowManager.LayoutParams.TYPE_SYSTEM_ALERT));
new Thread() {
     public void run() {
            SystemClock.sleep(2000);
            hanlder.post(new Runnable() {
                    @Override
                    public void run() {
                          dialog.show();
                    }
            });
     };
}.start();

参考来源于:developerzjy/ServiceDemo
(3)参考链接
Android Context 上下文 你必须知道的一切

5.4 为什么开启Service去创建子线程而不是Activity中?

(1)场景:①若直接在Activity中新开一条线程来做耗时操作,当该Activity退出到桌面或其他情况时将成为一个后台进程。②若在Service中新启动线程,则此时Android会依据进程中当前活跃组件重要程度,将其判断为服务进程,优先级比①高。
(2)因为服务进程的优先级比后台进程的优先级高,所以对于一个需要启动一个长时间操作的activity来说,开启service去创建子线程比Activity中创建子线程更好,尤其是对于操作将很可能超出activity的持续时间时。
(3)比如要上传一个图片文件,应该开启一个service来进行上传工作,这样在用户离开activity时工作仍在进行。使用service将会保证操作至少有服务进程的优先级。

5.5 如何保证Service在后台不被杀死,进程保活?

在我的另一篇博客详细解析:Android进阶之进程优先级及提高优先级的方法(进程保活)

5.6 Service与IntentService的区别?

(1)IntentService,它是继承于Service并处理异步耗时请求的一个类。
(2)可以通过startService(Intent)方式来提交请求,和启动传统Service一样;
(3)该Service会在需要的时候创建;
(4)当完成所有的任务以后会自动关闭;
(5)可以启动多次,而每一个耗时操作会以工作队列的方式在onHandleIntent()中执行,每次只会执行一个工作线程;
(6)解决了传统的Service中处理完耗时操作忘记停止并销毁Service的问题。
(7)在我的另一篇博客详细解析:Android基础之深入理解IntentService

7 参考链接

Android:Service生命周期最全面解析

Android中startService和bindService的区别 - 简书
【推荐理由】详细全面地回答了上面的问题。

Service: onStartCommand 诡异的返回值
【推荐理由】通过实例来演示onStartCommand那诡异的返回值。

【源码解析】Service几个重要的方法运行在哪个线程

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值