pyjnius库:一个用于访问Java类的Python库

参考链接:

https://pyjnius.readthedocs.io/en/stable/installation.html#installation-for-windows

1 Windows的安装

Python 和 pip 必须安装并显示在 PATH 环境变量中。

下载并安装包含 JRE 的 JDK:

Http://www.oracle.com/technetwork/java/javase/downloads/index.html

编辑系统的环境变量(在路径中使用适当的 Java 位和版本) :

  • JAVA_HOME: C:\Program Files\Java\jdk1.7.0_79\bin
  • PATH: C:\Program Files\Java\jdk1.7.0_79\jre\bin\server 包含导入和使用 PyJnius 所必需的 jvm.dll。

JAVA_HOME也添加到系统变量中,%JAVA_HOME%

下载并安装C语言编译器:

  1. Microsoft Visual C++ Compiler for Python 2.7:http://aka.ms/vcpython27

  2. MinGWPy for Python 2.7:https://anaconda.org/carlkl/mingwpy

  3. Microsoft Visual C++ Build Tools (command-line tools subset of Visual Studio) for Python 3.5 and 3.6:https://visualstudio.microsoft.com/downloads/

其他版本Python需要参照链接 Windows Compilers wiki.

更新pip和setuptools:

1
python -m pip install --upgrade pip setuptools

安装Cython:

1
python -m pip install --upgrade cython

安装Pyjnius:

1
pip install pyjnius

2 快速开始

一个简单的 Pyjnius 示例如下:

1
2
3
4
5
6
7
8
9
from jnius import autoclass

Stack = autoclass('java.util.Stack')
stack = Stack()
stack.push('hello')
stack.push('world')

print stack.pop() # --> 'world'
print stack.pop() # --> 'hello'

只需将它保存为 test.py (或类似的名称) ,并使用 Python 解释器运行它。确保不要调用应用程序 jnius.py,因为它会与 Pyjnius 本身发生冲突:

1
2
3
$ python test.py
world
hello

如果返回类型不是本机类型,Pyjnius使用Java反射为您提供一个新的autoclass()。让我们看看这个例子。

1
2
System = autoclass('java.lang.System')
System.out.println('Hello World')

我们只声明了第一个System类,但是我们能够自然地使用所有静态字段和方法。继续深入:

1
2
3
4
5
6
7
>>> System = autoclass('java.lang.System')
>>> System
<class 'jnius.java.lang.System'>
>>> System.out
<java.io.PrintStream at 0x234df50 jclass=java/io/PrintStream jself=37921360>
>>> System.out.println
<jnius.JavaMethodMultiple object at 0x236adb8>

递归反射始终为您提供一个反映返回的 Java 对象的适当对象。

3 API接口

3.1 反射类

class jnius.JavaClass

Java反射类的基础。这个想法是对这个 JavaClass 进行子类化,添加一些 JavaMethod、 JavaStaticMethod、 JavaField、 JavaStaticField,然后就完成了。

您至少需要定义__javaclass__属性,并将__metaclass__设置为 MetaJavaClass

最简单的定义应该像下面一样:

1
2
3
4
5
from jnius import JavaClass, MetaJavaClass

class Stack(JavaClass):
__javaclass__ = 'java/util/Stack'
__metaclass__ = MetaJavaClass

__metaclass__

属性必须设置为MetaJavaClass, 否则,声明的所有方法/字段将不会链接到 JavaClass

确保选择正确的元类说明符。在 Python 2中有 __metaclass__类属性,在Python 3中有一个新的语法类 Stack (JavaClass,metaclass = MetaJavaClass)。

__javaclass__

表示格式为‘ org/lang/Class’(例如‘ Java/util/Stack’)而不是‘ org.lang. Class’的 Java 类名。

__javaconstructor__

如果没有设置,我们假设缺省构造函数没有参数。否则,它可以是构造函数的所有可能签名的列表。例如,String 类的反射如下:

1
2
3
4
5
6
7
8
9
10
class String(JavaClass):
__javaclass__ == 'java/lang/String'
__metaclass__ = MetaJavaClass
__javaconstructor__ == (
'()V',
'(Ljava/lang/String;)V',
'([C)V',
'([CII)V',
# ...
)

class jnius.JavaMethod

方法的反射。

__init__(signature, static=False)

创建一个 Java 方法的反射。签名是 JNI 格式的。例如:

1
2
3
4
5
6
class Stack(JavaClass):
__javaclass__ = 'java/util/Stack'
__metaclass__ = MetaJavaClass

peek = JavaMethod('()Ljava/lang/Object;')
empty = JavaMethod('()Z')

与该方法关联的名称是从 JavaClass 本身的声明中自动设置的。

可以通过 javap -s找到签名。例如,如果您想获取 java.util.Stack可用的签名。:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ javap -s java.util.Stack
Compiled from "Stack.java"
public class java.util.Stack extends java.util.Vector{
public java.util.Stack();
Signature: ()V
public java.lang.Object push(java.lang.Object);
Signature: (Ljava/lang/Object;)Ljava/lang/Object;
public synchronized java.lang.Object pop();
Signature: ()Ljava/lang/Object;
public synchronized java.lang.Object peek();
Signature: ()Ljava/lang/Object;
public boolean empty();
Signature: ()Z
public synchronized int search(java.lang.Object);
Signature: (Ljava/lang/Object;)I
}

class jnius.JavaStaticMethod

静态java方法的反射。

class jnius.JavaField

Java字段的反射。

__init__(signature, static=False)

创建Java字段的反射。签名格式为JNI。

与方法关联的名称是在JavaClass本身的声明中自动设置的。

class jnius.JavaStaticField

Java静态字段的反射。

class jnius.JavaMultipleMethod

可以从多个签名调用的Java方法的反射。例如,可以调用String类中的getBytes方法

1
2
3
public byte[] getBytes(java.lang.String)
public byte[] getBytes(java.nio.charset.Charset)
public byte[] getBytes()

声明该方法的例子:

1
2
3
4
5
6
7
8
class String(JavaClass):
__javaclass__ = 'java/lang/String'
__metaclass__ = MetaJavaClass

getBytes = JavaMultipleMethod([
'(Ljava/lang/String;)[B',
'(Ljava/nio/charset/Charset;)[B',
'()[B'])

然后,当您尝试访问此方法时,它将根据您使用的参数类型选择可用的最佳方法。在内部,我们为每个可用的签名计算一个“匹配”分数,并选择最好的一个。在不深入细节的情况下,得分计算看起来是这样的:

  • 直接类型匹配为 + 10
  • 间接类型匹配(如使用 float 作为 int 参数)为 + 5
  • 类型未知的对象(JavaObject)是 + 1
  • 否则,将视为错误情况,并返回 -1

3.2 反射函数

jnius.autoclass

返回一个表示从名称传递的类的 JavaClass。名称必须使用 a.b.c 格式,而不是 a/b/c 格式。

1
2
3
>>> from jnius import autoclass
>>> autoclass('java.lang.System')
<class 'jnius.reflect.java.lang.System'>

Autoclass 也可以表示嵌套的 Java 类:

1
2
>>> autoclass('android.provider.Settings$Secure')
<class 'jnius.reflect.android.provider.Settings$Secure'>

有时候,Java 类包含一个 Python 关键字成员(如 from、 class 等)。您需要使用 getattr ()来访问这个成员,然后您就可以调用它:

1
2
3
from jnius import autoclass
func_from = getattr(autoclass('some.java.Class'), 'from')
func_from()

还有一种特殊情况,就是作为 Some Class.getClass ()的结果或者在 _javaclass_ python 属性中可以找到 Some Class.class 文本。

警告:目前SomeClass.getClass()返回一个不同的Python对象,因此要安全地比较Java中是否有相同的类,请使用A.hashCode() == B.hashCode()。

3.3 Python中Java类的实现

class jnius.PythonJavaClass

从 Python 类创建 Java 类的基础。这使我们可以完全用 Python 实现 Java 接口。

实际上,您将创建一个模仿声明的 _ _ javainterface _ _ 列表的 Python 类。当您将这个类的一个实例交给 Java 时,Java 只会接受它并按照声明调用接口方法。在底层,我们捕获调用,并将其重定向为使用声明的 Python 方法。

您的类将充当 Java 接口的代理。

您至少需要定义__javainterfaces__属性,并使用 java_method()装饰符声明 java 方法。

请注意,这个地方不支持静态方法和静态字段

例如,您可以在Python中实现Java/util/ListIterator接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from jnius import PythonJavaClass, java_method

class PythonListIterator(PythonJavaClass):
__javainterfaces__ = ['java/util/ListIterator']

def __init__(self, collection, index=0):
super(TestImplemIterator, self).__init__()
self.collection = collection
self.index = index

@java_method('()Z')
def hasNext(self):
return self.index < len(self.collection.data) - 1

@java_method('()Ljava/lang/Object;')
def next(self):
obj = self.collection.data[self.index]
self.index += 1
return obj

# etc...

__javainterfaces__

需要代理的 Java 接口的列表,格式为‘ org/lang/Class’(例如‘ Java/util/Iterator’) ,而不是‘ org.lang.Class’。

__javacontext__

指示要使用哪个类装入器,“ system”还是“ app”。默认值是“ system”。

  • 默认情况下,我们假设您将实现JavaAPI中声明的 Java 接口。它将使用“ system”类装入器。
  • 在 android 上,您在 APK 中提供的所有 Java 接口都不能通过系统类装入器访问,而是通过应用程序线程类装入器访问。因此,如果你想实现一个类,你已经在你的应用程序的接口,使用’应用程序’。

`jnius.java_method(java_signature, name=None)

用于 PythonJavaClass 的装饰函数。java_signature必须与接口的所需签名相匹配。默认情况下,该方法的名称将是 Python 方法的名称。在使用相同 Java 方法名称的多个签名的情况下,仍然可以强制执行。

例如:

1
2
3
4
5
6
7
8
class PythonListIterator(PythonJavaClass):
__javainterfaces__ = ['java/util/ListIterator']

@java_method('()Ljava/lang/Object;')
def next(self):
obj = self.collection.data[self.index]
self.index += 1
return obj

另一个具有相同 Java 方法名称但有两个不同签名的示例:

1
2
3
4
5
6
7
8
9
10
11
class TestImplem(PythonJavaClass):
__javainterfaces__ = ['java/util/List']

@java_method('()Ljava/util/ListIterator;')
def listIterator(self):
return PythonListIterator(self)

@java_method('(I)Ljava/util/ListIterator;',
name='ListIterator')
def listIteratorWithIndex(self, index):
return PythonListIterator(self, index)

3.4 Java签名格式

Java 签名有一种特殊的格式,一开始可能很难理解。我们来看看细节。签名的格式如下:

1
(<argument1><argument2><...>)<return type>

签名任何部分的所有类型都可以是:

L < Java class > ; = 表示类型为 < Java class > 的 Java 对象

Z = 表示 java/lang/Boolean;

B = 表示 java/lang/Byte;

C = 表示 java/lang/Character;

S = 表示 java/lang/Short;

I = 表示 java/lang/Integer;

J = 代表 java/lang/Long;

F = 表示 java/lang/Float;

D = 表示 java/lang/Double;

V = 表示 void,仅对返回类型可用

所有类型都可以有指示数组的[前缀。返回类型可以是 V 或空。

像这样的签名:

1
2
3
4
5
6
7
8
9
10
11
12
13
(ILjava/util/List;)V
-> 参数1是integer
-> 参数2是java.util.List对象
-> 这个方法不返回任何值

(java.util.Collection;[java.lang.Object;)V
-> 参数1Collection
-> 参数2是Object数组
-> 不返回任何值

([B)Z
-> 参数1是Byte数组
-> 返回boolean

当您用Python实现Java时,Java方法的签名必须匹配。Java提供了一个名为javap的工具来获取任何Java类的签名。

1
2
3
4
5
6
7
8
9
10
$ javap -s java.util.Iterator
Compiled from "Iterator.java"
public interface java.util.Iterator{
public abstract boolean hasNext();
Signature: ()Z
public abstract java.lang.Object next();
Signature: ()Ljava/lang/Object;
public abstract void remove();
Signature: ()V
}

3.5 JVM选项和类路径

在调用 import jnius 之前需要设置 JVM 选项,因为在 VM 启动之后不能更改这些选项。为此,你可以:

1
2
3
4
import jnius_config
jnius_config.add_options('-Xrs', '-Xmx4096')
jnius_config.set_classpath('.', '/usr/local/fem/plugins/*')
import jnius

If a classpath is set with these functions, it overrides any CLASSPATH environment variable. Multiple options or path entries should be supplied as multiple arguments to the add_ and set_ functions. If no classpath is provided and CLASSPATH is not set, the path defaults to ‘.’. This functionality is not available on Android.

如果用这些函数设置了类路径,它将覆盖任何 CLASSPATH 环境变量。多个选项或路径条目应作为多个参数提供给 add_ 和 set _ 函数。如果未提供类路径且未设置 CLASSPATH,则路径默认为‘.’ 。这个功能在Android上是不可用的。

3.6 Pyjnius和线程

jnius.detach()

每次在 Python 中创建本机线程并使用 Pyjnius 时,对 Pyjnius 方法的任何调用都会强制将本机线程附加到当前 JVM。但是你必须在离开线程之前把它分离出来,而 Pyjnius 不能为你做这件事。

例如:

1
2
3
4
5
6
7
8
import threading
import jnius

def run(...):
try:
# use pyjnius here
finally:
jnius.detach()

如果你不这样做,它将在 dalvik 和 ART/Android 上崩溃:

1
2
3
4
D/dalvikvm(16696): threadid=12: thread exiting, not yet detached (count=0)
D/dalvikvm(16696): threadid=12: thread exiting, not yet detached (count=1)
E/dalvikvm(16696): threadid=12: native thread exited without detaching
E/dalvikvm(16696): VM aborting

或者:

1
2
3
4
5
6
7
8
W/art     (21168): Native thread exiting without having called DetachCurrentThread (maybe it's going to use a pthread_key_create destructor?): Thread[16,tid=21293,Native,Thread*=0x4c25c040,peer=0x677eaa70,"Thread-16219"]
F/art (21168): art/runtime/thread.cc:903] Native thread exited without calling DetachCurrentThread: Thread[16,tid=21293,Native,Thread*=0x4c25c040,peer=0x677eaa70,"Thread-16219"]
F/art (21168): art/runtime/runtime.cc:203] Runtime aborting...
F/art (21168): art/runtime/runtime.cc:203] (Aborting thread was not attached to runtime!)
F/art (21168): art/runtime/runtime.cc:203] Dumping all threads without appropriate locks held: thread list lock mutator lock
F/art (21168): art/runtime/runtime.cc:203] All threads:
F/art (21168): art/runtime/runtime.cc:203] DALVIK THREADS (16):
...

pyjnius库:一个用于访问Java类的Python库
https://fulequn.github.io/2022/11/Article202211181/
作者
Fulequn
发布于
2022年11月18日
许可协议