Ver mensagens sem resposta | Ver tópicos ativos Hoje é 15 Set 2019, 19:34



Responder Tópico  [ 1 Mensagem ] 
 [TUTORIAL] Content Provider, criando e usando 
Autor Mensagem
Google employee
Google employee

Data de registro: 17 Jul 2011, 11:55
Mensagens: 2657
Localização: São Paulo
Mensagem [TUTORIAL] Content Provider, criando e usando
PARTE 1: Content Provider, criando um

Content Provider é uma interface genérica para acesso a dados no ambiente Android.
Somente através do Content Provider é possível compartilhar dados entre aplicações.
O acesso a um Content Provider é padronizado, e como sempre, acessar um Content Provider nativo ou um definido por você segue o mesmo formato, sem distinção.
Acessamos um Content Provider por intermédio da classe cliente ContentResolver.
Nesse teste, vamos implementar um Content Provider usando o SQLite como repositório de dados.
Vamos fazer um Content Provider de localizações geográficas.
O exemplo é baseado no sample NotePad. Então, toda semelhança não é mera coincidência!

Como pretendemos acessar o Content Provider em múltiplas threads, estamos testando também o uso de uma trava de leitura-escrita para tornar os métodos de acesso ao Content Provider seguros em relação às threads.
Devido ao fato de que esse banco de dados pode ficar muito grande, também implementaremos uma forma de acessar os dados usando a clausula "limit" do SQL.

Vamos seguir os seguintes passos para criar nosso Content Provider:

1. Criar uma classe contrato.

Essa é uma classe com as constantes do Content Provider, como URIs, MIME types, nome das colunas da tabela, etc.
Esta classe estabelece o contrato entre o Content Provider e as aplicações clientes.

MyLocation.class

package br.agorandroid.mylocationprovider;

import android.content.ContentUris;
import android.net.Uri;
import android.provider.BaseColumns;

/**
 * @author A H Gusukuma
 *
 *         agorandroid.blogspot.com
 */

public final class MyLocation {
    public static final String AUTHORITY =
            "br.com.agorandroid.mylocation.provider";

    // This class cannot be instantiated
    private MyLocation() {
    }

    /**
     * Location table contract
     */

    public static final class Locations implements BaseColumns {
        // This class cannot be instantiated
        private Locations() {}

        /**
         * The table name offered by this provider
         */

        public static final String TABLE_NAME = "locations";

        /*
         * URI definitions
         */


        /**
         * The scheme part for this provider's URI
         */

        private static final String SCHEME = "content://";

        /**
         * Path parts for the URIs
         */


        /**
         * Path part for the Locations URI
         */

        private static final String PATH_LOCATIONS = "/locations";

        /**
         * Path part for the Location ID URI
         */

        private static final String PATH_LOCATION_ID = "/locations/";

        /**
         * 0-relative position of a Location ID segment in the path part of a location ID URI
         */

        public static final int LOCATION_ID_PATH_POSITION = 1;

        /**
         * Limit query parameter key
         */

        public static final String LIMIT_PARAMETER_KEY = "limit";
       
        /**
         * The content:// style URL for this table
         */

        public static final Uri CONTENT_URI =  Uri.parse(SCHEME + AUTHORITY + PATH_LOCATIONS);

        /**
         * The content URI base for a single location. Callers must
         * append a numeric location id to this Uri to retrieve a location
         */

        public static final Uri CONTENT_ID_URI_BASE
            = Uri.parse(SCHEME + AUTHORITY + PATH_LOCATION_ID);

        /**
         * The content URI match pattern for a single location, specified by its ID. Use this to match
         * incoming URIs or to construct an Intent.
         */

        public static final Uri CONTENT_ID_URI_PATTERN
            = Uri.parse(SCHEME + AUTHORITY + PATH_LOCATION_ID + "/#");

        /*
         * MIME type definitions
         */


        /**
         * The MIME type of {@link #CONTENT_URI} providing a directory of locations.
         */

        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.br.com.agorandroid.provider.locations";

        /**
         * The MIME type of a {@link #CONTENT_URI} sub-directory of a single
         * location.
         */

        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.br.com.agorandroid.provider.locations";

        /*
         * Column definitions
         */


        /**
         * Column name for the provider of the location
         * <P>Type: TEXT   (String from location.getProvider())</P>
         */

        public static final String COLUMN_NAME_PROVIDER = "provider";

        /**
         * Column name for the time
         * <P>Type: INTEGER (long from location.getTime())</P>
         */

        public static final String COLUMN_NAME_TIME = "time";

        /**
         * Column name for the accuracy
         * <P>Type: FLOAT (float from location.getAccuracy())</P>
         */

        public static final String COLUMN_NAME_ACCURACY = "accuracy";

         /**
         * Column name for the latitude
         * <P>Type: FLOAT (double from location.getLatitude())</P>
         */

        public static final String COLUMN_NAME_LATITUDE = "latitude";

         /**
         * Column name for the longitude
         * <P>Type: FLOAT (double from location.getLongitude())</P>
         */

        public static final String COLUMN_NAME_LONGITUDE = "longitude";

 
        /**
         * The default sort order for this table
         */

        public static final String DEFAULT_SORT_ORDER = COLUMN_NAME_TIME + " DESC";
     
        /**
         * make Uri + id
         */

        public static Uri getUriItemId(long id) {
            Uri mUri = ContentUris.withAppendedId(CONTENT_URI, id);
            return mUri;
        }

    }
}

 




2. Definição da base de dados


Vamos extender a classe SQLiteOpenHelper e implementar os métodos onCreate e onUpgrade

3. Criando o Content Provider

Além de implementar os métodos onCreate, query, insert, delete, update e getType vamos definir também uma instância da classe utilitária UriMatcher.
O UriMatcher nos ajudará a manipular as URIs para identificar qual formato estamos tratando.
Também vamos instanciar um trava ReentrantReadWriteLock.

Vejamos o código:

MyLocationProvider.class


/**
 *
 */

package br.agorandroid.mylocationprovider;

import java.util.HashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;

/**
 * @author A H Gusukuma
 *
 *         agorandroid.blogspot.com
 */

public class MyLocationProvider extends ContentProvider {

    // Used for debugging and logging
    private static final String TAG = "MyLocationProvider";

    /**
     * The database that the provider uses as its underlying data store
     */

    private static final String DATABASE_NAME = "mylocation.db";

    /**
     * The database version
     */

    private static final int DATABASE_VERSION = 1;

    /**
     * A projection map used to select columns from the database
     */

    private static HashMap<String, String> sLocationsProjectionMap;

    /**
     * Standard projection for the interesting columns of a normal note.
     */

    private static final String[] READ_LOCATION_PROJECTION = new String[] {
            MyLocation.Locations._ID,                   // Projection position 0, the Location id
            MyLocation.Locations.COLUMN_NAME_PROVIDER,  // Projection position 1, the Location provider
            MyLocation.Locations.COLUMN_NAME_TIME,      // Projection position 2, the Location time
            MyLocation.Locations.COLUMN_NAME_ACCURACY,  // Projection position 3, the Location accuracy
            MyLocation.Locations.COLUMN_NAME_LATITUDE,  // Projection position 4, the Location latitude
            MyLocation.Locations.COLUMN_NAME_LONGITUDE  // Projection position 5, the Location longitude
    };
    private static final int READ_LOCATION_PROVIDER  = 1;
    private static final int READ_LOCATION_TIME      = 2;
    private static final int READ_LOCATION_ACCURACY  = 3;
    private static final int READ_LOCATION_LATITUDE  = 4;
    private static final int READ_LOCATION_LONGITUDE = 5;

    /*
     * Constants used by the Uri matcher to choose an action based on the pattern
     * of the incoming URI
     */

    // The incoming URI matches the Locations URI pattern
    private static final int LOCATIONS = 1;

    // The incoming URI matches the Locations URI pattern with Limit
    private static final int LOCATIONS_LIMITED = 2;
   
    // The incoming URI matches the Location ID URI pattern
    private static final int LOCATION_ID = 3;

   
    /**
     * A UriMatcher instance
     */

    private static final UriMatcher sUriMatcher;

    // Handle to a new DatabaseHelper.
    private DatabaseHelper mOpenHelper;

    // ReadWriteLock to control concurrent access to db
    private final ReadWriteLock mLock = new ReentrantReadWriteLock();
    private final Lock rLock = mLock.readLock();
    private final Lock wLock = mLock.writeLock();

    /**
     * A block that instantiates and sets static objects
     */

    static {

        /*
         * Creates and initializes the URI matcher
         */

        // Create a new instance
        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

        // Add a pattern that routes URIs terminated with "locations" to a LOCATIONS operation
        sUriMatcher.addURI(MyLocation.AUTHORITY, MyLocation.Locations.TABLE_NAME, LOCATIONS);

        // Add a pattern that routes URIs terminated with "locations/limit" to a LOCATIONS operation with Limit
        sUriMatcher.addURI(MyLocation.AUTHORITY, MyLocation.Locations.TABLE_NAME + "/" +  MyLocation.Locations.LIMIT_PARAMETER_KEY + "*",
                LOCATIONS_LIMITED);
       
        // Add a pattern that routes URIs terminated with "locations" plus an integer
        // to a location ID operation
        sUriMatcher.addURI(MyLocation.AUTHORITY,  MyLocation.Locations.TABLE_NAME + "/#", LOCATION_ID);

        /*
         * Creates and initializes a projection map that returns all columns
         */


        // Creates a new projection map instance. The map returns a column name
        // given a string. The two are usually equal.
        sLocationsProjectionMap = new HashMap<String, String>();

        // Maps the string "_ID" to the column name "_ID"
        sLocationsProjectionMap.put(MyLocation.Locations._ID, MyLocation.Locations._ID);

        // Maps "title" to "title"
        sLocationsProjectionMap.put( MyLocation.Locations.COLUMN_NAME_PROVIDER,  MyLocation.Locations.COLUMN_NAME_PROVIDER);

        // Maps "note" to "note"
        sLocationsProjectionMap.put(MyLocation.Locations.COLUMN_NAME_TIME, MyLocation.Locations.COLUMN_NAME_TIME);

        // Maps "created" to "created"
        sLocationsProjectionMap.put(MyLocation.Locations.COLUMN_NAME_ACCURACY, MyLocation.Locations.COLUMN_NAME_ACCURACY);

        // Maps "modified" to "modified"
        sLocationsProjectionMap.put( MyLocation.Locations.COLUMN_NAME_LATITUDE, MyLocation.Locations.COLUMN_NAME_LATITUDE);

        // Maps "modified" to "modified"
        sLocationsProjectionMap.put( MyLocation.Locations.COLUMN_NAME_LONGITUDE, MyLocation.Locations.COLUMN_NAME_LONGITUDE);
    }

    /**
    *
    * This class helps open, create, and upgrade the database file.
    */

   static class DatabaseHelper extends SQLiteOpenHelper {

       DatabaseHelper(Context context) {

           // calls the super constructor, requesting the default cursor factory.
           super(context, DATABASE_NAME, null, DATABASE_VERSION);
       }

       /**
        *
        * Creates the underlying database with table name and column names taken from the
        * MyLocation class.
        */

       @Override
       public void onCreate(SQLiteDatabase db) {
           db.execSQL("CREATE TABLE " + MyLocation.Locations.TABLE_NAME + " ("
                   + MyLocation.Locations._ID + " INTEGER PRIMARY KEY,"
                   + MyLocation.Locations.COLUMN_NAME_PROVIDER + " TEXT,"
                   + MyLocation.Locations.COLUMN_NAME_TIME + " INTEGER,"
                   + MyLocation.Locations.COLUMN_NAME_ACCURACY + " FLOAT,"
                   + MyLocation.Locations.COLUMN_NAME_LATITUDE + " FLOAT,"
                   + MyLocation.Locations.COLUMN_NAME_LONGITUDE  + " FLOAT"
                   + ");");
       }

       /**
        *
        * Demonstrates that the provider must consider what happens when the
        * underlying datastore is changed. In this sample, the database is upgraded the database
        * by destroying the existing data.
        * A real application should upgrade the database in place.
        */

       @Override
       public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

           // Logs that the database is being upgraded
           Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
                   + newVersion + ", which will destroy all old data");

           // Kills the table and existing data
           db.execSQL("DROP TABLE IF EXISTS " + MyLocation.Locations.TABLE_NAME);

           // Recreates the database with a new version
           onCreate(db);
       }
   }

   /**
   *
   * Initializes the provider by creating a new DatabaseHelper. onCreate() is called
   * automatically when Android creates the provider in response to a resolver request from a
   * client.
   */

  @Override
  public boolean onCreate() {

      // Creates a new helper object. Note that the database itself isn't opened until
      // something tries to access it, and it's only created if it doesn't already exist.
      mOpenHelper = new DatabaseHelper(getContext());

      // Assumes that any failures will be reported by a thrown exception.
      return true;
  }

  @Override
  public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
          String sortOrder) {
      rLock.lock();
      try {
          // Constructs a new query builder and sets its table name
          SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
          qb.setTables(MyLocation.Locations.TABLE_NAME);
          String sLimit = null;
          /**
           * Choose the projection and adjust the "where" clause based on URI pattern-matching.
           */

          switch (sUriMatcher.match(uri)) {
              /* If the incoming URI is for a single location identified by its ID, chooses the
               * location ID projection, and appends "_ID = <locationID>" to the where clause, so that
               * it selects that single location
               */

              case LOCATION_ID:
                  qb.setProjectionMap(sLocationsProjectionMap);
                  qb.appendWhere(
                      MyLocation.Locations._ID +    // the name of the ID column
                      "=" +
                      // the position of the location ID itself in the incoming URI
                      uri.getPathSegments().get(MyLocation.Locations.LOCATION_ID_PATH_POSITION));
                  break;
              // If the incoming URI is for locations with or without limit , chooses the Locations projection  
              case LOCATIONS:
              case LOCATIONS_LIMITED:
                  qb.setProjectionMap(sLocationsProjectionMap);
                  sLimit = uri.getQueryParameter(MyLocation.Locations.LIMIT_PARAMETER_KEY);
                  break;
              default:
                  // If the URI doesn't match any of the known patterns, throw an exception.
                  throw new IllegalArgumentException("Unknown URI " + uri);
          }
   
   
          String orderBy;
          // If no sort order is specified, uses the default
          if (TextUtils.isEmpty(sortOrder)) {
              orderBy = MyLocation.Locations.DEFAULT_SORT_ORDER;
          } else {
              // otherwise, uses the incoming sort order
              orderBy = sortOrder;
          }
   
          // Opens the database object in "read" mode, since no writes need to be done.
          SQLiteDatabase db = mOpenHelper.getReadableDatabase();
   
          /*
           * Performs the query. If no problems occur trying to read the database, then a Cursor
           * object is returned; otherwise, the cursor variable contains null. If no records were
           * selected, then the Cursor object is empty, and Cursor.getCount() returns 0.
           */

          Cursor c = qb.query(
              db,            // The database to query
              projection,    // The columns to return from the query
              selection,     // The columns for the where clause
              selectionArgs, // The values for the where clause
              null,          // don't group the rows
              null,          // don't filter by row groups
              orderBy,        // The sort order
              sLimit         // The limit
          );
   
          // Tells the Cursor what URI to watch, so it knows when its source data changes
          c.setNotificationUri(getContext().getContentResolver(), uri);
          return c;
      } finally {
          rLock.unlock();
      }
     
  }

  @Override
  public Uri insert(Uri uri, ContentValues initialValues) {
      wLock.lock();
      try {
          // Validates the incoming URI. Only the full provider URI is allowed for inserts.
          if (sUriMatcher.match(uri) != LOCATIONS) {
              throw new IllegalArgumentException("Unknown URI " + uri);
          }
   
          // A map to hold the new record's values.
          ContentValues values;
   
          // If the incoming values map is not null, uses it for the new values.
          if (initialValues != null) {
              values = new ContentValues(initialValues);
   
          } else {
              // Otherwise, create a new value map
              throw new IllegalArgumentException("Empty values");
          }
   
          // Opens the database object in "write" mode.
          SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   
          // Performs the insert and returns the ID of the new location.
          long rowId = db.insert(
              MyLocation.Locations.TABLE_NAME,        // The table to insert into.
              MyLocation.Locations.COLUMN_NAME_PROVIDER,  // A hack, SQLite sets this column value to null
                                               // if values is empty.
              values                           // A map of column names, and the values to insert
                                               // into the columns.
          );
   
          // If the insert succeeded, the row ID exists.
          if (rowId > 0) {
              // Creates a URI with the note ID pattern and the new row ID appended to it.
              Uri noteUri = MyLocation.Locations.getUriItemId(rowId);
   
              // Notifies observers registered against this provider that the data changed.
              getContext().getContentResolver().notifyChange(noteUri, null);
              return noteUri;
          }
   
          // If the insert didn't succeed, then the rowID is <= 0. Throws an exception.
          throw new SQLException("Failed to insert row into " + uri);
      } finally {
          wLock.unlock();
      }
  }

  @Override
  public int delete(Uri uri, String where, String[] whereArgs) {
      wLock.lock();
      int count;
      try {
          // Opens the database object in "write" mode.
          SQLiteDatabase db = mOpenHelper.getWritableDatabase();
          String finalWhere;
   
          // Does the delete based on the incoming URI pattern.
          switch (sUriMatcher.match(uri)) {
   
              // If the incoming pattern matches the general pattern for locations, does a delete
              // based on the incoming "where" columns and arguments.
              case LOCATIONS:
                  count = db.delete(
                      MyLocation.Locations.TABLE_NAME,  // The database table name
                      where,                     // The incoming where clause column names
                      whereArgs                  // The incoming where clause values
                  );
                  break;
   
                  // If the incoming URI matches a single location ID, does the delete based on the
                  // incoming data, but modifies the where clause to restrict it to the
                  // particular location ID.
              case LOCATION_ID:
                  /*
                   * Starts a final WHERE clause by restricting it to the
                   * desired location ID.
                   */

                  finalWhere =
                          MyLocation.Locations._ID +                              // The ID column name
                          " = " +                                          // test for equality
                          uri.getPathSegments().                           // the incoming note ID
                              get(MyLocation.Locations.LOCATION_ID_PATH_POSITION)
                  ;
   
                  // If there were additional selection criteria, append them to the final
                  // WHERE clause
                  if (where != null) {
                      finalWhere = finalWhere + " AND " + where;
                  }
   
                  // Performs the delete.
                  count = db.delete(
                      MyLocation.Locations.TABLE_NAME,  // The database table name.
                      finalWhere,                // The final WHERE clause
                      whereArgs                  // The incoming where clause values.
                  );
                  break;
   
              // If the incoming pattern is invalid, throws an exception.
              default:
                  throw new IllegalArgumentException("Unknown URI " + uri);
          }
   
          /*Gets a handle to the content resolver object for the current context, and notifies it
           * that the incoming URI changed. The object passes this along to the resolver framework,
           * and observers that have registered themselves for the provider are notified.
           */

          getContext().getContentResolver().notifyChange(uri, null);
   
          // Returns the number of rows deleted.
          return count;
      } finally {
          wLock.unlock();
      }
  }

  @Override
  public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
      wLock.lock();
      try {
          // Opens the database object in "write" mode.
          SQLiteDatabase db = mOpenHelper.getWritableDatabase();
          int count;
          String finalWhere;
   
          // Does the update based on the incoming URI pattern
          switch (sUriMatcher.match(uri)) {
   
              // If the incoming URI matches the general notes pattern, does the update based on
              // the incoming data.
              case LOCATIONS:
   
                  // Does the update and returns the number of rows updated.
                  count = db.update(
                      MyLocation.Locations.TABLE_NAME, // The database table name.
                      values,                   // A map of column names and new values to use.
                      where,                    // The where clause column names.
                      whereArgs                 // The where clause column values to select on.
                  );
                  break;
   
              // If the incoming URI matches a single location ID, does the update based on the incoming
              // data, but modifies the where clause to restrict it to the particular location ID.
              case LOCATION_ID:
                  /*
                   * Starts creating the final WHERE clause by restricting it to the incoming
                   * location ID.
                   */

                  finalWhere =
                          MyLocation.Locations._ID +                              // The ID column name
                          " = " +                                          // test for equality
                          uri.getPathSegments().                           // the incoming location ID
                              get(MyLocation.Locations.LOCATION_ID_PATH_POSITION)
                  ;
   
                  // If there were additional selection criteria, append them to the final WHERE
                  // clause
                  if (where !=null) {
                      finalWhere = finalWhere + " AND " + where;
                  }
   
   
                  // Does the update and returns the number of rows updated.
                  count = db.update(
                      MyLocation.Locations.TABLE_NAME, // The database table name.
                      values,                   // A map of column names and new values to use.
                      finalWhere,               // The final WHERE clause to use
                                                // placeholders for whereArgs
                      whereArgs                 // The where clause column values to select on, or
                                                // null if the values are in the where argument.
                  );
                  break;
              // If the incoming pattern is invalid, throws an exception.
              default:
                  throw new IllegalArgumentException("Unknown URI " + uri);
          }
   
          /*Gets a handle to the content resolver object for the current context, and notifies it
           * that the incoming URI changed. The object passes this along to the resolver framework,
           * and observers that have registered themselves for the provider are notified.
           */

          getContext().getContentResolver().notifyChange(uri, null);
   
          // Returns the number of rows updated.
          return count;
  } finally {
      wLock.unlock();
  }
  }

 
  @Override
  public String getType(Uri uri) {

      /**
       * Chooses the MIME type based on the incoming URI pattern
       */

      switch (sUriMatcher.match(uri)) {

          // If the pattern is for locations, returns the general content type.
          case LOCATIONS:
              return MyLocation.Locations.CONTENT_TYPE;

          // If the pattern is for location IDs, returns the location ID content type.
          case LOCATION_ID:
              return MyLocation.Locations.CONTENT_ITEM_TYPE;

          // If the URI pattern doesn't match any permitted patterns, throws an exception.
          default:
              throw new IllegalArgumentException("Unknown URI " + uri);
      }
   }


 
}

 


4. Registrar o Content Provider no Manifest file.



        <provider android:exported="false"
            android:authorities="br.com.agorandroid.mylocation.provider"
            android:name="MyLocationProvider">

            <grant-uri-permission android:pathPattern=".*"/>
        </provider>
 



Com a opção android:exported="false" estamos informando que esse Content Provider é privado à aplicação.


Pronto!
Estamos com o Content Provider criado, no próximo post veremos como usá-lo.

PARTE 2: Content Provider, usando um

Para usar o Content Provider MyLocationProvider definido no último post, vamos tomar como base a mesma aplicação que testamos em diversos posts anteriores.
O app tem as seguintes características:

1. A activity inicia lendo os dados existentes no Content Provider e os mostra numa ListView. Para isso usamos um ManagedQuery, como o cursor resultante da leitura será gerenciado, o ListView será atualizado automaticamente quando o Content Provider for alterado.
Usamos um ListAdapter extendido de um CursorAdapter para tratar os dados do cursor.
A leitura dos dados é feita com um Limit de 20 linhas para ficar mais rápido e exemplificar o uso da clausula no db.
A ordem é decrescente de data. O último location gravado é o primeiro do ListView.
No método onStart é iniciado o service para coleta dos dados dos Locations Providers.
Temos tres botões:
.Start - inicia a coleta de dados no service
.Stop - para a coleta de dados no service
.Clear DB - Deleta todos os registros existentes no Content Provider

2. No service:
.Uso da tecnica produtor/consumidor para gravar os dados através de uma worker thread.
.Uso de uma BlockingQueue para passar os Locations da thread principal para o thread consumidora
.A worker thread é iniciada no onBind e fica aguardando os Locations para gravar no Content Provider
.Uso da tecnica de controle do encerramento de uma thread através do envio de um objeto indicativo de fim

Vamos aos códigos:

AndroidManifest.xml


<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="br.agorandroid.mylocationprovider"
    android:versionCode="1"
    android:versionName="1.0" >


    <uses-sdk android:minSdkVersion="7" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >

        <provider android:exported="false"
            android:authorities="br.com.agorandroid.mylocation.provider"
            android:name="MyLocationProvider">

            <grant-uri-permission android:pathPattern=".*"/>
        </provider>
        <activity
            android:name=".MyLocationActivity"
            android:label="@string/app_name" >

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
                <service android:name=".MyLocationService"
        >
</service>
    </application>

</manifest>
 





main.xml




<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:weightSum="1">

        <TextView
        android:id="@+id/status"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:text=""/>


        <LinearLayout android:orientation="horizontal" android:id="@+id/linearLayout1"
            android:layout_width="fill_parent"  android:layout_height="wrap_content">

                <Button android:layout_width="wrap_content" android:id="@+id/start" android:layout_weight="1"
                    android:text="Start" android:layout_height="wrap_content">
</Button>
                <Button android:layout_width="wrap_content" android:id="@+id/clear" android:layout_weight="1"
                    android:text="Clear DB" android:layout_height="wrap_content">
</Button>
                <Button android:layout_width="wrap_content" android:id="@+id/stop" android:layout_weight="1"
                    android:text="Stop" android:layout_height="wrap_content">
</Button>
        </LinearLayout>

        <View android:layout_height="2dip" android:layout_width="fill_parent"  android:background="#ff909090"  />

     <ListView android:id="@+id/listView1"
            android:layout_height="0dip"
            android:layout_width="fill_parent"
            android:layout_weight="1">
</ListView>
</LinearLayout>

 



linhalista.xml



<?xml version="1.0" encoding="utf-8"?>
<TableLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content">

    <TableRow >
        <TextView android:text="TextView" android:id="@+id/textView1" android:layout_weight="1"
        android:layout_width="wrap_content" android:layout_height="wrap_content">
</TextView>
        <TextView android:text="TextView" android:id="@+id/textView2" android:layout_weight="1"
        android:layout_width="wrap_content" android:layout_height="wrap_content">
</TextView>
     </TableRow>
    
    <TableRow >
        <TextView android:text="TextView" android:id="@+id/textView3" android:layout_weight="1"
        android:layout_width="wrap_content" android:layout_height="wrap_content">
</TextView>
        <TextView android:text="TextView" android:id="@+id/textView4" android:layout_weight="1"
        android:layout_width="wrap_content" android:layout_height="wrap_content">
</TextView>
     </TableRow>    
</TableLayout>

 



ListAdapter.java



/**
 *
 */

package br.agorandroid.mylocationprovider;


import java.text.SimpleDateFormat;
import java.util.Date;
import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.TextView;


/**
 * @author A H Gusukuma
 *
 *         agorandroid.blogspot.com
 */

public class ListAdapter extends CursorAdapter {
    private final SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
    /**
     * @param context
     * @param c
     */

    public ListAdapter(Context context, Cursor c) {
        super(context, c);
        // TODO Auto-generated constructor stub
       
    }


    @Override
    public void bindView(View view, Context ctx, Cursor cur) {
       
        TextView tv1 = (TextView) view.findViewById(R.id.textView1);
        tv1.setText("Lat: " + (double) cur.getDouble(cur.getColumnIndex(MyLocation.Locations.COLUMN_NAME_LATITUDE)));
   
        TextView tv2 = (TextView) view.findViewById(R.id.textView2);
        tv2.setText("Long: " + (double) cur.getDouble(cur.getColumnIndex(MyLocation.Locations.COLUMN_NAME_LONGITUDE)));
   
        TextView tv3 = (TextView) view.findViewById(R.id.textView3);
        tv3.setText(sdf.format(new Date(cur.getLong(cur.getColumnIndex(MyLocation.Locations.COLUMN_NAME_TIME)))));

        TextView tv4 = (TextView) view.findViewById(R.id.textView4);
        tv4.setText(cur.getString(cur.getColumnIndex(MyLocation.Locations.COLUMN_NAME_PROVIDER)) +" "+
                    cur.getFloat(cur.getColumnIndex(MyLocation.Locations.COLUMN_NAME_ACCURACY)));
    }


    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        // TODO Auto-generated method stub
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflater.inflate(R.layout.linhalista, null);
        return v;
    }

}

 



MyLocationActivity.java


package br.agorandroid.mylocationprovider;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import br.agorandroid.mylocationprovider.MyLocationService.LocalBinder;

/**
 * @author A H Gusukuma
 *
 *         agorandroid.blogspot.com
 */

public class MyLocationActivity extends Activity {

    final private int MAX_LISTVIEW = 20;
    private ListAdapter listAdapter;
    private ListView listView;
 
    private MyLocationService mService;
    boolean mBound = false;
    private TextView tv;
   
    private Cursor cursor;
 
     /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
 
        Button button = (Button)findViewById(R.id.start);
        button.setOnClickListener(mStartListener);
        button = (Button)findViewById(R.id.clear);
        button.setOnClickListener(mClearListener);
        button = (Button)findViewById(R.id.stop);
        button.setOnClickListener(mStopListener);
        tv = (TextView) findViewById(R.id.status);
       
        Uri mUri = MyLocation.Locations.CONTENT_URI;
       
        cursor = managedQuery(mUri.buildUpon().appendQueryParameter
                (MyLocation.Locations.LIMIT_PARAMETER_KEY, String.valueOf(MAX_LISTVIEW)).build(), null, null, null, null);
             
        listView = (ListView) findViewById(R.id.listView1);
        listView.setStackFromBottom(true);
        listAdapter = new ListAdapter(this, cursor);
        listView.setAdapter(listAdapter);

    }
   
    @Override
    protected void onStart() {
        super.onStart();
        if (!mBound) {
            Intent intent = new Intent(this, MyLocationService.class);
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        }

    }
   
    @Override
    protected void onPause() {
        super.onPause();
        tv.setText("");
        if (mBound) {               
            mService.stopColeta();
        }
    }
   
    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }
    }
    protected void onDestroy() {
        super.onDestroy();
        if (mBound) {
            unbindService(mConnection);
            mBound = false;
        }

    }
   
   
    private OnClickListener mStartListener = new OnClickListener() {
        public void onClick(View v) {    
            if (mBound) {               
                tv.setText("Coleta iniciada");
                mService.startColeta();
            }
        }
    };

    private OnClickListener mClearListener = new OnClickListener() {
        public void onClick(View v) {
            int count = getContentResolver().delete(MyLocation.Locations.CONTENT_URI, "1", null);
            Toast.makeText(MyLocationActivity.this, String.valueOf(count) + " rows deleted" , Toast.LENGTH_LONG).show();
        }
    };

    private OnClickListener mStopListener = new OnClickListener() {
        public void onClick(View v) {
            if (mBound) {               
                tv.setText("Coleta finalizada");
                mService.stopColeta();
            }
        }
    };
   
    private ServiceConnection mConnection = new ServiceConnection() {
       
        @Override
        public void onServiceConnected(ComponentName className, IBinder service) {
            LocalBinder binder = (LocalBinder) service;
            mService = binder.getService();
            mBound = true;
        }
       
        @Override
        public void onServiceDisconnected(ComponentName className) {
            tv.setText("Coleta finalizada");
            mBound = false;
        }
       
    };
 
}


 




MyLocationService.java



/**
 *
 */

package br.agorandroid.mylocationprovider;

import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import android.app.Service;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

/**
 * @author A H Gusukuma
 *
 *         agorandroid.blogspot.com
 */

public class MyLocationService extends Service {

    private final IBinder mBinder = new LocalBinder();
   
    private LocationManager lm;
   
    public class LocalBinder extends Binder {
        MyLocationService getService() {
            return MyLocationService.this;
        }
    }
   
    private BlockingQueue<Location> queue = new LinkedBlockingQueue<Location>(10);
    private final Location LOCATION_END = new Location("END");
    private Thread mThread;
    private LocationListener myLocationListener = new LocationListener() {
        public void onLocationChanged(Location location) {
            Location mLocal = new Location(location);
            try {
                queue.put(mLocal);
            } catch (InterruptedException e) {
            }
        }
        public void onProviderDisabled(String provider) {
           
        }
        public void onProviderEnabled(String provider) {
           
        }
        public void onStatusChanged(String provider, int status, Bundle extras) {
           
        }
    };
   
    @Override
    public IBinder onBind(Intent intent) {
        // TODO Auto-generated method stub
        lm = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
        mThread = new Thread(new WriteLocation());
        mThread.start();
        return mBinder;
    }
   
    public void startColeta() {    
     //     lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, myLocationListener);
        List<String> providersEnabled;
        providersEnabled = lm.getProviders(true);
        if (providersEnabled.size() > 0) {
            for (int i=0; i<providersEnabled.size(); i++) {
                String prov = providersEnabled.get(i);
                lm.requestLocationUpdates(prov, 0, 0, myLocationListener);                 
            }
        }    
       
       
     }

    public void stopColeta() {        
        lm.removeUpdates(myLocationListener);    
    
    }
   
    @Override
    public void onDestroy() {
        while (true) {
            try {
                queue.put(LOCATION_END);
                break;
            } catch (InterruptedException e) {
            }
        }
    }
   
    private class WriteLocation implements Runnable {

        @Override
        public void run() {
           
                while(true) {
                    Location location;
                    try {
                        location = queue.take();

                        if (location == LOCATION_END) {
                            break;
                        }
                        ContentValues initialValues = new ContentValues();
                   
                        initialValues.put(MyLocation.Locations.COLUMN_NAME_PROVIDER, location.getProvider());  
                        initialValues.put(MyLocation.Locations.COLUMN_NAME_TIME, location.getTime());      
                        initialValues.put(MyLocation.Locations.COLUMN_NAME_ACCURACY, location.getAccuracy());
                        initialValues.put(MyLocation.Locations.COLUMN_NAME_LATITUDE, location.getLatitude());
                        initialValues.put(MyLocation.Locations.COLUMN_NAME_LONGITUDE, location.getLongitude());  
                        ContentResolver cr = getContentResolver();
                        cr.insert(MyLocation.Locations.CONTENT_URI, initialValues);
                    } catch (InterruptedException e) {
                        // ignore
                    }
                }
               
            }
           
        }
   
}

 




Algumas observações finais:
.Normalmente não gravamos todos os Locations recebidos, fazemos uma seleção dos mais precisos, por tempo, etc.
.Ou, definimos critérios para os providers nos enviar somente os locations em conformidade com os mesmos.
.O metodo ManagedQuery está deprecated a partir do API 11, Android 3.0.x
.Foi substituído pelo CursorLoader
.O acesso aos dados para o ListView está na UI Thread, se ficar muito demorado usar uma AsyncTask.
.Não testei o método update

Vejam o resultado, no Xperia mini:

Imagem


Publicados nos seguintes posts do meu blog:
http://agorandroid.blogspot.com.br/2012 ... do-um.html
http://agorandroid.blogspot.com.br/2012 ... sando.html

Abraços

_________________
Abraços
___________
Novo App: CalcMat - Calculadora de materiais para concreto
Blog: Agorandroid - sobre programação Android
Twitter: @Agorandroid
___________
Campanha: Facilite sua vida e a dos outros usuários
Cuide do ciclo de vida do seu tópico:
no onCreate(): seja claro, se necessário poste o código e as mensagens de erro.
no onClick(): responda às sugestões.
no onStop(): evite "ninguém?", "alguém?", etc. Procure acrescentar alguma nova informação.
no onDestroy(): resolvido o assunto, poste imediatamente a solução, e, coloque no título do primeiro post [Resolvido].


11 Dez 2012, 04:29
Perfil
Mostrar mensagens anteriores:  Organizar por  
Responder Tópico   [ 1 Mensagem ] 

Quem está online

Usuários vendo este fórum: ademir.carvalhojr, agtavares, Alan Unger, alexandreufcg, amarildolacerdas, americano, Andre, Andre Brito, arkanjo, arnaldo.miranda, Arthas, azero, bbourbon, beeshop, breko, Brunohc, caio, cariabs8, Carlos, carminati, cassiano, cfranca, Chanceler Supremo Finis Valorum, ciro, claudio, cleber, Clone Trooper, Darth Sidious, Delão, diemesleno, difrene, diogeneskelsen, edwarvelarde, Eneias, erikopa, espinhara.net, EvertonLB, fabiano_eletro, Felipe Ferreira, felipedornelas, Filipe larizzatti, free_w3000, furlanrapha, FVB, gapler, glmsistemas, Google Desktop, Gui Pereira, Guilherme, Guilherme Cobain, guitarro17, guto.pro, hostdesigner, ICCrawler - ICjobs, iuri_freire, Jar Jar Binks, jonasminas, Jorge Machin, Josinhaz, kleberperea, lalaine, laucode, laurj, levita, Lincoln, lucasmadeira, Maiquell, maolveira, Marcelo, marcosf63, memnoch, mmiottobarbosa, n3t0, nandokanarski, NeruLL, nocivus, Nute Gunray, Odigooogle™, Orivalde, otpor, pamonteiro, paulabr, pbcjunior, pedro, raragao, regis.ror, ricdigital, rixargolo, romualdo, ruizsa, sasuke_sarutobi, Shaman286, siker C3PO, talves, Tangerina, viniciusllima, W3 [Sitesearch], xikin, Yoshihury, Zam Wesell e 2 visitantes


Você não pode criar novos tópicos neste fórum
Você não pode responder tópicos neste fórum
Você não pode editar suas mensagens neste fórum
Você não pode excluir suas mensagens neste fórum
Você não pode enviar anexos neste fórum

Procurar por:

© 2007 - 2016 Portal Android - Comunidade de Desenvolvedores Android

Estamos no Linkedin    Siga-nos no twitter


Powered by phpBB - Hospedado por Bemobi