Mostrando entradas con la etiqueta Programación. Mostrar todas las entradas
Mostrando entradas con la etiqueta Programación. Mostrar todas las entradas

viernes, 18 de enero de 2013

Crear un HorizontalScrollView programáticamente en Android

Deseaba crear un HorizontalScrollView similar al que usa Angry Birds al momento de elegir al nivel que queremos jugar, pero de manera programática porque hacerlo a través de XMLs me complicaba un poco el cálculo de los tamaños de los objetos.
 
Fue un dolor de cabeza y por más que buscaba en Internet no encontraba algún artículo que me explicara paso por paso cómo hacerlo, hasta que encontré el siguiente link que me ayudó a entender algunos conceptos: http://www.arpitonline.com/blog/2012/07/01/creating-custom-layouts-for-android/, pero además tuve que ingeniarme algunas cositas extras para hacerlo funcionar tal como quería. Posteo aquí los pasos a seguir:
  1. Crea una clase que herede de ViewGroup. ViewGroup es un View que te permite agregar Views dentro de él. Llamaremos a esta clase MyGroup. Esta clase tendrá dos objetos que nos ayudarán a crear el efecto del Scroll, además de los Views que se agregarán en él:
  2.  
    HorizontalScrollView scroll;
    LinearLayout layout;
     
  3. Dentro del constructor de MyGroup insertamos el siguiente código para agregar el scroll:
  4.  
    scroll = new HorizontalScrollView(context);
    scroll.setHorizontalScrollBarEnabled(false);
    addView(scroll);
    layout = new LinearLayout(context);
    scroll.addView(layout);
     
    HorizontalScrollView es una clase que solamente permite agregarle un hijo, éste será nuestro layout, el cual contendrá todos los views que formarán parte del scroll.
     
  5. Agrega al layout los objetos que se mostrarán en el scroll a través de la sentencia layout.addView(view).
  6.  
  7. Tal como lo describe el artículo, los objetos internos del ViewGroup no se mostrarán a menos que les indiquemos su posición y tamaño, esto lo hacemos sobreescribiendo el método onLayout. En este método, para cada view dentro de nuestro ViewGroup debemos llamar su método layout, algo similar a esto:
  8.  
    for(int i=0;i
        View v = getChildAt(i);
        v.layout(x1,y1,x2,y2);
    }
     
    Esto funciona para views simples, pero en el caso de layouts, éstos también deben indicarle a sus hijos su posición y tamaño ya que hasta ese momento tienen tamaño 0. Una forma común y sugerida en el artículo para resolver esto es sobreescribir el método onMeasure del layout para así poder indicarle a sus hijos el tamaño que tendrán. En mi caso, el único objeto que forma parte del ViewGroup es el scroll, y dentro de éste esta el layout y dentro del layout están los demás views que también deben recibir su tamaño y posición. Así que utilice el onLayout para indicar estos tamaños. Pero probando de varias maneras los objetos no salían como deseaba, hasta que me di cuenta de algo: layout debe tener un ancho igual a la suma de los anchos de sus hijos mas los espacios entre ellos, y el scroll debe tener el ancho en el que deseo aparezcan los views. Aunque puede parecer obvio, puede generar mucha confusión; si voy a poner un objeto seguido de otro sus anchos se calculan diviendo el ancho de la pantalla entre el numero de objetos, pero si quiero poner espacios entre ellos, hay que tomar en cuenta que el layout debe contemplar estos espacios y que es probable que sea incluso más grande que el scroll. El código sería algo así:
     
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        //Quiero que el scroll sea de alto la mitad de la pantalla y que esté centrado
        scroll.layout(0, b/4, r, b*3/4);
     
        //Calculo el ancho que tendrá cada objeto
        int w = (int)(b/2f*tasa);
     
        //El ancho del layout será igual a la suma de los anchos más las separaciones más la posicion inicial
        layout.layout(0, 0, (w+separacion)*(n-1)+w+pos_inicial, b/2);
     
        //Calculo la posición de los views dentro del layout
        for(int i=0; i< layout.getChildCount(); i++) {
            View v = layout.getChildAt(i);
            v.layout((w+separacion)*i+pos_inicial, 0, (w+separacion)*i+w+pos_inicial, b/2);
        }
    }
     
Una vez hecho, el scroll funcionó perfectamente en cualquier dispositivo. Ahora habrá que añadirle el onTouch y los damos eventos.
 
No es la única forma de hacerlo, pero me funcionó muy bien!

miércoles, 20 de julio de 2011

Utilizando SSLSockets en un ambiente de prueba

He estado desarrollando una aplicación en Java que utiliza Sockets para la comunicación entre el cliente y el servidor. Inicialmente lo hice con Sockets normales, pero a la hora de pasarlo a SSLSockets me encontré con varios problemitas que complicaron la implementación, después de invesitgar un poco y hacer cambios a prueba y error logré hacerlo funcionar.

Siguiendo esta lista de pasos, podrás hacer funcionar tu aplicación con SSLSockets en un ambiente de pruebas donde quizás no tengas instalado un certificado SSL válido (Este post asume que conoces, al menos de manera general, qué es SSL y cómo funciona):


Servidor

Lo primero que se necesita hacer es crear un certificado SSL para el servidor, el cual será utilizado para establecer la comunicación con el cliente. Para la creación de este certificado se puede utilizar la herramienta keytool que viene en el JDK y en el JRE de Sun Microsystems.

Para crear este certificado corremos la siguiente instrucción:
keytool -genkey -keystore miKeyStore -alias unAlias -keypass clave -storepass claveDeKeyStore
-keystore genera un archivo que es el almacén de claves (KeyStore), en él se guardará el certificado autofirmado y el par de claves pública/privada. Si no especificamos este comando se creará un archivo en el directorio raíz del usuario con el nombre ".keystore".

-alias es el nombre con el que haremos referencia al par de claves creado.

-keypass es el password para acceder a la clave privada

-storepass es la clave para acceder al KeyStore


Para saber que escribir en cada una de los inputs que el programa esperará, o para conocer que otras características puedes utilizar con keytool puedes consultar alguno de estos links: http://www.adictosaltrabajo.com/tutoriales/tutoriales.php?pagina=securitySSLKeytool o http://download.oracle.com/javase/1.3/docs/tooldocs/win32/keytool.html. Una vez creado el archivo deberá ser copiado en el directorio de trabajo donde correrá nuestro Servidor.

Ahora estamos listos para programar nuestro servidor en Java, el cual debiera ser algo similar a esto:

public class Servidor {
    public static void main(String args[]) {
      System.setProperty("javax.net.ssl.keyStore", "miKeyStore");
      System.setProperty("javax.net.ssl.keyStorePassword", "claveDeKeyStore");
      try {
        SSLServerSocketFactory ssf = (SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
        SSLServerSocket sss = (SSLServerSocket)ssf.createServerSocket(9999);

        SSLSocket s = (SSLSocket)sss.accept();
        LineNumberReader in = new LineNumberReader(new InputStreamReader(s.getInputStream()));
        PrintWriter out = new PrintWriter(s.getOutputStream());

        String linea = in.readLine();
        System.out.println(linea);
        out.println("respuesta");
        out.flush();
      } catch (Exception e) {e.printStackTrace();}
    }
}


No es estrictamente necesario incluir los setProperty en el código, pueden agregarse en la línea de comando al momento de correr el programa, de la siguiente manera:

java -Djavax.net.ssl.keyStore=miKeystore -Djavax.net.ssl.keyStorePassword=claveDeKeyStore Server

Aquí puede ser que tengas tu primer problema, y que al momento de correr el servidor obtengas el siguiente error:

java.net.SocketException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: com.sun.net.ssl.internal.ssl.DefaultSSLContextImpl)

Este error se debe a que no pudo obtenerse la clave del certificado y puede ser por varias razones, la más común es que no estés enviando la clave correcta o que no se encuentre el archivo especificado; pero si se tiene seguridad que no es debido a estos problemas, el error puede deberse a que indicaste una clave distinta en el keypass y el storepass. Como estamos en un ambiente de prueba podemos establecer ambos passwords con el mismo valor y de esta manera el error desaparecerá. Para implementarlo en un ambiente de producción, deberás programas un KeyManager ya que JSSE no provee una manera adecuada de especificar estos passwords a través de System Properties.


Cliente

El código para el cliente podría ser de la siguiente forma:

public class Cliente {

    public static void main(String args[]) {
      try {
        SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
        SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket("localhost", 9999);

        LineNumberReader in;
        PrintWriter out;
        in = new LineNumberReader(new InputStreamReader(s.getInputStream()));
        out = new PrintWriter(s.getOutputStream());

        out.println("hola");
        out.flush();
        System.out.println(in.readLine());

      } catch (Exception exception) { exception.printStackTrace(); }
    }
}

Pero muy probablemente obtendremos el siguiente error:

javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated

Este error se debe a que el certificado que estamos utilizando no es un certificado SSL válido. El esquema utilizado por SSL es conocido como X.509 y Java utiliza un TrustManager para este esquema, llamado x509TrustManager. Para poder evitar este error tenemos que crear un TrustManager diferente que acepte todos los certificados y no nos genere un error y utilizar este para la creación del Socket. Nuestro código quedaría algo así:

public class Cliente {

    public static void main(String args[]) {
      try {
        X509TrustManager tm = new X509TrustManager() {  
          public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException { }
          public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException { }
          public X509Certificate[] getAcceptedIssuers() { return null; }
        };            

      SSLContext ctx = SSLContext.getInstance("TLS");
      ctx.init(null, new TrustManager[]{tm}, null);

      SSLSocketFactory sslsocketfactory = ctx.getSocketFactory();
      SSLSocket s = (SSLSocket) sslsocketfactory.createSocket("192.168.0.32", 9999);

      LineNumberReader in;
      PrintWriter out;
      in = new LineNumberReader(new InputStreamReader(s.getInputStream()));
      out = new PrintWriter(s.getOutputStream());

      out.println("hola");
      out.flush();
      System.out.println(in.readLine());

      } catch (Exception exception) {
      exception.printStackTrace();
      }
    }
}

Una vez hecho esto, nuestros dos procesos se comunicarán utilizando SSL sin problemas :).