viernes, 24 de octubre de 2014

Aplicacion Android Auto Actualizable Sin Market

Hola de nuevo!!

Hoy traeremos la solucion para muchos que no pueden subir su aplicacion a Google Play (Ex Market) para que se actualice automáticamente con cada version, o para los que no quieran hacerlo por ser una version privada o por cualquier motivo sea.

Esto conciste en que la aplicacion automaticamente buscara una version mas reciente en el servidor, de haber una actualizacion la bajara e instalara, reemplazando la actual. Todo esto sin tener que depender de ningun market.

Para este proyecto voy a trabajar sobre el Android Studio de Google, sobre el lenguaje Java, Pero pueden usar cualquier IDE como Eclipse, sobre cualquier lenguaje siguiendo la logica.

Yo subire el archivo APK a un servidor propio por FTP, pero tambien se puede usar cualquier servicio de nuve que nos permita obtener el link publico (Dropbox recomiendo o Google Drive creo).

Comenzemos.

En el servidor vamos a subir dos archivos:
El APK de la aplicacion terminada y
un TXT que le llamaremos "version.txt":



en donde "versionCode" y "versionName" son los tags usados en el AndroidManifest.xml
y "downloadURL" es la direccion URL del APK (este link puede ser tambien uno de Dropbox u otro siempre que sea enlace directo). Recordemos que este archivo lo tendremos que cambiar manualmente con cada actualizacion (Para hacerlo automaticamente tambien hay una forma la cual voy a poner bajo de todo).


CODIGO!!!!

Primero y antes de que nos de error estableceremos los permisos que necesitamos en el AndroidManifest.xml


El primero para poder conectarnos a internet y el segundo para guardar el instalador en la memoria antes de ejecutarlo.

Ahora en nuestro proyecto android crearemos una clase Autoupdater.java
esta clase se encargara basicamente de todo. Tambien ejecutara las conecciones a internet a travez de AsyncTasck s para no conectarse en el hilo principal (Como ya sabran para evitar el NetworkOnMainThreatException).
En la clase que creamos (Autoupdater.java) ponemos este codigo.


Esa es la clase principal que va a hacer casi todo.

Ahora tenemos que insertarlo en la clase de UI (Interfaz de Usuario).
para eso recomiendo poner un ProgressBar en un RelativeLayout en alguna parte del layout donde vamos a ejecutar el Autoupdater.

Algo simple como esto (importante el id para reconocer):

Y tendremos que poner algo de codigo en la clase Principal, en este ejemplo hicimos que haga la actualizacion ni bien comienza la aplicacion.
Quedaria algo como esto:


Eso es lo que va en algun lado de tu pantalla donde se actualizara.
Tambien lo podemos hacer mediante un boton. Simplemente hay que llamar al metodo
comenzarActualizacion() desde donde quieras.
Algo asi querdaria con un boton:




Esto seria todo lo necesario para hacer que la aplicacion sea autoactualizable, si ven el codigo de la clase Autoupdate.java veran que esta todo comentado para que lo entiendan y modifiquen.

Ahora para cada actualizacion lo unico que tienen que hacer es subir el APK al servidor con un link publico (ya sea web propia, o servido de cloud) y modificar los TAGS en el archivo version.txt del servidor. Tambien recuerden de cambiar los TAGS en el AndroidManifest.xml en donde dice:


Pasandolo con cada aplicacion a algo de este estilo:



Recuerden que este metodo verifica la version instalada con la version en el servidor. Y si cambian una y no la otra, siempre pensara que hay una actualizacion disponible.

Aqui les dejo los archivos necesarios:



Y los agradecimientos a quienes me dieron esta idea y sobre los cual esta basada:

Androcode.es

Juro: StackOverflow


Esto es todo, Muchas gracias y espero que les sirva.

Cualquier cosa en los comentarios.



52 comentarios :

  1. Buenas, muchas gracias por el codigo , pero tengo un problemilla con el a ver si puedes ayudarme por favor: me va todo bien hasta que va a instalar la nueva version, el emulador me da el siguiente error:

    an existing package by the same name with a conflicting signature is already installed

    no se si es que se ha desistalado mal o q , puedes ayudarme por favor?

    ResponderEliminar
    Respuestas
    1. Eso pasa cuando una versión instalada es por medio del shell del IDE, es decir en modo debug (Haciendo click en el play verde del IDE), y la otra es una versión de release. Para probar esta función deberías instalar en el emulador el apk como si fuera un telefono comun, mediante el archivo, como si lo bajaras de tu pagina. Y si la otra versión esta construida con las mismas configuraciones deberías estar bien.

      Si te salto ese error vas por buen camino.

      Eliminar
  2. El error se produce porque el APK que esta instalado tiene distinta firma que el que se quiere instalar (El que se encuentra en el servidor).

    Esto se produce porque cuando se instala un paquete (app) mediante la consola de debugg (cuando le das el icono de play o iniciar en el IDE) se instala con una firma distinta de cuando creas una versión release (versión final).

    Lo que tiene que hacer es instalar manualmente la primera versión. y a medida que subas versiones en el servidor se instalara sin problemas. Para hacer esto creas la versión 1.0(por ejemplo) la copias en la maquina virtual, la instalas como si instalaras un apk común. Luego para probarla subís al servidor la versión 1.1 y ejecutas la instalada. Tendría que de esta forma realizar la actualización sin problemas.

    Yo por ejemplo. Trabajo directamente sobre mi teléfono. Y para probar que esta función ande correctamente, hago el release lo instalo manualmente. Y luego subo la versión mas nueva al servidor.

    Una vez asegurado que la función anda como debería. El teléfono dejara de correr los release y correrá las versiones de debug para mayor comodidad. cuando los otros clientes (los que usan la app) están instalados manualmente y se actualizan sin problemas.

    ¿Entendiste?

    ResponderEliminar
  3. Buenas, muchas gracias, problema solucionado .

    Quiero añadir que lleveis cuidado con el archivo build.gradle , si tienes puesto lo siguiente:

    versionCode 2 <----This
    versionName "1.1" <----This

    Siempre os pondre la vesion por defecto en 2 y pensara que hay una version actualizada por instalar, ojito amigos ;)

    Gracias de nuevo por tu ayuda Guillermo .

    Un saludo.

    ResponderEliminar
  4. Gracias por el Aporte,

    Tengo un problema al buscar el archivo apk, mediante la url de mi pagina web, el archivo txt lo leo perfectamente, pero al buscar mi archivo apk me da error "java.io.FileNotFoundException:".

    Tienes alguna idea de porque, ya revise mi url desde mi escritorio y me descarga la apk automáticamente.

    Seguí todos los pasos mencionados en este tutorial.

    Saludos y gracias...!!!

    ResponderEliminar
    Respuestas
    1. ue dice el mensaje de la excepción? (ex.getMessage() )
      Eso seguramente se debe a que bajas la app pero no puedes leer la SD, tienen que asegurarte de tener el permiso para leer la memoria.

      Con este permiso en el manifest debería arreglar tu problema

      -uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /-
      (Reemplazar guiones con mayores y menores)

      Eliminar
    2. este es mi mensaje de error:

      outputFile:(23262): /storage/sdcard0/download/app.apk
      InputMethodManager(23262): onWindowFocus: null softInputMode=32 first=false flags=#1810180
      E/Update error!(23262): http://admin...3_0.apk
      E/Update error!(23262): java.io.FileNotFoundException: http://admin...3_0.apk

      Ya agrege el permiso WRITE y READ_EXTERNAL_STORAGE, pero sigue persistiendo el mismo problema (Aclaro nuevamente que es dentro de una pagina web en donde tengo mi apk, eso influye en algo?).

      Gracias.

      Saludos...!!!

      Eliminar
    3. La pagina web del archivo seria algo asi como:

      http://www.mipagina.com/algunaCarpeta/miApp.apk

      con esa forma?

      Eliminar
    4. Asi es,
      http://admin.ejemplo.com.mx:9697/Android/MiApp3_0.apk

      El error lo esta poniendo al ejecutar el:
      InputStream is = c.getInputStream();

      El archivo lo estoy descargando correctamente desde mi pc e incluso desde mi dispositivo android al colocar la url en el navegador, por lo tanto el archivo si existe.

      Eliminar
    5. Quite "c.setDoOutput(true);" y funciono sin problema, pero tengo ahora otra pregunta, de casualidad conoces la instruccion para que se instale automaticamente sin preguntar si quiero instalarla, es decir que no deplieque el cuadro donde indica los cambios y muestra el boton de instalar.

      Gracias y Saludos...!!!

      Eliminar
    6. Eso es imposible, el unico que puede hacer eso es Google Play, por ser aplicacion de sistema. El resto de las aplicaciones y tiendas tienen que recurrir al instalador del sistema. Es decir ejecutar el instalador y ceder en control de pantalla. Incluso la Tienda de Amazon App Store hace esto.

      Eliminar
  5. Buenas, ante todo gracias por el codigo pero me sale un problema a la hora de descargar, me sale un mensaje que dice: Error de analisis se ah producido un error al analizar el paquete. Le eh dado mil vueltas y no consigo solucionarlo, instalado manualmente si que puedo pero mediante la aplicación no, espero puedas ayudarme Gracias.

    ResponderEliminar
    Respuestas
    1. La aplicacion lo que hace es descargar y ejecutar el apk mas actualizado. Deberias verificar si lo descarga bien. Si te fijas en la carpeta de descargas deberia estar el apk. Si esta pero el problema sigue probablemente sea porque no puedes ejecutar el APK. cosa que podria pasar por la opcion de "Instalaciones Desconocidas" o permisos de algun tipo.

      Eliminar
    2. Lo eh comprobado y no llega a descargarse pero al colocar la URL de descarga en el navegador si la descarga y puedo instalarla sin problemas.

      Eliminar
    3. Podrías chequear si tienes todos los permisos necesarios. Deverias ver el Logcat a ver si te da una pista de porque no lo descarga

      Eliminar
  6. Hola,

    Muchas gracias por tu gran aportación.

    Me he inspirado en tu código para hacer que mi aplicación sea autoactualizable.

    El problema que me encuentro, es que el usuario tiene que aceptar para instalar la actualización, y una vez instalada te indica:
    LISTO o ABRIR.

    Me gustaría que lo hiciese todo automáticamente y al acabar abriera la aplicación.

    Esto es posible?

    Muchas gracias

    ResponderEliminar
    Respuestas
    1. Hola.
      No, la única forma de lograr eso es a través de Play Store. Incluso aplicaciones mas profesionales como la Amazon App Store también recurren al instalador del sistema.

      Eliminar
  7. Hola, estoy intentando agregar los archivos, pero no se donde van

    ResponderEliminar
  8. Hola Guillermo,
    antes de nada darte las gracias por el código y la explicación. Lo he implementado en mi app pero recibo este error y no se a que es debido a ver si me puedes ayudar.
    El logcat me devuelve este error;
    ___________________________________________________________
    at06.telecontrol E/AutoUpdate: Ha habido un error con el JSON org.json.JSONException: Value (JSONObject.java:160)
    at org.json.JSONObject.(JSONObject.java:173)
    at at06.telecontrol.Autoupdater.getData(Autoupdater.java:96)
    at at06.telecontrol.Autoupdater.access$000(Autoupdater.java:20)
    at at06.telecontrol.Autoupdater$1.doInBackground(Autoupdater.java:232)
    at android.os.AsyncTask$2.call(AsyncTask.java:295)
    at java.util.concurrent.FutureTask.run(FutureTask.java:237)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:234)
    atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
    at java.lang.Thread.run(Thread.java:818)
    ____________________________________________________
    He revisado la url y puedo acceder a ella desde el navegador del móvil sin problema, estoy utilizando el móvil en vez de el emulador y estoy instalando la app una vez publicada y como relase tanto la que instalo como la que tengo en dropbox.

    Un saludo y gracias.

    ResponderEliminar
    Respuestas
    1. Hola. El problema parece ser de como esta escrito el .txt, fijate que tiene que estar en formato json. Si me lo pasas puedo ver a ver que onda en que te equivocaste

      Eliminar
  9. Gracias por tu respuesta.
    Este es el enlace al .txt:
    https://www.dropbox.com/s/3q782grbo92pk8p/version.txt?dl=0

    ResponderEliminar
    Respuestas
    1. Por favor te invito a que me mandes un mail a guille.marcel04@gmail.com o me hables por el chat de google.

      Eliminar
    2. deberias ver algo de este estilo en el link:
      http://guillermomarcel.xyz/prueba_archivo_json.txt

      Si ves algo mas eso te va a traer problemas a la hora de convertir a json y que tu app lo interprete.

      Eliminar
    3. El problema es que eso te devuelve una pagina completa.
      Lo que necesitas es que cuando el dispositivo pregunte por ese link se le devuelva esto: "{
      "versionCode":2,
      "versionName":"2.0",
      "downloadURL":"https://www.dropbox.com/s/3x4bhclsznc177d/app.apk?dl=0"
      }"

      el problema es que recive un codigo html... (si apretas Ctrl + U) podes ver que es el codigo de la pagina completa (con las cosas de dropbox tambien), lo que necesitas que te muestre ese link es el archivo unicamente, necesitarias algo como el link directo

      Eliminar
    4. Hola, muchísimas gracias por el código, a mi me pasa exactamente lo mismo que al compañero, pero llevo días dándole vueltas y no consigo un enlace que solo muestre la información. lo tengo en dropbox, ¿podrías indicarme como se hace?

      Eliminar
    5. Podes probar con un alojamiento web gratuito como 000webhost.com donde te dan un alojamiento para que almacenes todos tus archivos y un enlace publico a eleccion para accederlos desde internet.

      Eliminar
  10. Buen día antes que nada muchas gracias por el codigo. Todo funciona perfectamente pero cuando descarga la app solo descarga 619kb no la descarga toda la app por eso no puede abrirla ya cambie de servidor y siempre da el mismo problema si descargo la app desde el navegador la descarga sin problemas. a que se debera esto?

    ResponderEliminar
    Respuestas
    1. Tendías que chequear el Logcat al momento de la descarga. A ver si tira algún error o advertencia.
      Fíjate si podes debugear en el momento de la descarga e instalacion. Y avísame si ves algo raro.

      Eliminar
  11. Hola, he tratado de implementar tu código en Xamarin con C#, pero he tenido problemas al convertir algunas de las funciones que tienes, en especial donde utilizas el Runnable, ya que no encuentra una conversión exacta en C#, ¿podrías ayudarme?

    de ante mano muchas gracias.

    ResponderEliminar
    Respuestas
    1. La clase Runnable es una de las clases particulares de este lenguaje. Su objetivo es poder correr código en background. En android es importante ya que por regla del lenguaje no se pueden hacer tareas de red (comunicarse con el servidor) en el hilo principal. Tendrías que chequear las restricciones del hilo principal de tu lenguaje y framework. Igualmente tu lenguaje y framework deben tener alguna clase para poder ejecutar código en background. Tendrías que investigar eso en la documentación del lenguaje o en stackoverflow.com

      Eliminar
  12. Hola amigo ya use todo eso pero me tira error en public me dice lo siguiente (Esta clase no debe ser "pública" sincera el nombre de la clase dos noticias coincide con el nombre del archivo)
    Te agradezco si me respondes porfavor

    ResponderEliminar
    Respuestas
    1. Hola, no entiendo que error te tira, de que tipo es? De compilación o de Runtime? y no entiendo en que claso y archivo te tira el error. Por favor explicate un poco mas.

      Eliminar
  13. me ayudas actualmente tengo este error FATAL EXCEPTION: AsyncTask #2
    Autoupdater.java:281) //context.startActivity(intent);
    Autoupdater.java:251) //private AsyncTask downloadInstaller = new AsyncTask()

    ResponderEliminar
    Respuestas
    1. Me tendrias que enviar un poco mas de codigo, y descripcion de mensajes completa, el stack de llamadas y descripcion completa del error o el logcat, asi es muy complicado que yo sepa porque es que no anda.

      Eliminar
  14. Gracias, amigo, tu código me sirvió de mucho aunque tuve algunos problemas para Nougat pero fue debido al necesario uso del Fileprovider. Saludos

    ResponderEliminar
  15. Gracias, el codigo ayuda mucho, el problema que entengo si es que me puedes ayudar es en alojar el archivo .apk ya que he tratado con (Dropbox, Google Drive, OneDrive) por que puse mi el txt en mi ftp y ningun problema, pero creo que el servicio me bloquea los archivos .apk no se deja descargarlo,

    ResponderEliminar
    Respuestas
    1. En dropbox le pones al final del link un ?dl=1

      Eliminar
    2. Pueden ser medidas se seguridad, los servicios estos de nube cambian bastante con el tiempo. Por ahi les puede dar mejor resultado un web host gratuito como 000webhost.com en donde les dan un lugar donde alojar todos los archivos y accederlos de forma publica con una URL a elección

      Eliminar
  16. Sería posible alojar el fichero en una cuenta de Mega para bajarlo desde ese servidor. ?

    ResponderEliminar
    Respuestas
    1. Hola. No creo, ya que este cifra todo su contenido, la idea es poder acceder al archivo lo mas simple posible, con la menor cantidad de pasos posibles, lo ideal seria que tengas tu alojamiento propio como en 000webhost.com y ahí almacenes el archivo y accedas a el de forma directa

      Eliminar
  17. Hola compañero buen tutorial tengo un problema al manifiesto y la id donde cascan?

    ResponderEliminar
  18. Gracias por la explicación, lo he adaptado un poco a mi aplicación, pero tengo el mismo problema que otro compañero más arriba, el LogCat me muestra lo sieguiente:

    019-01-30 11:19:44.861 22987-23125/aplication.android.als.menumante E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #2
    Process: aplication.android.als.menumante, PID: 22987
    java.lang.RuntimeException: An error occurred while executing doInBackground()
    at android.os.AsyncTask$3.done(AsyncTask.java:353)
    at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
    at java.util.concurrent.FutureTask.run(FutureTask.java:271)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
    at java.lang.Thread.run(Thread.java:764)
    Caused by: android.os.FileUriExposedException: file:///storage/emulated/0/Download/ManteApp.apk exposed beyond app through Intent.getData()
    at android.os.StrictMode.onFileUriExposed(StrictMode.java:1960)
    at android.net.Uri.checkFileUriExposed(Uri.java:2348)
    at android.content.Intent.prepareToLeaveProcess(Intent.java:9885)
    at android.content.Intent.prepareToLeaveProcess(Intent.java:9839)
    at android.app.Instrumentation.execStartActivity(Instrumentation.java:1610)
    at android.app.Activity.startActivityForResult(Activity.java:4487)
    at android.support.v4.app.BaseFragmentActivityApi16.startActivityForResult(BaseFragmentActivityApi16.java:54)
    at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:67)
    at android.app.Activity.startActivityForResult(Activity.java:4445)
    at android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:720)
    at android.app.Activity.startActivity(Activity.java:4806)
    at android.app.Activity.startActivity(Activity.java:4774)
    at aplication.android.als.Actualizar.Autoupdater$2.doInBackground(Autoupdater.java:284)
    at aplication.android.als.Actualizar.Autoupdater$2.doInBackground(Autoupdater.java:254)
    at android.os.AsyncTask$2.call(AsyncTask.java:333)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245) 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) 
    at java.lang.Thread.run(Thread.java:764) 

    No termino de comprender ese "FATAL EXCEPTION: AsyncTask #2" , descarga la aplicación, pero a la hora de instalarla es cuando da el error. Esa parte no se ha modificado en el código, ya que lo único que he hecho es quitar el progressBar (me daba problemas) y dos botones: "COMPROBAR" y "ACTUALIZAR" mediante un activity, sin hacerlo con AlertDialog

    ResponderEliminar
    Respuestas
    1. ZeroCool puedes compartir la solución, me pasa igual cual fue la solución, de antemano muchas gracias.

      Eliminar
    2. Vas a encontrar la solucion aca:
      https://stackoverflow.com/questions/38200282/android-os-fileuriexposedexception-file-storage-emulated-0-test-txt-exposed

      Eliminar
  19. existe un error que da por falta de comillas dobles en el 2 en:
    {
    "versionCode":1,
    "versionName":"0.1",
    "downloadURL":"http://tupagina.com/Android/AndroidApp1.apk"
    }

    la solución es esta, ami me funciono para la descarga.

    error "versionCode":1,

    corregido "versionCode":"1",

    ResponderEliminar
    Respuestas
    1. Eso depende del modulo codificador/decodificador de JSON. Segun La Documentacion ambas formas estan bien. El Componente JSON te va a decir que implementacion va a poder recivir o no.
      Te agradezco el detalle por si alguien mas tiene ese problema.

      Eliminar
  20. Hola Guillermo, me gustaria poder hablar contigo, mi wathsapp 3815167777. J.C. Lencina, de Tucuman, Argentina. Gracias

    ResponderEliminar
    Respuestas
    1. Hola. Te podes comunicar conmigo al mail: guille.marcel04@gmail.com
      Atte. Guillermo C. Marcel

      Eliminar
  21. Hola Guillermo, si no es mucho pedir, puedes actualizar el codigo en lo referido a la solicitud de permisos que se requieren en la aplicacion, trate de hacerlo, pero me enrredado mucho, Gracias

    ResponderEliminar
    Respuestas
    1. Hola.
      Lo lamento pero el código no esta mantenido.
      Recomiendo seguir las guias de desarrollador establecidas por Google para los permisos necesarios.
      Para este caso, necesitaran permiso de escribir y leer la memoria del telefono. Creo que no son necesarios otros permisos. Esto se consigue basicamente preguntando, antes de empezar, si tenemos permisos sobre la memoria, de ser asi continuar comunmente, de lo contrario llamar a un metodo de la clase context.

      Te recomiendo la siguiente lectura:
      https://developer.android.com/training/permissions/requesting?hl=es-419

      Atte. Guillermo Marcel

      Eliminar