Android四大组件之Service

Service

Service(服务)是一个可以在后台执行长时间运行操作而不使用用户界面的应用组件。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。 例如,服务可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。

一、创建Service

直接定义一个类,继承自Service,和activity一样,也要在AndroidManifest.xml中配置该Service。代码如下:

public class MyService extends Service{

    //绑定服务时才会调用,必须要实现的方法
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

AndroidManifest.xml中配置如下:

 <application
          <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!--这里需要注意的是,service的配置也是在application标签中-->
        <service android:name=".MyService"/>
</application>

二、Service的两种开启方式

1.startService() 启动服务。
开启服务后,服务就会长期的后台运行,即使调用者退出了,服务仍然在后台继续运行,服务和调用者没有什么关系,调用者是不可以访问服务里面的方法。
2.bindService() 绑定服务。
服务开启后,生命周期与调用者相关联,调用者挂了,服务也会跟着挂掉,调用者和服务绑定在一起,调用者可以间接的调用到服务里面的方法。

三、Service的生命周期

接下来分别从两种开启方式生命周期说起,官方为我们提供了Service生命周期示意图:
Service两种形式的生命周期示意图

1.startService() 启动服务
下面我们通过代码来分析需要重写的回调方法有哪些?

public class MyService extends Service{

    // 绑定服务时才会调用,必须要实现的方法
    @Override
    public IBinder onBind(Intent intent) {
        Log.e("TAG","-MyService---onBind()----");
        return null;
    }
    //首次创建服务时,系统将调用此方法来执行一次性设置程序,如果服务已在运行,则不会调用此方法。该方法只被调用一次
    @Override
    public void onCreate() {
        super.onCreate();
        Log.e("MyService","-MyService---onCreate()----");
    }
   //每次通过startService()方法启动Service时都会被回调。
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e("MyService","-MyService---onStartCommand()---intent:"
                + intent.getStringExtra("data"+"--flags="+flags+"-----startId"+startId));  
      return super.onStartCommand(intent, flags, startId);
    }
   //服务销毁时的回调
    @Override
    public void onDestroy() {
        Log.e("MyService","-MyService---onDestroy()----");
        super.onDestroy();
    }
}

我们可以将Service看出一个没有前台界面的Activity,看看如何启动一个Service服务。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.e("TAG", "---MainActivity--onCreate-----");

        //启动服务
        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyService.class);
                intent.putExtra("data", "测试传递数据");
                //我们启动一个Activity的时候是startActivity();
                //在这里我们是启动一个service
                startService(intent);
            }
        });
        //停止服务
        findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setClass(MainActivity.this, MyService.class);
                stopService(intent);
            }
        });
    }
    //生命周期方法
    ........
}

接下来,我们看下运行结果

TAG: —MainActivity–onCreate-----
TAG: —MainActivity–onStart-----
TAG: —MainActivity–onResume-----
TAG: -MyService—onCreate()----
TAG: -MyService—onStartCommand()—intent=测试传递数据—flags=0-----startId=1
TAG: -MyService—onStartCommand()—intent=测试传递数据—flags=0-----startId=2
销毁MainActivity
TAG: —MainActivity–onPause-----
TAG: —MainActivity–onStop-----
TAG: —MainActivity–onDestroy-----
重新开启MainActivity
TAG: —MainActivity–onCreate-----
TAG: —MainActivity–onStart-----
TAG: —MainActivity–onResume-----
销毁service
TAG: -MyService—onDestroy()----

当我们第一次启动服务时执行onCreate()、onStartCommand方法,当我们再次启动服务的时候,是没有执行onCreate()方法的,**只有当Service没有创建的时候才会回调这个方法,一旦Service被创建后再次startService启动Service也不会再次回调这个方法。**当MainActivity销毁时,我们发现MyService中并没有执行onDestroy(),只有当我们再次启动MainActivity,点击了停止服务后,MyService服务才被销毁了,即使调用者退出了,服务仍然在后台继续运行。
关于这几个方法的说明如下:

  • onBind()
    当另一个组件想通过调用 bindService() 与服务绑定时,系统将调用此方法。在此方法的实现中,必须返回 一个IBinder 接口的实现类,供客户端用来与服务进行通信。无论是启动状态还是绑定状态,此方法必须重写,但在启动状态的情况下直接返回 null。
  • onCreate()
    首次创建服务时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或onBind() 之前)。如果服务已在运行,则不会调用此方法,该方法只调用一次
  • onStartCommand(Intent intent, int flags, int startId)
    当另一个组件(如 Activity)通过调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 如果自己实现此方法,则需要在服务工作完成后,通过调用 stopSelf() 或 stopService() 来停止服务。(在绑定状态下,无需实现此方法。)
    我们看下这三个参数:
  1. intent :启动时,启动组件传递过来的Intent,如Activity可利用Intent封装所需要的参数并传递给Service
  2. flags:表示启动请求时是否有额外数据
    可选值有0,START_FLAG_REDELIVERY,START_FLAG_RETRY
    0 代表没有;
    START_FLAG_REDELIVERY 这个值代表了onStartCommand方法的返回值为
    START_REDELIVER_INTENT,而且在上一次服务被杀死前会去调用stopSelf方法停止服务。其中START_REDELIVER_INTENT意味着当Service因内存不足而被系统kill后,则会重建服务,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),此时Intent时有值的。
    START_FLAG_RETRY该flag代表当onStartCommand调用后一直没有返回值时,会尝试重新去调用onStartCommand()。
  3. 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。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
  • onDestroy()
    当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等,这是服务接收的最后一个调用。

由于每次启动服务(调用startService)时,onStartCommand方法都会被调用,因此我们可以通过该方法使用Intent给Service传递所需要的参数,然后在onStartCommand方法中处理的事件,最后根据需求选择不同的Flag返回值,以达到对程序更友好的控制。

2.bindService() 绑定服务

前面了解了启动和停止服务的方法,虽然服务是在活动(Activity)里启动的,但在启动了服务后,活动与服务基本上就没有什么关系了。我们调用了startService()方法来启东MyService这个服务,然后MyService的OnCreate()和onStartCommand()方法就会得到执行,之后服务会一直处于运行状态,但是具体运行的是什么逻辑,活动就控制不了了。那么我们该如何在活动中去控制Service呢,这就需要借助onBind()方法了。
比如,我们希望在MyService里提供一个下载功能,然后在活动中可以决定何时开始下载,以及查看下载进度,创建一个专门的Binder对象来对下载功能进行模拟:

public class MyService extends Service {

    private DownloadBinder mBinder = new DownloadBinder();

    // 绑定服务时才会调用,必须要实现的方法
    @Override
    public IBinder onBind(Intent intent) {
        Log.e("TAG", "-MyService---onBind()----");
        return mBinder;
    }

    //首次创建服务时,系统将调用此方法来执行一次性设置程序,如果服务已在运行,则不会调用此方法。该方法只被调用一次
    @Override
    public void onCreate() {
        super.onCreate();
        Log.e("TAG", "-MyService---onCreate()----");
    }
    
    //每次通过startService()方法启动Service时都会被回调。
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.e("TAG", "-MyService---onStartCommand()---intent=" + intent.getStringExtra("data") + "---flags=" + flags + "-----startId=" + startId);
        return super.onStartCommand(intent, flags, startId);
    }

    //服务销毁时的回调
    @Override
    public void onDestroy() {
        Log.e("TAG", "-MyService---onDestroy()----");
        super.onDestroy();
    }


    class DownloadBinder extends Binder {
        public void startDownload() {
            Log.e("TAG", "开始下载......");
        }

        public int getProgress() {
            Log.e("TAG", "下载进度......");
            return 0;
        }
    }
}

在这里,我们创建一个DownloadBinder类,并继承自Binder,然后在其内部模拟开始下和查看下载进度的方法,分别打印下日志信息。在MyService中创建DownloadBinder 的实例,然后在onBind()方法中返回这个实例,这样MyService中的工作就全部完成了。
接下来我们在MainActivity中调用这个方法

public class MainActivity extends AppCompatActivity {
    private MyService.DownloadBinder downloadBinder;

    private ServiceConnection serviceConnection = new ServiceConnection() {
        /**
         * 系统会调用该方法以传递服务的 onBind() 方法返回的 IBinder。
         * 其中service便是服务端返回的IBinder实现类对象,
         * 通过该对象我们便可以调用获取LocalService实例对象,
         * 进而调用服务端的公共方法。
         * 而ComponentName是一个封装了组件(Activity, Service, BroadcastReceiver, or ContentProvider)信息的类,
         * 如包名,组件描述等信息,较少使用该参数。
         */
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            downloadBinder = (MyService.DownloadBinder) service;
            downloadBinder.startDownload();
            downloadBinder.getProgress();
        }
        /**
         * Android 系统会在与服务的连接意外中断时(例如当服务崩溃或被终止时)调用该方法。
         * 注意:当客户端取消绑定时,系统“绝对不会”调用该方法。
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

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

        Log.e("TAG", "---MainActivity--onCreate-----");

        //绑定服务
        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyService.class);
                bindService(intent, serviceConnection, BIND_AUTO_CREATE);
            }
        });
        //解绑服务
        findViewById(R.id.btn_stop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(serviceConnection);
            }
        });
    }
}

我们看下运行结果

TAG: -MyService—onCreate()----
-MyService—onBind()----
TAG: 开始下载…
下载进度…
TAG: -MyService—onDestroy()----

首先创建了一个ServiceConnection 的匿名类,在里面重写了onServiceConnected()方法和onServiceDisconnected()方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用。在onServiceConnected()方法中,通过向下转型得到了DownloadBinder实例,有了这个实例,我们就可以进行操作服务了。
在这里我们可以看到也是需要Intent对象来实现的,和启动服务不一样的是:启动服务时 startService(),而绑定服务的时候是bindService(),bindService()方法接收3个参数,第一个参数就是Intent对象,第二个参数就是ServiceConnection 的实例,第三个参数BIND_AUTO_CREATE 表示活动和服务进行绑定后自动创建服务。这会使得MyService中的OnCreate()方法得到执行,但是不会执行onStartCommand()方法。
我们想要解除活动和服务之间的绑定,就需要调用unbindService()方法,传入当前的ServiceConnection实例即可。
需要注意的是,任何一个服务在整个应用程序范围内都是通用多的,MyService 可以和任何一个活动进行绑定,绑定完成后它们都可以获取相同的DownloadBinder实例。

四、关于绑定服务的注意点

1.多个客户端可同时连接到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind() 方法来检索 IBinder。系统随后无需再次调用 onBind(),便可将同一 IBinder 传递至任何其他绑定的客户端。当最后一个客户端取消与服务的绑定时,系统会将服务销毁(除非 startService() 也启动了该服务)。
2.通常情况下我们应该在客户端生命周期(如Activity的生命周期)的引入 和退出时刻设置绑定和取消绑定操作,以便控制绑定状态下的Service,一般有以下两种情况:
(1)如果只需要在 Activity 可见时与服务交互,则应在 onStart() 期间绑定,在 onStop() 期间取消绑定。
(2)如果希望 Activity 在后台停止运行状态下仍可接收响应,则可在 onCreate() 期间绑定,在 onDestroy() 期间取消绑定。需要注意的是,这意味着 Activity 在其整个运行过程中(甚至包括后台运行期间)都需要使用服务,因此如果服务位于其他进程内,那么当提高该进程的权重时,系统很可能会终止该进程。
3.通常情况下(注意),切勿在 Activity 的 onResume() 和 onPause() 期间绑定和取消绑定,因为每一次生命周期转换都会发生这些回调,这样反复绑定与解绑是不合理的。此外,如果应用内的多个 Activity 绑定到同一服务,并且其中两个 Activity 之间发生了转换,则如果当前 Activity 在下一次绑定(恢复期间)之前取消绑定(暂停期间),系统可能会销毁服务并重建服务,因此服务的绑定不应该发生在 Activity 的 onResume() 和 onPause()中。
4.我们应该始终捕获 DeadObjectException DeadObjectException 异常,该异常是在连接中断时引发的,表示调用的对象已死亡,也就是Service对象已销毁,这是远程方法引发的唯一异常,DeadObjectException继承自RemoteException,因此我们也可以捕获RemoteException异常。
5.应用组件(客户端)可通过调用 bindService() 绑定到服务,Android 系统随后调用服务的 onBind() 方法,该方法返回用于与服务交互的 IBinder,而该绑定是异步执行的。

IntentService

一、IntentService与Service的区别

IntentService是继承并处理异步请求的一个类,在IntentService内有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统的Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要我们手动去控制或stopSelf()。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent回调方法中执行,并且,每次只会执行一个工作线程,执行完第一个再执行第二个,以此类推。

IntentService的源码

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public IntentService(String name) {
        super();
        mName = name;
    }

    /**
     * Sets intent redelivery preferences.  Usually called from the constructor
     * with your preferred semantics.
     *
     * <p>If enabled is true,
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_REDELIVER_INTENT}, so if this process dies before
     * {@link #onHandleIntent(Intent)} returns, the process will be restarted
     * and the intent redelivered.  If multiple Intents have been sent, only
     * the most recent one is guaranteed to be redelivered.
     *
     * <p>If enabled is false (the default),
     * {@link #onStartCommand(Intent, int, int)} will return
     * {@link Service#START_NOT_STICKY}, and if the process dies, the Intent
     * dies along with it.
     */
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }

    @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    /**
     * You should not override this method for your IntentService. Instead,
     * override {@link #onHandleIntent}, which the system calls when the IntentService
     * receives a start request.
     * @see android.app.Service#onStartCommand
     */
    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

    /**
     * Unless you provide binding for your service, you don't need to implement this
     * method, because the default implementation returns null.
     * @see android.app.Service#onBind
     */
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }

    /**
     * This method is invoked on the worker thread with a request to process.
     * Only one Intent is processed at a time, but the processing happens on a
     * worker thread that runs independently from other application logic.
     * So, if this code takes a long time, it will hold up other requests to
     * the same IntentService, but it will not hold up anything else.
     * When all requests have been handled, the IntentService stops itself,
     * so you should not call {@link #stopSelf}.
     *
     * @param intent The value passed to {@link
     *               android.content.Context#startService(Intent)}.
     *               This may be null if the service is being restarted after
     *               its process has gone away; see
     *               {@link android.app.Service#onStartCommand}
     *               for details.
     */
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

IntentService继承自Service,内部有一个HandlerThread对象。
    在onCreate的时候会创建一个HandlerThread对象,并启动线程。紧接着创建ServiceHandler对象,ServiceHandler继承自Handler,用来处理消息。ServiceHandler将获取HandlerThread的Looper就可以开始正常工作了。
    每启动一次onStart方法,就会把数消息和数据发给mServiceHandler,相当于发送了一次Message消息给HandlerThread的消息队列。mServiceHandler会把数据传个onHandleIntent方法,onHandleIntent是个抽象方法,需要在IntentService实现,所以每次onStart方法之后都会调用我们自己写的onHandleIntent方法去处理。处理完毕使用stopSelf通知HandlerThread已经处理完毕,HandlerThread继续观察消息队列,如果还有未执行玩的message则继续执行,否则结束。

IntentService的实现:

public class MyIntentService extends IntentService{

    /**
     * 无参构造方法 一定要实现此方法否则Service运行出错。
     *
     * 错误如下java.lang.RuntimeException: Unable to instantiate service com.monkey.servicedemo.MyIntentService:
     * java.lang.InstantiationException: java.lang.Class<com.monkey.servicedemo.MyIntentService> has no zero argument constructor
     */
    public MyIntentService() {
        super("MyIntentService");
    }

    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public MyIntentService(String name) {
        super(name);
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        Log.e("MyIntentService--", "onHandleIntent()");
        //耗时操作
        for(int i = 0; i < 3; i++){
            Log.e("onHandleIntent--",  i + "--" + Thread.currentThread().getName());
        }

    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.e("MyIntentService--", "onCreate()");
    }

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        Log.e("MyIntentService--", "onStartCommand()");
        return super.onStartCommand(intent, flags, startId);

    }

    @Override
    public void onDestroy() {
        Log.e("MyIntentService--", "onDestroy()");
        super.onDestroy();
    }
}

启动IntentService

  //启动前一定不要忘记注册
   <service android:name=".MyIntentService"/>

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

运行结果

E/MyIntentService–: onCreate()
E/MyIntentService–: onStartCommand()
E/MyIntentService–: onHandleIntent()
E/onHandleIntent–: 0–IntentService[MyIntentService]
1–IntentService[MyIntentService]
2–IntentService[MyIntentService]
E/MyIntentService–: onDestroy()

IntentService开启后,执行完onHandleIntent里面的任务就自动销毁结束,并不需要我们去主动的关闭。
总结:
1、启动 IntentService 是不需要新建线程。IntentService内部的HandlerThread 继承自 Thread,内部封装了 Looper,在这里新建线程并启动,所以启动 IntentService 不需要新建线程。
2、不建议通过 bindService() 启动 IntentService。IntentService 源码中的 onBind() 默认返回 null;不适合 bindService() 启动服务,如果你执意要 bindService() 来启动 IntentService,可能因为你想通过 Binder 或 Messenger 使得 IntentService 和 Activity 可以通信,这样那么 onHandleIntent() 不会被回调,相当于在你使用 Service 而不是 IntentService。
3、多次启动 IntentService 会顺序执行事件,停止服务后,后续的事件得不到执行。IntentService 中使用的 Handler、Looper、MessageQueue 机制把消息发送到线程中去执行的,所以多次启动 IntentService 不会重新创建新的服务和新的线程,只是把消息加入消息队列中等待执行,而如果服务停止,会清除消息队列中的消息,后续的事件得不到执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值