Pyjnius: PyJnius frente a JPype

Creado en 16 jul. 2020  ·  27Comentarios  ·  Fuente: kivy/pyjnius

Soy el autor principal de JPype. Como parte de la actualización de la documentación de JPype, agregué PyJnius a la lista de códigos alternativos a JPype. Desafortunadamente, después de dos horas de jugar con PyJnius, no pude encontrar nada que pareciera una ventaja de PyJnius sobre JPype. Todos los aspectos que miré desde Proxies, personalizadores, manejo de matrices multidimensionales, integración de javadoc, manejo de GC, búferes, integración de código científico (numpy, matplotlib, etc.), métodos sensibles al llamador, documentación e incluso la velocidad de ejecución en la mayoría de los casos están cubiertos actualmente. en JPype de forma más completa que PyJnius. Sin embargo, siendo el autor de JPype quizás estoy mirando aspectos que valoro más que los del equipo de PyJnius. ¿Puede articular mejor las ventajas de este proyecto? ¿Cuál es la propuesta de valor de este proyecto, cuál es su público objetivo y qué se pretende hacer que las alternativas aún no cubren?

Comentario más útil

Me comuniqué con todos los códigos de puente de Java hace aproximadamente 2 años. Desafortunadamente, el código PyJnius aparentemente se perdió, ya que nunca apareció en mi búsqueda. También me lo habría perdido en esta ronda, excepto que estaba buscando la página que hizo el buen comunicado de prensa de tecnología la última vez y encontré un blog en el que se discutían los dos proyectos. No estoy seguro de cómo me perdí otro proyecto activo en la misma área durante dos años, pero claramente fue mi culpa.

Parece que confundiste JPype con Py4J, que es el otro código puente importante. Hacen todo lo posible utilizando enchufes con los beneficios y las desventajas que ello conlleva. De manera similar, descubrí que el proyecto no cumplía con mis requisitos.

No he realizado ninguna investigación sobre lo que se requiere para admitir Android. Aunque si tengo algunas especificaciones técnicas, debería ser posible. No hay nada que estemos haciendo que esté fuera de JNI nativo y simples llamadas antiguas a la API C de Python.

En cuanto al enfoque, JPype usa JNI exclusivamente para casar una JVM con Python usando el comando "startJVM ()". Aunque la próxima versión (2.0) también proporcionará la capacidad de hacer lo contrario en el que Python se puede iniciar desde dentro de Java. Lo hace a través de un enfoque de capas. Hay una capa de Python que contiene todas las clases de alto nivel que sirven como interfaz, un módulo privado de CPython con clases base que contienen los puntos de entrada, una capa de respaldo de C ++ que se ocupa de todas las conversiones de tipos y coincidencias, además de actuar como módulo nativo. para la biblioteca de Java, y una biblioteca de Java que realiza todas las tareas de la utilidad (mantener la vida útil de los objetos, crear clases de respaldo para cortes y excepciones, y extractores / renderización de javadoc).

Hace 8 años, JPype era un desastre. Intentaba admitir tanto Ruby como Python, por lo que la capa C ++ era una maraña de envoltorios con los que trabajar y el front-end estaba todo en Python, por lo que era muy lento. También se bifurcó en los tipos de retorno, ya que se podría compilar un gran número de soportes para que se devolvieran objetos diferentes. Necesitaba muchas clases de adaptadores como JException para actuar como proxies donde el objeto nativo de Python y Java difería. Pero todos esos problemas se han resuelto en los 3 años desde que me uní al proyecto. Los dos objetivos principales (para mí) en JPype son proporcionar una sintaxis lo suficientemente simple para que los físicos estén familiarizados con la programación para poder usar Java y tener altos niveles de integración con el código científico de Python. Hacemos esto haciendo que los objetos que están respaldados por Java tengan envoltorios de objetos CPython "todos los adornos". En lugar de convertir una matriz primitiva de Java, hacemos una matriz primitiva de Java un nuevo tipo nativo de Python que implementa los mismos puntos de entrada que numpy y todos estos están respaldados por transferencias de búfer de memoria. Por lo tanto, tenemos conversiones rápidas llamando list(jarray) o np.array(jarray) .

Para adaptarlo a Android, la secuencia de inicio probablemente necesitaría ser reelaborada y el código thunk que usa para cargar su biblioteca interna tendría que ser reemplazado por un modelo JNI más tradicional. Ya eliminé el código de procesador en la siguiente versión, por lo que ya se cumplió con la última. Solo se requeriría el primero.

Las diferencias clave en el enfoque que puedo ver es que PyJnius convierte las matrices que van hacia y desde. Esto parecería ser muy prohibitivo para la codificación científica donde pasar grandes matrices de un lado a otro (a menudo que nunca se convierten) es el estilo JPype preferido. La decisión de requerir la conversión obliga a opciones como pasar por valor y pasar por referencia, pero eso potencialmente conduce a problemas mayores, ya que si tiene una llamada de múltiples argumentos, está eligiendo una política para todos los argumentos. También dificultaría el manejo de matrices multidimensionales. (Creo que una clase de adaptador usada como obj.method(1, jnius.byref(list1), list2) habría proporcionado un mejor control si se pudiera lograr). Además, hay muchos problemas que JPype ha resuelto, como el enlace GC, los métodos sensibles al llamador, etc. Si no hay nada más, revise el código JPype y vea si hay buenas ideas que pueda usar.

Todos 27 comentarios

¡Gracias por contactarnos!

Puede que no lo recuerde porque han pasado años sin mirar jpype, pero pensé que estaba usando un enfoque diferente, de hablar con la JVM a través de un servidor (por lo tanto, IPC) en lugar de la memoria compartida (pero su archivo Léame insinúa lo contrario, y no veo pistas que refuten eso de un vistazo rápido al código, así que probablemente estoy totalmente equivocado), y este enfoque lo hizo inviable en Android, por ejemplo (que fue la razón principal para desarrollar pyjnius, aunque algunas personas lo usan en plataformas de escritorio). Por otro lado, no veo muchas pistas sobre el soporte de Android en la base de código JPype, a menos que de eso se trate el directorio native , ya que su jni.h parece haber sido tomado de AOSP.

Pero, sinceramente, no sabíamos nada de JPype cuando se inició el proyecto, era solo un paso adelante de tener que codificar el código jni manualmente para interactuar con clases específicas de Android para el soporte de kivy. Creo que @tito investigó un poco para comparar más tarde, cuando nos

Hola. Recuerdo el nombre de JPype, pero en el momento de buscar qué podíamos usar, no recuerdo por qué no se usó en absoluto, hace 8 años :) El único objetivo al principio era poder comunicarnos con Android API, pero sin usar un servidor RPC intermedio como estaba haciendo el proyecto P4A en ese momento.

Me comuniqué con todos los códigos de puente de Java hace aproximadamente 2 años. Desafortunadamente, el código PyJnius aparentemente se perdió, ya que nunca apareció en mi búsqueda. También me lo habría perdido en esta ronda, excepto que estaba buscando la página que hizo el buen comunicado de prensa de tecnología la última vez y encontré un blog en el que se discutían los dos proyectos. No estoy seguro de cómo me perdí otro proyecto activo en la misma área durante dos años, pero claramente fue mi culpa.

Parece que confundiste JPype con Py4J, que es el otro código puente importante. Hacen todo lo posible utilizando enchufes con los beneficios y las desventajas que ello conlleva. De manera similar, descubrí que el proyecto no cumplía con mis requisitos.

No he realizado ninguna investigación sobre lo que se requiere para admitir Android. Aunque si tengo algunas especificaciones técnicas, debería ser posible. No hay nada que estemos haciendo que esté fuera de JNI nativo y simples llamadas antiguas a la API C de Python.

En cuanto al enfoque, JPype usa JNI exclusivamente para casar una JVM con Python usando el comando "startJVM ()". Aunque la próxima versión (2.0) también proporcionará la capacidad de hacer lo contrario en el que Python se puede iniciar desde dentro de Java. Lo hace a través de un enfoque de capas. Hay una capa de Python que contiene todas las clases de alto nivel que sirven como interfaz, un módulo privado de CPython con clases base que contienen los puntos de entrada, una capa de respaldo de C ++ que se ocupa de todas las conversiones de tipos y coincidencias, además de actuar como módulo nativo. para la biblioteca de Java, y una biblioteca de Java que realiza todas las tareas de la utilidad (mantener la vida útil de los objetos, crear clases de respaldo para cortes y excepciones, y extractores / renderización de javadoc).

Hace 8 años, JPype era un desastre. Intentaba admitir tanto Ruby como Python, por lo que la capa C ++ era una maraña de envoltorios con los que trabajar y el front-end estaba todo en Python, por lo que era muy lento. También se bifurcó en los tipos de retorno, ya que se podría compilar un gran número de soportes para que se devolvieran objetos diferentes. Necesitaba muchas clases de adaptadores como JException para actuar como proxies donde el objeto nativo de Python y Java difería. Pero todos esos problemas se han resuelto en los 3 años desde que me uní al proyecto. Los dos objetivos principales (para mí) en JPype son proporcionar una sintaxis lo suficientemente simple para que los físicos estén familiarizados con la programación para poder usar Java y tener altos niveles de integración con el código científico de Python. Hacemos esto haciendo que los objetos que están respaldados por Java tengan envoltorios de objetos CPython "todos los adornos". En lugar de convertir una matriz primitiva de Java, hacemos una matriz primitiva de Java un nuevo tipo nativo de Python que implementa los mismos puntos de entrada que numpy y todos estos están respaldados por transferencias de búfer de memoria. Por lo tanto, tenemos conversiones rápidas llamando list(jarray) o np.array(jarray) .

Para adaptarlo a Android, la secuencia de inicio probablemente necesitaría ser reelaborada y el código thunk que usa para cargar su biblioteca interna tendría que ser reemplazado por un modelo JNI más tradicional. Ya eliminé el código de procesador en la siguiente versión, por lo que ya se cumplió con la última. Solo se requeriría el primero.

Las diferencias clave en el enfoque que puedo ver es que PyJnius convierte las matrices que van hacia y desde. Esto parecería ser muy prohibitivo para la codificación científica donde pasar grandes matrices de un lado a otro (a menudo que nunca se convierten) es el estilo JPype preferido. La decisión de requerir la conversión obliga a opciones como pasar por valor y pasar por referencia, pero eso potencialmente conduce a problemas mayores, ya que si tiene una llamada de múltiples argumentos, está eligiendo una política para todos los argumentos. También dificultaría el manejo de matrices multidimensionales. (Creo que una clase de adaptador usada como obj.method(1, jnius.byref(list1), list2) habría proporcionado un mejor control si se pudiera lograr). Además, hay muchos problemas que JPype ha resuelto, como el enlace GC, los métodos sensibles al llamador, etc. Si no hay nada más, revise el código JPype y vea si hay buenas ideas que pueda usar.

@Thrameos una cosa reciente que estamos buscando fusionar es el uso de Python lambdas para interfaces funcionales de Java. Ver https://github.com/kivy/pyjnius/pull/515 ; ¿No vi eso en JPype?

JPype tiene soporte para lambdas desde interfaces funcionales a partir de 1.0.0. Fue parte de los 30 tirones en 30 días de marzo hasta 1.0.

JPype ha tenido un largo período de incubación. Originalmente comenzó en 2004 hasta 2007. Luego tuvo un gran impulso cuando el grupo de usuarios lo resucitó para portarlo a Python 3 alrededor de 2015. Luego, en 2017, fue recogido para su uso en un laboratorio nacional que lo llevó desde 0.6. 3 a 0.7.2. Durante ese período, todos los esfuerzos se centraron en mejorar y fortalecer la tecnología central que proporciona la interfaz. Pero eso finalmente se completó en marzo después de la segunda reescritura del núcleo, por lo que finalmente pudimos impulsar la versión 1.0.0. Desde entonces, hemos estado agregando todo lo que "faltaba" durante mi campaña de lista de deseos "30 extracciones en 30 noches". La acumulación de todo lo que simplemente no pude implementar porque sería demasiado trabajo finalmente se resolvió (los problemas se redujeron de 50 a 20, solicitan a los usuarios que pregunten qué necesitan, etc.). Por lo tanto, puede haber una serie de funciones que no encontró en versiones anteriores que ahora están disponibles. He estado entregando la mayoría de las solicitudes de funciones durante menos de una semana, de modo que todo lo que me queda son los 3 grandes (puente inverso, clases extendidas en Python, capacidad para iniciar una segunda JVM).

El proyecto volverá a quedarse dormido ya que llevo 2 meses en un esfuerzo de 6 meses para completar el código de puente inverso que permitirá a Java llamar a Python y generar códigos auxiliares para las bibliotecas de Python para que puedan usarse como bibliotecas nativas de Java. Utiliza ASM para crear clases de Java sobre la marcha, de modo que se pueda lograr el soporte nativo para Python. Todavía no está completamente integrado como Jython, pero quizás lo suficientemente cerca como para que no haya mucha diferencia.

Muchas gracias por las explicaciones detalladas, y de hecho, en todo caso, seguramente hay ideas que podríamos usar, y valdría la pena estudiar el código, mirar el código antes, lo que vi parece bastante limpio tanto en la calidad del código como en la estructura de el proyecto, así que felicidades por todo el trabajo. Tienes razón sobre mi confusión con Py4J, supongo que cuando miré JPype, debió haber estado en el estado desordenado que describiste, y usarlo debe haber sido mucho más complicado que PyJNIus en este momento.

Sus puntos sobre pasar por valor / conversión son muy ciertos, y también desencadenaron una discusión reciente aquí, ya que @ hx2A investigó cómo mejorar el rendimiento e hizo que la conversión a tipos de Python sea opcional (como pasar una lista desechable a Java, obtenerla convertido a una lista de Java, modificado por Java o no, y convertido de nuevo a Python, solo para recolectar la basura, fue ciertamente subóptimo, ahora podemos al menos evitar la segunda parte, a costa de usar argumentos de palabras clave, lo cual es seguro ya que java no los admite, por lo que no hay conflicto de firmas, pero ciertamente es un poco más ruidoso en cuanto a sintaxis).

En cuanto a una posible diferencia entre JPype y PyJNIus, podemos implementar interfaces java usando clases de python y pasarlas a java para usarlas como devoluciones de llamada, por otro lado, necesitaríamos generar código de bytes java, si quisiéramos extender las clases java de Python, ya que es un requisito usar alguna API de Android, que no podemos cubrir en este momento, no estoy seguro de si infiero correctamente de su comentario, pero tal vez no tenga la capacidad de que las clases de Java llamen a su código de Python como este (usando interfaces).

JPype puede implementar interfaces en Python. Simplemente agregue decoradores a las clases normales de Python.

from java.util.function import Consumer

@jpype.JImplements(Consumer)
class MyConsumer:
   @jpype.JOverride
   def apply(self, obj):
       pass

Use una cadena en @JImplements si la clase se define antes de que se inicie la JVM, se pueden implementar múltiples interfaces a la vez, pero todos los métodos se verifican para ver si están implementados. La principal diferencia es que JPype usa métodos de envío (todas las sobrecargas van al mismo método) en lugar de los métodos sobrecargados. Esto se debe a que nos gusta poder llamar a los métodos tanto desde Java como desde Python. Puedo agregar sobrecargas individuales si esa es una característica deseada, pero nadie la ha solicitado.

(Editar: la razón por la que no usamos la herencia de Python es que en el momento en que se agregó esto, todavía admitíamos Python 2, lo que causó muchos problemas de metaclase, lo limpiaremos aún más una vez que las extensiones de clase estén en su lugar. Entonces, anotación que evaluar una vez en el momento de la declaración fueron más limpios).

Usamos el mismo sistema para implementar personalizadores (¿dunder?) Para las clases.

@jpype.JImplementationFor("java.util.ArrayList")
class ArrayListImpl:
    def __getitem__(self, i):
        return self.get(i)
    @jpype.JOverride
    def addAll(self, list):
        # Decide if we need to convert or can call directly.
        ...

También usamos anotaciones para definir los convertidores implícitos, como "Todos los objetos de la ruta de Python se convierten en java.io.File".

 @jpype.JConversion("java.io.File", instanceof=pathlib.PurePath)
 def _JFileConvert(jcls, obj):
       Paths = jpype.JClass("java.nio.file.Paths")
       return Paths.get(str(obj))

Obviamente, usar este tipo de lógica es un poco más lento que los métodos especiales de C, pero mantiene alta la legibilidad y la flexibilidad. He estado impulsando caminos críticos que han demostrado ser cuellos de botella de regreso a C según sea necesario.

También proporcionamos algo de azúcar de sintaxis para limpiar las cosas MyJavaClass@obj => convertir a MyJavaClass (equivalente a Java (MyJavaClass)obj ) o cls=JInt[:] => crear un tipo de matriz ( cls=int[].class ) o a=JDouble[10][5] => crear una matriz multidim ( double[][] a = new double[10][5] ).

He estado trabajando en el prototipo para extender clases de JPype. Tenemos el mismo problema, ya que algunas clases de Swing y otras API requieren clases de extensión. La solución que he preparado hasta ahora es crear una clase extendida con un HashMap para cada uno de los métodos anulados. Si no hay nada en el mapa hash para ese punto de entrada, lo pasa a super, de lo contrario, llama al controlador del método proxy. Pero decido que esto sería más fácil de implementar después de que se complete el puente inverso para que Java realmente pueda manejar los métodos de Python en lugar de pasar por el método de proxy de Java. Así que todavía faltan unos 6 meses para que el prototipo funcione. Puede mirar la rama epypj (mi nombre para el puente inverso) para ver cómo funciona la llamada desde Java a Python, así como los patrones para generar un invocador usando ASM para crear clases de Java sobre la marcha.

En cuanto a la gestión de la recolección de basura, hay pocas piezas de JPype que hagan ese trabajo. Primero está JPypeReferenceQueue (native / java / org / jpype / ref / JPypeReferenceQueue) que vincula la vida de un objeto Python a un objeto Java. Esto se utiliza para crear búferes y otras cosas en las que Java necesita acceder a un concepto de Python durante un tiempo. El segundo es el uso de referencias globales para que Python pueda contener un objeto Java dentro del alcance. Esto requiere el enlace del recolector de basura (native / common / jp_gc.cpp) que escucha a cualquiera de los sistemas para activar un GC y lo envía al otro cuando se dan ciertas condiciones (tamaño de las agrupaciones, crecimiento relativo). Los últimos proxies necesitan usar referencias débiles porque de lo contrario formarían bucles (ya que el proxy tiene una referencia a la mitad de Java y la mitad de Java apunta a la implementación de Python). Finalmente, tengo la intención de usar un agente para permitir que Python atraviese Java, pero eso está en el futuro.

Soy una de las personas que usa pyjnius en el escritorio y no en Android. No sabía sobre JPype cuando comencé a construir mi proyecto, pero investigué un poco para ver cuáles son las diferencias.

Una característica única de pyjnius es que la persona que llama puede decidir si incluir o no los métodos y campos protegidos y privados. Mi preferencia es solo para el público, pero entiendo el argumento de que hacer que los campos y métodos no públicos estén disponibles es útil.

El rendimiento es fundamental para mi proyecto. Hice algunas pruebas con la siguiente clase:

package org.pkg;

public class MyClass {

  public MyClass() {
  }

  public int number = 42;

  public float add1(float x, float y) {
    return x + y;
  }

  public float add2(float x, float y) {
    return x + y;
  }

  public float add2(int x, int y) {
    return x + y;
  }
}

En JPype:

In [1]: import jpype
   ...: import jpype.imports
   ...: jpype.startJVM()
   ...: from org.pkg import MyClass
   ...: myInstance = MyClass()
   ...:

In [2]: %timeit myInstance.number
640 ns ± 2.65 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [3]: %timeit myInstance.add1(10.3, 20.5)
2.13 µs ± 24.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [4]: %timeit myInstance.add2(10.3, 20.5)
2.19 µs ± 9.41 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

En pyjnius:

In [1]: import jnius

In [2]: MyClass = jnius.autoclass('org.pkg.MyClass')

In [3]: myInstance = MyClass()

In [4]: %timeit myInstance.number
161 ns ± 0.104 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [5]: %timeit myInstance.add1(10.3, 20.5)
1.04 µs ± 8.16 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [6]: %timeit myInstance.add2(10.3, 20.5)
2.71 µs ± 11.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Pyjnius es bastante más rápido, excepto por el método sobrecargado. Debemos comparar notas sobre cómo decidir a qué método llamar cuando el método está sobrecargado. Pyjnius tiene este mecanismo de puntuación que parece agregar mucha sobrecarga. JPype toma esa decisión mucho más rápido.

Finalmente, para propósitos de referencia:

In [9]: def add(x, y):
   ...:     return x + y
   ...:

In [10]: %timeit add(10.3, 20.5)
82.9 ns ± 0.187 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

Por supuesto, las diferencias de unos pocos µs son triviales, pero eso se suma si uno está haciendo miles de pequeñas llamadas muy rápidamente, lo que tengo que hacer.

La integración de JPype con numpy es bastante agradable y fácil de usar. Puedo ver cómo los investigadores podrían usar esto para escribir scripts que pasen matrices grandes a bibliotecas de Java sin una sintaxis complicada. También necesito pasar matrices grandes y lo hago con tobytes() y un código Java especial que puede recibir los bytes, pero obviamente eso no es tan ordenado ni conveniente.

La velocidad de JPype, desafortunadamente, es una especie de perspectiva de "es lo que es". JPype es muy defensivo tratando de protegerse contra cosas malas, lo que significa que hay muchos gastos generales no triviales. Eso significa, por ejemplo, que cada vez que se realiza una llamada a Java, se asegura de que el hilo esté conectado para que no se produzca un error de segmentación si se llama desde un hilo externo como un IDE. Mi grupo de usuarios locales son todos científicos, por lo que los puntos de entrada están muy protegidos contra un cableado cruzado bastante horrible. Si alguna vez algo les falla (no importa lo loco que haya sido), entonces he fallado. (Lo que explicaría las 1500 pruebas que incluyen la construcción deliberada de objetos defectuosos).

En segundo lugar, la velocidad de las acciones individuales probablemente varía mucho en diferencias muy pequeñas en el trabajo que se realiza. El ejemplo trivial que dio fue uno de los peores casos. Dependiendo del tipo de campo al que se accede, la velocidad de acceso puede cambiar en algunos casos.

Para su ejemplo de velocidad, está solicitando un campo int.

  • En PyJnius crea un descriptor para la búsqueda del objeto, accede al campo, crea un nuevo Python largo y luego lo devuelve.
  • En JPype, crea un descriptor para la búsqueda de objetos, accede al campo, crea un nuevo Python largo, luego crea un tipo de contenedor para un int de Java, copia la memoria larga de Python al JInt (porque Python carece de una forma de crear un derivado integer class directamente), luego vincula la ranura con un valor de Java y finalmente devuelve el JInt resultante.

Entonces, incluso algo tan trivial como hacer una evaluación comparativa de velocidad para acceder a un campo, no es realmente tan trivial.
La diferencia es que uno devolvió un Python largo y el otro devolvió un entero de Java real (que sigue las reglas de conversión de Java) que podría pasarse a otro método sobrecargado y vincularse correctamente. El trabajo para devolver un tipo de contenedor es mucho más que simplemente devolver un tipo de Python, por lo tanto, una gran diferencia en la velocidad.

Traté de demostrar esto probando algunos tipos de campos diferentes. Desafortunadamente, cuando probé los campos de Objeto, jnius falló en el código "harness.objectField = harness". No estoy seguro de por qué ese trozo de cableado cruzado en particular causó un problema, pero me falló. No he estado muy interesado en la velocidad de JPype más allá de eliminar a los infractores graves, lo que dio un factor de 3-5 aceleración para llamadas y 300 veces para ciertos accesos a la matriz. Pero quizás debería revisar y ver qué áreas se pueden mejorar. Sin embargo, dudo que pueda reducirlo al mínimo como PyJnius sin eliminar la seguridad o eliminar los contratos de devolución (lo cual no puedo hacer). Como máximo, todavía es posible una aceleración del 10-30%,

En cuanto a la función de acceder a campos privados y protegidos, ciertamente es posible hacerlo. Prefiero que los usuarios usen la reflexión u otros métodos de acceso interno en lugar de exponer el objeto directamente. Si tuviera que proporcionar algo así, probablemente crearía un campo llamado _private que contendría campos que no están expuestos públicamente. JPype solo proporciona una envoltura de clase para cada tipo, por lo que no tengo muchos controles de grano fino. Por lo tanto, no podría seleccionar crear una clase que tenga acceso privado y luego crear un segundo objeto del mismo tipo y no terminar con los privados expuestos. Seguí ese camino con la conversión de cadenas, y fue un desastre que algunas bibliotecas eligieran una política y otras una diferente, lo que resultó en incompatibilidades.

Ejecuté algunas pruebas usando la lista de matrices.

import jpype
import timeit
jpype.startJVM()
ArrayList = jpype.JClass("java.util.ArrayList")

def pack():
    ja = ArrayList()
    for i in range(1000):
        ja.add(i)

def iter(ja):
    u = 0
    for i in ja:
        u+=i

def access(ja):
    u = 0
    for i in range(len(ja)):
        u+=ja.get(i)

def access2(ja):
    u = 0
    for i in range(len(ja)):
        u+=ja[i]


ja = ArrayList()
for i in range(1000):
   ja.add(i)

print("Pack arraylist %e"%( timeit.timeit("pack()", globals=globals(), number=1000)/1e6))
print("Iterate arraylist %e"%(timeit.timeit("iter(ja)", globals=globals(), number=1000)/1e6))
# Get is a direct call
print("Access(get) arraylist %e"%(timeit.timeit("access(ja)", globals=globals(), number=1000)/1e6))
# [] is emulated
print("Access([]) arraylist %e"%(timeit.timeit("access2(ja)", globals=globals(), number=1000)/1e6))

JPype

Paquete lista de matrices 2.768904e-06
Iterar lista de matrices 5.208071e-06
Acceder (obtener) lista de matrices 4.037985e-06
Acceda ([]) lista de matrices 4.690264e-06

Jnius

Pack lista de matrices 3.322248e-06
Iterar lista de matrices 4.099314e-06
Acceder (obtener) lista de matrices 5.653444e-06
Acceder ([]) lista de matrices 7.762727e-06

No es una historia muy consistente más que decir que probablemente sean trivialmente diferentes, excepto cuando brindan una funcionalidad muy diferente. Si todo lo que está haciendo es acceder a métodos, es probable que sean bastante similares. Pasar matrices que son JPype preconvertidas es 100 veces más rápido, convertir listas y tuplas JPype es 2 veces más lento (actualmente no usamos acceso vectorial ni tenemos derivaciones especiales para tuplas). Entonces, la conclusión es que dependiendo de su estilo de codificación, puede ser mucho más rápido con uno u otro. Pero entonces mis usuarios generalmente seleccionan JPype por su facilidad de uso y construcción a prueba de balas en lugar de velocidad. (¡Ah, a quién estoy engañando! Es muy probable que se tropiecen fuera de Internet porque JPype resultó ser el primer enlace que encontraron en Google).

En cuanto a cómo JPype vincula el método, ese detalle debe estar en la guía del desarrollador / usuario. El enlace de métodos comienza clasificando previamente la lista de métodos para el envío de acuerdo con las reglas dadas en la especificación de Java, de modo que si algo oculta algo más, aparezca primero en la lista (el código se puede encontrar en native / java / org / jpype como nosotros utilizar una clase de utilidad Java para realizar la clasificación cuando se crea el envío por primera vez). Además, a cada método se le da una lista de preferencias de qué método oculta otro. La resolución comienza verificando primero cada argumento para ver si hay una "ranura Java" para el argumento. Las ranuras de Java apuntan a objetos existentes que no necesitan conversión, por lo que eliminarlos antes de la coincidencia significa que podemos usar reglas directas en lugar de implícitas. Luego empareja el argumento basado en los tipos en 4 niveles (exacto, implícito, explícito, ninguno). Atajos en explícito y ninguno para saltar al siguiente método. Si alguna vez obtiene una respuesta exacta, abrevia todo el proceso para saltar a la llamada. Si hay una coincidencia, ocultaría los métodos que tienen un enlace menos específico. Si se encuentran dos coincidencias implícitas que no estaban ocultas, se procede a un TypeError. Una vez que se agotan todas las coincidencias, se ejecutan las rutinas de conversión. Luego libera el bloqueo global de Python y realiza la llamada y vuelve a adquirir el bloqueo global. Se busca el tipo de retorno y se crea un nuevo contenedor de Python basado en el tipo devuelto (utiliza retornos covariantes, por lo que el tipo devuelto es el más derivado en lugar del tipo del método). Esto es principalmente lineal con el número de sobrecargas, aunque hay algunas complejidades para los argumentos variadic, pero las tablas preconstruidas significan que probaría foo (largo, largo) antes de probar foo (doble, doble) y un acierto en (largo , long) evitaría que el doble, el doble se emparejen debido a las reglas de resolución del método Java. Todavía hay algunas aceleraciones que puedo implementar, pero eso requeriría tablas de almacenamiento en caché adicionales.

Heredé el sistema de pedidos con atajos cuando comencé con el proyecto en 2017. Agregué el método que oculta el caché y las ranuras de Java para eliminar la mayor parte de nuestra sobrecarga.

Optimicé la ruta de ejecución de los métodos. Los números revisados ​​para JPype son

Paquete lista de matrices 2.226081e-06
Iterar lista de matrices 4.082152e-06
Acceder (obtener) lista de matrices 2.962606e-06
Acceso ([]) lista de matrices 3.644642e-06

Mi grupo de usuarios locales son todos científicos, por lo que los puntos de entrada están muy protegidos contra un cableado cruzado bastante horrible. Si alguna vez algo les falla (no importa lo loco que haya sido), entonces he fallado.

Sí, los segfaults son horribles y obtuve cientos de ellos cuando comencé a usar pyjnius. Sin embargo, no he recibido ninguno en mucho tiempo porque tal vez resolví los problemas de seguridad y lo incorporé a mi código. Ahora todo funciona de forma fiable. Sin embargo, entiendo su caso de uso. Si sus usuarios son científicos que trabajan con los objetos de Java directamente para realizar análisis de datos con varias bibliotecas de Java, una falla de segmentación les haría perder todo su trabajo. JPype parece estar mejor diseñado para realizar trabajos científicos en los que los usuarios finales trabajan directamente con los objetos Java a través de Python. Sin embargo, el caso de uso principal de pyjnius es diferente, que se comunica con Android. En ese caso, los problemas de seguridad son el problema del desarrollador, por lo que quizás sea apropiado tomar diferentes decisiones sobre la seguridad frente a la velocidad.

Admito que no soy un gran admirador de "es seguro siempre que pise estos cuadrados en este orden". Cuando comencé a trabajar en JPype, tardé cerca de un año en hacer a prueba de balas todos los puntos de entrada lo suficiente como para poder pasar el código a mi grupo local. Y he agregado dos años adicionales de armadura API desde entonces. Aparte de unas pocas personas cuya JVM no se carga (lo que es muy difícil de resolver), quedan pocos problemas, ya que JPype se ha adaptado a los estándares de código de producción.

En cuanto a la compensación con la velocidad y la seguridad, la velocidad es excelente, pero si está obteniendo velocidad escatimando en operaciones seguras, generalmente es una compensación pobre para la mayoría de los usuarios. Ya sea que esté cambiando la creación de prototipos de código o escribiendo un sistema de producción, tener que dejar de trabajar e intentar solucionar una falla de segmento es una distracción que los usuarios no deberían enfrentar.

Si alguien está dispuesto a darme algunos ejemplos de cómo probar JPype en un emulador de Android, puedo ver cómo realizar las modificaciones necesarias.

Para usar en Android, empaquetamos pyjnius como el arte de una distribución de Python construida por python-for-android, (a menudo usando buildozer como una interfaz más fácil, pero es lo mismo), y luego construimos una aplicación de Python que envía esta distribución, entonces su código de Python puede importar pyjnius o cualquier otra biblioteca de Python que se construyó en la distribución de Python cuando el usuario ejecuta la aplicación.

Por lo tanto, el primer paso es compilar jpype en una distribución, lo que hace p4a (https://github.com/kivy/python-for-android/) cuando le dice que es parte de sus requisitos, generalmente ( pero no siempre) se necesita una "receta" para explicar a p4a cómo construir bibliotecas que no sean Python puro, la de Pyjnius se puede encontrar aquí https://github.com/kivy/python-for-android/blob/ desarrolle / pythonforandroid / recipes / pyjnius / __ init__.py como ejemplo. Si usa buildozer, puede usar la configuración p4a.local_recipes en buildozer.spec para declarar un directorio en el que se pueden encontrar recetas para los requisitos, por lo que no necesita bifurcar python-for-android para haga que se utilice su receta.

Aconsejaría usar buildozer, ya que automatiza más cosas https://buildozer.readthedocs.io/en/latest/installation.html#targeting -android y aún puede configurar sus recetas locales para probar cosas. La primera compilación llevará tiempo, ya que necesita compilar python y varias dependencias para arm, y necesitará descargar Android ndk y sdk para ello. Probablemente pueda usar el bootstrap kivy predeterminado para la aplicación y crear una aplicación similar a "hola mundo", que importaría jpype y solo mostraría el resultado de algún código en una etiqueta, o incluso en el logcat usando print, yo no recuerde lo bien que se ejecuta kivy en el emulador de Android, nunca lo usé, pero creo que algunos usuarios lo hicieron, y con la configuración de aceleración debería funcionar, afaik, de lo contrario, podría usar el contenedor sdl2 o webview, y usar un matraz o servidor de botellas para mostrar cosas, primero probaría con el kivy, ya que es el más probado con diferencia.

Necesitará una máquina linux u osx (VM está bien y WSL en Windows 10 está bien), para compilar para Android.

Si comienza a trabajar en una receta jpype para python-for-android, sería bienvenido abrir un PR en progreso al respecto para cualquier discusión que pueda surgir. Sería genial si funcionara, especialmente si de hecho puede resolver algunas limitaciones de Pyjnius de larga data. Como se discutió anteriormente en el hilo, pyjnius esencialmente cubre nuestros requisitos básicos para el uso de kivy, pero no tiene suficiente desarrollo para ir más allá de esto.

@inclement Configuré un PR para el puerto de Android en jpype-project / jpype # 799. Desafortunadamente, no estoy muy seguro de a dónde ir desde aquí. Parece estar intentando ejecutar gradle, que en realidad no es la ruta de compilación correcta.

Las acciones a realizar son las siguientes:

  • [x] Incluya todos los archivos jpype / *. py en la compilación (o versiones precompiladas de ellos).
  • [x] Ejecute Apache ant en native / build.xml y coloque el archivo jar resultante en algún lugar donde se pueda acceder a él.
  • [x] Incluya el archivo jar (o equivalente) en la compilación.
  • [x] Compile el código C ++ de native / common y native / python en un módulo llamado _jpype para incluirlo en la compilación.
  • [x] Incluye un archivo main.py que simplemente lanza un shell interactivo para que podamos probarlo manualmente por ahora.
  • [] En el futuro, necesito incluir "ASM" o algo que funcione así para Android para poder cargar clases creadas dinámicamente.
  • [x] Parche el código C ++ para que use un bootstrap personalizado para cargar la JVM y el archivo jar complementario y conectar todos los métodos nativos.
  • [] Parchea el jvmfinder con algo que funcione en Android y "startJVM" se llama automáticamente en lugar de al comienzo de main.
  • [] Parche org.jpype para que el sistema de navegación jar (que es como funcionan las importaciones) pueda funcionar en Android.

Revisé algunos de los documentos, pero nada realmente se destacó sobre cómo lograr esto. El diseño de mi proyecto es algo diferente de lo normal ya que no ponemos todo debajo del módulo principal (ya que en realidad estamos construyendo 3 módulos que componen el sistema. Jpype, _jpype y org.jpype). Es probable que necesite una receta personalizada para realizar todas estas acciones y deshabilitar patrones no deseados, como ejecutar gradle (a menos que esté haciendo algo útil que no puedo decir).

Parece estar intentando ejecutar gradle, que en realidad no es la ruta de compilación correcta.

Gradle es la herramienta de compilación utilizada como paso final para empaquetar un APK, probablemente no esté relacionado con su inclusión de jpype.

Incluya todos los archivos jpype / *. Py en la compilación (o versiones precompiladas de ellos).

En general, si jpype esencialmente funciona como un módulo Python normal, entonces su primer intento de receta probablemente haga la mayor parte del trabajo pesado: CppCompiledComponentsPythonRecipe hace algo como ejecutar python setup.py build_ext y python setup.py install utilizando el entorno NDK. Esto debería instalar el paquete jpype python dentro del entorno python que se está construyendo para su inclusión dentro de la aplicación.

Incluya el archivo jar (o equivalente) en la compilación.

Este es probablemente un paso adicional que la receta necesitará hacer, se reducirá a copiar su archivo jar (o lo que necesite) en algún lugar apropiado dentro del proyecto de Android que python-for-android está construyendo.

Compile el código C ++ de native / common y native / python en un módulo llamado _jpype para incluirlo en la compilación.

Si esto es manejado por setup.py, esto ya debería estar funcionando, pero podría necesitar algunos ajustes. Si no es así, puede incluir sus comandos de compilación en la receta (y hacer que se compilen para el entorno de Android configurando las variables de entorno apropiadas, como verá que se hace usando self.get_env en otras recetas).

Parche el código C ++ para que utilice un programa de arranque personalizado para cargar la JVM y el archivo jar complementario y conectar todos los métodos nativos.

Espero que esta parte sea bastante sencilla, solo dependiendo del uso de la función de interfaz JNI de Android correcta. Hacemos esto de una manera un poco hacky al parchear pyjnius en lugar de hacer una compilación condicional apropiada, ya que diferentes bibliotecas auxiliares proporcionan diferentes wrappres, como lo demuestra, por ejemplo, este parche . Esta complejidad no debería afectarlo, simplemente puede llamar a la función api de Android correcta.

Parchea el jvmfinder con algo que funcione en Android y "startJVM" se llama automáticamente en lugar de al inicio de main.

No estoy lo suficientemente familiarizado con la JVM para saberlo realmente, pero creo que Android quiere que siempre acceda a una jvm existente y no puede iniciar una nueva instancia. ¿Importará eso (o simplemente está mal?)?

Ejecute Apache ant en native / build.xml y coloque el archivo jar resultante en algún lugar donde se pueda acceder a él.

Tampoco estoy seguro de este debido a que no estoy familiarizado, ¿es solo un paso interno de compilación de jpype? No estoy seguro de cómo interactúan las versiones de Java, pero supongo que usar la hormiga nativa del sistema debería estar bien aquí.

Dio una advertencia de que el buildozer no respeta setup.py, por lo que saltó directamente desde la parte superior al paso de gradle. De ahí la necesidad de agregar esos pasos intermedios manualmente. Nuestro setup.py realiza un montón de pasos de compilación personalizados, como crear el archivo hooks para fusionar el archivo jar con dll, que probablemente no sean aplicables aquí, por lo que estuvo bien que se omitiera, pero tampoco pudo ubicar los archivos cpp y java. que se definen en setup.py. Parte del problema es que mi setup.py era tan grande que tuve que dividirlo en el módulo setupext.

Supongo que primero necesito averiguar cómo puedo ejecutar un comando limpio para poder hacer que el proceso se ejecute una vez y mostrar el registro. (Lo ejecuté poco a poco la primera vez mientras jugaba, así que no tengo un registro limpio). También nos gustaría trasladar esta conversación al RP para que podamos mantenernos más en el tema del hilo.

FWIW Pude portar el núcleo de mi proyecto que no es de Android a JPype. Hubo dos dificultades o diferencias menores:

  1. El classpath kwarg en jpype.startJVM() parece ignorarse sin importar lo que haga. Sin embargo, la función addClassPath() funcionó. Los desarrolladores que se preocupan por el orden de las rutas de clases pueden necesitar hacer algunos ajustes en comparación con cómo usaron jnius_config.add_classpath() .

  2. La implementación de interfaces Java funciona muy bien, pero es un poco diferente. En mi código, tenía una función que devolvía una lista de cadenas de Python. En retrospectiva, probablemente debería haber convertido cada cadena en una cadena Java, pero no pensé en eso e hice que la definición de la función de interfaz devolviera una lista de objetos Java para que esto funcione. Eso funciona bien en pyjnius, lo que hace que las cadenas de Python sean una subclase de objetos Java. Eso no funciona en JPype, pero lo solucioné fácilmente convirtiendo las cadenas con la clase JString JPype.

Escribir @JOverride para los métodos implementados es mucho más simple que un código como @java_method('(Ljava/lang/String;[Ljava/lang/Object;)V') .

Una vez que resolví esos dos problemas, básicamente todo funcionó bien. Tengo algunas matrices de bytes de paso de código que tendrán que cambiar, pero JPype tiene un buen soporte para eso. Además, las conversiones de tipo implícitas de JPype son muy convenientes y podrían reemplazar a muchos de los decoradores que tuve que esparcir por todas partes.

@Thrameos Respeto tu determinación aquí. Mucha suerte para que JPype funcione en Android.

Gracias por los comentarios.

Sin embargo, no estoy seguro de qué podría estar mal en la ruta de clase, si tuviera que adivinar, sería desde donde estaba iniciando Python. A veces, las personas inician la JVM desde un módulo y debido a que las reglas para las rutas de Java y las rutas de Python con respecto al directorio de inicio son diferentes, puede hacer que se pierdan los archivos jar. (Hay una sección en la guía del usuario sobre este tema).

Utilizo la función classpath a diario y es parte de nuestros patrones de prueba. Entonces, si los classpaths no fueran respetados, causaría fallas bastante importantes. Dicho esto, no sería el primero en tener dificultades para encontrar el conjunto mágico de ubicaciones y caminos absolutos necesarios para que un patrón complejo funcione.

Aquí hay un ejemplo en el que estoy iniciando mi sistema de prueba desde un directorio Py relativo al área de desarrollo.

import jpype
import os

devel = os.path.dirname(__file__)
devel = os.path.join(devel, '..', '..')
devel = os.path.abspath(devel)  # Notice that I converted the path to absolute so that it doesn't matter where the
# PWD of Java will be when this script is called.   Otherwise, if I import this from a different location it will use the
# original PWD and Java will find nothing in the classpath.

classpath = [
    '%s/gov.llnl.math/dist/*' % devel,
    '%s/gov.llnl.rdak/dist/*' % devel,
    '%s/gov.llnl.rnak/dist/*' % devel,
    '%s/gov.llnl.rtk/dist/*' % devel,
    '%s/gov.llnl.rtk.gadras/dist/*' % devel,
    '%s/gov.llnl.rtk.response/dist/*' % devel,
    '%s/gov.llnl.utility/dist/*' % devel,
    ]

jpype.startJVM(classpath=classpath, convertStrings=False)

Las rutas relativas y las absolutas funcionan, pero esto dependerá mucho de dónde empieces. AddClassPath tiene una magia especial para asegurarse de que todas las rutas sean con respecto a la ubicación de la persona que llama. Supongo que se necesita la misma lógica en los argumentos de la palabra clave classpath.

Puedo ver el problema al devolver una lista de cadenas. Dependerá del tipo de devolución en cuanto al comportamiento que se desencadena. Si el método se declaró devolviendo String[] , esperaría forzar cada cadena de Python en un Java al regresar. Si el método se declara que devuelve List<String> , habría un problema ya que los genéricos de Java lo eliminan, por lo que sería List<Object> . Dependiendo de cómo JPype vea la lista, puede o no intentar convertir, pero ese debe ser un comportamiento definido, así que lo verificaré. La solución segura es una lista comprensiva que debería poder convertir todos los artículos para la devolución.

Sin embargo, no estoy seguro de qué podría estar mal en la ruta de clase, si tuviera que adivinar, sería desde donde estaba iniciando Python. A veces, las personas inician la JVM desde un módulo

¡Eso es lo que estaba haciendo!

Utilizo la función classpath a diario y es parte de nuestros patrones de prueba. Entonces, si los classpaths no fueran respetados, causaría fallas bastante importantes. Dicho esto, no sería el primero en tener dificultades para encontrar el conjunto mágico de ubicaciones y caminos absolutos necesarios para que un patrón complejo funcione.

Sí, pensé que esto es algo que notarías de inmediato y que no fui la primera persona en tener este problema. Gracias por la muestra de código, que es útil. Como resultado, hice algunas mejoras en mi código.

La solución segura es una lista comprensiva que debería poder convertir todos los artículos para la devolución.

Eso es exactamente lo que hice y ahora funciona correctamente. ¡Gracias!

Hace unos años hice una comparación entre py4j y jnius y descubrí que jnius era mucho más rápido.

Cuando vi que se mencionaba jpype en algunos lugares, siempre asumí que se refería a: http://jpype.sourceforge.net/ y me mantuve alejado de él porque parecía no mantenido (no vi el nuevo proyecto de manera extraña)

Al leer este hilo, probé mis puntos de referencia nuevamente (mi caso de prueba principal para la velocidad es leer y calificar archivos PMML), y descubrí que jpype era bastante eficaz.
Algunos resultados:
https://gist.github.com/AbdealiJK/1dd5b7677435ba22f9ab3e26016bb3e7

# jpype
# createjvm: 0.550s
# loadmodel: tot=1.466451 max=1.064521s avg=0.014665s
# fields   : tot=0.019881 max=0.009795s avg=0.000199s
# score    : tot=0.033356 max=0.023338s avg=0.000334s

# jnius
# createjvm: 0.249s
# loadmodel: tot=1.773011 max=1.385274s avg=0.017730s
# fields   : tot=0.039058 max=0.012234s avg=0.000391s
# score    : tot=0.067590 max=0.031904s avg=0.000676s

# py4j
# createjvm: 0.222s
# loadmodel: tot=0.616913 max=0.027464s avg=0.006169s
# fields   : tot=0.699152 max=0.026426s avg=0.006992s
# score    : tot=0.389583 max=0.017620s avg=0.003896s

Para ser justos, JPype tenía su API de interfaz escrita en Python (a diferencia de CPython) hasta marzo de 2020. Por lo tanto, hay muchos puntos de referencia más antiguos para mostrar que era bastante lento para lo que proporcionaba. Simplemente había tantos problemas de backend que tenían que resolverse con la administración de memoria, eliminando envoltorios multicapa completos para admitir Ruby, subprocesos múltiples y similares, que la velocidad fue lo último en lo que se trabajó.

En este punto debería ser bastante rápido. Pero si encuentra algo que necesita trabajo adicional en relación con otros paquetes, simplemente deje una nota en los problemas. Debido a los contratos de devolución, existen algunas limitaciones, pero las rutas principales para realizar llamadas, transferencias de matrices y almacenamiento en búfer de memoria están bastante resueltas en este punto.

También estoy obteniendo buenos resultados con JPype. En particular, estoy obteniendo un buen valor de la integración con matrices numpy. He podido agregar nuevas funciones que no podía hacer antes y que me han permitido mejorar el rendimiento de manera significativa.

Si alguien puede ser tan amable como para intentar actualizar la herramienta kivy-remote-shell para que se compile con el Python3 actual y el buildozer y mejorar las instrucciones para que sean más paso a paso, ayudaría con el esfuerzo de migración de JPype. muy. He completado todos los pasos necesarios hasta la fase de arranque y prueba. Pero sin un entorno para completar el proceso de arranque, será difícil avanzar. Puedo intentar nuevamente actualizar la herramienta de shell remoto este fin de semana (y tal vez tener éxito) o una parte interesada con un mejor conocimiento de kivy puede completar esta tarea de requisito previo y luego puedo pasar el fin de semana completando el trabajo técnico para el que estoy más calificado para completar . Aunque ofrezco libremente mi tiempo para ayudar a otros, es un recurso finito y cualquier trabajo que haga en los esfuerzos de migración de Android es un retraso en el puente de Python desde Java en el que otras personas también están interesadas.

Espero que el esfuerzo de transferencia de Android pueda evitar seguir el camino del esfuerzo de migración de PyPy, donde pasé varias semanas reelaborando el código central para poder manejar las diferencias, pero luego encontré un problema técnico en el que una diferencia trivial en el sistema de objetos produjo un error, y no pude encontrar a nadie que pudiera ayudarme a rastrear cómo depurar un informe de error generado en el código generado. Aunque no lloro por la leche derramada y todo ese esfuerzo se realizó para mejorar el código JPype a otras formas significativas, al final del día, los usuarios que querían usar JPype se quedaron drogados y secos. Si este esfuerzo falla, no todo está perdido, ya que volveré a él, pero una vez que algo está al final de la cola, tengo dificultades para volver a hacerlo durante 6 meses, a menos que alguien pueda dedicar tiempo para ayudarme. .

Actualización de progreso para las partes interesadas.

Conseguí que JPype arrancara con éxito desde pythonforandroid y pude probar la funcionalidad básica. Aunque algunas de las funciones avanzadas pueden no ser posibles debido a las diferencias en la JVM, creo que la gran mayoría de JPype estará disponible para su uso en la plataforma Android. El puerto tomó un tiempo, ya que requirió algunas actualizaciones en los proyectos buildozer y pythonforandroid (por lo tanto, leer mucho la fuente y pedir ayuda). Muchas gracias a los desarrolladores aquí por responder para poder completar la parte más difícil del proceso en un fin de semana. No hubiera sido posible sin su participación. He puesto los cambios relacionados como relaciones públicas, pero mirando la acumulación de relaciones públicas, puede pasar un tiempo antes de que se considere. Ahora que tengo las especificaciones técnicas clave que necesito, debería poder integrarlo y tener un código de lanzamiento que funcione en algún lugar alrededor de JPype, 1.2 nominalmente en el calendario para fines del otoño. Puedo impulsarlo si hay un gran interés del usuario, pero está compitiendo con Python de Java, que es una característica importante para otros proyectos.

Si alguien quisiera ayudar a acelerar el esfuerzo, el siguiente paso difícil será descubrir cómo construir una imagen de la ventana acoplable con todo en su lugar con un sistema de compilación parcial para que podamos ejecutar una compilación, cargar un emulador y ejecutar el banco de pruebas. del androide en azure pipelines (o algún otro sistema CI). Una vez que tengamos un CI en funcionamiento que pueda detectar qué funciona y qué no, estaremos mucho más cerca de poder implementarlo como software estable. No estoy seguro de si esto debería estar alojado en el proyecto JPype o si deberíamos tener un proyecto de prueba de Android separado.

¿Fue útil esta página
0 / 5 - 0 calificaciones