Inicio > Delphi, Tethering, XE7 > Mando a distancia; Tethering con Delphi XE7

Mando a distancia; Tethering con Delphi XE7

viernes, 17 de octubre de 2014 Dejar un comentario Ir a comentarios
Share Button

El tema de Tethering, del que ya he hablado antes en el blog (Tethering, operaciones básicas), se incorporó a la versión XE6 de RAD Studio. Hasta ese momento podíamos utilizar esta característica para conectar aplicaciones y dispositivos utilizando Wifi.

blueT_WifiPara esta nueva versión XE7 se a añadido la posibilidad de conexión mediante Bluetooth.
Además, a las características ya existentes de conexión wifi, se han añadido opciones para poder filtrar o discriminar los dispositivos a los que nos vamos a conectar a partir de máscaras para la IP o para la subred.

Como ya he comentado anteriormente, el Tethering es una característica que podemos utilizar (aunque se haya introducido en las últimas versiones de Delphi) no sólo en aplicaciones FireMonkey (tanto móviles como de escritorio) sino también, en aplicaciones ”antiguas” diseñadas con la VCL.

INTRODUCCIÓN

La idea de esta entrada es repasar más ampliamente en las características de Tethering, de las que ya hablé anteriormente, y profundizar en las posibilidades de esta tecnología, utilizando un proyecto más complejo y completo que el que vimos en la introducción.

Para ello utilizaremos 2 aplicaciones diferentes. Una basada en la VCL y otra desarrollada con Firemonkey para dispositivos móviles.

Reproductor y Mando

PROYECTO VCL

Como proyecto VCL he recuperado un antiguo reproductor que comencé a realizar hace un tiempo y que tenía aparcado pendiente de acabar. Inicialmente está programado en Delphi 6 y debería compilar sin problemas en esta versión y en posteriores (hasta Delphi XE7) a excepción de las características que le he añadido con esta última, como son los estilos y la inclusión de App Tethering.

Las características básicas con las que cuenta son las siguientes:

  • Para la visualización y las funciones básicas utiliza BASS.DLL. La librería BASS,  para los que no la conozcáis, es una librería de audio bastante potente, multiplataforma y  libre para uso no comercial. Para más detalles podéis echarle un vistazo a la web.
  • Utiliza conexión a Internet y los componentes Indy para obtener información de la canción que está reproduciendo en cada momento. Para ello utiliza la información del propio fichero tal y como os mostré en una entrada anterior del blog titulada “Obtener información de una canción”. Todo ello se encapsula en una clase que utiliza Threads para no bloquear el programa principal mientras se hacen las consultas.
  • Utilizando las ultimas versiones de delphi, le he añadido soporte para estilos, de forma que se puede cambiar entre varios de ellos en ejecución.

Os adjunto alguna imagen del proyecto tal y como ha quedado y en el vídeo posterior lo veréis funcionando.

Captura__17_repro_Todo

Al proyecto original (diseñado con Delphi 6/7) utilizando la VCL le he añadido código para poder utilizar Tethering y comunicarse con una aplicación móvil que hará las veces de «mando a distancia».
Lo primero es incluir los dos componentes nuevos e imprescindibles que nos permitirán trabajar con Tethering; Un TTetheringManager y un TTetheringAppProfile. El primero podríamos decir, que es el encargado de todo lo relacionado con la conexión entre dispositivos y el segundo el que controla todas las acciones y recursos que comparten las aplicaciones una vez que ya están conectadas.

Además de estos dos componentes (que son indispensables), vamos añadir Acciones y recursos a nuestra aplicación. Con ellos podremos completar todas las operaciones/comunicaciones que deseamos realizar entre ambos programas.

AccionesVCLACCIONES

El programa de por si, tiene unas acciones predefinidas propias de un reproductor (incluidas en un TActionList estándar), como son el PLAY, PAUSE, STOP, SIGUIENTE,… como se aprecian el la imagen derecha.

Lo que he hecho es «mapear» estas acciones en el componente TTetheringAppProfile como públicas y con la propiedad Kind a Shared. De esta forma podrán ser llamadas/invocadas desde otra aplicación conectada a esta, utilizando, por ejemplo, el método RunRemoteAction de la clase Action_PlayTTetheringAppProfile.

A la izquierda se puede ver una imagen de cómo está «mapeada» la acción PLAY del reproductor. De forma similar a esta se hace con el resto. Tiene asignado un nombre único “rPlay”, que será con el que se invoque y está asociada a la acción del programa ActionPlay.

Cuando desde el mando a distancia ejecutemos el siguiente código:

ttProfiler.RunRemoteAction(pInfo, 'rPlay');

Se ejecutará la acción ActionPlay definida en nuestro programa. Más adelante cuando hablemos del mando a distancia, repasaremos esto.

En cuanto a las acciones no hay que hacer nada más, tan simple como esto. Debemos/podemos “mapear” las acciones que necesitemos ejecutar utilizando el Inspector de objetos.

RECURSOS

Si utilizando las acciones podemos conseguir ejecutar comandos entre diferentes aplicaciones, utilizando recursos lo que vamos a conseguir es poder compartir información entre los programas que se están comunicando. Utilizando ambos, deberíamos poder hacer todo lo necesario.

En el componente TTetheringAppProfile (igual que con las acciones) vamos a definir los recursos que utilizaremos para enviar información al “Mando a distancia”.

En mi caso, he utilizado diferentes recursos para la información que deseo enviar desde el reproductor y que deseo que reciba el mando a distancia; Es decir, todo lo referente a la canción que se está reproduciendo en ese momento. De esta forma, definiré recursos para el título, el artista, género, duración de la canción, volumen,…

Todos ellos se irán actualizando en cada momento con la información actual de la reproducción.

Recursos

Durante la ejecución de la aplicación (reproductor) lo único que tenemos que hacer es ir actualizando el valor de los recursos a medida que estos van cambiando. Cuando selecciono una canción nueva, modifico los recursos del Título, Artista, Longitud de la canción,… mientras que se está reproduciendo tendré que actualizar el recurso que posee la posición (en tiempo) de la canción o cuando cambio el volumen, el que mantiene este valor.

Para ello bastan unas líneas como estas:

// actualizar los recursos relacionados con la canción actual
ttProfiler.Resources.FindByName('rTiempoTotal').Value := lblDuracion.Caption;
ttProfiler.Resources.FindByName('rMaxCancion').Value := IntToStr(tbCancion.Max);
ttProfiler.Resources.FindByName('rTiempoCancion').Value := lblPosition.Caption;
ttProfiler.Resources.FindByName('rPosCancion').Value := IntToStr(tbCancion.Position);
ttProfiler.Resources.FindByName('rMaxCancion').Value := IntToStr(tbCancion.Max);
...
 
// Al modificar el volumen actualizamos el recurso
ttProfiler.Resources.FindByName('rVolumen').Value := IntToStr(tbVolumen.Position);
...

Aquí accedo a los recursos a partir de su nombre de forma única, utilizando FindByName, aunque también podría hacerlo utilizando la propiedad Items o FindItemID.

En este caso la estrategia que hemos escogido es definir unos recursos donde almacenaremos valores, disponibles para que otras aplicaciones los consulten.

Otra alternativa es enviar directamente los datos a recursos definidos en otras aplicaciones conectadas. Es lo que vamos a hacer para enviar la carátura (imagen JPG) a la aplicación del mando a distancia. Der esta forma podremos ver la alternativa a intercambiar datos.

Para ello vamos a utilizar el método SendStream de la clase TTetheringAppProfile, con un código como este:

// enviar datos a los perfiles remotos (si tenemos alguno) 
  if (ttManager.RemoteProfiles.Count > 0) then begin
 
    // acceder al perfil 
    pInfo := ttManager.RemoteProfiles[0];
 
    // La imagen está en disco
 
    JPEGImage := TJPEGImage.Create(); 
    CaractulaStream := TMemoryStream.Create; 
    try 
      JPEGImage.LoadFromFile(InfoTracks[ID].PathImage); 
      JPEGImage.SaveToStream(CaractulaStream); 
      // Una vez almacenada en un Stream la enviamos 
      ttProfiler.SendStream(pInfo, 'rCaratula', CaractulaStream); 
    finally 
      CaractulaStream.Free; 
      JPEGImage.Free; 
    end; 
  end;

Una vez realizado el envío, en la aplicación que está conectada nos saltará un evento OnResourceReceived que tiene la siguiente cabecera:

procedure TMainForm.ttProfilerResourceReceived
  (const Sender: TObject; const AResource: TRemoteResource);

Basta con comprobar que es el recurso deseado y realizar las acciones pertinentes. En mi caso, como es una imagen, directamente se puede cargar sobre un TImage y mostrar así en el mando a distancia la carátula de la canción que se está reproduciendo en ese momento:

procedure TMainForm.ttProfilerResourceReceived(const Sender: TObject; 
  const AResource: TRemoteResource);
 
begin
 
  _Log('Resource received; ' + AResource.Name); 
  _Log('Hint= ' + AResource.Hint); 
  _Log('Valor= ' + AResource.Value.AsString);
 
  // Es el recurso que esperamos? 
  if (AResource.Hint = 'rCaratula') then begin 
 
    // Cargarlo sobre un TImage 
    imgCaratula.Bitmap.LoadFromStream(AResource.Value.AsStream); 
  end;

Con esto ya tenemos descrito el sistema que utilizamos para enviar información desde el reproductor al mando a distancia conectado.

PROYECTO MÓVIL

El proyecto móvil, como ya se ha visto antes, es un mando a distancia para el reproductor de música. No sólo he intentado que tenga las funciones básicas para controlar la aplicación, sino que además reciba información de la canción que se está reproduciendo en cada momento; Una forma de plasmar el estado del reproductor en el dispositivo que hace de “mando a distancia”.

Mandos

Al entrar en el programa, disponemos de un botón que nos permitirá conectar con nuestro reproductor, si este está presente. Para ello se utiliza el método DiscoverManagers. Si encontramos alguna aplicación a la que poder unirnos, se realiza un PairManager.

Durante este proceso, como ya he comentado antes, “saltan” eventos en ambas aplicaciones, que nos permiten comprobar el estado de cada operación. Algunos de ellos son OnNewManager, OnPairedFromLocal, OnPairedToRemote,…

Una vez completado el “emparejamiento” lo que hago desde la aplicación móvil es subscribirme a los recursos publicados por la aplicación de escritorio. De esta forma, una vez que la suscripción se ha completado, cuando esos recursos se modifiquen, el control remoto recibirá automáticamente una notificación.

// Suscribirse a los recursos remotos 
b := ttProfiler.SubscribeToRemoteItem(RemoteProfiles[0], 'rArtista'); 
b := ttProfiler.SubscribeToRemoteItem(RemoteProfiles[0], 'rAlbum'); 
b := ttProfiler.SubscribeToRemoteItem(RemoteProfiles[0], 'rCancion'); 
b := ttProfiler.SubscribeToRemoteItem(RemoteProfiles[0], 'rGenero'); 
b := ttProfiler.SubscribeToRemoteItem(RemoteProfiles[0], 'rAnyo'); 
b := ttProfiler.SubscribeToRemoteItem(RemoteProfiles[0], 'rVolumen'); 
b := ttProfiler.SubscribeToRemoteItem(RemoteProfiles[0], 'rTiempoTotal'); 
b := ttProfiler.SubscribeToRemoteItem(RemoteProfiles[0], 'rTiempoCancion');
 
…
 
b := ttProfiler.SubscribeToRemoteItem(RemoteProfiles[0], 'ListaCanciones');

Además la aplicación “solicita” mediante una acción remota, que se le envíen los datos iniciales. De esta forma justo después de conectarse recibe toda la información del estado del reproductor. A partir de ese momento, sólo se irán recibiendo aquellos recursos que se van modificando. Esta llamada a una acción remota se realiza con un código como este:

// Perfiles remotos 
i := RemoteProfiles.Count;
 
// Hay alguno? 
  if (i > 0) then begin 
    // Accedemos al primero 
    pInfo := RemoteProfiles[0]; 
    // Ejecutamos la acción remota 
    ttProfiler.RunRemoteAction(pInfo, 'ActionSendInfoToRemote');
 
…

Más o menos con lo explicado hasta ahora ya podemos ver cómo el control remoto se mantiene “actualizado” con el estado que en cada momento tiene el reproductor.

La otra parte del trabajo es la de que el control remoto se comporte realmente como un “control remoto” de nuestra aplicación. Para ello basta con utilizar las acciones publicadas desde la aplicación del reproductor que vimos anteriormente.

Acciones_publicadas

El el componente AppProfiler hemos creado acciones que “mapean” las acciones del formulario (las de siempre).

De esta forma, creamos una acción pública (IsPublic=True) compartida (Shared) llamada rPlay (nombre único) y que ejecutará la acción ActionPlay. Igual para el Pause, Stop,…

Desde nuestro mando a distancia sólo nos queda ejecutar la acción remota rPlay, para que en el reproductor se ejecute la acción ActionPlay.

// Alguien conectado?
if (ttManager.RemoteProfiles.Count > 0) then begin
pInfo := ttManager.RemoteProfiles[0];
 
// Ejecutamos acciones remotas…
ttProfiler.RunRemoteAction(pInfo, 'rPlay');
 
…

 

TIPOS DE CONEXIÓN

Una de las mejoras más importantes en la última versión de Delphi con respecto a Tethering, es la inclusión de un nuevo protocolo de comunicación. A partir de RAD Studio XE7 en el componente TTetheringManager encontramos la propiedad AllowedAdapters, que nos permite seleccionar entre Network (wifi) o Bluetooth. No sólo eso, sino que podemos seleccionar ambos, utilizando “;” o “pipes” (|), de forma que se inicializarán ambos.

Debería bastar con modificar esta propiedad para hacer funcionar nuestro programa con uno u otro protocolo (**).

(**) NOTA: Mi intención, era probar esta nueva característica y poder enseñarla en esta entrada, pero desgraciadamente no he conseguido conectar mi ordenador con la aplicación móvil utilizando bluetooth. Creo que los problemas de conexión me los está generando el ordenador portátil (que ya es algo viejo) y cuyos drivers bluetooth no están correctamente instalados, así que por ahora me he quedado sin poder probar/enseñar esta opción. Sigo intentándolo, pero no quería demorar más la publicación de esta entrada por este tema.

A medida que descubra más sobre el problema lo añadiré a la entrada (prometido).

CONCLUSIÓN

La sensación que deja esta característica una vez que la usas en una aplicación y ves el funcionamiento, es que realmente es algo muy “simple”, pero a la vez muy “útil”. De aquellas cosas que piensas cuando las ves, que ”porqué no se le habrán ocurrido a alguien antes”…

;-D

Salvando las primeras pruebas, cuando ya se dominan un poco los métodos y propiedades de ambos componentes y se entiende el funcionamiento básico de “Acciones” y “Recursos”, la utilización se vuelve bastante simple.

He grabado un vídeo para que se pueda ver funcionando ambas aplicaciones, espero que sea útil junto con el resto de la entrada.

Os adjunto los links para descargar tanto los ejecutables como los fuentes de ambos aplicaciones:

Código fuente del reproduvctor

Código fuente del mando a distancia

Ejecutable del reproductor

Ejecutable del mando a distancia

Apliación APK del mando a distancia (actualizado 25/02/2015)

Código fuente del package PBass.BPL

DLLs y BPLs extras de delphi 6 (PBass.bpl)

NOTA: Hay diferentes versiones de la librería BASS.DLL; en mi caso estoy utilizando una compatible con 64 bits. Si os da problemas en otras versiones/siatemas avisadme por favor.

NOTA2: Gracias a Cocce, que me ha comentado que le daba un error al ejecutar si no está delphi 6 instalado (debido a la compilación del package PBass.bpl -que os dije que había compilado con D6-). He subido el código fuente del package y un par de BPL’s que necesita para ejecutar.


AÑADIDO (25/02/2015): (Gracias iretai).

El usuario Iretai me comenta que la aplicación ha “salido” con demasiados permisos y tiene razón. Por defecto Delphi tiene una serie de permisos “predefinidos” activados, que si no nos acordamos de desactivar quedan activados en su publicación. No es algo sin importancia y menos si la aplicación va a ser una aplicación que posteriormente se distribuya en la tienda de Google.  En este caso, como estábamos hablando de un ejemplo no le di más importancia, pero si la tiene.

Además los permisos activados no son “triviales”, pues hablamos de acceso a las llamadas, a la cámara o al calendario. En concreto las siguientes líneas están en el XML:

  • <uses-permission android:name=»android.permission.CALL_PHONE» />
  • <uses-permission android:name=»android.permission.CAMERA» />
  • <uses-permission android:name=»android.permission.INTERNET» />
  • <uses-permission android:name=»android.permission.READ_CALENDAR» />
  • <uses-permission android:name=»android.permission.READ_EXTERNAL_STORAGE» />
  • <uses-permission android:name=»android.permission.READ_PHONE_STATE» />
  • <uses-permission android:name=»android.permission.WRITE_CALENDAR» />
  • <uses-permission android:name=»android.permission.WRITE_EXTERNAL_STORAGE» />

El código fuente de la aplicación ha estado disponible desde que publiqué la entrada, así que basta con cambiarlos y recompilar, pero ya que también he publicado el APK compilado, los he modificado yo y lo he vuelto a subir actualizado.

También he subido la aplicación a Google Play (donde al descargar nos avisa de los permisos activados) para que también se pueda descargar desde allí.

Os adjunto la captura donde aparecen los cambios de permisos al subir la actualización a Google Play (cosa que está muy bien).

Nuevos_permisos

Como siempre los comentarios, sugerencias, críticas,…  serán bien recibidas.

Un saludo y hasta la próxima.

Vota este post
Categories: Delphi, Tethering, XE7 Tags: , ,
Subscribe
Notify of
guest

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

32 Comments
Inline Feedbacks
Ver todos los comentarios
casimiro
casimiro
9 years ago

Impresionante :)

Cocce
Cocce
9 years ago

I tried your project but i have an error executing the line
H := LoadPackage(‘PBass.BPL’);

PBass.BPL requires «rtl60.bpl» and then the program aborts.

Would you please provide the PBass.bpl source project?

Thanks in advance
Cocce

Eliseo GN
Eliseo GN
9 years ago

Que decir, una entrada excelente amigo Germán, si ésto no convence a los que aún tienen dudas que Delphi está haciendo cosas geniales, entonces nada los convencerá.

Impresionante la forma como abordaste el ejercicio amigo, muchas felicidades.

Saludos

dec
dec
9 years ago

Muy interesante el artículo así como la simplicidad y potencia que el invento proporciona. ¿Qué hay debajo de los componentes que se mencionan? ¿Cómo funcionan «realmente»? Sea como sea muchas gracias por la aportación Germán. :)

dec
dec
9 years ago

Muchas gracias por la información Germán.

Javier
Javier
9 years ago

Hola.-

Gran artículo y muy bien explicado.

Solo tengo una cuestión, si quisiera enviar desde el reproductor al mando además de los datos propios de la canción (titulo, autor, etc.) la canción misma (supongo que mediante un TStream), tienes idea de como deberia enfocar este tema?

Saludos y muchas gracias por tus articulos.

Javier
Javier
9 years ago

@Germán Estévez

Hola. Gracias por tu respuesta. Actualmente utilizo esto como metodo de envío:

procedure TForm1.PlayerRemoto(Sender: TObject);
var
newDish: TStream;
begin
newDish.Create;
newDish := StringToStream(LoadFile(‘/sdcard2/musica/EMANUELLE.mp3’));
AppProfile.SendStream(Manager.RemoteProfiles[0], ‘Musica’, newDish); <——Error
end;

function TForm1.LoadFile(const FileName: TFileName): string;
begin
with TFileStream.Create(FileName,
fmOpenRead or fmShareDenyWrite) do begin
try
SetLength(Result, Size);
Read(Pointer(Result)^, Size);
except
Result := ''; // Deallocates memory
Free;
raise;
end;
Free;
end;
end;

Desgraciadamente, me devuelve un error cuando envío el stream en la línea indicada más arriba. Se que con imagenes es facil ya que en la propiedad bitmap tenemos la funcion SaveToStream, sin embargo, en audio, no existe esto.

Saludos.

Javier
Javier
9 years ago

Javier :
@Germán Estévez
Hola. Gracias por tu respuesta. Actualmente utilizo esto como metodo de envío:
procedure TForm1.PlayerRemoto(Sender: TObject);
var
newDish: TStream;
begin
newDish.Create;
newDish := StringToStream(LoadFile(‘/sdcard2/musica/EMANUELLE.mp3?));
AppProfile.SendStream(Manager.RemoteProfiles[0], ‘Musica’, newDish); <——Error
end;
function TForm1.LoadFile(const FileName: TFileName): string;
begin
with TFileStream.Create(FileName,
fmOpenRead or fmShareDenyWrite) do begin
try
SetLength(Result, Size);
Read(Pointer(Result)^, Size);
except
Result := »; // Deallocates memory
Free;
raise;
end;
Free;
end;
end;
Desgraciadamente, me devuelve un error cuando envío el stream en la línea indicada más arriba. Se que con imagenes es facil ya que en la propiedad bitmap tenemos la funcion SaveToStream, sin embargo, en audio, no existe esto.
Saludos.

Por cierto, el error que me devuelve es error en parámetros (en newDish). Mi problema es que newDish posiblemente aunque es del tipo correcto, no contenga el audio correctamente.

Saludos.

Javier
Javier
9 years ago

Gracias. Probaré eso que me comentas. Quería hacerlo de la misma forma como se reproducen los wav, pero según parece, los mp3 no se pueden reproducir directamente desde memoria, sino como tu bien dices, grabandolo primero a disco (o a la tarjeta).

Saludos.

Javier
Javier
9 years ago

Gracias. He escrito el siguiente código:

procedure TForm1.PlayerRemoto(Sender: TObject);
var
newDish: TMemoryStream;
//sound: TSoundItem;
begin
newDish:=TMemoryStream.Create;
try
newDish.LoadFromFile(‘/sdcard2/musica/EMANUELLE.mp3’);
newDish.Position:=0;
AppProfile.SendStream(Manager.RemoteProfiles.Items[0], ‘Musica’, newDish);
finally
newDish.Free
end;
end;

Pero desafortunadamente me da error de «Parameter out of range» en el paso de parámetros en el appProfile. Me temo que no carga correctamente el archivo mp3 en el memorystream o bien al enviar newDish no es la forma correcta de enviarlo.

mike
mike
9 years ago

gran artículo. No pienso lo mismo de tu gusto musical :D

iretai
iretai
9 years ago

Muy interesante, gracias por comparti.

el apk bajado, solicita permisos com hacer llamadas telefonicas, localizarme a traves del GPS, Leer informacion como ID e IMEI, Acceder a mi camara y finalmente permisos para manipular mi disco. Creo que ninguna de estas solicitudes es necesaria, claro que son interesantes de utilizar si uno quiere usmear al que bajo la apk.

MiguelC-II
MiguelC-II
8 years ago

Saludos a Todos.

He estado indagando sobre como hacer un APP buscador de musica (vía Streaming/Internet), utilizando Firemonkey/Delphi(preferiblemente FireMonkey por lo de los iOS/Android); es decir, poder escribir el nombre de una canción o artista, y acto seguido me muestre el/las canciones encontrada(s); Poder reproducirla o hacer el download de la canción. Tengo la idea, de que esto amerita esta inscrito en algun servicio de musica (Spotify, etc) y usar algun API; pero la verdad que encuentro muy poca documentación o ejemplos claros de como hacerlo.
Gracias de Antemano por cualquier orientación.

victor
victor
8 years ago

Hola Neftalí, por que será que siempre que estoy por empezar un proyecto termino en tu blog??

Tengo en mente realizar un programa que se comunique vía bluetooth con un arduino, con fines didácticos, se trata de controlar dirección y velocidad de un auto de juguete, el líneas generales.

No se realizar la comunicación bluetooth desde delphi -este será mi primer intento- y este trabajo tuyo es lo primero que leo, pero desde ya intuyo que lo mio es mucho más modesto que lo que aquí encuentro. Alguna recomendación que puedas darme??? todo comentario se agradece y ayuda.

victor
victor
8 years ago

Muchas gracias por tu respuesta, ya me pongo a estudiarlo.

Saludos!!

Vinicius
Vinicius
8 years ago

Hola Neftalí,

En primer lugar gracias por compartir sus conocimientos, fue muy útil a sus enseñanzas sobre » Tethering Delphi XE7″ desarrollaron un sistema muy similar, pero tengo un problema que no he encontrado una solución, tal vez usted me puede ayudar, tengo un servidor con un programa principal y una aplicación de Android, todo funciona muy bien, conectado a través de wifi, pero la conexión en adroid se perdió al quedarse fuera del alcance de wifi tiene un error: SOCKET ERROR…CONNECTION RESET BY PEER, y volver al trabajo tienen que cerrar la aplicación en Android y cerca también en el servidor, ya sabes alguna manera de evitar que el programa se bloquea el servidor y que no es necesario cerrar y abrir de nuevo?

muchas gracias.

Arturo Serrallés
Arturo Serrallés
7 years ago

Interesante artículo!, lamentablemente los fuentes no están disponibles, ¿es posibles resubirlos?.

Arturo Serrallés
Arturo Serrallés
7 years ago

Muchas gracias Neftalí, ya pude descargarlos.

32
0
Would love your thoughts, please comment.x
()
x