Android之Binder的简单使用

作者: 学海摆渡人 | 来源:发表于2016-12-24 00:02 被阅读868次

    由于公司项目需求,需要远程调度启动客户端输入法输入内容。

    Paste_Image.png

    这就是大致的需求流程,这篇首先讲远程与服务控制端通讯。首先控制服务端定义好一个Service,且在ServiceManager注册添加服务。

    在这里我讲解远程端与服务控制端通讯(主要通过C++往ServiceManager注册服务)。

    首先我们得获取到服务控制端注册在ServiceManager的服务IBinder对象,通过Java反射机制获得Ibinder接口对象。

        public static IBinder getRemoteBinder(){
            try {
                Class<?> serviceManager = Class.forName("android.os.ServiceManager");
                Method getService = serviceManager.getMethod("getService", String.class);
                IBinder iBinder = (IBinder) getService.invoke(serviceManager.newInstance(), "InputService");
                
                if(iBinder==null){
                    Log.e(PinyinIME.TAG,"getService InputService : is empty");
                    printServerList();//打印系统所提供的所有服务
                }
                return iBinder;
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
            return null;
        }
    
    //具体源码在android.os.ServiceManager
        /**
         * Returns a reference to a service with the given name.
         * 
         * @param name the name of the service to get
         * @return a reference to the service, or <code>null</code> if the service doesn't exist
         */
        public static IBinder getService(String name) {
            try {
                IBinder service = sCache.get(name);
                if (service != null) {
                    return service;
                } else {
                    return getIServiceManager().getService(name);
                }
            } catch (RemoteException e) {
                Log.e(TAG, "error in getService", e);
            }
            return null;
        }
    

    获取到IBinder对象作用是跨进程,举个例子,输入法程序是怎么和应用编辑框通讯的呢?怎么通过什么控制输入法弹起隐藏的呢。也是通过这个IBinder来通讯的,不信你翻翻源码,这里不做详细介绍。

    而服务控制端则是由C++层注入服务:

    class IServiceManager : public IInterface
    {
    public:
        DECLARE_META_INTERFACE(ServiceManager);
    
        /**
         * Retrieve an existing service, blocking for a few seconds
         * if it doesn't yet exist.
         */
        virtual sp<IBinder>         getService( const String16& name) const = 0;
    
        /**
         * Retrieve an existing service, non-blocking.
         */
        virtual sp<IBinder>         checkService( const String16& name) const = 0;
    
        /**
         * Register a service.
         */
        virtual status_t            addService( const String16& name,
                                                const sp<IBinder>& service,
                                                bool allowIsolated = false) = 0;
    
        /**
         * Return list of all existing services.
         */
        virtual Vector<String16>    listServices() = 0;
    
        enum {
            GET_SERVICE_TRANSACTION = IBinder::FIRST_CALL_TRANSACTION,
            CHECK_SERVICE_TRANSACTION,
            ADD_SERVICE_TRANSACTION,
            LIST_SERVICES_TRANSACTION,
        };
    };
    
    //上面C++层注册服务提供一个IBinder接口子类,需要实现onTransact方法
        virtual status_t onTransact(uint32_t code,
            const Parcel& data,
            Parcel* reply,
            uint32_t flags = 0)
        {
            LOGD("enter MyService onTransact and the code is %d", code);
            switch (code)
            {
            case BINDER_HANDLE:
                LOGD("MyService interface handle");
                reply->writeCString("handle reply");
                break;
            case BINDER_SET_SCREEN:
                LOGD("MyService interface set screen");
                reply->writeCString("set screen reply");
                break;
            case BINDER_SET_CHAR:
            {//call cb
                LOGD("MyService interface set char before");
                reply->writeCString("set char reply");
                cb = data.readStrongBinder();
                if (cb != NULL)
                {
                    LOGD("MyService interface set char : %s", data.readCString());
                    Parcel in, out;
                    in.writeInterfaceToken(String16(BINDER_NAME));
                    in.writeInt32(n++);
                    in.writeString16(String16("This is a string."));
                    cb->transact(1, in, &out, 0);
                    show();
                }
                break;
            }
            default:
                return BBinder::onTransact(code, data, reply, flags);
            }
            return 0;
        }
    

    这样我们可以通过刚刚获取到IBinder对象与之通讯了,这里我只讲个例子:
    当远程端设备输入法激活的时候,我将传递输入法输入类型和输入法展示的多功能键传递给服务控制端。

     //当输入法被激活的时候,会调用onStartInputView(EditorInfo,boolean)
            Parcel data = Parcel.obtain();
            data.writeInt(editorInfo.inputType);
            data.writeInt(editorInfo.imeOptions);
            
            Log.d(TAG, "isActives:" + isActives);
            
            if (isActives) {
                if (mController != null) {
                    mController.startInput(data, Parcel.obtain());
                } else {
                    isNeedActives = true;
                    tmp = data;
                    mController = new Controller(remoteBinder,this);
                }
            } else {
                isNeedActives = true;
                tmp = data;
                if (mController != null) {
                    mController.serviceConnection();
                } else {
                    mController = new Controller(remoteBinder,this);
                }
            }
    
    //这里我将两个int参数写入到Parce对象中开始进行通讯
    
    /**
         * 开始输入
         * 
         * @param data
         *            写入输入类型和多功能
         * @param reply
         */
        public void startInput(final Parcel data, final Parcel reply) {
            Log.d(PinyinIME.TAG, getClass().getName() + ":\t startInput");
    
            if (!PinyinIME.isActives) {
                Log.d(PinyinIME.TAG, "not yet check success , start input failure");
                dealHandler.sendEmptyMessage(Constant.HANDER_RELINK);
                return;
            }
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    if (remoteBinder != null && remoteBinder.isBinderAlive()) {
                        try {
                            if (remoteBinder.transact(
                                    Constant.INPUT_METHOD_ACTIVATION, data, reply,
                                    IBinder.FLAG_ONEWAY)) {
                                PinyinIME.isNeedActives = false;
                                Log.d(PinyinIME.TAG,
                                        "input method to activate, notify the success");
                            } else {
                                Log.d(PinyinIME.TAG,
                                        "input method to activate, notify the failure");
                            }
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        } finally {
                            data.recycle();
                            reply.recycle();
                        }
                    }else{
                        dealHandler.sendEmptyMessage(Constant.HANDER_RELINK);
                    }
                }
            }).start();
        }
    

    这样我们就可以通过获取到的Ibinder对象的transact方法进行通讯。

    //code必须双方定义好,否则接收数据无法正常,
    //第一个是我们装载的序列化数据,
    //第二我们可以直接传个对象,最好一个是需要返回结果的标识,
    //0代表需要返回内容,FLAG_ONEWAY单方面无需返回结果的标识
    
    public boolean transact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException;
    
    

    当我们调用了ibinder.transact(int,parce,parce,int)方法,这是注册的服务中的IBinder对象的onTransact(int,parce,parce,int)方法就会被响应,这样我们就实现了远程端跟服务控制端通讯了。

    到了这里,有个问题,服务控制端接收到客户端输入的内容咋办,怎通知远程端输入法输入内容到编辑框中呢。

    其实也很简单,我们只需要在远程端输入法程序实现一个Ibinder对象,传递给服务控制端,这样就可以实现,具体怎么传递了?

    //首先我们得让远程输入法程序拥有属于自己的ibinder类。
    package com.redfinger.inputmethod.server;
    
    import com.android.inputmethod.pinyin.PinyinIME;
    
    import android.annotation.SuppressLint;
    import android.os.Binder;
    import android.os.IBinder;
    import android.os.IInterface;
    import android.os.Parcel;
    import android.os.RemoteException;
    import android.util.Log;
    import android.view.KeyEvent;
    
    public interface InputBinder extends IInterface{
        
        public static class Stub extends Binder implements InputBinder{
            private static final java.lang.String DESCRIPTOR = "com.redfinger.inputmethod.service.InputBinder";
            public PinyinIME pinyinIME;
            
            public Stub(PinyinIME pinyinIME) {
                this.pinyinIME = pinyinIME;
                this.attachInterface(this, DESCRIPTOR);
            }
            
            public InputBinder asInterface(IBinder obj){
                if(obj == null){
                    return null;
                }
                IInterface iInterface = obj.queryLocalInterface(DESCRIPTOR);
                if(iInterface!=null&&iInterface instanceof InputBinder){
                    return (InputBinder)iInterface;
                }
                return new Stub.Proxy(obj);
            }
            
            @Override
            public IBinder asBinder() {
                return this;
            }
            
            @SuppressLint({ "NewApi", "Recycle" })
            @Override
            protected boolean onTransact(int code, Parcel data, Parcel reply,
                    int flags) throws RemoteException {
                switch (code) {
                case Constant.CONNECTION_HANDSHAKE2:
                    String dataString = data.readString();
                    Log.d(PinyinIME.TAG, "The second handshake start [data = "+dataString +"]");
                    if("CONNECTION_RESPONED".equals(dataString)){                                   
                        Parcel parcel = Parcel.obtain();
                        parcel.writeString("CONNECTION_FINISH");
                        pinyinIME.getRemoteBinder().transact(Constant.CONNECTION_HANDSHAKE3, parcel, Parcel.obtain(), IBinder.FLAG_ONEWAY);
                        PinyinIME.isActives = true;
                        Log.d(PinyinIME.TAG, "The third handshake success");
                        
                        if (PinyinIME.isNeedActives) {
                            PinyinIME.mController.startInput(pinyinIME.getTmp(), Parcel.obtain());
                        }
                        if (PinyinIME.isNeedCloseInputMethod) {
                            PinyinIME.mController.finishInput();
                        }
                        
                    }else{
                        Log.d(PinyinIME.TAG, "The third handshake failure , agent connect ! ");
                        PinyinIME.mController.serviceConnection();
                    }
                    break;
                case Constant.FUNCTION_INPUT:
                    ....
                    switch (keyCode) {
                    case 14:
                        pinyinIME.simulateKeyEventDownUp(KeyEvent.KEYCODE_DEL);
                        return true;
                    case 28:
                        pinyinIME.simulateKeyEventDownUp(KeyEvent.KEYCODE_ENTER);
                        return true;
                    case 65:
                        pinyinIME.requestHideSelfFromClient = true;
                        pinyinIME.requestHideSelf(0);
                        break;
                    }  
                    break;
                case Constant.CHARACTER_INPUT: 
                    ....
                    return true;
                case Constant.DISCONNECTION:
                    ....
                    break;
                case Constant.INPUT_METHOD_PLATFORM:
                    ....
                    break;
                }
                return super.onTransact(code, data, reply, flags);
            }
            
            public static class Proxy implements InputBinder{
                
                private android.os.IBinder mRemote;
                
                public Proxy(android.os.IBinder mRemote) {
                    this.mRemote = mRemote;
                }
                @Override
                public IBinder asBinder() {
                    return mRemote;
                }
                public java.lang.String getInterfaceDescriptor()
                {
                    return DESCRIPTOR;
                }
            }
            static final int receiveChar = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        }
    }
    

    是不是特变像AIDL文件的内容一样,aidl其实就是Android自己给我写好的ibinder代码一样。

    这样我们就可以在获取到服务控制端ibinder对象中写入我们自己ibinder对象,传递过去让他通过transact方法来与输入法程序ibinder对象通讯了。

      //Parce类中提供了这样的一个方法,就是用于写入ibinder对象的。
        public final void writeStrongBinder(IBinder val) {
            nativeWriteStrongBinder(mNativePtr, val);
        }
    

    这样我们就可以在InputBinder类中来处理返回的数据了。

    下篇讲解如何通过自定义View来接收输入法输入的内容。

    相关文章

      网友评论

        本文标题:Android之Binder的简单使用

        本文链接:https://www.haomeiwen.com/subject/pphlvttx.html