Service、IntentService、AIDL简介

本文深入解析了Android中的Service组件,包括Service的基本概念、使用方法、启动和服务绑定等核心内容。同时介绍了IntentService的工作原理及其内部实现机制,探讨了本地与远程服务绑定的区别,并演示了如何利用AIDL实现远程服务的访问。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Service是什么?

在android中Service是只在后台执行操作且没有页面的组件。Service可以被Service、Activity、ContentProvider启动/绑定,但是不可以被Broadcast启动/绑定。换句话说,Activity、ContentProvider可以绑定到Service并与之交互,甚至进行IPC通讯。

服务根据用途不同而分为两类:启动的服务和绑定的服务。

Service的基本用法。

要实现自己的Service去完成一些后台操作,必须继承自Service,并重写它的onCreate(), onStartCommand(), onDestroy()方法。

    public class MyService extends Service{
        public static final String TAG = "MyService";
        
        @Override
        public void onCreate(){
            super.onCreate();
        }
        
        @Override
        public int onStartCommand(Intent intent, int flags, int startId){
            return super.onStartCommand(intent, flags, startId);
        }
        
        @Override
        public void onDestroy(){
            super.onDestroy();
        }
        
        @Override
        public IBinder onBind(Intent intent){
            return null;
        }
    }
复制代码

然后在androidManifest.xml中进行注册

    <Service android:name="packageName.MyService"/>
复制代码

这样一个完整的Service就完成了,现在我们只需要启动它就可以了。启动Service的方法和启动Activity的方法类似:

    Intent intent = new Intent(this, MyService.class);
    startService(intent);
复制代码

既然有启动Service,那么肯定有停止Service,停止Service的方法为:

    Intent intent = new Intent(this, MyService.class);
    stopService(intent);
复制代码

注意:无论启动多少次Service,只要Service还没有被stop,那么Service#onCreate方法始终只执行一次。换言之,如果Service已经被启动了,Service#onCreate就不再执行了,但onStartCommand每次都会执行。

Service虽然被用来处理一些后台任务,但它运行在主线程(MainThread),因此如果直接在onStartCommand()中执行耗时操作,则会阻塞主线程,具体表现为:除非onStartCommand()的耗时操作执行完毕,否则界面上无法进行任何操作。所以如果要在Service中执行耗时操作(例如网络请求,IO流处理),则必须创建Thread来完成这些耗时操作。那么有的朋友可能会问,既然后台的耗时操作要使用Thread来执行,那么又为什么使用Service呢?直接使用Thread就可以了呀!其实使用Service的原因在于android系统将Service的优先级设置的很高,也就是说,当系统因为内存不足而要回收某些任务时,Thread会比Service先被销毁来释放内存,而且Thread的存活与否与启动它的宿主(例如Activity)的销毁与否无关,那么如果宿主被销毁了,我们就无法控制Thread的存活了,但是Service则不同,我们可以随时随地的控制Service的存活。

其实,android还提供了一个叫做IntentService的组件来实现耗时操作的Service,也就是说,如果你使用IntentService替代Service,那么就不需要new一个Thread来执行耗时操作了,IntentService本身就提供了这种机制。

那么,现在我们来考虑一下:首先在Activity中启动Service,如果之后该Activity被finish掉了,那么此时Service会被destroy吗?答案是NO。想想看,既然Service是用来执行后台操作的,那么它肯定要独立于启动它的组件,否则Service就无法一直在后台执行了。但是,后面我们要说的绑定Service,则不是这样,如果绑定Service的客户端被销毁了,则该Service也被销毁,具体我们后边再来详细说明。

Service#onStartCommand()的返回值

在上一节我们注意到在Service#onStartCommand中返回了一个int值,那么这个值有什么作用呢?

Service#onStartCommand()可以返回四个值:START_STICKY, START_NOT_STICKY, START_REDELIVER_INTENT, START_STICKY_COMPATIBILITY.

START_STICKY:返回该值则表示如果Service所在的进程被系统kill之后,将该Service置于started(已启动)状态,但是不会将第一次启动Service时的intent值恢复。因为该Service处于started状态,所以会保证重启Service之后再次执行onStartCommand()方法,此时如果没有intent传递过来,则将以null object的形式去调用。

START_NOT_STICKY:返回该值则表示如果Service所在的进程被系统kill之后,则系统不会主动重启该Service。

START_REDELIVERY_INTENT:见名知意,返回该值则表示如果Service所在的进程被系统kill之后,将该Service置于started(已启动)状态并将之前的intent值恢复。

START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,而且不保证Service所在的进程被kill之后,Service会被重启。

IntentService的基本用法及原理

正如上面所说的,IntentService是一种特殊的service,可以直接在里面执行耗时操作。

IntentService的基本用法

首先我们肯定要继承IntentService,然后实现onHandleIntent(Intent intent)方法。

    public class MyIntentService extends IntentService {

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

        @Override
        protected void onHandleIntent(Intent intent) {
            if (intent != null) {
                //do something consuming time.
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }   
            }
        }
    }
复制代码

然后在AndroidManifest中注册:

    <service
            android:name=".service.MyIntentService"
            android:exported="false"/>
复制代码

之后我们像启动/停止Service一样去启动/停止IntentService即可。之后我们就可以在IntentService#onHandleIntent中执行耗时操作。

我们看到在注册service时,有时会使用android:exported这个属性,这个属性用来说明该Service是否可以被外部应用使用。默认为false。

IntentService原理解析

在android中,通常使用Handler来实现线程切换及协作,使用HandlerThread来创建一个带有Looper的线程。那么我们来看一下IntentService的源码,看看它是不是使用了Handler和HandlerThread来实现的。


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(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(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
    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)}.
     */
    @WorkerThread
    protected abstract void onHandleIntent(Intent intent);
}

复制代码

从源码我们可以看出,IntentService正是使用了Handler和HandlerThread来实现的,具体实现流程如下: 首先,我们要启动IntentService,那么则肯定会先执行onCreate方法,我们来看看其源码:

@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);
    }

复制代码

在onCreate中,首先使用HandlerThread来创建一个带有Looper的线程,指定该线程的名字叫做mName,然后启动线程,接着得到该线程的Looper对象,得到Looper之后再去初始化Handler。至此,就完成了线程创建和线程切换的准备工作。

接着会去调用IntentService#onStartCommand()【先忽略返回值吧?】,而onStartCommand又调用了onStart(),那么我们来看看onStart()方法的具体实现:

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

从源码可以看出,在onStart中向Handler发送了一个Message,handler在接收到该Message之后会回调Handler#handlerMessage(Msg)方法,我们再来看看ServiceHandler的实现:

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);//停止自己
        }
}
复制代码

可以看到,在handleMessage方法中执行了耗时操作onHandleIntent,执行完耗时操作之后就停止自己。

至此,IntentService的实现原理已经非常清楚了:使用了HandlerThread来创建线程执行耗时操作,使用Handler来发送消息。

注意: IntentServie执行完耗时操作之后会自动停止

在此,还有一个隐藏的知识点需要说明:我们已经知道,多次启动Service之后,Service#onCreate()只会执行一次,Service#onStartCommand()则会执行多次,那么在IntentService中,handler就会多次发送消息并执行耗时操作。由于Handler的消息机制使用了队列,那么在IntentService中,多次启动Service之后,这些耗时操作也会排队执行。

在此有的朋友就会问了:在IntentService中,执行完耗时操作之后会stopSelf,那么此时有多个任务排队等待处理,这岂不是自相矛盾吗?事实上不是如此,接下来我们一一说明:

我们可以看到在Service#onStartCommand(Intent intent, int flags, int startId)和stopSelf(int startId)中有一个参数startId,该参数标识每次启动Service的请求。startId初始值为1,如果在启动Service时,该Service已经被启动且未执行完毕,那么startId递增;如果启动Service时,该Service之前未被启动或已经自停止,则startId为1。startId标识了启动Service这个请求和对应的intent。所以每次stopSelf(startId)时,会关闭当前startId对应的Service,和队列中其他的Service无关。

Bound Service

前面我们使用启动Service的方式使得Service来完成一些后台任务,但是启动Service的客户端和Service之间并没有做任何交互,那么二者如果要进行交互,则必须使用绑定Service的方式。

我们知道四大组件都可以通过设置android:process=":remote"属性让其本身处于另一个进程,所以当我们绑定Service时,就有本地绑定和远程绑定之分。很明显本地绑定是指Service和客户端处于同一个进程,远程绑定则是Service和客户端处于不同的进程。其实使用startService(intent)启动Service时,也有本地启动和远程启动之分,但是通过startService(intent)启动的Service和客户端之间并没有做任何交互,而远程服务通常提供一些系统服务,需要客户端和Service之间进行交互,因此启动Service时无需使用远程模式。

本地绑定

首先我们来看一下本地绑定的基本用法。在最开始的MyService中,有一个onBind()方法,我们让它返回null

        @Override
        public IBinder onBind(Intent intent){
            return null;
        }
复制代码

其实onBind方法就是实现绑定服务的关键方法,要实现绑定服务必须要返回一个IBinder对象。

    public class MyService extends Service{
        public static final String TAG = "MyService";
        
        @Override
        public void onCreate(){
            super.onCreate();
        }
        
        @Override
        public int onStartCommand(Intent intent, int flags, int startId){
            return super.onStartCommand(intent, flags, startId);
        }
        
        @Override
        public void onDestroy(){
            super.onDestroy();
        }
        
        @Override
        public IBinder onBind(Intent intent){
            return new MyBinder();
        }
        
        public class MyBinder extends Binder {
            public void doSomething(){
                //do something
            }
        }
    }
复制代码

虽然我们返回了一个IBinder对象,但是我们如何在客户端来接受这个IBinder对象呢?系统为我们实现了ServiceConnection这个类来实现客户端与服务的连接。

private ServiceConnection conn = new ServiceConnection(){
    
    @Override
    public void onServiceConnected(ComponentName name, IBinder service){
        MyBinder mb = (MyService.MyBinder)service;
        mb.doSomething();
    }
    /**
     * 当服务连接异常终止后调用
     */
    @Override
    public void onServiceDisconnected(ComponentName name){
        
    }
}

....

/**
 * 自定义方法,来实现绑定Service.
 */
private void bindService(){
    Intent bindIntent = new Intent(this, MyService.class);  
    bindService(bindIntent, conn, BIND_AUTO_CREATE);
}

/**
 * 自定义方法,来实现解绑Service.
 */
private void unbindService(){
    unbindService(conn);  
}
复制代码

从这段代码我们可以看出,通过使用ServiceConnection来建立客户端与Service的连接。当连接成功之后会回调ServiceConnection#onServiceConnected(ComponentName name, IBinder service)方法,将IBinder对象传递过来,这样我们就可以像操作自己的方法一样去操作Service中Binder对象的方法。

注意:Service只能unbind一次,否则会报错: java.lang.IllegalArgumentException: Service not registered

远程绑定

现在我们来想想,如果我们直接将MyService改为远程服务,会有什么效果呢?不妨来做一个测试:直接设置MyService的android:process=":remote"。设置完之后,当执行bindService时,报以下错误:

AndroidRuntime: FATAL EXCEPTION: main                                   Process: cn.codemperor, PID: 10998
java.lang.ClassCastException: android.os.BinderProxy cannot be cast to cn.codemperor.service.MyService$MyBinder
复制代码

咦?很奇怪!怎么会有android.os.BinderProxy这个东东呢?我们在任何地方都没有声明或使用它呀,它是怎么来的呢?是怎样和Service关联起来的呢?

要解决这个问题,必须先知道,远程绑定或者说访问远程服务都是IPC的,什么是IPC呢?IPC就是Inter Process Communication,即进程间通讯。在android中实现继承间通讯的方式是AIDL(Android Interface Define Language),即android接口定义语言。接下来我们先详细了解一下AIDL。

AIDL基本概念及用途

首先我们应该明白AIDL的作用是:实现p1进程与p2进程之间的通信。也就是说AIDL提供了一种机制:只要定义了AIDL文件,并在AIDL文件中规定业务逻辑,然后系统就会自动根据业务逻辑实现p1与p2之间的通信。具体表现如下:

            AIDL文件-->定义业务逻辑
                |
            系统生成实现进程间通信的类文件-->生成Stub接口
                |
            实现Stub接口-->实现业务逻辑
            
复制代码

这样一看,我们局很明确的知道AIDL实现进程间通讯的机制。其中Stub是Binder的子类。

那么接下来我们通过一个实例来具体说明一下远程绑定的实现

远程服务实例--数据访问

首先,我们新建IDataAccess.aidl文件,并在启动数据访问方法accessData。

**注意:**在aidl文件中,不能出现private, public, protect修饰符。在AIDL文件中仅能支持基本类型、Map、List、自定义类型。因为自定义类型和其他类型的用法有所区别,我们先来看看非自定义类型的用法。

package cn.codempero.summary;
// Declare any non-default types here with import statements

interface IDataAccess {

    boolean accessData();

    /**
     * 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);
}
复制代码

然后系统会在/build/generated/source/aidl/debug/packageName/文件夹下自动生成IDataAccess.java文件,这个接口就实现了IPC。我们来看一下它的源代码

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\LAB\\Development\\Summary\\app\\src\\main\\aidl\\cn\codemperor\summary\\IDataAccess.aidl
 */
package cn.codemperor.summary;
// Declare any non-default types here with import statements

public interface IDataAccess extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements cn.codempero.summary.IDataAccess
{
private static final java.lang.String DESCRIPTOR = "cn.codempero.summary.IDataAccess";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an cn.codempero.summary.IDataAccess interface,
 * generating a proxy if needed.
 */
public static cn.codempero.summary.IDataAccess asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof cn.codempero.summary.IDataAccess))) {
return ((cn.codempero.summary.IDataAccess)iin);
}
return new cn.codempero.summary.IDataAccess.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{.
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_accessData:
{
data.enforceInterface(DESCRIPTOR);
boolean _result = this.accessData();
reply.writeNoException();
reply.writeInt(((_result)?(1):(0)));
return true;
}
case TRANSACTION_basicTypes:
{
data.enforceInterface(DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
long _arg1;
_arg1 = data.readLong();
boolean _arg2;
_arg2 = (0!=data.readInt());
float _arg3;
_arg3 = data.readFloat();
double _arg4;
_arg4 = data.readDouble();
java.lang.String _arg5;
_arg5 = data.readString();
this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements cn.codempero.summary.IDataAccess
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public boolean accessData() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
boolean _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_accessData, _data, _reply, 0);
_reply.readException();
_result = (0!=_reply.readInt());
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
/**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
@Override public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(anInt);
_data.writeLong(aLong);
_data.writeInt(((aBoolean)?(1):(0)));
_data.writeFloat(aFloat);
_data.writeDouble(aDouble);
_data.writeString(aString);
mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_accessData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public boolean accessData() throws android.os.RemoteException;
/**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
}

复制代码

我们可以看到,在IDataAccess接口里有一个叫做Stub的类,它实现了IDataAccess接口并继承自Binder,那么该类就是用来实现IPC通讯的本地Stub类。

那么现在我们还需要创建一个远程服务来实现具体的业务逻辑accessData。

public class MyRemoteService extends Service {

    private static final String TAG = "MyRemoteService";

    public MyRemoteService(){}
    
    public IBinder onBind(Intent intent){
        
    }
    
    public class DataAccessBinder extends IDataAccess.Stub {
    
        @Override
        public boolean accessData(){
            Log.i(TAG, "You can access Me!");
            return true;
        }
        
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString){}
    }
}
复制代码

接下来我们就需要将MyRemoteService注册在androidManifest.xml中

<service 
    android:name="cn.codemperor.service.MyRemoteService"
    android:exported="true"
    android:enable="true"
    android:process=":remote"
/>
复制代码

那么现在我们只需要像绑定本地服务一样绑定远程服务即可(真的可以吗?回想一下讲解远程绑定是的第一个异常

)。

private ServiceConnection conn = new ServiceConnection(){
    
    @Override
    public void onServiceConnected(ComponentName name, IBinder service){
        MyRemoteService.MyBinder mb = (MyRemoteService.MyBinder)service;
        try {
            mb.endCall();
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
    /**
     * 当服务连接异常终止后调用
     */
    @Override
    public void onServiceDisconnected(ComponentName name){
        
    }
}

....
/**
 * 自定义方法,来实现绑定Service.
 */
private void bindService(){
    Intent bindIntent = new Intent(this, MyService.class);  
    bindService(bindIntent, conn, BIND_AUTO_CREATE);
}

/**
 * 自定义方法,来实现解绑Service.
 */
private void unbindService(){
    unbindService(conn);  
}
复制代码

卧槽!卧槽!卧槽!怎么会奔溃了呢?什么玩意儿?客官别急,请先尝试一下下面的方法:

private ServiceConnection conn = new ServiceConnection(){
    
    @Override
    public void onServiceConnected(ComponentName name, IBinder service){
        IDataAccess iDataAccess = IDataAccess.Stub.asInterface(service);
            try {
                iDataAccess.accessData();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
    }
    /**
     * 当服务连接异常终止后调用
     */
    @Override
    public void onServiceDisconnected(ComponentName name){
        
    }
}
复制代码

哎呀!卧槽!对了!

我们来分析一下IDataAccess.Stub.asInterface(service)究竟做了什么

public static cn.codempero.summary.IDataAccess asInterface(android.os.IBinder obj){
    if ((obj==null)) {
        return null;
    }
    
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    
    if (((iin!=null)&&(iin instanceof cn.codempero.summary.IDataAccess))) {
        return ((cn.codempero.summary.IDataAccess)iin);
    }
    
    return new cn.codempero.summary.IDataAccess.Stub.Proxy(obj);
}
复制代码

看了代码之后,仔细一想,对哟,通过AIDL访问的可是远程服务哎,是不处于同一个进程的呀!那么传递过来的IBinder对象不可能是本进程的IDataAccess呀,所以必须使用代理来访问。

说了这么多,远程Service使用起来如此复杂,好像还不知道为什么要用远程Service呢?

远程Service通常用来提供一些进程间共享的服务,通俗一点来说就是为手机上的App提供一个公共的服务,每个App都可以访问这个服务。

呵呵呵,那怎么在其他的App中访问远程服务呢?毕竟不同的App有不同的UID呀!

在其他应用程序中访问远程服务

最终我们的目的是:所有具有权限的应用程序都可以访问远程服务。那么只需要遵循以下流程即可访问到远程服务:

1. 新建名为ClientTest项目
2. 将IDataAccess.aidl同包路径一起拷贝过来(该路径需要和ClientTest路径同级)

	  |——src
	  |  └── main
	  |  |  |  └── java
	  |  |  |  └── cn.codemperor.clienttest
	  |  |  |  └── cn.codemepror.summary
	  |  |  |  |  └── IDataAccess.aidl
  
复制代码
3. 使用隐式Intent来访问远程服务

但是,呵呵呵,android5.0及以上可是禁止通过隐式intent的方式来访问Service的呀。所以呢?我们来了解一下shareUserId吧!

在说shareUserId之前,我们先来说一下Android中的UID和PID吧!

我们已经知道,一个应用程序可以有多个进程,每个进程都有自己的PID,那么android中是如何来唯一标识应用程序的呢?linux是支持多用户多进程的,所以设计师采用UID来唯一标识一个用户,但是Android中只支持单用户,所以Google就将UID来唯一标识一个应用程序了,应用程序之间也可以通过UID来实现数据共享了。呵呵哒,说了这么多,具体应该怎么实现呢?so easy!只需要让两个应用程序拥有相同的shareUserId即可。

应用程序A

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.codemperor.aApplication"
&emsp;&emsp;&emsp;&emsp;&emsp; android:versionCode="1"
&emsp;&emsp;&emsp;&emsp;&emsp; android:versionName="1.0"
          android:sharedUserId="com.codemperor.share"/>
复制代码

应用程序B

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.codemperor.bApplication"
&emsp;&emsp;&emsp;&emsp;&emsp;android:versionCode="1"
&emsp;&emsp;&emsp;&emsp;&emsp;android:versionName="1.0"
          android:sharedUserId="com.codemperor.share"/>
复制代码

哈哈哈,现在我们就可以使用显示intent的方式来绑定远程Service了(当然也可以通过同样的方式启动Activity,本地Service,BroadcastReceiver)。但是机智的朋友就会问了:如果我知道了你的shareUserId,那么我就可以随意访问你的应用程序咯?哈哈,Google怎会如此lowB,开发者都知道在打包App时,都会有自己的签名文件,而且该签名文件可是机密哟。不同签名的App可是不能实现数据共享的。

在远程服务中使用自定义类型

前面我们提到,在远程服务中,仅支持几种类型的数据,虽然支持自定义类型,但用法有所不同。

首先我们定义一个Cat类型。据官方文档说明,要再远程服务中使用自定义类型必须Parcelable

package cn.codemperor.summary.bean;

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

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

    String name;
    String host;

    private Cat(Parcel in) {
        name = in.readString();
        host = in.readString();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeString(host);
    }
}
复制代码

然后在Cat所在的包下创建一个和自定义类型Cat同名的aidl文件,里面的内容固定为:

// Cat.aidl
package cn.codemperor.summary.bean;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Cat;
复制代码

注意:Cat.aidl和Cat.java的包名必须一致,否则会查找不到。

那么,现在我们就可以使用Cat类型了

//导入Cat类,必须写
import cn.codemperor.summary.bean.Cat;

interface IDataAccess {

    boolean accessData();

    Cat getCat();
    
    /**
     * 如果要在参数中使用自定义类型,必须添加in修饰符
     **/
    void setCat(in Cat cat);

    /**
     * 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);
}

复制代码

最后在DataAccessBinder中重写getCat(), setCat(in Cat cat)即可。

哈哈,前方高能,前方高能,前方高能 经过测试,使用getCat()返回的Cat对象不为空,但是cat.name, cat.host为null。

其他进程间通讯的方式

Messager

该部分较为简单,点我查看用法。

一些Service保活的方法

前台Service

具体参见这里,作者写的浅显易懂。

onStartCommand的返回值

不用再说了吧,哈哈哈哈哈哈

一些小知识点

android:process的值

(1). android:process="com.xxx.xxxx.remote" 完整的命名方式,属于全局进程,其它应用通过ShareUID方式可以和它跑在同一个进程中。

(2). android:process=":remote",进程以“:”开头的进程属于当前应用的私有进程,其它应用的组件不可以和它跑在同一个进程中,“:”命名的进程会在当前的进程名之前附加上包名如:“com.xxx.xxxx:remote”。

转载于:https://juejin.im/post/5a3d2707518825698e72434b

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值