android之Service之Binder学习

情到浓时终转凉″ 提交于 2019-12-30 22:03:25

一、Binder框架

Binder用于完成进程间通信(IPC),比如普通应用程可以调用音乐服务,它工作在内核态,属于一个驱动,只是这个驱动要用的“硬件”是内存。

Binder架构由三个模块构成:服务端接口,Binder驱动,客户端接口。我们分开来看:

  • 服务端

一个Binber服务端实际上是一个Binder类的对象,且一旦创建,内部就启动一个隐藏线程,用来接收Binder驱动发送的消息,收到消息后,会执行Binder对象中的onTransact()方法,并执照不同的参数执行不同的服务代码,所以实现一个Binder对象一定要重载onTransact()方法,onTransact()方法的参数来自于客户端调用transact()方法时的输入,两者的格式和顺序一定要一样。

  • Binder驱动

当服务端Binder对象创建时,同时会在Binder驱动中创建一个mRemote对象(这里不会再有新的线程),mRemote也是Binder类。当客户端访问远程服务时,是通过 mRemote对象 。

  • 客户端

要访问服务端,先得到远程对象对应的mRemote对象,然后就可以调用其transact()方法,而在Binder驱动中,mRemote对象也重载了transact()方法,重载的内容重要是:

1.以线程间通信的模式,向服务端发送客户端传递过来的参数。

2.挂起当前线程(客户端线程),并等待服务端后的通知(notify)。

3.接收到服务端线程的能通知,然后继续执行客户端线程。

 二、Server端的设计 

只要基于Binder类新建一个Server类就可以了,如下

public class MusicPlayerService extends Binder {
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        return super.onTransact(code, data, reply, flags);
    }
    
    public void start(String filePath){
        
    }
    public void stop (){
        
    }
}

要启动服务,只要初始化一个MusicPlayerService就可,如要Activity中初始化,会在ddms中看到多了一个线程。

  定义了服务类后,就要重载onTransact()方法,并从data中读取出客户端发送来的参数,比如start()中的filePath,这里有一个服务端与客户端约定的问题。

  假如客户端传入的第一个参数是filePath,则

 @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        //code表示想调用服务端的那个方法
        switch (code) {
            case 1000:
                //一种校验
                data.enforceInterface("MusicPlayerService");
                String filePath = data.readString();
                start(filePath);
                break;
        }
        return super.onTransact(code, data, reply, flags);
    }

如果IPC调用希望返回一 些结果,可以在返回的paracel reply中写入结果。

三、Binder客户端设计 

要想使用服务端,先要得到服务端在Binder驱动中对应的mRemote引用(怎么得到见后面),然后再调用mRemote变量的transact()方法。原型如下

 

public final boolean transact(int code, Parcel data, Parcel reply, int flags)

 

调用这个方法后,客户端线程进入Binder驱动,Binder驱动会挂起客户端线程,并向远程服务发送一个消息,消息中有客户端传递的参数,服务端执行完后,向Binder驱动发送一个notify消息,从而使客户端线程从Binder驱动代码区返回到客户端代码区。最后客户端就可以从reply中解析返回的数据了。

 

四、使用Service类

手写Binder服务端和客户端的过程有两个问题:

第一,客户端如何得到服务端的Binder引用(使用Service)

第二,客户端与服务端一定要事先约好两件事情(用aidl工具)

(1)服务端函数的参数的顺序。

(2)服务端不同的函数的int标识。

使用Binder是想提一个全局的服务,也就是在系统中的任何地方都可以访问。我们还有一个更傻瓜的方法:Service .

  • 如何得到Binder对象 

对于高手 ,完全可以不使用Service类,而只要使用Binder编写服务程序,但只是一部分。因为可以基于Binder类扩展系统服务,而客户端服务则一定要基于Service.这里的系统服务是指可以使用getSystemService()得到的服务,客户端服务是指app自定义的服务。

AmS提供了startService()来启动客户服务,对于客户端,可以使用下面的两个方法来与一个服务建立连接:

public Component startService(Intent intent )

这里只是启动了intent指定的服务,还没有得到服务端的Binder引用,所以还不能调用服务端的功能。

public boolean bindService (Intent service, ServiceConnection conn, int flags)

可以用于绑定一个服务,这是第一个问题的关键所在,里面的第二个参数是一个interface.

public interface ServiceConnection {
    /**
     * Called when a connection to the Service has been established, with
     * the {@link android.os.IBinder} of the communication channel to the
     * Service.
     *
     * @param name The concrete component name of the service that has
     * been connected.
     *
     * @param service The IBinder of the Service's communication channel,
     * which you can now make calls on.
     */
    public void onServiceConnected(ComponentName name, IBinder service);

    public void onServiceDisconnected(ComponentName name);
}

在客户端中,可以在onServiceConnected()方法中将参数service保存为一个全局变量,从而在客户端的任何地方都可以调用远程服务。  

 五、例子

工程目录如

先创建Fruit类

/**
 * Created by lsj on 2015/9/15.
 */
public class Fruit implements Parcelable{
    private String name ;
    private String color ;
    private int number ;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public static final Creator<Fruit> CREATOR = new Creator<Fruit>() {
        @Override
        public Fruit createFromParcel(Parcel source) {
            Fruit fruit = new Fruit();
            fruit.name = source.readString() ;
            fruit.color = source.readString();
            fruit.number= source.readInt() ;
            return fruit;
        }

        @Override
        public Fruit[] newArray(int size) {
            return new Fruit[size];
        }
    };

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeString(color);
        dest.writeInt(number);
    }
}

Fruit.aidl如下

 

IFruitBinder.aidl如下

在点击build-->make project后,会在如下的目录中生成代码 IFuitBinder.java 

分析如下

Service类如下

public class FruitAidlService extends Service {
    private Fruit mFruit ;

    @Override
    public void onCreate() {
        super.onCreate();
        mFruit = new Fruit();
        mFruit.setName("apple");
        mFruit.setColor("red");
        mFruit.setNumber(10);
    }

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

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

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

    /**
     * 分析IFruitBinder.java文件中的:
     * (1)Stub :基于Binder类,实现了AIDL接口,主要同服务端来使用,定义为一个abstract类,因为具体的服务
     * 函数由程序员实现,所以aidl文件中定义的接口在stub类中并没有实现,同时Stub类中重载了onTransact
     * 方法,由于transact()方法内部给包裹写入参数的顺序是由aidl工具定义的,所以在onTransact()方法中,
     * AIDL工具自然知道按什么顺序从包裹中取出数据。
     * (2)Proxy类:客户端访问服务端的代理,所谓的代理主要 就是为了前面所提到的第二个重要的问题:统一包裹
     * 内写入数据的顺序。
     *
     * 这是是Server端,要实现真正的功能。
     */
    private IFruitBinder.Stub serviceBinder = new IFruitBinder.Stub() {
        @Override
        public String getInfo() throws RemoteException {
            return "I am a server";
        }

        @Override
        public Fruit getFruit() throws RemoteException {
            return mFruit;
        }
    } ;
}

定义一个测试类Activity

/**
 * Created by lsj on 2015/9/16.
 */
public class FruitAidlActivity extends BaseActivity {
    private Button btn ;
    private IFruitBinder iFruitBinder;

    private ServiceConnection sc = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iFruitBinder = IFruitBinder.Stub.asInterface(service);

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            iFruitBinder = null;
        }
    } ;

    @Override
    protected void findView() {
        setContentView(R.layout.activity_aidl);
    }

    @Override
    protected void initView() {
        btn = (Button)findViewById(R.id.aidl_button);
       // Intent serviceIntent = new Intent(this, FruitAidlService.class);
        //bindService(serviceIntent, sc,BIND_AUTO_CREATE);
    }

    @Override
    protected void setOnClickListener() {
        btn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.aidl_button:
                if (iFruitBinder == null){
                    Log.d(TAG, "ibinder == null");
                }else {
                    try{
                        String str = "getFruit:"+ iFruitBinder.getFruit().getName()+
                                iFruitBinder.getFruit().getColor()+ iFruitBinder.getFruit().getNumber();
                        Log.d(TAG, str);
                    }catch (RemoteException e ){
                        e.printStackTrace();
                    }
                }
                break;
            default:
                break;
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        Intent serviceIntent = new Intent(this, FruitAidlService.class);
        bindService(serviceIntent, sc, BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        unbindService(sc);
    }
}

备注

在aidl中使用自定义的类后,要修改app下的build.gradle文件,在android添加如下

//加了后消除aidl中自定义类时找不到的问题
    //参考 http://blog.csdn.net/wuyuxing24/article/details/46948961
    //http://stackoverflow.com/questions/16596352/android-studio-cant-find-an-aidl-interface-for-use-in-class
    //里面有google的文档
    sourceSets {
        main {
            //Manifest.srcFile 'src/main/AndroidManifest.xml'
            java.srcDirs = ['src/main/java', 'src/main/aidl']
            resources.srcDirs = ['src/main/java', 'src/main/aidl']
            aidl.srcDirs = ['src/main/aidl']
            res.srcDirs = ['src/main/res']
            assets.srcDirs = ['src/main/assets']
        }
    }

为什么android选用Binder来实现进程间通信?

一、可靠性。在移动设备上,通常采用基于Client-Server的通信方式来实现互联网与设备间的内部通信。目前linux支持IPC包括传统的管道,System V IPC,即消息队列/共享内存/信号量,以及socket中只有socket支持Client-Server的通信方式。Android系统为开发者提供了丰富进程间通信的功能接口,媒体播放,传感器,无线传输。这些功能都由不同的server来管理。开发都只关心将自己应用程序的client与server的通信建立起来便可以使用这个服务。毫无疑问,如若在底层架设一套协议来实现Client-Server通信,增加了系统的复杂性。在资源有限的手机 上来实现这种复杂的环境,可靠性难以保证。

二、传输性能。socket主要用于跨网络的进程间通信和本机上进程间的通信,但传输效率低,开销大。消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的一块缓存区中,然后从内核缓存区拷贝到接收方缓存区,其过程至少有两次拷贝。虽然共享内存无需拷贝,但控制复杂。比较各种IPC方式的数据拷贝次数。共享内存:0次。Binder:1次。Socket/管道/消息队列:2次。

三、安全性。Android是一个开放式的平台,所以确保应用程序安全是很重要的。Android对每一个安装应用都分配了UID/PID,其中进程的UID是可用来鉴别进程身份。传统的只能由用户在数据包里填写UID/PID,这样不可靠,容易被恶意程序利用。而我们要求由内核来添加可靠的UID。

所以,出于可靠性、传输性、安全性。android建立了一套新的进程间通信方式。

 

相关参考

binder机制的简要理解 

http://www.linuxidc.com/Linux/2012-07/66195.htm

binder机制的设计分析(1)

http://www.linuxidc.com/Linux/2015-01/111148.htm

binder机制的设计分析(2)

http://www.linuxidc.com/Linux/2012-07/66196p2.htm

binder的讲解(综合)

http://www.cnblogs.com/angeldevil/p/3296381.html

aidl中出现自定义类时

http://www.cnblogs.com/xilinch/archive/2012/07/16/2593236.html

  

  

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!