zoukankan      html  css  js  c++  java
  • android之Service之Binder学习

    一、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

      

      

     

  • 相关阅读:
    linux下面安装maven
    go 安装
    linux scp 服务器远程拷贝
    git有merge时如何删除分支
    influxdb ERR: error parsing query: found -, expected
    influxDB学习总结
    influxdb Measurements
    go exec: "gcc": executable file not found in %PATH%
    php计算脚本执行时间
    安装nodejs和grunt以后出现 /usr/bin/env: node: No such file or directory
  • 原文地址:https://www.cnblogs.com/chuiyuan/p/4808919.html
Copyright © 2011-2022 走看看