新建RunDatabaseHelper类
package com.huangfei.runtracker;
import java.util.Date;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.location.Location;
/** * SQLiteOpenHelper类封装了一些存储应用数据的常用数据库操作,如创建、打开、以及更新数据库等。 */
public class RunDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "runs.sqlite";// 数据库名
private static final int VERSION = 1;// 数据库版本号
private static final String TABLE_RUN = "run";
private static final String COLUMN_RUN_ID = "_id";
private static final String COLUMN_RUN_START_DATE = "start_date";
private static final String TABLE_LOCATION = "location";
private static final String COLUMN_LOCATION_LATITUDE = "latitude";
private static final String COLUMN_LOCATION_LONGITUDE = "longitude";
private static final String COLUMN_LOCATION_ALTITUDE = "altitude";
private static final String COLUMN_LOCATION_TIMESTAMP = "timestamp";
private static final String COLUMN_LOCATION_PROVIDER = "provider";
private static final String COLUMN_LOCATION_RUN_ID = "run_id";
public RunDatabaseHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
/** * Android内置了操作SQLite的java前端,该前端的SQLiteDatabase类负责提供Cursor类实例形式的结果集。 * * 在onCreate(SQLiteDatabase)方法中,应为新建数据库创建表结构。 */
@Override
public void onCreate(SQLiteDatabase db) {
// 创建表run
db.execSQL("create table run ("
+ "_id integer primary key autoincrement, start_date integer)");
// 创建表location
db.execSQL("create table location ("
+ " timestamp integer, latitude real, longitude real, altitude real,"
+ " provider varchar(100), run_id integer references run(_id))");
}
/** * 在onUpgrade(...)方法中,可执行迁移代码,实现不同版本间的数据库结构升级或转换。 */
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
/** * 在表run中插入一条数据 */
public long insertRun(Run run) {
// 通过ContentValues对象标识的栏位名与值得名值对,将long类型的开始日期存入到数据库中。
ContentValues cv = new ContentValues();
cv.put(COLUMN_RUN_START_DATE, run.getSartDate().getTime());
/** * SQLiteOpenHelper类有两个访问SQLiteDatabase实例的方法: * getWritableDatabase()和getReadableDatabase()方法。这里的使用模式是,需要可写数据库时, * 使用getWritableDatabase()方法;需要只读数据库时,使用getReadableDatabase()方法。 */
return getWritableDatabase().insert(TABLE_RUN, null, cv);
}
/** * 在表location中插入一条数据 */
public long insertLocation(long runId, Location location) {
ContentValues cv = new ContentValues();
cv.put(COLUMN_LOCATION_LATITUDE, location.getLatitude());
cv.put(COLUMN_LOCATION_LONGITUDE, location.getLongitude());
cv.put(COLUMN_LOCATION_ALTITUDE, location.getAltitude());
cv.put(COLUMN_LOCATION_TIMESTAMP, location.getTime());
cv.put(COLUMN_LOCATION_PROVIDER, location.getProvider());
cv.put(COLUMN_LOCATION_RUN_ID, runId);
return getWritableDatabase().insert(TABLE_LOCATION, null, cv);
}
/** * 查询SQLiteDatabase可返回描述结果的Cursor实例。Cursor API使用简单。且可灵活支持各种类型的查询结果。 * Cursor将结果集看做是一系列的数据行和数据列,但仅支持String以及原始数据类型的值。 */
public RunCursor queryRuns() {
Cursor wrapped = getReadableDatabase().query(TABLE_RUN, null, null,
null, null, null, COLUMN_RUN_START_DATE + " asc");
return new RunCursor(wrapped);
}
/** * 限制查询只返回一条记录 */
public RunCursor queryRun(long id) {
Cursor cursor = getReadableDatabase().query(TABLE_RUN, null,
COLUMN_RUN_ID + " = ?", new String[] { String.valueOf(id) },
null, null, null, "1");
return new RunCursor(cursor);
}
/** * 获取指定旅程最近一次地理位置信息 */
public LocationCursor queryLastLocationForRun(long runId) {
Cursor wrapped = getReadableDatabase().query(TABLE_LOCATION,
null, // all columns
COLUMN_LOCATION_RUN_ID + " = ?", // limit to the given run
new String[]{ String.valueOf(runId) },
null, // group by
null, // having
COLUMN_LOCATION_TIMESTAMP + " desc", // order by latest first
"1"); // limit 1
return new LocationCursor(wrapped);
}
/** * CursorWrapper是一个内置的Cursor子类。CursorWrapper类设计用于封装当前的Cursor类,并转发所有的方法调用它。 * CursorWrapper类本身没有多大用途,但作为超类,它为创建适用于模型层对象的定制cursor打下了良好的基础。 * * RunCursor主要负责将run表中的各个记录转化为Run实例,并按要求对结果进行组织排序。 */
public static class RunCursor extends CursorWrapper {
public RunCursor(Cursor cursor) {
super(cursor);
}
public Run getRun() {
// 检查确认cursor未越界
if (isBeforeFirst() || isAfterLast())
return null;
Run run = new Run();
long runId = getLong(getColumnIndex(COLUMN_RUN_ID));
run.setId(runId);
long startDate = getLong(getColumnIndex(COLUMN_RUN_START_DATE));
run.setSartDate(new Date(startDate));
return run;
}
}
public static class LocationCursor extends CursorWrapper {
public LocationCursor(Cursor c) {
super(c);
}
public Location getLocation() {
if (isBeforeFirst() || isAfterLast())
return null;
String provider = getString(getColumnIndex(COLUMN_LOCATION_PROVIDER));
Location loc = new Location(provider);
loc.setLongitude(getDouble(getColumnIndex(COLUMN_LOCATION_LONGITUDE)));
loc.setLatitude(getDouble(getColumnIndex(COLUMN_LOCATION_LATITUDE)));
loc.setAltitude(getDouble(getColumnIndex(COLUMN_LOCATION_ALTITUDE)));
loc.setTime(getLong(getColumnIndex(COLUMN_LOCATION_TIMESTAMP)));
return loc;
}
}
}
package com.huangfei.runtracker;
import com.huangfei.runtracker.RunDatabaseHelper.RunCursor;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.TextView;
public class RunListFragment extends ListFragment {
private static final int REQUEST_NEW_RUN = 0;
/** * 当前Cursor的加载与关闭分别发生在onCreate(Bundle)和onDestroy()中,但我们不推荐这种做法, * 因为这不仅强制数据库查询在主线程(UI)上执行,在极端的情况下甚至会引发ANR(应用无响应)。 * 下一章,我们将使用Loader将数据库查询移到后台运行。 */
private RunCursor mCursor;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
mCursor = RunManager.get(getActivity()).queryRuns();
RunCursorAdapter adapter = new RunCursorAdapter(getActivity(), mCursor);
setListAdapter(adapter);
}
@Override
public void onDestroy() {
mCursor.close();
super.onDestroy();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.run_list_options, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_new_run:
Intent intent = new Intent(getActivity(), RunActivity.class);
startActivityForResult(intent, REQUEST_NEW_RUN);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == REQUEST_NEW_RUN){
mCursor.requery();//重新查询cursor
((RunCursorAdapter)getListAdapter()).notifyDataSetChanged();
}
}
/** * 因为我们指定了run_id表中的ID字段,CursorAdapter检测到该字段并将其作为id参数传递给onListItemClick(...)方法。 * 我们也因此能够直接将其作为附加信息传递给了RunActivity。 */
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Intent intent = new Intent();
intent.putExtra(RunActivity.EXTRA_RUN_ID, id);
startActivity(intent);
}
private static class RunCursorAdapter extends CursorAdapter {
private RunCursor mRunCursor;
public RunCursorAdapter(Context context, RunCursor c) {
/** * 为提倡使用loader,大多数flag已被废弃或有一些问题存在,因此,这里传入的值为0 */
super(context, c, 0);
mRunCursor = c;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
//这里的RunCursor是CursorAdapter已经定位的cursor
Run run = mRunCursor.getRun();
//view来自于newView(...)方法的返回值
TextView startDaTextureView = (TextView) view;
startDaTextureView.setText("Run at " + run.getSartDate());
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return inflater.inflate(android.R.layout.simple_list_item_1,
parent, false);
}
}
}
代码地址