首页 > 代码库 > Kivy A to Z -- 如何从python代码中直接访问Android的Service
Kivy A to Z -- 如何从python代码中直接访问Android的Service
在Kivy中,通过pyjnius扩展可以间接调用Java代码,而pyjnius利用的是Java的反射机制。但是在Python对象和Java对象中转来转去总让人感觉到十分别扭。好在android提供了binder这个进程间通信的功能,Java中的Service也是基于Binder的C++代码封装来实现进程间通信的,这也为从Python代码中绕开pyjnius直接访问Java代码提供了可能,既然Java的Service是基于C++的封装来实现的,也同样可以在Python中封装同样的C++代码,这篇文章讲解了如何通过binder在Python代码中直接访问Java的Service,如WifiService。
binder_wrap.h
#ifndef BINDER_WRAP_H #define BINDER_WRAP_H #ifdef __cplusplus extern "C" { #endif typedef int (*vector_visitor)(const char16_t* str16,int length,void *data); typedef int (*fnOnTransact)(uint32_t code,const void *data,void *reply,uint32_t flags,void *userData); int server_create(const char *name,const char *descriptor,fnOnTransact onTrans,void *data); void* binder_getbinder(const char *name); int binder_releasebinder(void* binder); int binder_listServices(vector_visitor visitor,void *data); int binder_getInterfaceDescriptor(void *binder,char16_t *descriptor,size_t size); int binder_transact(void* binder,int code,const void *data,void* reply,int flags); void* parcel_new(); int parcel_destroy(void* parcel); int parcel_writeInterfaceToken(void* parcel,const char *interface); int parcel_writeInt32(void *parcel,int val); int parcel_writeCString(void *parcel,const char* str); int parcel_writeString16(void *parcel,const char16_t* str, size_t len); int parcel_readInt32(void *parcel); long parcel_readInt64(void *parcel); int parcel_readString16(void *parcel,char16_t* str, size_t len); int parcel_readInplace(void *parcel,void* data, int len); int parcel_readExceptionCode(void *parcel); int parcel_dataAvail(void *parcel); #ifdef __cplusplus } #endif #endif
binder_wrap.cpp
#include <sys/types.h> #include <unistd.h> #include <grp.h> #include <binder/IPCThreadState.h> #include <binder/ProcessState.h> #include <binder/IServiceManager.h> #include <utils/Log.h> #include <binder/Parcel.h> #include "binder_wrap.h" using namespace android; void* binder_getbinder(const char *name) { android::sp<android::IServiceManager> sm = android::defaultServiceManager(); sp<IBinder> *binder = new sp<IBinder>(); do { *binder = sm->getService(android::String16(name)); if (binder != 0) { break; } usleep(500000); // 0.5 s } while(true); return reinterpret_cast<void *>(binder); } int binder_releasebinder(void* binder) { sp<IBinder> *bp = reinterpret_cast<sp<IBinder> *>(binder); if(bp == 0) { return 0; } delete bp; return 1; } //Vector<String16> listServices() = 0; int binder_listServices(vector_visitor visitor,void *data) { android::sp<android::IServiceManager> sm = android::defaultServiceManager(); Vector<String16> list = sm->listServices(); for (int i=0;i<list.size();i++) { visitor(list[i].string(),list[i].size(),data); } return list.size(); } int binder_getInterfaceDescriptor(void *binder,char16_t *descriptor,size_t size) { sp<IBinder> *bp = reinterpret_cast<sp<IBinder> *>(binder); if(bp == 0) { return 0; } if (descriptor == NULL || size <= 0) { return 0; } String16 des = (*bp)->getInterfaceDescriptor(); if (size > des.size()) { size = des.size(); } memcpy(descriptor,des.string(),size*2); return size; } //int binder_transact(void* binder,int code,const Parcel& data,Parcel* reply,int flags = 0) int binder_transact(void* binder,int code,const void *data,void* reply,int flags) { sp<IBinder> *bp = reinterpret_cast<sp<IBinder> *>(binder); if(bp == 0 || data =http://www.mamicode.com/= 0 || reply == 0)>
正如代码中所示,这里对C++的IBinder和Parcel两个对象进行了封装,而Java的Service的底层实现也正是对这两个类进行封装的结果,具体的可以看
frameworks\base\core\jni\android_util_Binder.cpp
的代码,
再来看下如何在Python中使用这些代码,这里用cython来封装这些C接口:
binder.pyx
cdef extern from "utils/Unicode.h": ctypedef short char16_t ctypedef unsigned int uint32_t cdef extern from "Python.h": ctypedef short Py_UNICODE ctypedef size_t Py_ssize_t object PyString_FromStringAndSize(const char *v, Py_ssize_t len) int PyString_AsStringAndSize(object obj, char **buffer, Py_ssize_t *length) object PyUnicode_FromUnicode(const Py_UNICODE *u, Py_ssize_t size) Py_UNICODE* PyUnicode_AS_UNICODE(object) Py_ssize_t PyUnicode_GetSize(object) void Py_INCREF(object) void Py_DECREF(object) cdef extern from "binder_wrap.h": ctypedef int (*vector_visitor)(const char16_t* str16,int length,void *data) int binder_listServices(vector_visitor visitor,void *data) ctypedef int (*fnOnTransact)(uint32_t code,const void *data,void *reply,uint32_t flags,void *userData) int server_create(const char *name,const char *descriptor,fnOnTransact onTrans,void *data) void* binder_getbinder(const char *name) int binder_releasebinder(void* binder) int binder_getInterfaceDescriptor(void *binder,char16_t *descriptor,int size) int binder_transact(void* binder,int code,const void *data,void* reply,int flags) void* parcel_new() int parcel_destroy(void* parcel) int parcel_writeInterfaceToken(void* parcel,const char *interface) int parcel_writeInt32(void *parcel,int val) int parcel_writeCString(void *parcel,const char* str) int parcel_writeString16(void *parcel,const char16_t* str, size_t len) int parcel_readInt32(void *parcel) int parcel_readInt64(void *parcel) int parcel_readString16(void *parcel,char16_t* str, size_t len) int parcel_readExceptionCode(void *parcel) int parcel_readInplace(void *parcel,void* data, int len) int parcel_dataAvail(void *parcel) cdef int visitor(const char16_t* str16,int length,void *data): arr = <object>data o = PyUnicode_FromUnicode(<Py_UNICODE*>str16,length) arr.append(o) def listServices(): arr = [] Py_INCREF(arr) binder_listServices(visitor,<void *>arr) Py_DECREF(arr) return arr cdef class Binder: cdef void *ptr def __cinit__(self,char *name): #, sp[IBinder] service): self.ptr = binder_getbinder(name) def __dealloc__(self): binder_releasebinder(self.ptr) def getInterfaceDescriptor(self): cdef char16_t descriptor[256] cdef int ret ret = binder_getInterfaceDescriptor(self.ptr,descriptor,sizeof(descriptor)) if not ret: return None return PyUnicode_FromUnicode(<Py_UNICODE*>descriptor,ret) def transact(self,int code,data,reply,int flags): cdef int dataPtr = data.getNativePtr() cdef int replyPtr = reply.getNativePtr() binder_transact(self.ptr,code,<void *>dataPtr,<void*>replyPtr,flags) return reply cdef class Parcel: cdef void *ptr cdef int nativePtr def __cinit__(self,unsigned int nativePtr=0): #, sp[IBinder] service): self.nativePtr = nativePtr if not nativePtr: self.ptr = parcel_new() else: self.ptr = <void *>nativePtr def __dealloc__(self): if not self.nativePtr: parcel_destroy(self.ptr) def getNativePtr(self): return <int>self.ptr def writeInterfaceToken(self,const char *interface): return parcel_writeInterfaceToken(<void *>self.ptr,interface) def writeInt(self,int val): self.writeInt32(val) def writeInt32(self,int val): return parcel_writeInt32(<void *>self.ptr,val) def writeCString(self,const char* cstr): return parcel_writeCString(<void *>self.ptr,cstr) def writeString16(self,ustr): cdef char16_t *un cdef int size if isinstance(ustr,unicode): un = <char16_t*>PyUnicode_AS_UNICODE(ustr) size = PyUnicode_GetSize(ustr) return parcel_writeString16(<void *>self.ptr,un,size) def readInt32(self): return parcel_readInt32(self.ptr) def readInt(self): return self.readInt32() def readInt64(self): return parcel_readInt64(self.ptr) def readExceptionCode(self): return parcel_readExceptionCode(self.ptr) def readString16(self): cdef char16_t str16[256] cdef int ret ret = parcel_readString16(self.ptr,str16,sizeof(str16)) if not ret: return None return PyUnicode_FromUnicode(<Py_UNICODE*>str16,ret) def readByteArray(self): return self.createByteArray() def createByteArray(self): length = self.readInt() print 'createByteArray:',length return self.readInplace(length) # int parcel_readInplace(void *parcel,void* data, size_t len) def readInplace(self,length): cdef char arr[512] ret = parcel_readInplace(self.ptr,arr,length) if ret == length: return PyString_FromStringAndSize(arr,length) else: return None # int parcel_dataAvail(void *parcel) def dataAvail(self): return parcel_dataAvail(self.ptr) def createTypedArrayList(self,creator): N = self.readInt() if N <= 0: return None arr = [] for i in range(N): if self.readInt() == 0: continue else: result = creator.createFromParcel(self) arr.append(result) return arr @classmethod def obtain(cls): return Parcel() @classmethod def recycle(cls): pass
好,再来看看如何来实现访问WifiService的功能:WifiService.py
from binder import Binder,Parcel WIFI_SERVICE = "wifi"; DESCRIPTOR = "android.net.wifi.IWifiManager"; FIRST_CALL_TRANSACTION = 1 TRANSACTION_getConfiguredNetworks = (FIRST_CALL_TRANSACTION + 0); TRANSACTION_addOrUpdateNetwork = (FIRST_CALL_TRANSACTION + 1); TRANSACTION_removeNetwork = (FIRST_CALL_TRANSACTION + 2); TRANSACTION_enableNetwork = (FIRST_CALL_TRANSACTION + 3); TRANSACTION_disableNetwork = (FIRST_CALL_TRANSACTION + 4); TRANSACTION_pingSupplicant = (FIRST_CALL_TRANSACTION + 5); TRANSACTION_startScan = (FIRST_CALL_TRANSACTION + 6); TRANSACTION_getScanResults = (FIRST_CALL_TRANSACTION + 7); TRANSACTION_disconnect = (FIRST_CALL_TRANSACTION + 8); TRANSACTION_reconnect = (FIRST_CALL_TRANSACTION + 9); TRANSACTION_reassociate = (FIRST_CALL_TRANSACTION + 10); TRANSACTION_getConnectionInfo = (FIRST_CALL_TRANSACTION + 11); TRANSACTION_setWifiEnabled = (FIRST_CALL_TRANSACTION + 12); TRANSACTION_getWifiEnabledState = (FIRST_CALL_TRANSACTION + 13); TRANSACTION_setCountryCode = (FIRST_CALL_TRANSACTION + 14); TRANSACTION_setFrequencyBand = (FIRST_CALL_TRANSACTION + 15); TRANSACTION_getFrequencyBand = (FIRST_CALL_TRANSACTION + 16); TRANSACTION_isDualBandSupported = (FIRST_CALL_TRANSACTION + 17); TRANSACTION_saveConfiguration = (FIRST_CALL_TRANSACTION + 18); TRANSACTION_getDhcpInfo = (FIRST_CALL_TRANSACTION + 19); TRANSACTION_acquireWifiLock = (FIRST_CALL_TRANSACTION + 20); TRANSACTION_updateWifiLockWorkSource = (FIRST_CALL_TRANSACTION + 21); TRANSACTION_releaseWifiLock = (FIRST_CALL_TRANSACTION + 22); TRANSACTION_initializeMulticastFiltering = (FIRST_CALL_TRANSACTION + 23); TRANSACTION_isMulticastEnabled = (FIRST_CALL_TRANSACTION + 24); TRANSACTION_acquireMulticastLock = (FIRST_CALL_TRANSACTION + 25); TRANSACTION_releaseMulticastLock = (FIRST_CALL_TRANSACTION + 26); TRANSACTION_setWifiApEnabled = (FIRST_CALL_TRANSACTION + 27); TRANSACTION_getWifiApEnabledState = (FIRST_CALL_TRANSACTION + 28); TRANSACTION_getWifiApConfiguration = (FIRST_CALL_TRANSACTION + 29); TRANSACTION_setWifiApConfiguration = (FIRST_CALL_TRANSACTION + 30); TRANSACTION_startWifi = (FIRST_CALL_TRANSACTION + 31); TRANSACTION_stopWifi = (FIRST_CALL_TRANSACTION + 32); TRANSACTION_addToBlacklist = (FIRST_CALL_TRANSACTION + 33); TRANSACTION_clearBlacklist = (FIRST_CALL_TRANSACTION + 34); TRANSACTION_getWifiServiceMessenger = (FIRST_CALL_TRANSACTION + 35); TRANSACTION_getWifiStateMachineMessenger = (FIRST_CALL_TRANSACTION + 36); TRANSACTION_getConfigFile = (FIRST_CALL_TRANSACTION + 37); TRANSACTION_captivePortalCheckComplete = (FIRST_CALL_TRANSACTION + 38); mRemote = Binder(WIFI_SERVICE) def transact(TRANSACTION): _data = http://www.mamicode.com/Parcel()>
目前并没有实现所有的WifiService的功能,但是像startScan,getScanResults,setWifiEnabled,getWifiEnabledState,getDhcpInfo,setWifiApEnabled这些主要的接口已经实现了,其它接口没有实现并非是因为不能实现,而是比较繁琐,暂时未实现而己,后面会不断的完善。
再来看下测试代码:
test.py
import WifiService WifiService.setWifiEnabled(True) WifiService.startScan(True) print WifiService.pingSupplicant() print WifiService.getConfigFile() for i in range(10): time.sleep(1.0) result = WifiService.getScanResults() if result: print result break执行后将会打印出搜索到的Wifi信息。
另外就是代码的编译问题了。代码必须在android的源代码下进行编译。我试过在ndk上进行编译,经过一番努力,通过链接事先编译好的C++ binder库,也成功编译通过,但是程序不能正常运行,这应该是预先编译出来的库和ndk的库存在兼容性问题造成的,或许通过在ndk上编译binder库可以避免这个问题,但是目前还没有作过尝试。 但是编译出来的代码应该可以运行在各个不同的版本,我在4.0和4.2版本的设备上作了简单的测试,事实证明在4.2上编译的代码可以在4.0上运行,但是考虑到android的诸多版本,各个版本多多少少有些兼容性问题,更详细的还必须比较各个版本的binder代码,并通过测试才能得到结果。
我已经在github上创建了一个叫pydroid的项目,该项目的目的是希望在android平台上充分发挥python的开发优势,结合android上的python图形库kivy,希望能让在android平台上开发应用程序变得更加简单,更加有趣。当然,这个项目能走多远还不得而知,