2016年1月6日 星期三

Android Sql and Content provider

big picture


SQL part

1.Create a Contract

it describ how the data is accessed.
for example, we have these two table in our database,
So,first we cretae a contract class call WeatherContract
Itis becasue we have two table WEATHER and LOCATION,so we create two innear class,call LocationEntry and WeatherEntry


The two innear class implement BaseColumns


Now,add the variable to the two innear class.
For example, because the Location table has a column CITY_NAME,we add vairable :


public static final String COLUMN_CITY_NAME = "city_name";


Finished contract:


package com.example.android.sunshine.app.data;


/**
* Defines table and column names for the weather database.
*/
public class WeatherContract {


   


   /* Inner class that defines the table contents of the location table */
   public static final class LocationEntry implements BaseColumns {


      


       //SQLite part


       // Table name
       public static final String TABLE_NAME = "location";


       // The location setting string is what will be sent to openweathermap
       // as the location query.
       public static final String COLUMN_LOCATION_SETTING = "location_setting";


       // Human readable location string, provided by the API.  Because for styling,
       // "Mountain View" is more recognizable than 94043.
       public static final String COLUMN_CITY_NAME = "city_name";


       // In order to uniquely pinpoint the location on the map when we launch the
       // map intent, we store the latitude and longitude as returned by openweathermap.
       public static final String COLUMN_COORD_LAT = "coord_lat";
       public static final String COLUMN_COORD_LONG = "coord_long";


       //SQLite part


      
   }


   /* Inner class that defines the table contents of the weather table */
   public static final class WeatherEntry implements BaseColumns {


      


       public static final String TABLE_NAME = "weather";
       //SQLite part
       // Column with the foreign key into the location table.
       public static final String COLUMN_LOC_KEY = "location_id";
       // Date, stored as long in milliseconds since the epoch
       public static final String COLUMN_DATE = "date";
       // Weather id as returned by API, to identify the icon to be used
       public static final String COLUMN_WEATHER_ID = "weather_id";
       // Short description and long description of the weather, as provided by API.
       // e.g "clear" vs "sky is clear".
       public static final String COLUMN_SHORT_DESC = "short_desc";
       // Min and max temperatures for the day (stored as floats)
       public static final String COLUMN_MIN_TEMP = "min";
       public static final String COLUMN_MAX_TEMP = "max";
       // Humidity is stored as a float representing percentage
       public static final String COLUMN_HUMIDITY = "humidity";
       // Humidity is stored as a float representing percentage
       public static final String COLUMN_PRESSURE = "pressure";
       // Windspeed is stored as a float representing windspeed  mph
       public static final String COLUMN_WIND_SPEED = "wind";
       // Degrees are meteorological degrees (e.g, 0 is north, 180 is south).  Stored as floats.
       public static final String COLUMN_DEGREES = "degrees";


   


   
   }
}


now ,the contract is finish


2.Create SQLiteOpenHelper



A helper class to manage database creation and version management.
You create a subclass implementing onCreate(SQLiteDatabase), onUpgrade(SQLiteDatabase, int, int) and optionallyonOpen(SQLiteDatabase), and this class takes care of opening the database if it exists, creating it if it does not, and upgrading it as necessary. Transactions are used to make sure the database is always in a sensible state.


First,we creat a class


WeatherDbHelper extends SQLiteOpenHelper


and override two method
1.onCreate
2.onUpgrade


In onCreate, we need to create the table in the database.


in OnUPgrade, we neeed to handle the database up grade metter.We normally delete the table normally.


Finished code:


/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.android.sunshine.app.data;


import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;


import com.example.android.sunshine.app.data.WeatherContract.LocationEntry;
import com.example.android.sunshine.app.data.WeatherContract.WeatherEntry;


/**
* Manages a local database for weather data.
*/
public class WeatherDbHelper extends SQLiteOpenHelper {


  // If you change the database schema, you must increment the database version.
  private static final int DATABASE_VERSION = 2;


  static final String DATABASE_NAME = "weather.db";


  public WeatherDbHelper(Context context) {
      super(context, DATABASE_NAME, null, DATABASE_VERSION);
  }


  @Override
  public void onCreate(SQLiteDatabase sqLiteDatabase) {
      // Create a table to hold locations.  A location consists of the string supplied in the
      // location setting, the city name, and the latitude and longitude
      final String SQL_CREATE_LOCATION_TABLE = "CREATE TABLE " + LocationEntry.TABLE_NAME + " (" +
              LocationEntry._ID + " INTEGER PRIMARY KEY," +
              LocationEntry.COLUMN_LOCATION_SETTING + " TEXT UNIQUE NOT NULL, " +
              LocationEntry.COLUMN_CITY_NAME + " TEXT NOT NULL, " +
              LocationEntry.COLUMN_COORD_LAT + " REAL NOT NULL, " +
              LocationEntry.COLUMN_COORD_LONG + " REAL NOT NULL " +
              " );";


      final String SQL_CREATE_WEATHER_TABLE = "CREATE TABLE " + WeatherEntry.TABLE_NAME + " (" +
              // Why AutoIncrement here, and not above?
              // Unique keys will be auto-generated in either case.  But for weather
              // forecasting, it's reasonable to assume the user will want information
              // for a certain date and all dates *following*, so the forecast data
              // should be sorted accordingly.
              WeatherEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +


              // the ID of the location entry associated with this weather data
              WeatherEntry.COLUMN_LOC_KEY + " INTEGER NOT NULL, " +
              WeatherEntry.COLUMN_DATE + " INTEGER NOT NULL, " +
              WeatherEntry.COLUMN_SHORT_DESC + " TEXT NOT NULL, " +
              WeatherEntry.COLUMN_WEATHER_ID + " INTEGER NOT NULL," +


              WeatherEntry.COLUMN_MIN_TEMP + " REAL NOT NULL, " +
              WeatherEntry.COLUMN_MAX_TEMP + " REAL NOT NULL, " +


              WeatherEntry.COLUMN_HUMIDITY + " REAL NOT NULL, " +
              WeatherEntry.COLUMN_PRESSURE + " REAL NOT NULL, " +
              WeatherEntry.COLUMN_WIND_SPEED + " REAL NOT NULL, " +
              WeatherEntry.COLUMN_DEGREES + " REAL NOT NULL, " +


              // Set up the location column as a foreign key to location table.
              " FOREIGN KEY (" + WeatherEntry.COLUMN_LOC_KEY + ") REFERENCES " +
              LocationEntry.TABLE_NAME + " (" + LocationEntry._ID + "), " +


              // To assure the application have just one weather entry per day
              // per location, it's created a UNIQUE constraint with REPLACE strategy
              " UNIQUE (" + WeatherEntry.COLUMN_DATE + ", " +
              WeatherEntry.COLUMN_LOC_KEY + ") ON CONFLICT REPLACE);";


      sqLiteDatabase.execSQL(SQL_CREATE_LOCATION_TABLE);
      sqLiteDatabase.execSQL(SQL_CREATE_WEATHER_TABLE);
  }


  @Override
  public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
      // This database is only a cache for online data, so its upgrade policy is
      // to simply to discard the data and start over
      // Note that this only fires if you change the version number for your database.
      // It does NOT depend on the version number for your application.
      // If you want to update the schema without wiping data, commenting out the next 2 lines
      // should be your top priority before modifying this method.
      sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + LocationEntry.TABLE_NAME);
      sqLiteDatabase.execSQL("DROP TABLE IF EXISTS " + WeatherEntry.TABLE_NAME);
      onCreate(sqLiteDatabase);
  }
}


3.How to  Read/Write from a Database

Write

1.get a reference to a writeable database


SQLiteDatabase db = new WeatherDbHelper(
      this.mContext).getWritableDatabase();


2.create a ConstantValue object for what you want to insert


ContentValues weatherValues = new ContentValues();
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_LOC_KEY, locationRowId);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_DATE, TEST_DATE);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_DEGREES, 1.1);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_HUMIDITY, 1.2);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_PRESSURE, 1.3);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_MAX_TEMP, 75);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_MIN_TEMP, 65);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_SHORT_DESC, "Asteroids");
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_WIND_SPEED, 5.5);
weatherValues.put(WeatherContract.WeatherEntry.COLUMN_WEATHER_ID, 321);


3.Insert the ConstantValue and get a row ID back


db.insert().


Query

1.get a reference to a readabledatabase
2.query


the query return a cursor


After read and write we nedd to close the Database and coursor:


cursor.close();
sqb.close();


Now,the SQL database is finish!!


Content provider part

Why we need a content provider?

Allow a app can acces another app data
Can change the data cource,but still use the same way to access the data.
Loader and synadapter need content provider




Create a content Provider

4 steap


  1. Determine Uri

basic concept of Uri:
a uri has 3 part:


scheme
authority:always be the pacakage name of the application
location: point a a table in the application
query


for example:


scheme:content
authority: com.example.android.sunshine.app
location:weather
query:64111


in this example:we have these URI:


2.Update the Contract

some basicto create a Uri


Uri.parse


String to Uri


buildUpon()


if we want to create a Uri like:


"content://com.example.android.sunshine.app/location"


we need


Uri.Builder builder = new Uri.Builder();
builder.scheme("content") .authority("com.example.android.sunshine.app") .appendPath("location").build;


however, if we already we a uri:


Uri u=Uri.parse(“content://com.example.android.sunshine.app/”);


we can use buildUpon() to save time


Uri new=u.buildUpon()..appendPath("location").build;


withAppendedId


to add the Location to the URI, example:content://com.example.android.sunshine.app/location/61030


getPathSegments
for example:
content://com.ex09_2_contentprovider.diarycontentprovider/diaries/100


rowId = uri.getPathSegments().get(1)


will get 100


rowId = uri.getPathSegments().get(0)


will get diaries


getQueryParameter
for example,we have




uri.getQueryParameter("gws_rd");


retyrn ssl


Now we can update the Contract

for loaction table, we have Uri
So,


public static final String CONTENT_AUTHORITY = "com.example.android.sunshine.app";


public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);


public static final String PATH_WEATHER = "weather";
public static final String PATH_LOCATION = "location";


public static final Uri CONTENT_URI =
      BASE_CONTENT_URI.buildUpon().appendPath(PATH_LOCATION).build();  //build uri:  "content://com.example.android.sunshine.app/location"


for WEATHER table,we have URI


so


public static final Uri CONTENT_URI =
      BASE_CONTENT_URI.buildUpon().appendPath(PATH_WEATHER).build();


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


/*
  Student: Fill in this buildWeatherLocation function
*/
public static Uri buildWeatherLocation(String locationSetting) {
  return CONTENT_URI.buildUpon().appendPath(locationSetting).build();
}


public static Uri buildWeatherLocationWithStartDate(
      String locationSetting, long startDate) {
  long normalizedDate = normalizeDate(startDate);
  return CONTENT_URI.buildUpon().appendPath(locationSetting)
          .appendQueryParameter(COLUMN_DATE, Long.toString(normalizedDate)).build();


}


add the return type in the Contract



Morever, we need to add the return type,has two type,single iteam and multi iteam


for location entry


public static final String CONTENT_TYPE =
      ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_LOCATION;  //define the type , multiple iteam
public static final String CONTENT_ITEM_TYPE =
      ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_LOCATION;  //single iteam


for weather entry


public static final String CONTENT_TYPE =
      ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_WEATHER;
public static final String CONTENT_ITEM_TYPE =
      ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_WEATHER;


now,the Contract update is finish


4.Fill out the URi Mactcher

Basic of the Uri macther:
we give Uri to the matcher, it give us back a int


The content provider us the Matcher to match the Uri to different action
First ,define the number for each Uri


static final int WEATHER = 100;
static final int WEATHER_WITH_LOCATION = 101;
static final int WEATHER_WITH_LOCATION_AND_DATE = 102;
static final int LOCATION = 300;


Second,create a match and add the Uri to it
// 1) The code passed into the constructor represents the code to return for the root
// URI.  It's common to use NO_MATCH as the code for this case. Add the constructor below.
UriMatcher urim=new UriMatcher(UriMatcher.NO_MATCH);


// 2) Use the addURI function to match each of the types.  Use the constants from
// WeatherContract to help define the types to the UriMatcher.
urim.addURI(WeatherContract.CONTENT_AUTHORITY,WeatherContract.PATH_WEATHER,WEATHER);
urim.addURI(WeatherContract.CONTENT_AUTHORITY,WeatherContract.PATH_WEATHER+"/*",WEATHER_WITH_LOCATION);
urim.addURI(WeatherContract.CONTENT_AUTHORITY,WeatherContract.PATH_WEATHER+"/*"+"/#",WEATHER_WITH_LOCATION_AND_DATE);
urim.addURI(WeatherContract.CONTENT_AUTHORITY,WeatherContract.PATH_LOCATION,LOCATION);


if no Uri is match,will return number UriMatcher.NO_MATCH


Third use
public String getType(Uri uri) {


  // Use the Uri Matcher to determine what kind of URI this is.
  final int match = sUriMatcher.match(uri);


  switch (match) {
      // Student: Uncomment and fill out these two cases
      case WEATHER_WITH_LOCATION_AND_DATE:
          return WeatherContract.WeatherEntry.CONTENT_ITEM_TYPE;
      case WEATHER_WITH_LOCATION:
          return WeatherContract.WeatherEntry.CONTENT_TYPE;
      case WEATHER:
          return WeatherContract.WeatherEntry.CONTENT_TYPE;
      case LOCATION:
          return WeatherContract.LocationEntry.CONTENT_TYPE;
      default:
          throw new UnsupportedOperationException("Unknown uri: " + uri);
  }
}


5.Coding the content provider

First we need to register the content provider:


in Manifest.xml


<provider
      android:name="com.example.android.sunshine.app.data.WeatherProvider"
      android:authorities="com.example.android.sunshine.app" />
</application>


the name is the full name of the content provider class nam
authorities is the authority use by the Content Provider URI


Second ,create Content Provider class
in our example:


class WeatherProvider extends ContentProvider


Senond ,fill out the method


onCreate()



because we use SQLite in this example,sowe create a SQLiteOpenHelper in onCreate();


public boolean onCreate() {
  mOpenHelper = new WeatherDbHelper(getContext());
  return true;
}


getType()



to get the return type of different URI


public String getType(Uri uri) {


  // Use the Uri Matcher to determine what kind of URI this is.
  final int match = sUriMatcher.match(uri);


  switch (match) {
      // Student: Uncomment and fill out these two cases
      case WEATHER_WITH_LOCATION_AND_DATE:
          return WeatherContract.WeatherEntry.CONTENT_ITEM_TYPE;
      case WEATHER_WITH_LOCATION:
          return WeatherContract.WeatherEntry.CONTENT_TYPE;
      case WEATHER:
          return WeatherContract.WeatherEntry.CONTENT_TYPE;
      case LOCATION:
          return WeatherContract.LocationEntry.CONTENT_TYPE;
      default:
          throw new UnsupportedOperationException("Unknown uri: " + uri);
  }
}


query()

concept:
we use the URI matcher to matcher the URI  to different SQL query action


public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                  String sortOrder) {
  // Here's the switch statement that, given a URI, will determine what kind of request it is,
  // and query the database accordingly.
  Cursor retCursor;
  switch (sUriMatcher.match(uri)) {
      // "weather/*/#"
      case WEATHER_WITH_LOCATION_AND_DATE:
      {
          retCursor = getWeatherByLocationSettingAndDate(uri, projection, sortOrder);
          break;
      }
      // "weather/*"
      case WEATHER_WITH_LOCATION: {
          retCursor = getWeatherByLocationSetting(uri, projection, sortOrder);
          break;
      }
      // "weather"
      case WEATHER: {
          retCursor = mOpenHelper.getReadableDatabase().query(WeatherContract.WeatherEntry.TABLE_NAME,null,selection,selectionArgs,null,null,sortOrder);


          break;
      }
      // "location"
      case LOCATION: {
          retCursor = mOpenHelper.getReadableDatabase().query(WeatherContract.LocationEntry.TABLE_NAME,null,selection,selectionArgs,null,null,sortOrder);
          break;
      }


      default:
          throw new UnsupportedOperationException("Unknown uri: " + uri);
  }
  retCursor.setNotificationUri(getContext().getContentResolver(), uri);
  return retCursor;
}


Important!!!


setNotificationUri allow the cursor update at the same time when the database update,
need to use with getContext().getContentResolver().notifyChange(uri, null);


Insert()



it is because we only have two table, so only two case in our example.
we do the Sqlite insert in the Insert


public Uri insert(Uri uri, ContentValues values) {
  final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
  final int match = sUriMatcher.match(uri);
  Uri returnUri;


  switch (match) {
      case WEATHER: {
          normalizeDate(values);
          long _id = db.insert(WeatherContract.WeatherEntry.TABLE_NAME, null, values);
          if ( _id > 0 )
              returnUri = WeatherContract.WeatherEntry.buildWeatherUri(_id);
          else
              throw new android.database.SQLException("Failed to insert row into " + uri);
          break;
      }
      case LOCATION:
          long _id = db.insert(WeatherContract.LocationEntry.TABLE_NAME, null, values);
          if ( _id > 0 )
              returnUri = WeatherContract.WeatherEntry.buildWeatherUri(_id);
          else
              throw new android.database.SQLException("Failed to insert row into " + uri);
          break;


      default:
          throw new UnsupportedOperationException("Unknown uri: " + uri);
  }
  getContext().getContentResolver().notifyChange(uri, null);
  return returnUri;
}


Update and Delate()

almost same to the Insert()


now the Provider is finish!!!!


the full project:
https://github.com/roy989898/finished_Sunshine-Version-2-4.07.git



##important
We should not create so much SqliteDatabase.

if don't use the curosr,we need to close the cursor.

before close the SqliteDatabase,we need to close  all the relate cursor


How to Use a Content Provider

Basic knowledge



to query to a ContentProvider we need a resolver:


How to acces the content provider