代码改变世界

笔记之Android服务

2012-11-26 23:03  ...平..淡...  阅读(417)  评论(0编辑  收藏  举报

Android 支持两种服务类型的服务:本地服务和远程服务。

  1. 本地服务(Local Service):用于应用程序内部

  2. 远程服务(Remote Sercie):用于android系统内部的应用程序之间

前者用于实现应用程序自己的一些耗时任务,比如查询升级信息,并不占用应用程序比如Activity所属线程,而是单开线程后台执行,这样用户体验比较好。

后者可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可。

远程服务使用 AIDL(Android Interface Definition Language, Android接口定义语言)向客户端定义其自身。 也就是说,通过它我们可以定义进程间的通信接口

本地服务可以看http://byandby.iteye.com/blog/1026110

 

Android支持服务的概念有2个原因。第一,简化后台任务的实施,这种服务也就是本地服务;第二,在同一设备上运行的应用程序之间执行进程通信,这种服务也就是远程服务。本地服务与远程服务的一些重要区别。具体来讲,如果服务完全只供同一进程中的组件使用(以运行后台任务),那么客户端必须调用 Context.startService()来启动该服务。这种类型的服务为本地服务,因为它的一般用途是运行承载服务的应用程序的后台任务。如果服务支持onBind()方法,那么它属于远程服务,可通过进程间通信 (Context.bindService())进程调用。我们也将远程服务称为AIDL 支持服务,因为客户端使用 AIDL 与服务通信。 

 

尽管 android.app.Service接口同时支持本地服务和远程服务,但并不建议提供一种服务的实现同时支持两种类型。因为每种服务的类型都有预定义的生命周期,将两种服务合并在一起可能导致错误(尽管允许这么做)。 

说明:Android中的第二种服务类型有多种叫法:远程服务、AIDL服务、外部服务和RPC服务。这些名称都是指向同一类型的服务----------可供在设备上运行的其他应用程序远程访问的服务。

 

以下对远程服务进行分析:

我的总结-----通过AIDL进行进程间通信的流程:

1.创建.aidl文件。
2.创建aidl服务。
  (1)实现.aidl接口方法(首先通过创建内部类来实现"接口名.Stub"类或者声明一个"接口名.Stub"类的对象,然后实现方法);
  (2)实现Service.onBind(Intent)方法.返回一个IBinder对象。
  ps:还需要在AndroidManifest.xml文件中注册服务(包括intent-filter/action)。
3.创建aidl客户端。
  (1)提供ServiceConnection接口的实现。此接口定义了两个方法:一个供系统建立服务连接时调用,另一个在销毁服务连接时调用。
  (2)使用bindService方法与服务建立连接。
  ps:要复制服务端的.aidl文件(或者gen目录下的包)到客户端src/目录下。

 

详解:摘自http://www.poemcode.net/2010/05/aidl-ipc/ 和 官方文档

一、使用 AIDL 实现 IPC

实现IPC的步骤如下:

(1)创建.aidl 文件     在这个文件里定义 method 和 field。

(2)实现接口    编译器会根据.aidl文件生成一个.java的interface文件,这个 interface 有一个抽象的内部类,名字为 Stub你必须创建一个类,并且实现 .aidl文件中所声明的方法

(3)公开接口给客户端     如果创建的是 service,则应该继承自 Service,并且重载 Service.onBind() 返回实现接口的类的实例

Remote Service Binding 共包含有两个 .java 文件,三个 .aidl 文件,物理结构比较简单,但是逻辑结构就不那么简单,以下用 Class Diagram 来展示其中的关系。

Remote Service Binding

                Remote Service Binding

1、创建.aidl 文件

AIDL 语法简单,用来声明接口,其中的方法接收参数和返回值,但是参数和返回值的类型是有约束的,且有些类型是需要 import,另外一些则无需这样做。

AIDL 支持的数据类型划分为四类,第一类是 Java 编程语言中的基本类型,第二类包括 String、List、Map 和 CharSequence,第三类是其他 AIDL 生成的 interface,第四类是实现了 Parcelable protocol 的自定义类。

其中,除了第一类外,其他三类在使用时均需要特别小心。

使用第二类时,首先需要明白这些类不需要 import,是内嵌的。其次注意在使用 List 和 Map 此二者容器类时,需注意其元素必须得是 AIDL 支持的数据类型,List 可支持泛型,但是 Map 不支持,同时另外一端负责接收的具体的类里则必须是 ArrayList 和 HashMap

使用第三、四类时,需要留意它们都是需要 import 的,但是前者传递时,传递的是 reference,而后者则是 value。

在创建 .aidl 文件的过程中,应该注意一旦 method 有参数,则需注意在前面加上 in, out 或 inout,它们被称为 directional tag,但是对于基本类型的参数,默认就是 in,并且不会是其他值。

Remote Service Binding 共包括了三个 .aidl 文件,分别是IRemoteService.aidl、IRemoteServiceCallback.aidl、ISecondary.aidl,通过它们可以看到如何使用第一类和第三类的数据类型,稀罕的是,看不到第二类、第四类数据类型的使用,也没有看到 directional tag。

IRemoteService.aidl文件

// IRemoteService.aidl
package com.example.android;

// Declare any non-default types here with import statements

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    int getPid();

    /** Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

eclipse会自动在gen/目录下生成同名的.java文件。

2、实现 Interface

AIDL 为你生成一个.java的interface文件,文件名称和.aidl文件相同。生成的 interface 会包含一个抽象内部类 Stub,它声明了在 .aidl 文件里的所有方法。Stub 也定义了一些帮助方法,比较常用的有 asInterface(),其接收一个 IBinder 作为参数,并且返回一个 interface 的实例用来调用IPC方法。

要实现 interface,需要继承 Stub,实现.aidl中定义的方法,这在 RemoteService 和 RemoteServiceBinding 都可以找到相关代码。

private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
    public int getPid(){
        return Process.myPid();
    }
    public void basicTypes(int anInt, long aLong, boolean aBoolean,
        float aFloat, double aDouble, String aString) {
        // Does nothing
    }
};

 

对于实现AIDL接口,官方还提醒我们:
1. 调用者是不能保证在主线程执行的,所以从一调用的开始就需要考虑多线程处理,以及确保线程安全;
2. IPC调用是同步的。如果你知道一个IPC服务需要超过几毫秒的时间才能完成地话,你应该避免在Activity的主线程中调用。也就是IPC调用会挂起应用程序导致界面失去响应,这种情况应该考虑单独开启一个线程来处理。
3. 抛出的异常是不能返回给调用者(跨进程抛异常处理是不可取的)。

3、向客户端公开 Interface

在完成了接口的实现后需要向客户端暴露接口了,也就是发布服务,实现的方法是继承 Service,然后实现以Service.onBind(Intent)返回一个实现了接口的类对象。

官方解释:Once you've implemented the interface for your service, you need to expose it to clients so they can bind to it. To expose the interface for your service, extend Service and implement onBind() to return an instance of your class that implements the generated Stub (as discussed in the previous section). Here's an example service that exposes the IRemoteService example interface to clients.

就是说,一旦你创建服务实现了这个接口,你就需要将这个服务暴露给客户端,这样才能让他们绑定起来。至于如何暴露,你的服务就需要继承Service类并且实现onBind()方法,来返回一个实现了Stub类的实例。下面是一个实例。

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        return mBinder;
    }

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing
        }
    };
}

要将服务向客户端公开,还需要在AndroidManifest.xml文件中添加服务声明,还需要在intent-filter中声明action。

现在,当一个客户端(如活动)调用bindService()来连接到这个服务,客户端的onServiceConnected()回调方法就会接收由onBind()方法返回的mBinder实例。

此外,客户端必须能够访问接口类,因为客户端和服务可能在不同的应用程序,所以客户端的应用程序必须复制.aidl文件到其src /目录下。(即客户端能够访问AIDL方法)。

当客户端在onServiceConnected()回调方法中接收到IBinder对象,它就必须调用YourServiceInterface.Stub.asInterface(service)方法将返回的参数强制转化成YourServiceInterface类型。(

 

IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        mIRemoteService = null;
    }
};

4、使用Parcelable传值

 

创建支持Parcelable protocol协议的类,必须做到以下几点:

1.将要创建的类需要实现Parcelable接口。

2.实现writeToParcel方法,该方法保存了对象的当前状态,并写入到一个Parcel对象。

3.在类中添加一个静态的数据成员(field),它是一个实现了Parcelable.Creator接口的对象。

4.最后,创建一个.aidl文件来声明你创建的类(如下面的Rect.aidl文件)。

  ps:不太懂:If you are using a custom build process, do not add the .aidl file to your build. Similar to a header file in the C language, this .aidl file            isn't compiled.
  如果您使用的是自定义创建的进程,不要添加.aidl到你的工程中。类似于在C语言中的头文件,这个aidl文件不会被编译。

Rect.aidl文件

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

实现了Pracelable接口的Retc.java文件

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    public static final Parcelable.Creator<Rect> CREATOR = new
Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }
}

 

二、调用 IPC 方法(Calling an IPC Method)

这是调用远端接口的步骤:

 

1.复制.aidl文件到工程的src/目录下。

2.声明一个IBinder接口的实例(基于AIDL生成)

3.实现ServiceConnection

4.在实现的ServiceConnection类中调用Context.bindService()方法。

5.在你实现的onServiceConnected()回调方法会接收到IBinder对象。调用YourServiceInterface.Stub.asInterface(service)方法将返回值转化成YourServiceInterface类型。

6.调用接口中定义的方法。应该总是捕获连接被打断时抛出的DeadObjectException异常,这是远端方法唯一的异常。

7.调用Context.unbindService()来断开连接。

调用一个IPC服务的几点意见:

对象是在进程间进行引用计数

您可以发送匿名对象作为方法参数

客户端Activity.java类

View Code
public static class Binding extends Activity {
    /** The primary interface we will be calling on the service. */
    IRemoteService mService = null;
    /** Another interface we use on the service. */
    ISecondary mSecondaryService = null;

    Button mKillButton;
    TextView mCallbackText;

    private boolean mIsBound;

    /**
     * Standard initialization of this activity.  Set up the UI, then wait
     * for the user to poke it before doing anything.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.remote_service_binding);

        // Watch for button clicks.
        Button button = (Button)findViewById(R.id.bind);
        button.setOnClickListener(mBindListener);
        button = (Button)findViewById(R.id.unbind);
        button.setOnClickListener(mUnbindListener);
        mKillButton = (Button)findViewById(R.id.kill);
        mKillButton.setOnClickListener(mKillListener);
        mKillButton.setEnabled(false);

        mCallbackText = (TextView)findViewById(R.id.callback);
        mCallbackText.setText("Not attached.");
    }

    /**
     * Class for interacting with the main interface of the service.
     */
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // This is called when the connection with the service has been
            // established, giving us the service object we can use to
            // interact with the service.  We are communicating with our
            // service through an IDL interface, so get a client-side
            // representation of that from the raw service object.
            mService = IRemoteService.Stub.asInterface(service);
            mKillButton.setEnabled(true);
            mCallbackText.setText("Attached.");

            // We want to monitor the service for as long as we are
            // connected to it.
            try {
                mService.registerCallback(mCallback);
            } catch (RemoteException e) {
                // In this case the service has crashed before we could even
                // do anything with it; we can count on soon being
                // disconnected (and then reconnected if it can be restarted)
                // so there is no need to do anything here.
            }

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_connected,
                    Toast.LENGTH_SHORT).show();
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            mService = null;
            mKillButton.setEnabled(false);
            mCallbackText.setText("Disconnected.");

            // As part of the sample, tell the user what happened.
            Toast.makeText(Binding.this, R.string.remote_service_disconnected,
                    Toast.LENGTH_SHORT).show();
        }
    };

    /**
     * Class for interacting with the secondary interface of the service.
     */
    private ServiceConnection mSecondaryConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            // Connecting to a secondary interface is the same as any
            // other interface.
            mSecondaryService = ISecondary.Stub.asInterface(service);
            mKillButton.setEnabled(true);
        }

        public void onServiceDisconnected(ComponentName className) {
            mSecondaryService = null;
            mKillButton.setEnabled(false);
        }
    };

    private OnClickListener mBindListener = new OnClickListener() {
        public void onClick(View v) {
            // Establish a couple connections with the service, binding
            // by interface names.  This allows other applications to be
            // installed that replace the remote service by implementing
            // the same interface.
            bindService(new Intent(IRemoteService.class.getName()),
                    mConnection, Context.BIND_AUTO_CREATE);
            bindService(new Intent(ISecondary.class.getName()),
                    mSecondaryConnection, Context.BIND_AUTO_CREATE);
            mIsBound = true;
            mCallbackText.setText("Binding.");
        }
    };

    private OnClickListener mUnbindListener = new OnClickListener() {
        public void onClick(View v) {
            if (mIsBound) {
                // If we have received the service, and hence registered with
                // it, then now is the time to unregister.
                if (mService != null) {
                    try {
                        mService.unregisterCallback(mCallback);
                    } catch (RemoteException e) {
                        // There is nothing special we need to do if the service
                        // has crashed.
                    }
                }

                // Detach our existing connection.
                unbindService(mConnection);
                unbindService(mSecondaryConnection);
                mKillButton.setEnabled(false);
                mIsBound = false;
                mCallbackText.setText("Unbinding.");
            }
        }
    };

    private OnClickListener mKillListener = new OnClickListener() {
        public void onClick(View v) {
            // To kill the process hosting our service, we need to know its
            // PID.  Conveniently our service has a call that will return
            // to us that information.
            if (mSecondaryService != null) {
                try {
                    int pid = mSecondaryService.getPid();
                    // Note that, though this API allows us to request to
                    // kill any process based on its PID, the kernel will
                    // still impose standard restrictions on which PIDs you
                    // are actually able to kill.  Typically this means only
                    // the process running your application and any additional
                    // processes created by that app as shown here; packages
                    // sharing a common UID will also be able to kill each
                    // other's processes.
                    Process.killProcess(pid);
                    mCallbackText.setText("Killed service process.");
                } catch (RemoteException ex) {
                    // Recover gracefully from the process hosting the
                    // server dying.
                    // Just for purposes of the sample, put up a notification.
                    Toast.makeText(Binding.this,
                            R.string.remote_call_failed,
                            Toast.LENGTH_SHORT).show();
                }
            }
        }
    };

    // ----------------------------------------------------------------------
    // Code showing how to deal with callbacks.
    // ----------------------------------------------------------------------

    /**
     * This implementation is used to receive callbacks from the remote
     * service.
     */
    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        /**
         * This is called by the remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here will
         * NOT be running in our main thread like most other things -- so,
         * to update the UI, we need to use a Handler to hop over there.
         */
        public void valueChanged(int value) {
            mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0));
        }
    };

    private static final int BUMP_MSG = 1;

    private Handler mHandler = new Handler() {
        @Override public void handleMessage(Message msg) {
            switch (msg.what) {
                case BUMP_MSG:
                    mCallbackText.setText("Received from service: " + msg.arg1);
                    break;
                default:
                    super.handleMessage(msg);
            }
        }

    };
}

 

如果在 connection 中断的情况下,调用 IPC 方法,你会遇到 DeadObjectException,这是 remote method 能抛出的唯一异常(上述步骤6)。