迁移到 www.trinea.cn @Android @Java @性能优化 @开源,滴滴国际化项目 Android 端演进

Android Service介绍

本文主要介绍Service的概念及作用、使用(包括长时间运行的服务、应用内交互的服务、应用间交互的服务的分别举例、生命周期、使用场景)、特殊的Service

示例代码见ServiceDemo示例APK见:TrineaAndroidDemo.apk

 
1、概念及作用

由于ANR对Activity和BroadcastReceiver响应时间的限制(Activity对事件响应不超过5秒,BroadcastReceiver执行不超过10秒),使得在其中都不适合执行较耗时操作,这样像网络、数据库、复杂计算这类耗时操作的执行就需要一个组件来承担。Service作为Android四大组件之一,其功能之一就是耗时操作的执行,主要功能如下:

a. 执行需要长时间运行的操作,这个操作不与用户进行交互,如网络下载、大文件I/O、复杂计算。
b. 应用内或应用间数据通信,Android每个应用程序都在自己的dalvik虚拟机中运行,一个应用是不允许访问其他应用的内存信息的,为此Android引入了Content Provider在不同应用间共享数据,BroadcastReceiver广播信息给不同应用程序,但Content Provider更多用于数据的共享,BroadcastReceiver广播的信息会被所有应用接收较耗费系统资源,对于两个应用间动态的进行交互还需要通过Service来完成。

 

2、使用
(1) startService启动不可进行交互的Service

a. 示例代码及介绍

Service示例
public class MyService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        Toast.makeText(this, "Service Create", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onDestroy() {
        Toast.makeText(this, "Service Destroty", Toast.LENGTH_SHORT).show();
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Toast.makeText(this, "Service Start", Toast.LENGTH_SHORT).show();
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

服务都必须在AndroidManifest.xml文件中注册<service android:name=".MyService"/>,在Activity中定义对象private Intent myServiceIntent = new Intent(this, MyService.class);在onCreate函数中startService(myServiceIntent)启动服务; onDestroy函数中stopService(myServiceIntent)关闭服务;

 

b. 生命周期
通过startService启动服务,若服务未启动,会先执行onCreate函数(若服务已启动则不执行此函数),再执行onStartCommand函数。由此可知多次调用startService传入相同参数不会启动多个服务(onStartCommand函数会执行多次),所以最终只需要调用一次stopService或stopSelf函数停止服务;我们可以将service的处理逻辑放入onStartCommand函数中。服务一直运行,在程序退出后服务也不会停止,直到stopService或stopSelf函数被调用,当然可能被系统回收。

对于onStartCommand的返回值,若返回START_STICKY表示服务通过显式调用启动或停止,若返回START_NOT_STICKY orSTART_REDELIVER_INTENT表示服务仅在有请求发送过来处理时才处于运行状态。

 

c. 使用场景

因为这种方式Service无法与外部进行方便的动态交互,所以适合做后台服务,如网络下载(用户通过Intent传入Url到Service,推荐使用IntentService).

 

(2) bindService启动的Service应用内交互

a. 示例代码及介绍

在上面的方式中Context可以通过Intent向Service传入简单的信息,但是如果希望调用Service的接口进行操作或是获取Service的属性则无法实现,这里我们可以通过bindService实现,如下先定义自己的服务

自定义Service
public class MyService extends Service {

    private int      count;
    private MyBinder myBinder = new MyBinder();

    @Override
    public void onCreate() {
        Toast.makeText(this, "Service onCreate", Toast.LENGTH_SHORT).show();
        count = 0;
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        Toast.makeText(this, "Service onDestroy", Toast.LENGTH_SHORT).show();
        super.onDestroy();
    }

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

    /**
     * 服务被绑定时调用
     * 返回值用于让调用者和服务通信,传入ServiceConnection的public void onServiceConnected(ComponentName name, IBinder service)函数第二个参数
     */
    @Override
    public IBinder onBind(Intent intent) {
        return myBinder;
    }

    public int getCount() {
        return count;
    }

    public int increaseCount() {
        return ++count;
    }

    public int decreaseCount() {
        return --count;
    }

    public class MyBinder extends Binder {

        MyService getService() {
            return MyService.this;
        }
    }
}

从上可以看出我们重写onBind函数并返回自己的Binder用于调用者和服务之间通信。下面我们在调用者中进行调用

定义自己的Service、Intent、ServiceConnection对象
private MyService         myService;
private Intent            myServiceIntent;
private ServiceConnection con = new ServiceConnection() {

                                  /**
                                   * 服务所在进程被kill或是crash时系统调用,而不是unbindService时调用
                                   */
                                  @Override
                                  public void onServiceDisconnected(ComponentName name) {
                                      Toast.makeText(getApplicationContext(), "Service disconnect",
                                                     Toast.LENGTH_SHORT).show();
                                  }

                                  /**
                                   * 服务连接时调用,若已经连接不进行调用
                                   */
                                  @Override
                                  public void onServiceConnected(ComponentName name, IBinder service) {
                                      myService = ((MyBinder)service).getService();
                                      Toast.makeText(getApplicationContext(), "Service Connect", Toast.LENGTH_SHORT).show();
                                  }
                              };

接着调用bindService(myServiceIntent, con, Context.BIND_AUTO_CREATE);绑定服务,绑定成功返回true。这时会执行ServiceConnection对象的onServiceConnected函数为myService变量赋值。接着我们就可以通过myService.increaseCount();操作Service的属性,myService.getCount()获得Service的属性,这样我们就成功和Service进行了通信。在不需要通信时通过unbindService(con);解除服务绑定。

 

b. 生命周期
通过bindService绑定服务,若服务未启动,会先执行Service的onCreate函数,再执行onBind函数,最后执行ServiceConnection对象的onServiceConnected函数。若服务已启动但尚未绑定,先执行onBind函数,再执行ServiceConnection对象的onServiceConnected函数。若服务已绑定成功,则直接返回。这里不会自动调用onStartCommand函数。

通过unbindService解除绑定服务,若已绑定成功,会先执行Service的onUnbind函数,再执行onDestroy函数,注意这里不会执行ServiceConnection对象的onServiceDisconnected函数,因为该函数是在服务所在进程被kill或是crash时被调用。若服务尚未绑定系统会报服务尚未注册异常,我们可以通过如下代码解决

if (myService != null) {
    unbindService(con);
    myService = null;
}

与startService启动的Service不同,若服务通过bindService启动并且没有通过startService启动过后,则在连接断开时服务就会自动解绑(onUnbind)并终止(onDestroy),而在调用者(Activity)退出后会自动断开连接,所以这时服务会自己解绑并终止。若存在某个组件绑定了该服务,则调用该服务的stopService不会停止服务

 

c. 使用场景

应用内通信,如音乐播放器,在服务中控制播放器的播放、暂停、停止,在Activity中通过对服务操作控制播放器。


(3) bindService启动的Service应用间交互——AIDL

在Android AIDL应用间交互中 详细介绍见Android AIDL交互

 

通过上面的三个例子我们可以看出Context.startService()Context.bindService()的区别

a. bindService启动的Service可以和Context进行交互,而startService启动的Service不可以交互,因而使用场景也不同

b. 执行的生命周期不同

 

3、特殊的Service

(1). 异步服务IntentService

默认Service是运行在主线程内的 ,如果在Service内运行一个耗时操作就会阻塞主线程,可能导致ANR,为此我们可以在Service中自己新建线程去执行耗时操作,不过Android系统引入了IntentService方便的解决了这个问题, IntentService会启动一个工作线程去完成用户onHandleIntent中定义的操作,需要注意的是对于同一个IntentService的多次请求(startService调用),在同一个线程中处理,一次只会执行一个请求的onHandleIntent函数。对于不同IntentService的同时请求,在不同的线程中处理,所以每个请求的onHandleIntent函数可以并发执行。示例代码如下:

IntentService示例
public class MyIntentService extends IntentService {

    public MyIntentService(){
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        try {
            System.out.println("IntentService1 Begin Sleep. " + "Thread name: " + Thread.currentThread().getName()
                               + ", Thread Id: " + Thread.currentThread().getId());
            Thread.sleep(3000);
            System.out.println("IntentService1 End. ");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

在AndroidManifest.xml文件中注册服务<service android:name=".MyIntentService"/>,在Activity中定义service对象private Intent myIntentServiceIntent = new Intent(ServiceDemo.this, MyIntentService.class);在onCreate函数中startService(myIntentServiceIntent)启动服务;

从上面我们可以看出
a. IntentService只需要重定义onHandleIntent函数并定义一个无参构造函数(xml中服务注册初始化时使用)即可。
b. IntentService服务在onHandleIntent执行结束后会自动关闭。

 

IntentService和普通Service的区别如下
a. 普通Service运行在主线程中,IntentService运行在一个工作线程中不会阻塞主线程。
b. 普通Service需要手动调用停止接口,IntentService自动停止。
c. IntentService的onStartCommand函数根据mRedelivery属性值返回START_REDELIVER_INTENT或START_NOT_STICKY,而普通Service自定义返回。

 

参考:
http://developer.android.com/reference/android/app/Service.html

http://developer.android.com/guide/components/services.html

http://developer.android.com/reference/android/app/IntentService.html

posted @ 2012-11-08 23:42 Trinea 阅读(...) 评论(...) 编辑 收藏