Programant Android SDK

De Wikijoan
Dreceres ràpides: navegació, cerca

Contingut

Programant directament sobre el dispositiu

Per fi ja tinc un Android (HTC Dream de Movistar, 23 de setembre de 2009), i ja puc programar directament sobre el dispositiu. El primer pas és que el adb reconegui el dispositiu físic que està connectat al USB. Primer de tot, recordar que quan connecto el USB, en el dispositiu Android he d'acceptar la connexió (a vegades queda amagat i no és evident).

  1. Declare your application as "debuggable" in your Android Manifest.

In Eclipse, you can do this from the Application tab when viewing the Manifest (on the right side, set Debuggable to true). Otherwise, in the AndroidManifest.xml file, add android:debuggable="true" to the <application> element.

  1. Turn on "USB Debugging" on your device. (això és important).

On the device, go to the home screen, press MENU, select Applications > Development, then enable USB debugging.

  1. Setup your system to detect your device.

If you're developing on Ubuntu Linux, you need to add a rules file:

Login as root and create this file: /etc/udev/rules.d/51-android.rules.

per a Hardy (8.04) i per a Jaunty (9.04):

$ joe /etc/udev/rules.d/51-android.rules

8.04:

SUBSYSTEM=="usb", SYSFS{idVendor}=="0bb4", MODE="0666"

9.04

SUBSYSTEM=="usb", SYSFS{idVendor}=="0bb4", SYMLINK+="android_adb", MODE="0666"

i executar

$ chmod a+rx /etc/udev/rules.d/51-android.rules

i ara ja es detecta el dispositiu:

$ cd ~/android/tools
$ adb devices
HT94GNG02436  device

$ ./adb -s HT94GNG02436 shell
# ls 
# cd sdcard

funciona, però tanmateix no em deixa accedir al sdcard. No sé si el problema és que s'ha de ser root o una d'aquestes coses.

En principi

$ ./adb shell

també funciona doncs és el dispositiu per defecte. I a més, si no tinc engegat el Eclipse, segur que no hi ha cap emulador. Aleshores des del Eclipse ja puc executar el projecte HelloWorld i per defecte m'apareix el missatge en el dispositiu. En prinicipi he de poder escollir entre el dispositiu o l'emulador, però per defecte funciona pel dispositiu físic.

Hello, World!

És una aplicació mínima introductòria, bàsica per aclarir unes quantes idees.

http://developer.android.com/resources/tutorials/hello-world.html

Android Debug Bridge


Android Debug Bridge (adb) is a versatile tool that lets you manage the state of a device or emulator.

Some of ways you can use adb include:

adb [-d|-e|-s <serialNumber>] <command> 

Per saber els dispositius que tenim disonibles:

$ adb devices
List of devices attached 
emulator-5554	device

Instal.lar una aplicació

You can use adb to copy an application from your development computer and install it on an emulator/device instance. To do so, use the install command. With the command, you must specify the path to the .apk file that you want to install:

# adb install <path_to_apk>

Examining sqlite3 Databases from a Remote Shell

From an adb remote shell, you can use the sqlite3 command-line program to manage SQLite databases created by Android applications. The sqlite3 tool includes many useful commands, such as .dump to print out the contents of a table and .schema to print the SQL CREATE statement for an existing table. The tool also gives you the ability to execute SQLite commands on the fly.

To use sqlite3, enter a remote shell on the emulator instance, as described above, then invoke the tool using the sqlite3 command. Optionally, when invoking sqlite3 you can specify the full path to the database you want to explore. Emulator/device instances store SQLite3 databases in the folder /data/data/<package_name>/databases/.

Here's an example:

$ adb -s emulator-5554 shell
# sqlite3 /data/data/com.example.google.rss.rssexample/databases/rssitems.db
SQLite version 3.3.12
Enter ".help" for instructions
.... enter commands, then quit...
sqlite> .exit 

Once you've invoked sqlite3, you can issue sqlite3 commands in the shell. To exit and return to the adb remote shell, use exit or CTRL+D.

Amb la comanda adb es poden fer moltes coses, com ara copiar fitxers al dispositiu, etc.

Copiar fitxers del dispositiu local al remot i al revés

To copy a file or directory (recursively) from the emulator or device, use

$ adb pull <remote> <local>

To copy a file or directory (recursively) to the emulator or device, use

$ adb push <local> <remote>

In the commands, <local> and <remote> refer to the paths to the target files/directory on your development machine (local) and on the emulator/device instance (remote).

$ adb push foo.txt /sdcard/foo.txt

Si tinc problemes per copiar algun fitxer, com per ex sdcard que no té permisos d'escriptura,

chmod <MODE> <FILE>
chmod 777 /etc

Instal.lar una aplicació

You can store files directly on the mobile device or on a removable storage medium. By default, other applications cannot access these files.

To read data from a file, call Context.openFileInput() and pass it the local name and path of the file. It returns a standard Java FileInputStream object. To write to a file, call Context.openFileOutput() with the name and path. It returns a FileOutputStream object. Calling these methods with name and path strings from another application will not work; you can only access local files.

If you have a static file to package with your application at compile time, you can save the file in your project in res/raw/myDataFile, and then open it with Resources.openRawResource (R.raw.myDataFile). It returns an InputStream object that you can use to read from the file.

Treballant amb fitxers

http://deliriosdepereza.blogspot.com/2008/05/android-101-los-recursos-ese-gran.html

En aquest exemple es mira com escriure i obrir en un fitxer, i també visualitzar el contingut d'un fitxer guardat en la carpeta assets (en el Eclipse, a sota del projecte, hi ha la carpeta assets).

Quan instal.lem una aplicació com la que farem ara, que es diu Actividad, es crea el fitxer com.android.actividad.apk dins de /data/app. Aquest és el fitxer que s'instal.la en el dispositiu, i a dins, de forma empaquetada, hi haurà el contignut del fitxer archivoprueba.txt que haurem creat en la carpeta assets del Eclipse.

L'aplicació es composa de tres botons: el primer per obrir el contingut del fitxer archivoprueba.txt que haurem creat en la carpeta assets; el segon és per crear el fitxer archivoprueba2.txt, que es crearà a /data/data/com.android.actividad/files; el tercer és per obrir el contingut del fitxer archivoprueba2.txt.

La interfície gràfica nosaltres la fem de forma molt simple. Simplement hem de ficar en el fitxer del Eclipse res/layout/main.xml el següent contingut:

<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
>
<Button
android:id="@+id/btn1"
android:layout_width="83px"
android:layout_height="wrap_content"
android:text="Abrir asset"
android:layout_x="10px"
android:layout_y="352px"
>
</Button>
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="guardar "
android:layout_x="100px"
android:layout_y="352px"
>
</Button>
<Button
android:id="@+id/btn3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Abrir"
android:layout_x="180px"
android:layout_y="352px"
>
</Button>
<EditText
android:id="@+id/texto"
android:layout_width="197px"
android:layout_height="217px"
android:text=""
android:layout_x="20px"
android:layout_y="22px"
>
</EditText>
</AbsoluteLayout>

Aquí està definit el disseny de tots els botons. És la manera de fer del Eclipse, treballar amb fitxers xml que incorporen tot el disseny. Aquesta GUI la podríem crear amb una eina Java que es diu droiddraw (http://droiddraw.org/) (mirar)

El codi java és bastant curt, i mostra clarament com grabar el contingut d'un objecte EditText a un fitxer i al revés, com carregar el text des d'un fitxer.

package com.android.actividad;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import android.util.Log;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class Actividad extends Activity {
private static final String TAG = "Actividad";
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.main);
    //Accedemos al los objetos creados a través de su indentificador
    final EditText texto = (EditText) this.findViewById(R.id.texto);
    Button btn1 = (Button) this.findViewById(R.id.btn1);
    Button btn2 = (Button) this.findViewById(R.id.btn2);
    Button btn3 = (Button) this.findViewById(R.id.btn3);
    btn1.setOnClickListener(new OnClickListener(){
     //Accion a realizar en el click del boton 1
public void onClick(View vista) {
InputStream is;
try {
 is = Actividad.this.getAssets().open("archivoprueba.txt");
 int tam = is.available();
 byte [] buffer = new byte[tam];
 is.read(buffer);
 texto.setText(new String(buffer));
} catch (IOException e) {
 Log.d(TAG,"Error en la lectura",e);
}
}     
    });
    btn2.setOnClickListener(new OnClickListener(){
     //Accion a realizar en el click del boton 2
public void onClick(View vista) {
try {
 FileOutputStream fos =Actividad.this.openFileOutput("archivoprueba2.txt", MODE_WORLD_READABLE|MODE_WORLD_WRITEABLE);
 fos.write(texto.getText().toString().getBytes());
 fos.close();
} catch (IOException e) { 
 Log.d(TAG,"Error en la escritura",e);
}
}     
    });
btn3.setOnClickListener(new OnClickListener(){
//Accion a realizar en el click del boton 3
public void onClick(View vista) {
try {
 FileInputStream fis = Actividad.this.openFileInput("archivoprueba2.txt");
 int tam = fis.available();
 byte [] buffer = new byte[tam];
 fis.read(buffer);
 texto.setText(new String(buffer));
 fis.close();
} catch (IOException e) {
 Log.d(TAG,"Error en la lectura",e);
}

}     
});    
}
}

Crear i consultar una base de dades SQLite

El codi Java mínim per crear una base de dades, executar instruccions SQL (delete, insert), fer una consulta i mostrar els resultats és el es mostra.

En el Eclipse, crear un projecte Android a partir de la següent informació:

La classe import android.util.Log; ens pemet fer logs i controlar el flux del programa en una aplicació Android.

Per veure els log, en el Eclipse: windows->show view->other..->android->Log cat

En el codi puc posar: Log.i(tag,"database created");

package com.android.databasework;

import java.util.ArrayList;

import android.app.ListActivity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.EditText;

public class DataBaseWork extends ListActivity {

     private final String MY_DATABASE_NAME = "myCoolDB_2";
     private final String MY_DATABASE_TABLE = "Users";

     /** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle icicle) {
          super.onCreate(icicle);
          
          EditText et = new EditText(this);
          et.setSelection(et.getText().length());
          /* Will hold the 'Output' we want to display at the end. */
          ArrayList<String> results = new ArrayList<String>();

          SQLiteDatabase myDB = null;
          try {
               /* Create the Database (no Errors if it already exists) */
		myDB = this.openOrCreateDatabase(MY_DATABASE_NAME, MODE_PRIVATE, null);

               /* Create a Table in the Database. */
               myDB.execSQL("CREATE TABLE IF NOT EXISTS "
                                   + MY_DATABASE_TABLE
                                   + " (LastName VARCHAR, FirstName VARCHAR,"
                                   + " Country VARCHAR, Age INT(3));");

               /* Add two DataSets to the Table. */
               myDB.execSQL("INSERT INTO "
                                   + MY_DATABASE_TABLE
                                   + " (LastName, FirstName, Country, Age)"
                                   + " VALUES ('Gramlich', 'Nicolas', 'Germany', 20);");
               myDB.execSQL("INSERT INTO "
                                   + MY_DATABASE_TABLE
                                   + " (LastName, FirstName, Country, Age)"
                                   + " VALUES ('Doe', 'John', 'US', 34);");

               /* Query for some results with Selection and Projection. */
               Cursor c = myDB.rawQuery("SELECT FirstName,Age" +
                                        " FROM " + MY_DATABASE_TABLE
                                        + " WHERE Age > 10 LIMIT 7;",
                                        null);
               
               /* Get the indices of the Columns we will need */
               int firstNameColumn = c.getColumnIndex("FirstName");
               int ageColumn = c.getColumnIndex("Age");
               
               /* Check if our result was valid. */
               c.moveToFirst();
               if (c != null) {
                    /* Check if at least one Result was returned. */
                    if (c.isFirst()) {
                         int i = 0;
                         /* Loop through all Results */
                         do {
                              i++;
                              /* Retrieve the values of the Entry
                               * the Cursor is pointing to. */
                              String firstName = c.getString(firstNameColumn);
                              int age = c.getInt(ageColumn);
                              /* We can also receive the Name
                               * of a Column by its Index.
                               * Makes no sense, as we already
                               * know the Name, but just to show we can Wink */
                              String ageColumName = c.getColumnName(ageColumn);
                              
                              /* Add current Entry to results. */
                              results.add("" + i + ": " + firstName
                                             + " (" + ageColumName + ": " + age + ")");
                         } while(c.moveToNext());

                    
                    }
               }

          } finally {
               if (myDB != null)
                    myDB.close();
          }

          this.setListAdapter(new ArrayAdapter<String>(this,
                    android.R.layout.simple_list_item_1, results));
     }

}

Recuperar informació penjada a la xarxa

You can also use the network to store and retrieve data (when it's available). To do network operations, use the classes in the following packages:

Parsejar un fitxer XML que està a la xarxa

http://www.anddev.org/parsing_xml_from_the_net_-_using_the_saxparser-t353.html

Aquest document està molt ben explicat.

De fet això és el que necessito per al meu propòsit. Suposem que una aplicació, posem per cas Sugar CRM, conté en les seves taules Mysql una informació que jo puc ficar de forma ordenada en un fitxer XML (per exemple, llistes de clients, informació ToDo, trucades pendents, llista telèfons, contactes,...). A partir d'un script shell-cron jo puc generar els fitxers xml posem per cas cada 24h. Aleshores, des del Android em connecto via xarxa local o xarxa remota si està habilitada a aquests fitxers XML, i els parsejo. El contingut dels fitxers els puc ficar en fitxers de txt per obrir-los en NotePad, en una base de dades,... això ja és un altre tema de com mostro la informació. Allò important és poder generar els fitxers xml en el meu servidor, i que pugui recuperar la informació de forma fàcil i integrar-la en el Android.

He tingut problemes amb la línia

xr.parse(new InputSource(url.openStream()));

i el problema és que falla la connexió http perquè no s'ha donat permisos a l'aplicació per connectar-se a Internet. Això es soluciona afegint la línia

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

al fitxer AndroidManifest.xml, de manera que queda de la següent manera:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.android.parsingxml"
      android:versionCode="1"
      android:versionName="1.0.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".ParsingXML"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
</manifest> 

En comptes d'editar manualment aquest fitxer puc editar-lo amb el Open with Android Manifest Editor. És un assistent que m'ajuda a editar, i així també veig quins permisos més es poden donar. Per exemple android.permission.BLUETOOTH i molts més.

El codi java consta de tres fitxers:

1. ParsingXML.java

package com.android.parsingxml;

import java.net.URL;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;

public class ParsingXML extends Activity {
     
     private final String MY_DEBUG_TAG = "WeatherForcaster";

     /** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle icicle) {
          super.onCreate(icicle);

          /* Create a new TextView to display the parsingresult later. */
          TextView tv = new TextView(this);
          try {
               /* Create a URL we want to load some xml-data from. */
               //URL url = new URL("http://www.anddev.org/images/tut/basic/parsingxml/example.xml");
               URL url = new URL("http://192.168.1.131/example.xml");
               
               /* Get a SAXParser from the SAXPArserFactory. */
               SAXParserFactory spf = SAXParserFactory.newInstance();
               SAXParser sp = spf.newSAXParser();

               /* Get the XMLReader of the SAXParser we created. */
               XMLReader xr = sp.getXMLReader();
               /* Create a new ContentHandler and apply it to the XML-Reader*/
             ExampleHandler myExampleHandler = new ExampleHandler();
             xr.setContentHandler(myExampleHandler);
               
               /* Parse the xml-data from our URL. */
             xr.parse(new InputSource(url.openStream()));
               /* Parsing has finished. */

               /* Our ExampleHandler now provides the parsed data to us. */
             ParsedExampleDataSet parsedExampleDataSet =
                                             myExampleHandler.getParsedData();

               /* Set the result to be displayed in our GUI. */
               tv.setText(parsedExampleDataSet.toString());
               
          } catch (Exception e) {
               /* Display any Error to the GUI. */
               tv.setText("Error: " + e.getMessage());
               Log.e(MY_DEBUG_TAG, "WeatherQueryError", e);
          }
          /* Display the TextView. */
          this.setContentView(tv);
     }
}

2. ExampleHandler.java

package com.android.parsingxml;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;


public class ExampleHandler extends DefaultHandler{

     // ===========================================================
     // Fields
     // ===========================================================
     
     private boolean in_outertag = false;
     private boolean in_innertag = false;
     private boolean in_mytag = false;
     
     private ParsedExampleDataSet myParsedExampleDataSet = new ParsedExampleDataSet();

     // ===========================================================
     // Getter & Setter
     // ===========================================================

     public ParsedExampleDataSet getParsedData() {
          return this.myParsedExampleDataSet;
     }

     // ===========================================================
     // Methods
     // ===========================================================
     @Override
     public void startDocument() throws SAXException {
          this.myParsedExampleDataSet = new ParsedExampleDataSet();
     }

     @Override
     public void endDocument() throws SAXException {
          // Nothing to do
     }

     /** Gets be called on opening tags like:
      * <tag>
      * Can provide attribute(s), when xml was like:
      * <tag attribute="attributeValue">*/
     @Override
     public void startElement(String namespaceURI, String localName,
               String qName, Attributes atts) throws SAXException {
          if (localName.equals("outertag")) {
               this.in_outertag = true;
          }else if (localName.equals("innertag")) {
               this.in_innertag = true;
          }else if (localName.equals("mytag")) {
               this.in_mytag = true;
          }else if (localName.equals("tagwithnumber")) {
               // Extract an Attribute
               String attrValue = atts.getValue("thenumber");
               int i = Integer.parseInt(attrValue);
               myParsedExampleDataSet.setExtractedInt(i);
          }
     }
     
     /** Gets be called on closing tags like:
      * </tag> */
     @Override
     public void endElement(String namespaceURI, String localName, String qName)
               throws SAXException {
          if (localName.equals("outertag")) {
               this.in_outertag = false;
          }else if (localName.equals("innertag")) {
               this.in_innertag = false;
          }else if (localName.equals("mytag")) {
               this.in_mytag = false;
          }else if (localName.equals("tagwithnumber")) {
               // Nothing to do here
          }
     }
     
     /** Gets be called on the following structure:
      * <tag>characters</tag> */
     @Override
    public void characters(char ch[], int start, int length) {
          if(this.in_mytag){
          myParsedExampleDataSet.setExtractedString(new String(ch, start, length));
     }
    }
}

3. ParsedExampleDataSet.java

package com.android.parsingxml;

public class ParsedExampleDataSet {
    private String extractedString = null;
    private int extractedInt = 0;

    public String getExtractedString() {
         return extractedString;
    }
    public void setExtractedString(String extractedString) {
         this.extractedString = extractedString;
    }

    public int getExtractedInt() {
         return extractedInt;
    }
    public void setExtractedInt(int extractedInt) {
         this.extractedInt = extractedInt;
    }
    
    public String toString(){
         return "ExtractedString = " + this.extractedString
                   + "\nExtractedInt = " + this.extractedInt;
    }
}

Importació d'una BD externa al SQLite de l'Android

TBD

Aprofitar un fitxer d'una aplicació del market

Si vull aprofitar un fitxer d'una aplicació del market (per exemple una foto), o vull per exemple canviar una icona d'una aplicació que tinc instal.lada, es pot fer. La idea és copiar a local el fitxer .apk, passar-lo a zip, fer els canvis, tornar-lo a passar a apk, i copiar el fitxer al dispositiu.

Per exemple, en el meu cas concret, en el workspace/Proj18 estic treballant amb la migració de FingerPlay MIDI al meu codi. Vull fer un controlador midi que sigui unes tecles de piano per fer sonar un sintetitzador com el fluidsynth. Doncs bé, les tecles de piano les treuré de l'aplicació Musical Lite que tinc instal.lada.

$ ./adb -s HT94GNG02436 pull data/app/souvey.musical.apk /home/joan
788 KB/s (209283 bytes in 0.259s)

renombro from .apk to .zip i entrem dins el directori res/drawable, i veiem les imatges de les tecles del piano que vull aprofitar. Per exemple, keyboard_white_down.jpg

Faig els canvis oportuns, renombro el zip a apk i ho copio a /media/sdcard

'Nota: quan connecto el Android a l'ordinador, si activo la connexió USB podré accedir a /media/sdcard des de l'ordinador, però no podré accedir al contingut de sdcard des de la consola adb. Si no activo la connexió USB em passarà al revés. Per tant, després de copiar el fitxer apk a /media/sdcard desmonto, desconnecto, torno a connectar, no activo USB, i ja puc fer: (lo de mount no ho veig clar...)

$ adb shell
# su
# mount -o remount,rw -t yaffs2 /dev/block/mtdblock4 /system
# cat /sdcard/souvey.musical.apk > /system/app/souvey.musical.apk 

creat per Joan Quintana Compte, febrer 2009

Eines de l'usuari
Espais de noms
Variants
Accions
Navegació
IES Jaume Balmes
Màquines recreatives
CNC
Informàtica musical
joanillo.org Planet
Eines