Service完全解析

Service完全解析


1.Service生命周期

两种启动Service的模式:
1. context.startService()
当我们首次使用startService启动一个服务时,系统会实例化一个Service实例,依次调用其onCreate和onStartCommand方法,然后进入运行状态,此后,如果再使用startService启动服务时,不再创建新的服务对象,系统会自动找到刚才创建的Service实例,调用其onStart方法;如果我们想要停掉一个服务,可使用stopService方法,此时onDestroy方法会被调用,需要注意的是,不管前面使用了多个次startService,只需一次stopService,即可停掉服务。
2. context.bindService()
在这种模式下,当调用者首次使用bindService绑定一个服务时,系统会实例化一个Service实例,并一次调用其onCreate方法和onBind方法,然后调用者就可以和服务进行交互了,此后,如果再次使用bindService绑定服务,系统不会创建新的Service实例,也不会再调用onBind方法;如果我们需要解除与这个服务的绑定,可使用unbindService方法,此时onUnbind方法和onDestroy方法会被调用。

两种模式有以下几点不同之处:startService模式下调用者与服务无必然联系,即使调用者结束了自己的生命周期,只要没有使用stopService方法停止这个服务,服务仍会运行;通常情况下,bindService模式下服务是与调用者生死与共的,在绑定结束之后,一旦调用者被销毁,服务也就立即终止,就像江湖上的一句话:不求同生,但愿同死。

以上两种模式的流程如下图所示:
生命周期

下面我们就结合实例来演示一下这两种模式的生命周期过程。我们新建一个名为service的项目,然后创建一个MyService的服务类,代码如下:

package com.scott.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {

    private static final String TAG = "MyService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate called.");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(TAG, "onStartCommand called.");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        Log.i(TAG, "onStart called.");
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind called.");
        return null;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.i(TAG, "onUnbind called.");
        return super.onUnbind(intent);
    }

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

然后在AndroidManifest.xml中配置服务信息:

<service android:name=".MyService">
            <intent-filter>
                 <action android:name="android.intent.action.MyService" />
                 <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>

如果服务只是在本应用中使用,大可以去掉< intent-filter>属性。

服务搭建完成之后,我们就来关注一下调用者MainActivity,它很简单,只有两个按钮,一个是启动服务,另一个是停止服务,我们来看一下他们的点击事件:

/**
     * 启动服务
     * @param view
     */
    public void start(View view) {
        Intent intent = new Intent(this, MyService.class);
        startService(intent);
    }

    /**
     * 停止服务
     * @param view
     */
    public void stop(View view) {
        Intent intent = new Intent(this, MyService.class);
        stopService(intent);
    }

接下来我们就先点击一次启动按钮,看看都发生了些什么。日志打印结果如下:这里写图片描述

当然我们觉得还不过瘾,再点击一次,我们会发现结果略有不同:
这里写图片描述

我们看到第二次点击时onCreate方法就不再被调用了,而是直接调用了onStartCommand方法(onStartCommand中又调用了onStart方法)。我们选择“Settings->Application s->Running services”就会发现我们刚刚启动的服务:
这里写图片描述

然后我们点击停止按钮,试图停止服务,我们发现如下现象:
这里写图片描述

我们会发现onDestroy方法被调用了,此时服务就停止运行了。我们再次查看“Running services”,就会发现MyService这个服务已全无踪迹。
在这个过程中,onBind方法和onUnbind方法始终没被调用,我们下面就让这两位show一下自己。

我们修改一下MainActivity的代码,使其可以可以以bindService的方式启动一个服务,代码如下:

private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //connected
            Log.i(TAG, "onServiceConnected called.");
        }

        /**
         *  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 not remove the ServiceConnection itself.
         *  this binding to the service will remain active,
         *  and you will receive a call to onServiceConnected when the Service is next running.
         */
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    /**
     * 绑定服务
     * @param view
     */
    public void bind(View view) {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    /**
     * 解除绑定
     * @param view
     */
    public void unbind(View view) {
        unbindService(conn);
    }

在使用bindService绑定服务时,我们需要一个ServiceConnection代表与服务的连接,它只有两个方法,onServiceConnected和onServiceDisconnected,前者是在操作者在连接一个服务成功时被调用,而后者是在服务崩溃或被杀死导致的连接中断时被调用,而如果我们自己解除绑定时则不会被调用,所以我们这里只研究onServiceConnected这个方法。
看样子是可以去绑定一个服务了,其实还不行,因为我们前面服务中的onBind方法返回值为null,这样是不行的,要想实现绑定操作,必须返回一个实现了IBinder接口类型的实例,该接口描述了与远程对象进行交互的抽象协议,有了它我们才能与服务进行交互。我们于是有了这样的代码:

@Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind called.");
        return new Binder() {};
    }

我们返回了一个Binder的实例,而这个Binder恰恰是实现了IBinder接口,所以这样就可以实现绑定服务的操作了,一起来演示一下。
先点击一下绑定按钮,我们会发现在MainActivity中打印日志如下:
这里写图片描述

是的,onServiceConnected方法被调用了,看来绑定连接已经成功了,看看MyService如何:
这里写图片描述

onCreate方法和onBind方法被调用了,此时服务已进入运行阶段,如果再次点击绑定按钮,onCreate和onBinder并不会再次被调用,这个过程中它们仅被调用一次。
然后点击解除绑定按钮,我们会发现MyService打印如下:
这里写图片描述

可以看到onUnbind方法和onDestroy方法被调用了,此时MyService已被销毁,整个生命周期结束。
另一方面,当我们退出MainActivity时,服务也会随之而结束,从这一点上看,MyService可以说是誓死追随着MainActivity。
需要注意的是,在连接中断状态再去做解除绑定操作会引起一个异常,在MainActivity销毁之前没有进行解除绑定也会导致后台出现异常信息,此时我们就要想办法确保不会出现此类情况,可以这样做:

private boolean binded;

    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        binded = true;
    }

    /**
     * 解除绑定
     * @param view
     */
    public void unbind(View view) {
        unbindService();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService();
    }

    /**
     * 解除服务绑定
     */
    private void unbindService() {
        if (binded) {
            unbindService(conn);
            binded = false;
        }
    }

以上就是bindService的生命周期,正如我们上面讲的一样,使用bindService启动服务后调用者和服务绑定到了一起,当调用者被销毁,服务也立即结终止。
通常情况下是这样的,不过也有特殊情况。当startService和bindService在同一场合下使用时,就会出现稍微不同的现象。
如果我们先以startService方式启动服务,然后再用bindService绑定到这个服务,之后使用unbindService解除绑定,此时服务并不会因此而终止,而是继续运行,直到我们使用stopService来停止这个服务。下面我们再修改一下代码以验证这个过程。MyService保持不变,我们只需修改一下MainActivity。MainActivity最新代码如下:

package com.scott.service;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.View;

public class MainActivity extends Activity {

    private static final String TAG = "MainActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG, "onServiceConnected called.");
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    /**
     * 启动服务
     * @param view
     */
    public void start(View view) {
        Intent intent = new Intent(this, MyService.class);
        startService(intent);
    }

    /**
     * 绑定服务
     * @param view
     */
    public void bind(View view) {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    /**
     * 解除绑定
     * @param view
     */
    public void unbind(View view) {
        unbindService(conn);
    }

    /**
     * 停止服务
     * @param view
     */
    public void stop(View view) {
        Intent intent = new Intent(this, MyService.class);
        stopService(intent);
    }
}

在MainActivity中包含了四个按钮事件,分别是startService、bindService、unbindService和stopService,我们逐一地按下,看看都发生了什么。
首先按下启动服务的按钮,MyService打印如下:
这里写图片描述

恩,意料之中。然后我们再按下绑定服务的按钮,MyService打印如下:
这里写图片描述
此时,只有onBind被调用,之后两者就绑定成功。我们再按下解除绑定的按钮,MyService打印如下:
这里写图片描述

此时,onUnbind方法方法被调用,注意,此时MyService并没有因解除绑定而终止,而是继续运行。也许我们心里会问,如果多次按下绑定服务的按钮或重复以上两个步骤,结果如何呢?答案是onBind和onUnbind都不会再被调用了。看不到onBind被调用,是不是没有绑定成功啊,我们来看一下MainActivity打印信息:
这里写图片描述

重复按下绑定按钮,几次都绑定成功了。最后我们按下停止服务的按钮,MyService打印如下:
这里写图片描述

此时,onDestroy被调用了,此时MyService停止了运行,整个生命周期结束。
以上就是关于MyService生命周期的讲解,下面我们来介绍一下如何与服务进行通信。与服务之间的通信可以分为两种,进程内的通信和进程间的通信,前者调用者和服务在同一应用进程内,而后者是分布在不同应用进程中的。


2.进程内与服务通信

进程内与服务通信实际上就是通过bindService的方式与服务绑定,获取到通信中介Binder实例,然后通过调用这个实例的方法,完成对服务的各种操作。我们上面也介绍了不少关于bindService的内容,下面我们就针对实际需求对代码做改动。首先是MyService,代码如下:

package com.scott.service;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;

public class MyService extends Service {

    private static final String TAG = "MyService";

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind called.");
        return new MyBinder();
    }

    /**
     * 绑定对象
     * @author user
     *
     */
    public class MyBinder extends Binder {

        /**
         * 问候
         * @param name
         */
        public void greet(String name) {
            Log.i(TAG, "hello, " + name);
        }
    }
}

我们创建了一个MyBinder的内部类,定义了一个greet方法,在onBind方法中就将这个MyBinder的实例返回,只要调用者获取到这个实例,就可以像拿着游戏手柄一样对服务进行操作。我们来看一下调用者的代码吧,MainActivity代码如下:

package com.scott.service;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;

public class MainActivity extends Activity {

    /**
     * 绑定对象实例
     */
    private MyService.MyBinder binder;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }

    private ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            binder = (MyService.MyBinder) service;  //获取其实例
            binder.greet("scott");                  //调用其方法
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

    /**
     * 绑定服务
     * @param view
     */
    public void bind(View view) {
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, conn, Context.BIND_AUTO_CREATE);
    }

    /**
     * 解除绑定
     * @param view
     */
    public void unbind(View view) {
        unbindService(conn);
    }
}

在上面的代码中,我们是在绑定服务成功时将IBinder类型的service参数强转为MyService.MyBinder类型,获取绑定中介实例,然后调用其greet方法。
操作一下,看看效果如何。先点击绑定服务的按钮,MyService打印如下:
这里写图片描述

需要注意的是,与服务绑定是一个异步的过程,也就是说,在这一刻我们绑定服务,下一刻我们去操作binder对象,也许它还为null,这就容易引起空指针异常,正确的做法是把这些操作放到绑定成功之后,确保万无一失。
以上就是进程内通信的内容。


3.Service和Thread的关系

Service和Thread到底有什么关系呢?什么时候应该用Service,什么时候又应该用Thread?答案可能会有点让你吃惊,因为Service和Thread之间没有任何关系!

之所以有不少人会把它们联系起来,主要就是因为Service的后台概念。Thread我们大家都知道,是用于开启一个子线程,在这里去执行一些耗时操作就不会阻塞主线程的运行。而Service我们最初理解的时候,总会觉得它是用来处理一些后台任务的,一些比较耗时的操作也可以放在这里运行,这就会让人产生混淆了。但是,如果我告诉你Service其实是运行在主线程里的,你还会觉得它和Thread有什么关系吗?让我们看一下这个残酷的事实吧。
在MainActivity的onCreate()方法里加入一行打印当前线程id的语句:

Log.d("MyService", "MainActivity thread id is " + Thread.currentThread().getId());  

然后在MyService的onCreate()方法里也加入一行打印当前线程id的语句:

Log.d("MyService", "MyService thread id is " + Thread.currentThread().getId()); 

现在重新运行一下程序,并点击Start Service按钮,会看到如下打印日志的结果竟然是用一个线程id。

可以看出,它们的线程id完全是一样的,由此证实了Service确实是运行在主线程里的,也就是说如果你在Service里编写了非常耗时的代码,程序必定会出现ANR的。

那我要Service又有何用呢?其实大家不要把后台和子线程联系在一起就行了,这是两个完全不同的概念。Android的后台就是指,它的运行是完全不依赖UI的。即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行。比如说一些应用程序,始终需要与服务器之间始终保持着心跳连接,就可以使用Service来实现。你可能又会问,前面不是刚刚验证过Service是运行在主线程里的么?在这里一直执行着心跳连接,难道就不会阻塞主线程的运行吗?当然会,但是我们可以在Service中再创建一个子线程,然后在这里去处理耗时逻辑就没问题了。

那么,既然在Service里也要创建一个子线程,那为什么不直接在Activity里创建呢?这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。


4.前台Service和普通Service

Service几乎都是在后台运行的,一直以来它都是默默地做着辛苦的工作。但是Service的系统优先级还是比较低的,当系统出现内存不足情况时,就有可能会回收掉正在后台运行的Service。如果你希望Service可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。当然有时候你也可能不仅仅是为了防止Service被回收才使用前台Service,有些项目由于特殊的需求会要求必须使用前台Service,比如说墨迹天气,它的Service在后台更新天气数据的同时,还会在系统状态栏一直显示当前天气的信息,如下图所示:
这里写图片描述

那么我们就来看一下如何才能创建一个前台Service吧,其实并不复杂,修改MyService中的代码,如下所示:

public class MyService extends Service {  

    public static final String TAG = "MyService";  

    private MyBinder mBinder = new MyBinder();  

    @Override  
    public void onCreate() {  
        super.onCreate();  
        Notification notification = new Notification(R.drawable.ic_launcher,  
                "有通知到来", System.currentTimeMillis());  
        Intent notificationIntent = new Intent(this, MainActivity.class);  
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,  
                notificationIntent, 0);  
        notification.setLatestEventInfo(this, "这是通知的标题", "这是通知的内容",  
                pendingIntent);  
        startForeground(1, notification);  
        Log.d(TAG, "onCreate() executed");  
    }  

    .........  

}  

这里只是修改了MyService中onCreate()方法的代码。可以看到,我们首先创建了一个Notification对象,然后调用了它的setLatestEventInfo()方法来为通知初始化布局和数据,并在这里设置了点击通知后就打开MainActivity。然后调用startForeground()方法就可以让MyService变成一个前台Service,并会将通知的图片显示出来。
现在重新运行一下程序,并点击Start Service或Bind Service按钮,MyService就会以前台Service的模式启动了,并且在系统状态栏会弹出一个通栏图标,下拉状态栏后可以看到通知的详细内容,如下图所示。
这里写图片描述


5.IntentService

IntentService是Service类的子类,用来处理异步请求。客户端可以通过startService(Intent)方法传递请求给IntentService。IntentService在onCreate()函数中通过HandlerThread单独开启一个线程来处理所有Intent请求对象(通过startService的方式发送过来的)所对应的任务,这样以免事务处理阻塞主线程。执行完所一个Intent请求对象所对应的工作之后,如果没有新的Intent请求达到,则自动停止Service;否则执行下一个Intent请求所对应的任务。
IntentService在处理事务时,还是采用的Handler方式,创建一个名叫ServiceHandler的内部Handler,并把它直接绑定到HandlerThread所对应的子线程。 ServiceHandler把处理一个intent所对应的事务都封装到叫做onHandleIntent的虚函数;因此我们直接实现虚函数onHandleIntent,再在里面根据Intent的不同进行不同的事务处理就可以了。
另外,IntentService默认实现了Onbind()方法,返回值为null。
  使用IntentService需要两个步骤:
  1、写构造函数
  2、实现虚函数onHandleIntent,并在里面根据Intent的不同进行不同的事务处理就可以了。
好处:处理异步请求的时候可以减少写代码的工作量,比较轻松地实现项目的需求
注意:IntentService的构造函数一定是参数为空的构造函数,然后再在其中调用super(“name”)这种形式的构造函数。
因为Service的实例化是系统来完成的,而且系统是用参数为空的构造函数来实例化Service的。

IntentService有何优点呢:
- 普通的service ,默认运行在ui main 主线程
- 这是带有异步处理的service类,
- 异步处理的方法 OnHandleIntent()
- OnHandleIntent() 处理耗时的操作
- Android的进程处理器现在会尽可能的不kill掉你

package com.lenovo.robin.test;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;

public class MyIntentService extends IntentService {
final static String TAG="robin";
 public MyIntentService() {
  super("com.lenovo.robin.test.MyIntentService");
  Log.i(TAG,this+" is constructed");
 }
 @Override
 protected void onHandleIntent(Intent arg0) {
  Log.i(TAG,"begin onHandleIntent() in "+this);
  try {
   Thread.sleep(10*1000);
  } catch (InterruptedException e) {
     e.printStackTrace();
  }
  Log.i(TAG,"end onHandleIntent() in "+this);
 }
 public void onDestroy()
 {
  super.onDestroy();
  Log.i(TAG,this+" is destroy");
 }
}

启动MyIntentServic的代码片段

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

AndroidManifest.xml文件代码片段:

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

运行结果:

运行结果
09-14 22:23:34.730: I/robin(30943): com.lenovo.robin.test.MyIntentService@40541370 is constructed
09-14 22:23:34.730: I/robin(30943): begin onHandleIntent() in com.lenovo.robin.test.MyIntentService@40541370
09-14 22:23:44.730: I/robin(30943): end onHandleIntent() in com.lenovo.robin.test.MyIntentService@40541370
09-14 22:23:44.730: I/robin(30943): begin onHandleIntent() in com.lenovo.robin.test.MyIntentService@40541370
09-14 22:23:54.740: I/robin(30943): end onHandleIntent() in com.lenovo.robin.test.MyIntentService@40541370
09-14 22:23:54.740: I/robin(30943): begin onHandleIntent() in com.lenovo.robin.test.MyIntentService@40541370
09-14 22:24:04.739: I/robin(30943): end onHandleIntent() in com.lenovo.robin.test.MyIntentService@40541370
09-14 22:24:04.739: I/robin(30943): com.lenovo.robin.test.MyIntentService@40541370 is destroy

参考了很多资料,在此感谢他们的付出!
http://blog.csdn.net/liuhe688/article/details/6874378
http://blog.csdn.net/hudashi/article/details/7986130

向前走 就这么走 wman !

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章