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

本文介绍Service与Activity之间的通讯,文章包含如下内容:java

  • 1、Service基本用法
  • 2、经过AIDL实现Service与Activity跨进程通讯
  • 3、Binder链接池
  • 4、使用Messenger实现跨进程通讯
  • 5、本文的示例源码地址

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


1、Service基本用法

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

  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注册一个自定义回调便可。

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

Activityshell

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以下:app

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

经过代码能够看到,Activity和Service之间是经过一个binder对象来通讯的。


2、经过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。

一、新建一个AIDL文件

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

上面的操做无论右键哪一个目录,完成以后都会在src/main目录下生成了一个aidl目录,新建的IMyAidlInterface.aidl文件就在这个目录下,注意和eclipse的不一样。
打开这个文件发现就是一个接口(可能会默认生成一个basicTypes方法,这是示例方法,不用管,能够删掉),而后在里面定义一个本身的方法(须要其余的方法的话本身看着加)

代码以下:

interface IMyAidlInterface {
    void testMethod(String str);
}

二、编译项目

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

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

三、分析 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类,有一个自定义的方法。

四、修改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。

五、修改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();
                }
            }
        });
    }
}


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

至此,实现了跨进程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通讯和同应用多进程无本质区别。

七、跨进程下解注册回调

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里面的代码,流程套路基本相同,不相同的只是一些很简单的细节。



3、Binder链接池

经过上面的介绍,不难发现,一个Service对应一个Binder,实际项目总不能把全部逻辑都写在一块儿的,不一样业务逻辑是要分类的,不免会出现多个Binder的状况,总不能一个Binder对应一个Service,这时,就可使用Binder链接池了。

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

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

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

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

一、首先准备相应的类:新建一个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类是后面要讲的),那样的话结构上更加好看合理,示例的最终代码是之内部类的方式来写的。

二、编写链接池代码

链接池就是一个普通的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单例对象的时候不能在主线程里面。

三、使用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调用远程服务端的方法等。

四、测试

经过测试代码能够知道,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类


4、使用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;
}


5、本文的示例源码地址

https://github.com/developerzjy/ServiceDemo

相关文章
相关标签/搜索