Spanish [SOLUCIONADO] (B4A)¿Cómo hacer pausa hasta que se descarguen archivos desde la web de manera asíncr?

Seneca

Active Member
Licensed User
Hola.

Estoy trabajando en la manera de que la App compruebe en una base de datos un listado de nombres de archivos JPG, que posteriormente habrá de descargar a DirDefaultExternal, y así poder ser usados como imágenes de diversos botones. (Pongo el código completo más abajo)

Para ello he usado OkHttpUtils2, pero como esta descarga se hace de manera asícrona, he de buscar la manera de forzar una pausa hasta que todas las imágenes se hayan descargado antes de mostrar el conjunto de botones (con estas imágenes de fondo).

Lo he intentado con un DoWhile hasta que el evento JobDone se haya ejecutado tantas veces como imágenes a descargar. Pero me encuentro dos problemas. El primero es que no funciona, ya que mientras el DoWhile está dando vueltas, el evento JobDone no se produce. El otro problema es que aunque me funcionase, seguro que es un método muy rudimentario.

Os pido consejo.

Gracias.

B4X:
#Region  Project Attributes
    #ApplicationLabel: B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region

Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Dim SQL1 As SQL
End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.
    Private lstCategorias As List
    Dim intContador As Int
End Sub

Sub Activity_Create(FirstTime As Boolean)
    'Do not forget to load the layout file created with the visual designer. For example:
    'Activity.LoadLayout("Layout1")

    If FirstTime Then
        File.Delete(DBUtils.GetDBFolder, "basedatos.db")
        Dim rutaDB As String
        rutaDB=DBUtils.CopyDBFromAssets("basedatos.db")
        SQL1.Initialize (rutaDB, "basedatos.db", True)
    End If
  
    ProgressDialogShow("Espera un momento")

    CargaIconos 'Descarga todos los iconos desde la web de forma asícrona

    Do While intContador < lstCategorias.Size 'Espera hasta que la descarga de todos los archivos finalice
         Log (intContador & " - " & lstCategorias.Size)
    Loop
    ProgressDialogHide
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub CargaIconos
    Dim lstIconos As List
    Dim stUnIcono () As String

    'Carga todas las categorías desde la BD
    lstIconos = DBUtils.ExecuteMemoryTable(SQL1, "SELECT imagen_cat FROM categorias", Null,0)
  
    For NumIcono = 0 To lstIconos.Size - 1
        stUnIcono = lstIconos.Get(NumIcono)

        Dim job As HttpJob
        job.Initialize(stUnIcono(0), Me)
        job.Download("http://www.miservidor.com/" & stUnIcono(0))
    Next
End Sub

Sub JobDone(Job As HttpJob)
    If Job.Success Then
        Dim out As OutputStream
        out = File.OpenOutput(File.DirDefaultExternal,Job.JobName,False )
        File.Copy2(Job.GetInputStream, out)
        out.Close
        Log ("OK - " & Job.JobName)
    Else
        Log("FALLO - " & Job.JobName)
    End If
    Job.Release
    intContador = intContador + 1
End Sub
 

rscheel

Well-Known Member
Licensed User
Longtime User
De la siguiente manera...

B4X:
Sub CargaIconos
     ProgressDialogShow2("Cargando Datos...", False)
End Sub

B4X:
Sub JobDone(Job As HttpJob)
     ProgressDialogHide

End Sub

Espero te sirva, Saludos.
 

Seneca

Active Member
Licensed User
Gracias rscheel por tu rápida respuesta.

No sé si algo no he hecho bien, pero de la manera que me indicas el ProgressDialog se oculta cuando se descarga el primer archivo (cuando se ejecuta JobDone la primera vez). Lo que necesito es esperar a que todos los archivos (listados en la base de datos) estén descargados.

Saludos.
 

rscheel

Well-Known Member
Licensed User
Longtime User
Gracias rscheel por tu rápida respuesta.

No sé si algo no he hecho bien, pero de la manera que me indicas el ProgressDialog se oculta cuando se descarga el primer archivo (cuando se ejecuta JobDone la primera vez). Lo que necesito es esperar a que todos los archivos (listados en la base de datos) estén descargados.

Saludos.

Entonces crea un variable global con la cantidad de iconos que trae el query, y otra variable global con la cantidad de pasadas por el JobDone, entonces una ves que la cantidad de pasadas sea igual a la cantidad de iconos de la variable del query ejecutas ProgressDialogHide para esconder el mensaje.

Te quedaría
B4X:
Sub JobDone(Job As HttpJob)
varVueltasJob = varVueltasJob +1
If varCantIcon == varVueltasJob Then
     ProgressDialogHide
     varVueltasJob = 0
End If

End Sub
algo como esto
 

Seneca

Active Member
Licensed User
Lo de la variable que va contando el número de pasadas del JobDone la tengo implementada en el ejemplo (intContador). Pero si no tengo mal entendido, el ProgreddDialog no paraliza la ejecución. Y lo que necesito es que se no continue hasta que todas los archivos estén descargados. Esto es indispensable ya que son las imágenes de los iconos que habrán de aparecer en el Layout principal de la App.

Saludos.
 

inakigarm

Well-Known Member
Licensed User
Longtime User
Puedes poner un panel transparente en primer término (que consuma los eventos clicks ) y cuando haya terminado la descarga, lo escondes
 

Seneca

Active Member
Licensed User
Gracias inakigarm. Tu mensaje y los de rscheel me han dado la idea para una solución que en principio me funciona. Por simplificar, el código que puse al principio realmente es un extracto de lo que estoy desarrollando. En ese código, justo después de:

B4X:
    Do While intContador < lstCategorias.Size 'Espera hasta que la descarga de todos los archivos finalice
         Log (intContador & " - " & lstCategorias.Size)
    Loop
    ProgressDialogHide

no es que se acabase el Activity_Create, sino que continuaba con el código que muestra en pantalla un ScrollView con los botones cuyas imágenes se estaban descargando del servidor. Por eso puse el Do While para entreterme ahí hasta que todas las imágenes estuviesen descargadas y a continuación seguir con el Activity_Create y mostrar los iconos. El problema es que el Do While hace que los eventos JobDone no se activen.

Lo que ahora he pensado es poner todo el código que muestra los iconos en un Sub independiente en vez de en el Activity_Create. También quitaría el DO While y el ProgressDialogHide, de manera que el Activity Create finalice en la llamada a CargaIconos. Sería ahora en el Sub JobDone donde comprobaría si todas las imágenes se han descargado, poniendo en marcha entonces el nuevo Sub que muestra los iconos en pantalla.

No sé si esta técnica es demasiado rudimentaria, pero la usaré mientras no descubra otra más avanzada.

Gracias por la ayuda.

El código quedaría así:

B4X:
#Region  Project Attributes
    #ApplicationLabel: B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region

Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Dim SQL1 As SQL
End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.
    Dim intContador As Int
    Dim intTotalImagenes As Int
End Sub

Sub Activity_Create(FirstTime As Boolean)
    'Do not forget to load the layout file created with the visual designer. For example:
    'Activity.LoadLayout("Layout1")

    If FirstTime Then
        File.Delete(DBUtils.GetDBFolder, "basedatos.db")
        Dim rutaDB As String
        rutaDB=DBUtils.CopyDBFromAssets("basedatos.db")
        SQL1.Initialize (rutaDB, "basedatos.db", True)
    End If
  
'    ProgressDialogShow("Espera un momento")

    CargaIconos 'Descarga todos los iconos desde la web de forma asíncrona
End Sub

Sub MuestraIconos
    'aquí iría el código que muestra los iconos en pantalla
    'una vez se han descargado todas las imágenes desde el servidor
End Sub

Sub Activity_Resume

End Sub

Sub Activity_Pause (UserClosed As Boolean)

End Sub

Sub CargaIconos
    Dim lstIconos As List
    Dim stUnIcono () As String

    ProgressDialogShow2("Cargando Datos...", False)

    'Carga todas las categorías desde la BD
    lstIconos = DBUtils.ExecuteMemoryTable(SQL1, "SELECT imagen_cat FROM categorias", Null,0)
    intTotalImagenes = lstIconos.Size

    For NumIcono = 0 To lstIconos.Size - 1
        stUnIcono = lstIconos.Get(NumIcono)

        Dim job As HttpJob
        job.Initialize(stUnIcono(0), Me)
        job.Download("http://www.miservidor.com/" & stUnIcono(0))
    Next
End Sub

Sub JobDone(Job As HttpJob)
    intContador = intContador + 1
    If Job.Success Then
        Dim out As OutputStream
        out = File.OpenOutput(File.DirDefaultExternal,Job.JobName,False )
        File.Copy2(Job.GetInputStream, out)
        out.Close
        Log (intContador & " - " & intTotalImagenes & " - OK - " & Job.JobName)
    Else
        Log(intContador & " - " & intTotalImagenes & " - FALLO - " & Job.JobName)
    End If
    Job.Release
    If intContador = intTotalImagenes Then
        ProgressDialogHide
        MuestraIconos 'lanza la publicación de los iconos
    End If
End Sub
 

Seneca

Active Member
Licensed User
Si alguno de los expertos da como válida esta solución pondré [Solucionado] en el título.
 

JordiCP

Expert
Licensed User
Longtime User
Sin ser experto lo veo bastante bien. Quizás añadiría un "por si acaso"

En "MuestraIconos" (o antes) deberás contemplar la posibilidad de que algún icono no se haya descargado y tener uno por defecto, o volver a cargar, etc.... ya que ahora das el proceso por finalizado, se hayan descargado o no los archivos (intContador se incrementa siempre, tanto si Job.Success=True como si no)
 

Seneca

Active Member
Licensed User
En "MuestraIconos" (o antes) deberás contemplar la posibilidad de que algún icono no se haya descargado y tener uno por defecto, o volver a cargar, etc.... ya que ahora das el proceso por finalizado, se hayan descargado o no los archivos (intContador se incrementa siempre, tanto si Job.Success=True como si no)

Coincido contigo en esto. En este hilo me he centrado en la parte principal del código. Realmente hay que completarlo con lo que indicas, por si falla la descarga de alguna imagen, volver a intentarlo y si sigue fallando (por falta de la imagen en el servidor, por ejemplo) colocar una por defecto.

Es más, lo que tengo pensado no es que esto de las descarga de las imágenes del servidor se haga en cada ejecución de la App, en realidad solo se haría en la primera ejecución de la App tras su instalación o cuando se detecte que ha habido una actualización de la base de datos en el servidor (con una posible modificación de los iconos del menú). En caso de detectarse esa actualización de la BD en el servidor, habría que empezar por descargar la misma al dispositivo. En el resto de ocasiones el menú de iconos se mostraría al ejecutar la App cogiendo las imágenes de DirDefaultExternal, que es donde se habrían descargado en ejecuciones anteriores.

Gracias por el aporte.
 

bgsoft

Well-Known Member
Licensed User
Longtime User
Hola Séneca:

Varias cosas:
Has corregido bien lo del bucle de espera, por que como has podido comprobar inhibe el evento, por que no dejas que el SO haga otras cosas, para otra ocasión, mete un DoEvents en el bucle, "teoricamente" el DoEvents deja procesar los mensajes en espera de la cola de mensajes, pero esto no lo hace con algunos eventos, asi que mejor que no pongas un bucle cuando esperas, es mejor poner un Timer, y que una variable global te lo pare, o procese lo que quieres.

Como bien te ha dicho Jordi, tienes que contemplar que no se te baje un icono, o ninguno por que has perdido cobertura de datos o wifi :D. Yo haria varias cosas a este respecto, añade los iconos iniciales en la aplicación, al arrancar comprueba si existen en el directorio donde esten los iconos (DirDefaultExternal/Iconos), si no existen, simplemente copialos del File.DirAssets a la carpeta de iconos (DirDefaultExternal/Iconos).
Crea un servicio (en segundo plano) que te gestione toda la bajada de iconos a una carpeta temporal (DirDefaultExternal/Tmp), y conforme bajas, si el icono que has bajado es correcto, lo copias a la carpeta de iconos, y cuando bajes o cuentes el ultimo, matas el servicio.
Si pones la bajada de iconos en el formulario, a la que salgas de la aplicación, no tendras eventos, asi que tendrias que inhibir cualquier tipo de salida, y eso con el boton "home" es casi imposible a menos que pongas algo que lo vigile, por eso al ponerlo en un servicio, aunque salgas de la aplicacion seguirá bajandose los iconos.



Saludos
 

Seneca

Active Member
Licensed User
Hola.

Lo de contemplar fallos en la bajada de alguna o todas las imágenes por problemas en la conexión es algo que tenía en mente, pero lo que no se me había ocurrido controlar es el caso de un intento de salida de la App como tú me indicas. Me parece magnífica la indicación que me has dado, Jesús, de pasar la tarea a un servicio que gestione la descarga, aunque se interrumpa la App manualmente.

Aunque voy a optar por ese camino del servicio, comento seguidamente una alternativa al código que publiqué, y que se me ocurrió esta mañana, por si al alguien le pudiese resultar interesante:

Consistiría ya no en hacer una rutina que descargase del servidor todos los iconos, como paso previo a mostrarlos en pantalla. Por el contrario, sería empezar a publicar en pantalla el primer botón (icono), comprobando justo antes si la imagen del mismo existe en DirDefaultExternal. En caso de que no, colocaría el botón sin imagen y haría una llamada a un Sub que se encargaría de solicitar la descarga de dicha imagen individual desde el servidor. Seguidamente saltaría a publicar de la misma manera los sucesivos botones hasta llegar al último. De forma complementaria, los eventos JobDone se encargarían en segundo plano de colocar en cada botón (en aquellos que hubiesen quedado en blanco) la imagen que se hayan conseguido descargar, o bien les pondría una imagen genérica en caso de fallo de descarga. De esta manera la publicación del menú de iconos (botones) se haría rápida, sin necesidad de un ProgressDialogShow. Aunque las imágenes se fuesen mostrando con un poco de retraso conforme se fuesen descargando. En el peor de los casos (fallos de conexión) algunos o todos los iconos se mostrarían con una imagen genérica. Esto no sería demasiado grave porque además llevan un texto indicativo. Total, esto sería únicamente en la primera ejecución de la App o tras una actualización (siempre y cuando el dispositivo tenga conexión).

Doy por solucionado el tema ya que entiendo que los aportes hechos son suficientes.

Gracias a todos los participantes por hacerme ver más allá de lo evidente.

Saludos.
 
Last edited:

bgsoft

Well-Known Member
Licensed User
Longtime User
Hola.

Lo de contemplar fallos en la bajada de alguna o todas las imágenes por problemas en la conexión es algo que tenía en mente, pero lo que no se me había ocurrido controlar es el caso de un intento de salida de la App como tú me indicas. Me parece magnífica la indicación que me has dado, Jesús, de pasar la tarea a un servicio que gestione la descarga, aunque se interrumpa la App manualmente.

Aunque voy a optar por ese camino del servicio, comento seguidamente una alternativa al código que publiqué, y que se me ocurrió esta mañana, por si al alguien le pudiese resultar interesante:

Consistiría ya no en hacer una rutina que descargase del servidor todos los iconos, como paso previo a mostrarlos en pantalla. Por el contrario, sería empezar a publicar en pantalla el primer botón (icono), comprobando justo antes si la imagen del mismo existe en DirDefaultExternal. En caso de que no, colocaría el botón sin imagen y haría una llamada a un Sub que se encargaría de solicitar la descarga de dicha imagen individual desde el servidor. Seguidamente saltaría a publicar de la misma manera los sucesivos botones hasta llegar al último. De forma complementaria, los eventos JobDone se encargarían en segundo plano de colocar en cada botón (en aquellos que hubiesen quedado en blanco) la imagen que se hayan conseguido descargar, o bien les pondría una imagen genérica en caso de fallo de descarga. De esta manera la publicación del menú de iconos (botones) se haría rápida, sin necesidad de un ProgressDialogShow. Aunque las imágenes se fuesen mostrando con un poco de retraso conforme se fuesen descargando. En el peor de los casos (fallos de conexión) algunos o todos los iconos se mostrarían con una imagen genérica. Esto no sería demasiado grave porque además llevan un texto indicativo. Total, esto sería únicamente en la primera ejecución de la App o tras una actualización (siempre y cuando el dispositivo tenga conexión).

Doy por solucionado el tema ya que entiendo que los aportes hechos son suficientes.

Gracias a todos los participantes por hacerme ver más allá de lo evidente.

Saludos.

Buenos dias Seneca:

Esteticamente quedaria mejor si como te he dicho añades los iconos a la aplicación, arrancas la App, si no existen en la carpeta de iconos los copias, los pintas y empiezas a barjarte los mas actuales si tienes que hacerlo, y luego los cambias según bajen, si tienes botones con y sin imagen la estética no creo que sea muy buena.

Por otra parte si quieres automatizar este proceso, es tan facil como a los iconos darles un numero, que a este numero le puedes anteponer un texto si quieres; por ejemplo icono1, icono2, etc . Cuando tengas que pintar los iconos, en vez de crear un sub que te pinte todos a la vez, crea uno que te pinte solo 1 y le pases ese número como indice, de esta forma el proceso es rapido y visualmente mejor. Conforme descargas pintas el nuevo icono. Cuando tengas al inicio cargar todos los iconos, creas el mismo bucle y llamas a ese sub que te los pinta.

Saludos
 

Seneca

Active Member
Licensed User
Hola.

Seguiré la técnica que me indicas Jesús. La veo perfecta para lo que pretendo.

Gracias nuevamente.
 

Seneca

Active Member
Licensed User
...........

Crea un servicio (en segundo plano) que te gestione toda la bajada de iconos a una carpeta temporal (DirDefaultExternal/Tmp), y conforme bajas, si el icono que has bajado es correcto, lo copias a la carpeta de iconos, y cuando bajes o cuentes el ultimo, matas el servicio.

Hola.

Voy a poner en marcha la recomendación de pasar a un servicio la tarea de descargar del servidor la bajada de archivos (imágenes de los iconos).

¿Sería una buena opción adaptar el código de este hilo? https://www.b4x.com/android/forum/threads/downloading-files-using-service-module.7572/


Saludos.
 
Last edited:

Seneca

Active Member
Licensed User
Last edited:
Top