前言
最近因为公司项目需求,需要远程调度启动客户端输入法输入内容。
这就是大致的需求流程,这篇首先讲远程与服务控制端通讯。首先控制服务端定义好一个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类中来处理返回的数据了。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。
本文向大家介绍Android Retrofit的简单介绍和使用,包括了Android Retrofit的简单介绍和使用的使用技巧和注意事项,需要的朋友参考一下 Retrofit与okhttp共同出自于Square公司,retrofit就是对okhttp做了一层封装。把网络请求都交给给了Okhttp,我们只需要通过简单的配置就能使用retrofit来进行网络请求了,其主要作者是Android大神Ja
本文向大家介绍MongoDB学习笔记之GridFS使用介绍,包括了MongoDB学习笔记之GridFS使用介绍的使用技巧和注意事项,需要的朋友参考一下 GridFS简介 GridFS是MongoDB中的一个内置功能,可以用于存放大量小文件。 GridFS使用 MongoDB提供了一个命令行工具mongofiles可以来处理GridFS, 列出所有文件: 上传一个文件: 下载一个文件: 查找文件:
本文向大家介绍Android popupwindow简单使用方法介绍,包括了Android popupwindow简单使用方法介绍的使用技巧和注意事项,需要的朋友参考一下 先看下效果 1.首页 2.首页布局 3.popupwindow布局,可根据情况自行布局,这里是demo布局 4.popupwindow条目布局 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持呐喊教程。
本文向大家介绍angular2使用简单介绍,包括了angular2使用简单介绍的使用技巧和注意事项,需要的朋友参考一下 让我们从零开始,使用Typescript构建一个超级简单的 AngularJs 2应用。 先跑一个DEMO 运行这个 DEMO先来感受一下 AngularJS2 的应用。 下面是这个应用的文件结构 总结来说就是一个 index.html 文件和两个在 app 文件下的 Types
本文向大家介绍Android学习笔记——Menu介绍(一),包括了Android学习笔记——Menu介绍(一)的使用技巧和注意事项,需要的朋友参考一下 背景: Android3.0(API level 11)开始,Android设备不再需要专门的菜单键。 随着这种变化,Android app应该取消对传统6项菜单的依赖。取而代之的是提供anction bar来提供基本的用户功能。
本文向大家介绍Android学习笔记——Menu介绍(二),包括了Android学习笔记——Menu介绍(二)的使用技巧和注意事项,需要的朋友参考一下 知识点: 这次将继续上一篇文章没有讲完的Menu的学习,上下文菜单(Context menu)和弹出菜单(Popup menu)。 上下文菜单 上下文菜单提供对UI界面上的特定项或上下文框架的操作,就如同Windows中右键菜单一样。 在Andro