Android Service基本用法、AIDL、Binder连接池详解

本文介绍Service与Activity之间的通信,文章包含以下内容:

  • 一、Service基本用法
  • 二、通过AIDL实现Service与Activity跨进程通信
  • 三、Binder连接池
  • 四、使用Messenger实现跨进程通信
  • 五、本文的示例源码地址

文章有点长,主要分为上面5个部分,由于没找到在简书设置文内链接的方法,所以要想直接跳过基础看后面的部分,翻滚吧 !不过文章整体从简到繁,前面的基础对后面知识的理解会有帮助,所以建议按顺序看。


一、Service基本用法

基本用法即同进程下Activity与Service双向通信,先描述整体实现过程然后直接上代码:

  1. 新建一个继承自Service的类MyService,然后在AndroidManifest.xml里注册这个Service
  2. Activity里面使用bindService方式启动MyService,也就是绑定了MyService
    (到这里实现了绑定,Activity与Service通信的话继续下面的步骤)
  3. 新建一个继承自Binder的类MyBinder
  4. 在MyService里实例化一个MyBinder对象mBinder,并在onBind回调方法里面返回这个mBinder对象
  5. 第2步bindService方法需要一个ServiceConnection类型的参数,在ServiceConnection里可以取到一个IBinder对象,就是第4步onBinder返回的mBinder对象(也就是在Activity里面拿到了Service里面的mBinder对象)
  6. 在Activity里面拿到mBinder之后就可以调用这个binder里面的方法了(也就是可以给Service发消息了),需要什么方法在MyBinder类里面定义实现就行了。如果需要Service给Activity发消息的话,通过这个binder注册一个自定义回调即可。

代码如下,关键部分给出了对应上面步骤的注释:

Activity

public class MainActivity extends Activity {

    private static final String TAG = "zjy";
    public MyBinder mBinder;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            //第5步所说的在Activity里面取得Service里的binder对象
            mBinder = (MyBinder)iBinder;
            //第6步注册自定义回调
            mBinder.setOnTestListener(new MyBinder.OnTestListener() {
                @Override
                public void onTest(String str) {
                    Log.d(TAG, "receive msg from service: "+str);
                }
            });
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(MainActivity.this, MyService.class);
        bindService(intent,mConnection,BIND_AUTO_CREATE);

        findViewById(R.id.test_bt).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //点击按钮调用mBinder里面的方法,发送消息给Service
                mBinder.testMethod("hi, service.");
            }
        });
    }
}

Service

public class MyService extends Service {
    private static final String TAG = "zjy";
    // 第4步,实例化一个MyBinder对象
    private MyBinder mBinder = new MyBinder(this);

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;//第4步,返回这个mBinder对象
    }

    public void serviceMethod(String str){
        Log.d(TAG, "receive msg from activity: " + str);
    }
}

Binder

public class MyBinder extends Binder {
    private static final String TAG = "zjy";
    private MyService mService;
    private OnTestListener mListener;

    public MyBinder(MyService service) {
        this.mService = service;
    }

    public void testMethod(String str) {
        // Activity通过Binder来调用Service的方法将消息传给Service
        mService.serviceMethod(str);
        // 并回调mListener.onTest告诉Activity已收到消息
        mListener.onTest("hi, activity.");
    }

    // MyBinder 里面提供一个注册回调的方法
    public void setOnTestListener(OnTestListener listener) {
        this.mListener = listener;
    }

    //自定义一个回调接口
    public interface OnTestListener {
        void onTest(String str);
    }
}

代码很简单,首先Activity绑定Service得到一个MyBinder实例并注册MyBinder里面的OnTestListener回调监听,然后点击按钮的时候调用MyBinder里面的testMethod(String)方法将消息发出去,MyBinder持有一个MyService的实例,testMethod(String)里面调用MyService里面的方法就可以把Activity的消息传给Service了,然后testMethod(String)里面回调mListener.onTest(String)将Service的消息发给Activity。

MyBinder定义在MyService里面作为内部类也是很常见的写法,这里为了方便后面的讲解写成了普通类的形式。

至此就实现了同进程下Activity与Service的双向通信,运行代码,点击按钮后log如下:

( 2360): receive msg from activity: hi, service.
( 2360): receive msg from service: hi, activity.

通过代码可以看到,Activity和Service之间是通过一个binder对象来通信的。


二、通过AIDL实现Service与Activity跨进程通信

上面讲了Activity和Service在同进程下的通信,结论是:Activity和Service之间是通过一个binder对象来通信的,其实,这句话在多进程中同样有效,接下来就在多进程下验证这句话。到这你可能已经想到了,AIDL其实就是利用Binder实现跨进程通信的。先看一下官方文档是如何介绍AIDL的:

On Android, one process cannot normally access the memory of another process. So to talk, they need to decompose their objects into primitives that the operating system can understand, and marshall the objects across that boundary for you. The code to do that marshalling is tedious to write, so Android handles it for you with AIDL.

大概意思就是说Android进程之间不能直接通信,需要把对象转换成计算机能识别的原始语言,然后安排它跨越进程边界。但是做这些事很繁琐,于是Android提供了AIDL来做这件事。(换句话就是要实现跨进程需要编写很多复杂的代码,于是android提供了AIDL,通过编写简单的AIDL文件,编译器根据AIDL的规则生成那些复杂的代码)

总的来说,使用AIDL跨进程通信,整体过程和单进程一样,都是通过一个Binder来通信的,区别在于单进程的Binder是自己通过继承Binder类来手动实现的,而跨进程的Binder是通过AIDL自动生成的,那是一个牛逼的Binder。

对AIDL有个初步认识之后,开始实践,这里使用AndroidStudio实现AIDL,参考文章:Android Studio中AIDL使用方法

首先修改上面的代码,在AndroidManifest.xml里面用android:process=":remote"属性把Service指定到另一个进程中,这时候直接运行代码会报错,因为自定义的MyBinder不具有跨进程的能力,绑定Service的时候无法得到Binder。那么接下来就使用AIDL生成一个可以跨进程的Binder,然后用这个可跨进程的Binder替换MyBinder。

1、新建一个AIDL文件

和新建类文件相似:右键 -> new -> AIDL -> AIDL File,然后输入文件名点击finish完成(这里的示例代码是IMyAidlInterface)

上面的操作不管右键哪个目录,完成之后都会在src/main目录下生成了一个aidl目录,新建的IMyAidlInterface.aidl文件就在这个目录下,注意和eclipse的不同。
打开这个文件发现就是一个接口(可能会默认生成一个basicTypes方法,这是示例方法,不用管,可以删掉),然后在里面定义一个自己的方法(需要其他的方法的话自己看着加)

代码如下:

interface IMyAidlInterface {
    void testMethod(String str);
}

2、编译项目

Build -> Make Project
完成之后会在 app/build/generated/source/debug/ 目录下生成一个和AIDL文件同名的java文件 IMyAidlInterface.java

这个类文件就是用来提供进程间通信的,需要的Binder类就在这里面。
简单来说,AIDL就是一个用来生成代码的工具,最终的目的就是得到IMyAidlInterface.java这个类。这个和数据库框架GreenDao很像,都是通过一些简单的做法生成很多复杂而有用的代码,然后拿来直接用。当然那些复杂的代码也是可以手动编写的,比如可以尝试仿照IMyAidlInterface.java或者直接把IMyAidlInterface.java复制到java目录然后删掉aidl文件实现进程间通信。

3、分析 IMyAidlInterface.java

AndroidStudio切换到Project工程模式在app/build/generated/source/debug/路径下找到IMyAidlInterface.java文件并打开。生成的代码格式很乱,为了方便查看,可以使用格式化代码的快捷键格式化一下。

IMyAidlInterface.java里面是一个接口,接口里面有一个内部抽象类和一个方法。这个方法就是我们在aidl文件里定义的那个方法。内部抽象类就是我们要的Binder类,类名Stub。到这里不难想象接下来的工作:(1)Service里面new一个Stub实例并在onBinder里面返回这个Stub(或者说Binder)的实例 。(2)Activity里面绑定Service的时候取到这个Binder(强转成Stub类型)。(3)调用这个Binder里面的testMethod方法实现Activity和Service的通信。大的思路是这样,不过细节上还是有很多不同的。

IMyAidlInterface.java里面的其他代码(主要是一些方法)暂时不用看,用到的时候会说。到这里只需要知道这个java文件里面有一个Stub类,有一个自定义的方法。

4、修改Service代码

到这AIDL相关的代码已经完成,接下来就是使用AIDL为我们生成的代码。首先修改MyService只需把MyBinder替换成Stub,但是Stub是个抽象类,需要我们自己实现,那么新建一个继承自Stub的类,类名随意,这里取名AidlBinder,然后仿照同进程下的MyBinder实现testMethod()方法,代码如下:

public class AidlBinder extends IMyAidlInterface.Stub {

    private MyService mService;

    public AidlBinder(MyService service) {
        this.mService = service;
    }

    @Override
    public void testMethod(String str) throws RemoteException {
        mService.serviceMethod(str);
    }
}

上面代码中没有回调相关的代码,因为跨进程的回调和同进程下是不一样的,后面会说到。另外,这里为了方便讲解,专门定义了AidlBinder类作为Stub 的实现类,另一种在Service里面直接使用匿名内部类的方式实现Stub 也是很常见的。至于AidlBinder里面的代码和同进程下很像,不解释了。
然后MyService里面使用AidlBinder即可,代码如下:

public class MyService extends Service {

    private static final String TAG = "zjy";

    private AidlBinder mBinder = new AidlBinder(this);

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

    public void serviceMethod(String str) {
        Log.d(TAG, "receive msg from activity: " + str);
    }
}

和同进程基本一样,不解释了。
总结一下:到这里为止,除去理论的分析之外,实际的操作只有两步:(1)新建一个AIDL文件用来生成一些代码。(2)实现抽象类Stub,实现类是AidlBinder。(3)Service里面使用Stub的实现类AidlBinder替换原来的MyBinder。

5、修改Activity代码

先来分析一下,按照同进程通信的思路就是:声明一个IMyAidlInterface.Stub类型的Binder,然后在绑定Service的时候初始化这个Binder:mBinder = (IMyAidlInterface.Stub)service; 然后使用这个Binder来跟Service通信。
其实这样是不行的,如果这样做,绑定服务的时候 mBinder = (IMyAidlInterface.Stub)service; 这行代码会报一个异常java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.zjy.servicedemo.IMyAidlInterface$Stub
意思是传过来的Binder是BinderProxy类型的不能转换成Stub类型(因为Stub不是BinderProxy的子类而是Binder的子类)。

关于BinderProxy,我也不懂,通过一些资料了解到它与C++层有关,源码中无对应的java类,编译源码后会生成BinderProxy.class类,和Binder一样实现了IBinder接口。

源码位置\frameworks\base\core\jni\android_util_Binder.cpp->static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,jint code, jobject dataObj,jobject replyObj, jint flags)
出自:Android FrameWork——Binder机制详解(1)

Java層的Activity透過BinderProxy來與遠距的(Remote)服務進行溝通。
從Java層而觀之,myActivity可以經由bindService()而建立它與myBinder之間的連結。然而,這個連結是透過C++層的機制而達成的。
出自:認識Android的BinderProxy和Binder類別 (应该是台湾人写的,繁体字不是乱码 ^ ^!)

Activit如何使用传过来的Binder呢?AIDL生成的代码中提供了一个静态方法asInterface(IBinder),可以将IBinder转换成Aidl接口,所以可以这样做:IMyAidlInterface mService = IMyAidlInterface.Stub.asInterface(service);

艺术探索这本书中是这样介绍asInterface方法的:用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象。

所以同进程下,Activity有以下3种方式使用Service传过来的Binder:
IMyAidlInterface mService = IMyAidlInterface.Stub.asInterface(service);
IMyAidlInterface.Stub mBinder = (IMyAidlInterface.Stub)service;
IMyAidlInterface.Stub mService = (IMyAidlInterface.Stub)IMyAidlInterface.Stub.asInterface(service);
而跨进程只能使用第一种方式,最终Activity的代码如下:

public class MainActivity extends Activity {

    private static final String TAG = "zjy";
    public IMyAidlInterface mService;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mService = IMyAidlInterface.Stub.asInterface(iBinder);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(MainActivity.this, MyService.class);
        bindService(intent, mConnection, BIND_AUTO_CREATE);

        findViewById(R.id.test_bt).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    mService.testMethod("hi, service.");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}


6、跨进程回调接口的实现

至此,实现了跨进程Activity给Service发送消息,接下来实现Service收到消息后回应Activity。大的方向还是和单进程一样使用回调实现,不一样的是细节。
首先,回调接口需要定义成aidl接口而不是普通接口,所以新建一个IMyCallbackListener.aidl文件,里面定义一个onRespond方法作为回调函数:

interface IMyCallbackListener {
    void onRespond(String str);
}

扩展IMyAidlInterface.aidl,里面定义一个注册回调监听的方法(相当于基础篇里面的那个setOnTestListener方法)

import com.zjy.servicedemo.IMyCallbackListener;

interface IMyAidlInterface {
    void testMethod(String msg);
    void registerListener(IMyCallbackListener listener);
}

注意aidl的语法规则,非系统的类即使在同一个包下也要import,比如上面代码的IMyCallbackListener,而系统的类String就不用import

这时编译会提示AidlBinder实现父类的抽象方法registerListener(),仿照同进程下的MyBinder里面的回调相关的代码,修改AidlBinder如下:

public class AidlBinder extends IMyAidlInterface.Stub {

    private MyService mService;
    private IMyCallbackListener mListener;

    public AidlBinder(MyService service) {
        this.mService = service;
    }

    @Override
    public void testMethod(String str) throws RemoteException {
        mService.serviceMethod(str);
        mListener.onRespond("hi, activity");
    }

    @Override
    public void registerListener(IMyCallbackListener listener) throws RemoteException {
        mListener = listener;
    }
}

有同进程通信的基础,看懂这个代码很容易。然后Activity里面在合适的地方注册回调,用来接收服务端的消息:

try{
    mService.registerListener(new IMyCallbackListener.Stub() {
        @Override
        public void onRespond(String str) throws RemoteException {
            Log.d(TAG, "receive message from service: "+str);
        }
    });
} catch (RemoteException e){
    e.printStackTrace();
}

至此,跨进程下Activity与Service的双向通信就完成了,运行代码,点击按钮log如下:

(11597): receive msg from activity: hi, service.
(11579): receive message from service: hi, activity

就本应用中的代码来看,代码的执行流程和单进程一样,只是一些实现的细节不同。另外,可以使用adb shell ps | grep "本应用的包名"命令查看进程信息,会看到如下两个进程:
com.zjy.servicetest
com.zjy.servicetest:remote
com.zjy.servicetest:remote 是Service所在的进程。如果是不同应用下的多进程,使用AIDL通信和同应用多进程无本质区别。

7、跨进程下解注册回调

Service回应Activity消息是通过注册回调接口实现的,接下来介绍解注册,和同进程的解注册不同,多进程需要借助RemoteCallbackList来完成,所以注册回调的相关方法也要改一下,改成使用RemoteCallbackList来注册回调,AidlBinder代码修改如下:

public class AidlBinder extends IMyAidlInterface.Stub {

    private MyService mService;
    private RemoteCallbackList<IMyCallbackListener> mListenerList = new RemoteCallbackList<>();

    public AidlBinder(MyService service) {
        this.mService = service;
    }

    @Override
    public void testMethod(String str) throws RemoteException {
        mService.serviceMethod(str);

        // 调用mListenerList里面所有已注册的监听
        int count = mListenerList.beginBroadcast();
        for (int i = 0; i < count; i++) {
            mListenerList.getBroadcastItem(i).onRespond("hi, activity");
        }
        mListenerList.finishBroadcast();
    }

    @Override
    public void registerListener(IMyCallbackListener listener) throws RemoteException {
        mListenerList.register(listener);
    }

    @Override
    public void unregisterListener(IMyCallbackListener listener) throws RemoteException {
        mListenerList.unregister(listener);
    }
}

上面代码里的unregisterListener方法像registerListener一样添加进去,并在里面实现解注册的功能。RemoteCallbackList的用法很简单,看代码就行了。

最后在Activity里面加一些测试解注册的代码即可,比如加一个按钮,点击的时候调用远程的解注册方法,下面是Activity里面的最终完整代码:

public class MainActivity extends Activity {

    private static final String TAG = "zjy";
    public IMyAidlInterface mService;

    private IMyCallbackListener.Stub mListener = new IMyCallbackListener.Stub() {
        @Override
        public void onRespond(String str) throws RemoteException {
            Log.d(TAG, "receive message from service: "+str);
        }
    };

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mService = IMyAidlInterface.Stub.asInterface(iBinder);
            try{
                //注册回调
                mService.registerListener(mListener);
            } catch (RemoteException e){
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(MainActivity.this, MyService.class);
        bindService(intent, mConnection, BIND_AUTO_CREATE);

        findViewById(R.id.test_bt).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                try {
                    mService.testMethod("hi, service.");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        findViewById(R.id.test2_bt).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    //解注册回调
                    mService.unregisterListener(mListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

整个代码最终的功能是:启动Activity的时候绑定Service并注册一个回调,点击 send message 按钮后Activity向Service发送消息"hi, service",然后Service收到消息后log打印 "receive message from activity: hi, service",并恢复一个消息 "hi, activity",Activity收到消息后log打印 "receive message from service: hi, activity"。然后点击unregisterListener按钮解注册回调监听,再点击 send message 后就只打印log "receive message from activity: hi, service",说明解注册成功。

AIDL的基本用法就介绍到这里,关于传递自定义的序列化对象和不同应用的多进程通信可以参考文章Android Studio中AIDL使用方法


这里总结一下:同进程下自定义MyBinder可以轻松实现Activity与Service通信,跨进程的话需要使用AIDL生成可以跨进程的Binder。至于Activity与Service里面的代码,流程套路基本相同,不相同的只是一些很简单的细节。



三、Binder连接池

通过上面的介绍,不难发现,一个Service对应一个Binder,实际项目总不能把所有逻辑都写在一起的,不同业务逻辑是要分类的,难免会出现多个Binder的情况,总不能一个Binder对应一个Service,这时,就可以使用Binder连接池了。

首先,用一种简单的方式介绍一下什么是Binder连接池:Binder连接池是一种类似设计模式的代码结构。可以直接把它当作一种设计模式来看待。
然后,这种模式要解决的问题:用一个Service管理多个AIDL(或者说管理多个Binder),而不是一个AIDL对应一个Service。

再解释一下,增强理解:我们知道设计模式对于编写代码、实现功能等并不是必须的,但是它有很多优点。Binder连接池也是一样,要实现一个Service管理多个AIDL也可以不使用它。但是它可以让代码结构优雅清晰,使代码维护扩展更加容易等。


简单了解连接池之后,接下来动手实现一个例子。在动手之前先整体了解一下最终的项目的目录结构,看下图:


项目结构图

如图,这里拿动物来举例。下面一步步来实现图中的代码。

1、首先准备相应的类:新建一个Activity和一个Service,新建多个AIDL文件。

(1)Activity和Service先什么都不用做,它们与要实现的Binder连接池无关,它们只是用来使用Binder连接池的。
新建多个AIDL,文件名如下:

  • IAnimal.aidl
  • IBird.aidl
  • IFish.aidl
  • IMonkey.aidl

它们的代码如下:

interface IAnimal {
    IBinder queryAnimal(int animalCode);
}
interface IBird {
    void fly();
}
interface IFish {
    void swim();
}
interface IMonkey {
    void climbTree();
}

以上代码不难理解,每种动物包含一个它的专有方法,IAnimal接口管理其它三种动物,它里面的方法接收一个参数,这个参数代表动物种类,后面的实现会根据动物种类返回一个对应的动物的Binder。

(2)编译项目,生成AIDL文件对应的Binder,AIDL生成的Binder是抽象类,接下来定义每个抽象Binder的实现类,类名分别为:AnimalBinder.java,BirdBinder.java,FishBinder.java,MonkeyBinder.java。代码如下:

public class AnimalBinder extends IAnimal.Stub{

    public static final int ANIMAL_CODE_BIRD = 1;
    public static final int ANIMAL_CODE_FISH = 2;
    public static final int ANIMAL_CODE_MONKEY = 3;

    @Override
    public IBinder queryAnimal(int animalCode) throws RemoteException {
        IBinder binder = null;
        switch (animalCode) {
            case ANIMAL_CODE_BIRD:
                binder = new BirdBinder();
                break;
            case ANIMAL_CODE_FISH:
                binder = new FishBinder();
                break;
            case ANIMAL_CODE_MONKEY:
                binder = new MonkeyBinder();
                break;
            default:
                break;
        }
        return binder;
    }
}
public class BirdBinder extends IBird.Stub{
    private static final String TAG = "zjy";
    @Override
    public void fly() throws RemoteException {
        Log.d(TAG, "I'm bird, I can fly.");
    }
}
public class FishBinder extends IFish.Stub{
    private static final String TAG = "zjy";
    @Override
    public void swim() throws RemoteException {
        Log.d(TAG, "I'm fish, I can swim.");
    }
}
public class MonkeyBinder extends IMonkey.Stub {
    private static final String TAG = "zjy";
    @Override
    public void climbTree() throws RemoteException {
        Log.d(TAG, "I'm monkey, I can climb the tree.");
    }
}

代码很简单,不解释了。有一点要说明一下,AnimalBinder作为管理,和三种动物Binder要区分开,更好的写法是把AnimalBinder写在代表连接池的类BinderPool里面作为内部类(BinderPool类是后面要讲的),那样的话结构上更加好看合理,示例的最终代码是以内部类的方式来写的。

2、编写连接池代码

连接池就是一个普通的java类,类名随意取,这里取名:BinderPool.java
类里面的代码主要分为几个简单的部分:

  • 给BinderPool.java实现单例模式
  • 绑定一个Service(绑定Service需要的Context是使用它的Activity传过来的)
  • 提供一个queryAnimal方法,根据参数给用户提供不同的binder
  • 以及前面说的把AnimalBinder作为BinderPool的内部类

BinderPool.java的全部代码如下:

public class BinderPool {

    private static final String TAG = "zjy";

    public static final int NO_ANIMAL = 0;
    public static final int ANIMAL_CODE_BIRD = 1;
    public static final int ANIMAL_CODE_FISH = 2;
    public static final int ANIMAL_CODE_MONKEY = 3;

    private Context mContext;
    @SuppressWarnings("all")
    private static BinderPool sInstance;
    private CountDownLatch mCountDownLatch;
    private IAnimal mAnimalPool;

    private BinderPool(Context context) {
        mContext = context.getApplicationContext();
        connectBinderPoolService();
    }

    public static BinderPool getInstance(Context context) {
        if (sInstance == null) {
            synchronized (BinderPool.class) {
                if (sInstance == null) {
                    sInstance = new BinderPool(context);
                }
            }
        }
        return sInstance;
    }

    private synchronized void connectBinderPoolService() {
        mCountDownLatch = new CountDownLatch(1);
        Intent intent = new Intent(mContext, MyService.class);
        mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

        try {
            mCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mAnimalPool = IAnimal.Stub.asInterface(service);
            mCountDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected: ");
        }
    };

    public IBinder queryAnimal(int animalCode) {
        IBinder binder = null;
        try {
            if (mAnimalPool != null) {
                binder = mAnimalPool.queryAnimal(animalCode);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return binder;
    }

    public static class AnimalBinder extends IAnimal.Stub {

        @Override
        public IBinder queryAnimal(int animalCode) throws RemoteException {
            IBinder binder = null;
            switch (animalCode) {
                case ANIMAL_CODE_BIRD:
                    binder = new BirdBinder();
                    break;
                case ANIMAL_CODE_FISH:
                    binder = new FishBinder();
                    break;
                case ANIMAL_CODE_MONKEY:
                    binder = new MonkeyBinder();
                    break;
                default:
                    break;
            }
            return binder;
        }
    }
}

根据划分的几个部分来看代码是很容易的,不过有一些细节需要注意:

  • 关于单例的内存泄漏风险,代码里把context成员转换成了Application的context
  • AIDL是支持并发访问的,代码里在绑定Service的时候使用synchronized和CountDownLatch做了线程同步处理,所以获取BinderPool单例对象的时候不能在主线程里面。

3、使用Binder连接池

到这里Binder连接池的代码就完成了,主要就是一个BinderPool类,接下来在Service和Activity中使用它。

Service的代码:

public class MyService extends Service {
    private BinderPool.AnimalBinder mBinder = new BinderPool.AnimalBinder();

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

注:不要忘记在AndroidManifest.xml里面用android:process=":remote"属性把Service指定到另一个进程中。

Activity的代码:

public class MainActivity extends Activity {
    private static final String TAG = "zjy";
    private BinderPool mBinderPool;

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

        findViewById(R.id.bt1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        mBinderPool = BinderPool.getInstance(MainActivity.this);
                        IBinder birdBinder = mBinderPool.queryAnimal(BinderPool.ANIMAL_CODE_BIRD);
                        IBinder fishBinder = mBinderPool.queryAnimal(BinderPool.ANIMAL_CODE_FISH);
                        IBinder monkeyBinder = mBinderPool.queryAnimal(BinderPool.ANIMAL_CODE_MONKEY);

                        IBird bird = IBird.Stub.asInterface(birdBinder);
                        IFish fish = IFish.Stub.asInterface(fishBinder);
                        IMonkey monkey = IMonkey.Stub.asInterface(monkeyBinder);

                        try {
                            bird.fly();
                            fish.swim();
                            monkey.climbTree();
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        });
    }
}

通过Service和Activity的代码可以看到,BinderPool使用起来很简单。从使用者的角度来看,Binder连接池就是把应该在Activity里面做的事封装成了BinderPool类,比如绑定Service、客户端通过Binder调用远程服务端的方法等。

4、测试

通过测试代码可以知道,Service是在Activity中点击按钮的时候通过初始化BinderPool单例对象的时候绑定的(也可以在其他地方初始化BinderPool对象,随意,这里只是一种测试代码,但是不要在主线程里面),所以程序刚运行的时候只有一个Activity所在的进程,点击按钮之后才会开启Service进程。

(1)运行代码,执行命令 adb shell ps | grep "com.zjy.servicedemo" 可以看到一个进程

u0_a97    2228  523   1012056 57324 00000000 f774c915 S com.zjy.servicedemo

(2)点击按钮,可以看到打印log

D/zjy  ( 2264): I'm bird, I can fly.
D/zjy  ( 2264): I'm fish, I can swim.
D/zjy  ( 2264): I'm monkey, I can climb the tree.

(3)再次执行命令 adb shell ps | grep "com.zjy.servicedemo" ,此时可以看到有两个进程,说明点击按钮后启动了service并且service是运行在另一个进程的。

u0_a97    2228  523   1012056 57324 00000000 f774c915 S com.zjy.servicedemo
u0_a97    2264  523   995804 42180 00000000 f774c915 S com.zjy.servicedemo:remote

Binder连接池到此结束,主要就是一个BinderPool.java类


四、使用Messenger实现跨进程通信

Messenger也是用来做进程间通信的,与AIDL的区别,看官方文档的一段话:

When you need to perform IPC, using a Messenger for your interface is simpler than implementing it with AIDL, because Messenger queues all calls to the service, whereas, a pure AIDL interface sends simultaneous requests to the service, which must then handle multi-threading.
For most applications, the service doesn't need to perform multi-threading, so using a Messenger allows the service to handle one call at a time. If it's important that your service be multi-threaded, then you should use AIDL to define your interface.

意思就是Messenger比AIDL用起来简单,但是如果多个客户端同时给服务发消息的话,Messenger一次只能处理一个消息,而AIDL可以多线程处理。

Messenger本质也是用AIDL实现的,可以浏览下Messenger的源码(只有100多行),会看到一些AIDL相关的东西。

然后简单介绍一下Messenger的使用,首先列一下使用流程:

  1. Service里面实现一个Handler用来接收消息用
  2. 使用这个Handler创建一个Messenger对象
  3. 使用这个Messenger对象创建一个Binder对象,并在onBind方法返回
  4. Activity里面绑定Service的时候使用传过来的Binder创建一个Messenger对象
  5. Activity里面使用这个Messenger对象给Service发消息
  6. Service里面的Handler收到消息并处理
  7. Activity里面实现一个Handler用来接收Service回复的消息
  8. 第5步发送消息的时候消息中携带一个Messenger对象,这个Messenger是用第7步的Handler创建的
  9. 第6步Service收到消息的时候取出消息中携带的Messenger
  10. 用第9步取出的Messenger给Activity发消息
  11. Activity中第7步的Handler处理Service回复的消息

整个流程和单进程通信的过程很像,都是围绕Binder完成的。上面第7步以后都是Service回复消息相关的。下面直接给出完整代码,注释与上面的流程相对应。

Service代码

public class MyService extends Service {
    private static final String TAG = "zjy";

    //1.Service里面实现一个Handler用来接收消息用
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //6.Service里面的Handler收到消息并处理
            if (msg.what==1) {
                Bundle bundle = msg.getData();
                Log.d(TAG, "receive message from activity: "+bundle.getString("string"));

                //9.取出消息中的Messenger对象
                Messenger replyMessenger = msg.replyTo;

                Message  replyMsg= new Message();
                replyMsg.what = 2;
                Bundle b = new Bundle();
                b.putString("string", "hi, activity");
                replyMsg.setData(b);
                try {
                    //10.使用Messenger给Activity发消息
                    replyMessenger.send(replyMsg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    };

    // 2.使用这个Handler创建一个Messenger对象
    private Messenger mMessenger = new Messenger(mHandler);

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //3.使用这个Messenger对象创建一个Binder对象,并在onBind方法返回
        return mMessenger.getBinder();
    }
}

Activity代码

public class MainActivity extends Activity {

    private static final String TAG = "zjy";

    private Messenger mMessenger;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //4.Activity里面绑定Service的时候使用传过来的Binder创建一个Messenger对象
            mMessenger = new Messenger(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent(MainActivity.this, MyService.class);
        bindService(intent,mConnection,BIND_AUTO_CREATE);

        findViewById(R.id.bt1).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message msg = new Message();
                msg.what = 1;

                Bundle bundle = new Bundle();
                bundle.putString("string", "hi, service");
                msg.setData(bundle);
                //8.发送消息的时候携带一个Messenger对象
                msg.replyTo = new Messenger(mGetReplyMsg);

                try {
                    //5.Activity里面使用这个Messenger对象给Service发消息
                    mMessenger.send(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    //7.Activity里面实现一个Handler用来接收Service回复的消息
    private Handler mGetReplyMsg = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //11.处理Service回复的消息
            if (msg.what==2) {
                Bundle bundle = msg.getData();
                Log.d(TAG, "receive message from service: "+bundle.getString("string"));
            }
        }
    };
}
需要注意的问题

(1)Messenger发送的消息是Message对象,组装Message消息的时候不要使用Message的obj字段,而是借用Bundle来组装数据。下面是《Android开发艺术探索》里面的一段话:

使用Messenger来传输Message,Message中能使用的载体只有what, arg1, arg2, Bundle以及replyTo。Message中的另一字段obj在同一个进程中很实用,但是在进程间通信的时候,在android2.2以前obj不支持跨进程,即便是2.2以后,也仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输。这就意味着自定义的Parcelable对象是无法通过obj字段来传输的。

(2)在接收端的代码中,取消息的时候是先从Message里面取出Bundle,然后直接从Bundle取数据。如果数据是自定义的Parcelable对象,是不能直接从Bundle里面取的,需要在取数据之前先给Bundle设置一个ClassLoader。“取数据之前”的意思不单单是指取自定义的Parcelable对象,而是包括基本数据类型和系统提供的Parcelable对象等所有数据之前。示例代码如下:

Bundle bundle = msg.getData();
bundle.setClassLoader(getClassLoader());//设置ClassLoader
bundle.getxxx(key);//取数据

关于这一点源码里面已经有相关注释说明了,Message类的getData方法注释如下:

/** 
 * Obtains a Bundle of arbitrary data associated with this
 * event, lazily creating it if necessary. Set this value by calling
 * {@link #setData(Bundle)}.  Note that when transferring data across
 * processes via {@link Messenger}, you will need to set your ClassLoader
 * on the Bundle via {@link Bundle#setClassLoader(ClassLoader)
 * Bundle.setClassLoader()} so that it can instantiate your objects when
 * you retrieve them.
 * @see #peekData()
 * @see #setData(Bundle)
 */
public Bundle getData() {
    if (data == null) {
        data = new Bundle();
    }
    
    return data;
}


五、本文的示例源码地址

https://github.com/developerzjy/ServiceDemo





最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容