星球作业(第二期) Service理解

Service的启动方式

Service(服务)是一种Android提供的可在后台运行,不具有用户交互界面的应用组件。当用户需要进行长期的或是耗时的操作,不需要用户感知时,可以使用开启服务的方式来让程序在后台运行。Service创建之后首先要在manifest中注册:

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        //注册Service
        <service android:name=".servicetest.NormalService" />
    </application>

在manifest中注册时,可以为Service附上不同的权限:exported属性可以控制是否能够隐式开启服务;process可以自定义服务启动时运行的进程(默认为当前进程),等等。

Service有两种启动方式:start和bind。根据不同的启动方式,Service会调用不同的回调函数,具有不同的特性。


start方式启动

Intent intent = new Intent(this, NormalService.class);
startService(intent);

和显示启动Activity的方式类似,可以用这种方式显示的启动一个服务。start方式启动后,即使Activity被销毁,Service依然可以存活。Service第一次被启动时会调用onCreate,start方式还会调用onStartCommand函数:

    /**
     * @param intent The Intent supplied to {@link android.content.Context#startService}, 
     * as given.  This may be null if the service is being restarted after
     * its process has gone away, and it had previously returned anything
     * except {@link #START_STICKY_COMPATIBILITY}.
     * @param flags Additional data about this start request.
     * @param startId A unique integer representing this specific request to 
     * start.  Use with {@link #stopSelfResult(int)}.
     * 
     * @return The return value indicates what semantics the system should
     * use for the service's current started state.  It may be one of the
     * constants associated with the {@link #START_CONTINUATION_MASK} bits.
     * 
     * @see #stopSelfResult(int)
     */
    public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
        onStart(intent, startId);
        return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
    }

我们来看一下这几个参数:
Intent intent: 启动时的intent对象,可以将数据从Activity经由intent对象传输过来
int flags: 启动时是否有额外数据。可选值有0, START_FLAG_REDELIVERY, START_FLAG_RETRY三种。其中0代表无额外数据;START_FLAG_REDELIVERY表示前一次启动的服务在调用stopSelf之前就已经被kill,在这种情况下会重新建立服务,并携带intent的值;START_FLAG_RETRY表示前一次启动服务时没有到达onStartCommand函数或是调用的onStartCommand并没有返回任何值,尝试重新调用。

    /**
     * This flag is set in {@link #onStartCommand} if the Intent is a
     * re-delivery of a previously delivered intent, because the service
     * had previously returned {@link #START_REDELIVER_INTENT} but had been
     * killed before calling {@link #stopSelf(int)} for that Intent.
     */
    public static final int START_FLAG_REDELIVERY = 0x0001;

    /**
     * This flag is set in {@link #onStartCommand} if the Intent is a
     * retry because the original attempt never got to or returned from
     * {@link #onStartCommand(Intent, int, int)}.
     */
    public static final int START_FLAG_RETRY = 0x0002;

int startId: 本次启动的标示ID,在stopSelfResult(int)被使用,准确的定位到将要关闭的服务

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

START_STICKY: 如果Service因为内存不足而被kill时,当内存有空闲时,会重新尝试创建Service。此时创建Service时调用的onStartCommand方法中的intent参数为null(或是有挂起的pendingIntent)。此种返回值适用于不需要传值,只需要一直运行在后台等待任务的服务。
START_NOT_STICKY: 如果Service因为内存不足而被kill时,当内存有空余时,也不会重新尝试创建Service,只能通过代码调用自行重启服务。
START_REDELIVER_INTENT: 当Service因为内存不足而被kill时,会重新启动服务,并将最后一次传递的intent(携带数据)传输过去。此种返回值适用于需要立刻恢复作业进度的服务。


bind方式启动

Intent intent = new Intent(this, NormalService.class);
bindService(intent, conn, Service.BIND_AUTO_CREATE);

bind启动方式启动服务,类似于启动一个客户端-服务端中的服务端。组件绑定了服务之后,可以向服务发送请求;同时服务也可以将请求的数据返还给组件。这里涉及到组件和服务如果不在统一进程内,需要进行IPC操作(本文不含IPC讲解,想要看的大佬们可以点击参考文献【1】)。而组件和服务的交互,通过的就是IBinder对象。我们通过绑定的调用函数来一点点分析。
上文中bindService中的第二个参数conn为ServiceConnection接口,实现如下:

private ServiceConnection conn = new ServiceConnection() {

        /**
         * Called when a connection to the Service has been established, with
         * the {@link android.os.IBinder} of the communication channel to the
         * Service.
         *
         * 当一个服务连接建立的时候调用,Service#onBind返回的IBinder对象作为双方通信的通道
         *
         * @param name The concrete component name of the service that has
         * been connected.
         * ComponentName 记录组件(四大组件)的类信息,如包名,class名等
         * @param iBinder The IBinder of the Service's communication channel,
         * which you can now make calls on.
         * IBinder Service#onBind返回,这里返回的是自定义的扩展Binder类,
         *    客户端可使用该对象对Service进行操作或数据传输
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder iBinder) {
            NormalService.NormalBinder binder = (NormalService.NormalBinder) iBinder;
            service = binder.getService();
        }

        /**
         * Called when a connection to the Service has been lost.  This typically
         * happens when the process hosting the service has crashed or been killed.
         * This does <em>not</em> remove the ServiceConnection itself -- this
         * binding to the service will remain active, and you will receive a call
         * to {@link #onServiceConnected} when the Service is next running.
         *
         * 当服务的连接意外中断时才会被调用(正常退出时不会调用)
         * 一般发生在服务的宿主进程崩溃或被kill的情况下
         * 但这并不会将ServiceConnection移除,当服务再次运行时会再次调用onServiceConnected
         *
         * @param name The concrete component name of the service whose
         * connection has been lost.
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {
            service = null;
        }
    };

在onServiceConnected中返回的IBinder对象,是Service#onBind中返回的,作为组件和服务交互的纽带。返回的IBinder是自定义的扩展Binder类,可以获取到Service对象。而flags则是指定绑定时是否自动创建Service。0代表不自动创建、BIND_AUTO_CREATE则代表自动创建。

对应Service中的实现如下:

public class NormalService extends Service {
    private static final String TAG = "NormalService";
    private NormalBinder binder = new NormalBinder();

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

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "NormalService -> onBind");
        //返回binder
        return binder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "NormalService -> onUnbind");
        //解除绑定时调用
        return super.onUnbind(intent);
    }

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

    //扩展Binder,提供获取Service对象的方法
    class NormalBinder extends Binder {
        NormalService getService() {
            return NormalService.this;
        }
    }
}

解除绑定:unbindService(conn);


混合启动

一个Service可以通过start启动,也可以通过bind启动,也可以同时使用两种启动方式。当一个Service第一次启动时,会先调用onCreate;如果先start再bind,函数调用顺序即为onCreate -> onStartCommand -> onBind;反之则为onCreate -> onBind -> onStartCommand。

当使用两种方式启动的Service需要销毁时,需要调用unbindService(conn)和stopService(intent)后,才会调用Service#onDestroy。其中unbindService会先调用onUnbind。


Service的运行环境

当启动一个Service时,服务会默认运行在当前宿主进程的主线程,不会创建自己的线程。所以在服务中如果需要进行耗时操作需要另开线程,否则会阻塞主线程。即,Service的生命周期,onBind等方法都运行在主线程。


参考资料

1.zejian_ - 关于Android Service真正的完全详解,你需要知道的一切
2.Android开发艺术探索-Service解析

猜你喜欢

转载自blog.csdn.net/asd501823206/article/details/81567588