源地址: http://www.vogella.com/tutorials/AndroidSQLite/article.html
1.SQLite 和 Android
1.1 什么是SQLite?
SQLite是开源数据库。SQLite支持标准的关系数据库的特性,例如SQL语法,事务操作,prepared statement. 只需要很少的运行内存(大概250k),很适合嵌入到一些应用程序中。
SQLite支持的数据类型有:TEXT
(类似java
中的String
)、INTEGER
(类似java
中的long
)、和REAL
(类似java
中的double
)。所有的类型在存入数据中前必须先转换成这几种类型。数据库自己不会去检查写入的数据是否是对应的数据类型,例如,你可以讲一个integer
类型数据写入到string
条目中,相反也可以。
更多关于SQLite的信息,可以访问SQLite网站:http://www.sqlite.org
1.2 Android中的SQLite
SQLite被嵌入在每一个Android设备中。在Android中使用SQLite数据库,不需要安装也不需要管理员权限。
你只需要定义创建和更新数据库的语句。然后Android系统会自动帮你维护这个数据库。
对SQLite数据库的访问需要访问文件系统。这可能比较慢。因此推荐异步执行数据库操作。
如果你的应用创建了数据库,那么数据库被默认保存在这个目录下:DATA/data/APP_NAME/databases/FILENAME
。
上面的数据库路径会符合这么几个规则:DATA
是Environment.getDataDirectory()
方法返回的路径。APP_NAME
是应用名字。FILENAME
是你在代码中为数据库指定的名字。
2.看这篇教程的必要基础
掌握Android开发的基础知识。
3.SQLite知识结构
3.1 有关的包
android.database
包 包括所有和数据库操作相关的类。android.database.sqlite
包括和SQLite相关的类。
3.2 使用SQLiteOpenHelper
来创建和更新数据库
可以通过创建一个继承SQLiteOpenHelper
类的子类来创建和升级数据库。在类的构造函数中调用SQLiteOpenHelper
的super()
方法,指定数据库的名字和当前版本。
在这个新创建的类中,你需要覆盖这么几个方法来创建和更新数据库:
onCreate()
- 如果数据库还没有创建,会被调用。onUpgrade()
- 如果数据库版本升级会被调用。这个方法允许你更新当前数据库的机构或者删掉当前的数据库使用onCreate()
重新创建数据库。两个方法都会收到SQLiteDatabase
对象作为参数,是数据库的java封装。SQLiteOpenHelper
提供两个方法getReadableDatabase()
、getWriteableDatabase()
来获取SQLiteDatabase
对象,用于从数据库中读取信息或者写入数据库。
数据库表中,应该用_id
来作为键值。几个Android功能依赖这一设定。
窍门:每个表建立一个类是个很好的编程习惯。这个类定义静态的
onCreate()
方法和onUpgrade()
方法。在SQLiteOpenHelper
对应的方法中调用这些方法。这样即使你有多个表,也可以保持SQLiteOpenHelper
实现类的可读性。
3.3 SQLiteDatabase
SQLiteDatabase
是Android中操作SQLite数据库的基础类,它提供了方法去打开、查询、更新和关闭数据库。
特定的,SQLiteDatabase
提供了insert()
、update()
、delete()
等方法。
另外还提供了execSQL()
方法,来直接执行一个SQL语句。ContentValues
对象可以用来定义键值对。键代表数据库表中某一条目的标识符,值代表数据库某一行纪录中这个条目对应的内容。ContentValues
可以用来插入或者更新数据库。
查询可以通过rawQuery()
或者query()
来完成。或者通过SQLiteQueryBuilder
类来完成。rawQuery()
直接接受select
查询语句。query()
提供一个指定SQL查询的接口。SQLiteQueryBuilder
是一个很方便构建SQL查询的类。
3.4 rawQuery()示例
下面是一个rawQuery()
调用例子:
Cursor cursor = getReadableDatabase().
rawQuery("select * from todo where _id = ?", new String[] { id });
3.5 query()示例
下面是一个query()
例子:
return database.query(DATABASE_TABLE,
new String[] { KEY_ROWID, KEY_CATEGORY, KEY_SUMMARY, KEY_DESCRIPTION },
null, null, null, null, null);
方法query()
有下面这几个参数:表 1. 方法query()
的参数
参数 | 解释 |
---|---|
String dbName | 需要查询的数据库表 |
String[] columnNames | 查询需要返回的列名集合,null 表示所有列 |
String whereClause | Where-clause,也就是过滤出需要返回的条目,null 会选择所有条目 |
String[] selectionArgs | whereClause 中可能包括? 占位符,这些占位符会被selectionArgs数组中的值代替 |
String[] groupBy | 表示如何将结果分组,null 表示不分组 |
String[] having | 用来过滤分组后的结果,null 表示不过滤 |
String[] orderBy | 被用来排序的条目,null 表示不排序 |
如果那个参数不需要,传null
就可以了,例如,group by语句。
“whereClause”语句中不包括“where”字符,例如一个“where”语句可能像这样“_id=19 and summary=?”。
如果你通过?
在where语句中指定占位符,那么需要在selectionArgs参数中传递对应的参数。
3.6 Cursor
一个查询操作会返回一个Cursor
对象。一个Cursor代表查询结果,基本上指向查询结果的一行。这样Android可以有效地缓存查询结果,因为不需要一次性把所有的数据装载到内存。
可以通过getCount()
来返回查询结果的数目。
可以通过方法moveToFirst()
、moveToNext()
从行与行之间切换。方法isAfterLast()
可以用来检查,是不是所有的查询结果已经被访问了。Cursor
提供get*()
方法,例如getLong(columnIndex)
,getString(columnIndex)
来访问当前行中的特定列的内容。“columnIndex”表示你要访问的列的下标。Cursor
用方法getColumnIndexOrThrow(String)
根据列名字返回列的下标。
一个Cursor
需要用close()
方法来关闭。
3.7 ListView, ListActivities 和 SimpleCursorAdapter
ListViews
是可以显示一系列元素的Views
。ListAcitivties
用来更方便的使用ListViews
。
为了将数据库和ListViews
连接在一起,你可以使用SimpleCursorAdapter
。SimpleCursorAdapter
允许为ListViews
的每一个元素创建layout。
你需要定义一个包含数据库列名的数组,和另外一个包含Views
中需要填充数据的元素的ID。SimpleCursorAdapter
会把Cursor
代表的数据和ListView
中的每个条目中需要填充数据的元素映射起来。
为了获得Cursor
对象,你需要使用Loader
类。
4.教程:使用SQLite
4.1 下面这些内容演示如何使用SQLite 数据库。我们将使用一个DAO对象来管理数据。这个DAO负责和数据库的连接,还有查询和修改数据。它也负责将数据库数据转换为java数据类型,所以用户界面的代码不需要处理和数据库的连接层。
app最终会想下面这样:
使用DAO并不总是正确的选择。DAO创建java的model
对象;直接使用数据库或者通过ContentProvider
可能在有效利用资源方面是更好的选择,因为你可以不用创建model
对象。
我仍然会展示如何使用DAO,作为一个相对简单的使用数据库的例子。使用的是Android4.0的系统。API等级是15.另外我更愿意介绍Loader
类,用来展示在Android3.0用来维护数据库Cursor
。而且这个类有额外的复杂性。
4.2 创建项目
以de.vogella.android.sqlite.first
来创建Android project,并同时创建一个名叫TestDatabaseActivity
的activity。
4.3 数据库和数据模型
创建一个MySQLiteHelper
类。这个类负责创建数据库。onUpgrade()
负责删掉现在的数据库并重新创建一个表。它也定义了几个常量表示表名和表里面的列名。
package de.vogella.android.sqlite.first;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class MySQLiteHelper extends SQLiteOpenHelper {
public static final String TABLE_COMMENTS = "comments";
public static final String COLUMN_ID = "_id";
public static final String COLUMN_COMMENT = "comment";
private static final String DATABASE_NAME = "commments.db";
private static final int DATABASE_VERSION = 1;
// Database creation sql statement
private static final String DATABASE_CREATE = "create table "
+ TABLE_COMMENTS + "(" + COLUMN_ID
+ " integer primary key autoincrement, " + COLUMN_COMMENT
+ " text not null);";
public MySQLiteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase database) {
database.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(MySQLiteHelper.class.getName(),
"Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + TABLE_COMMENTS);
onCreate(db);
}
}
创建Comment
类。这个类是我们的数据模型,包含着我们要保存到数据库中的用户界面数据。
package de.vogella.android.sqlite.first;
public class Comment {
private long id;
private String comment;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
// Will be used by the ArrayAdapter in the ListView
@Override
public String toString() {
return comment;
}
}
创建CommentsDataSource
类。这个类是我们的DAO。它维护着和数据库的连接,并且支持向数据库中添加数据和获取数据。
package de.vogella.android.sqlite.first;
import java.util.ArrayList;
import java.util.List;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
public class CommentsDataSource {
// Database fields
private SQLiteDatabase database;
private MySQLiteHelper dbHelper;
private String[] allColumns = { MySQLiteHelper.COLUMN_ID,
MySQLiteHelper.COLUMN_COMMENT };
public CommentsDataSource(Context context) {
dbHelper = new MySQLiteHelper(context);
}
public void open() throws SQLException {
database = dbHelper.getWritableDatabase();
}
public void close() {
dbHelper.close();
}
public Comment createComment(String comment) {
ContentValues values = new ContentValues();
values.put(MySQLiteHelper.COLUMN_COMMENT, comment);
long insertId = database.insert(MySQLiteHelper.TABLE_COMMENTS, null,
values);
Cursor cursor = database.query(MySQLiteHelper.TABLE_COMMENTS,
allColumns, MySQLiteHelper.COLUMN_ID + " = " + insertId, null,
null, null, null);
cursor.moveToFirst();
Comment newComment = cursorToComment(cursor);
cursor.close();
return newComment;
}
public void deleteComment(Comment comment) {
long id = comment.getId();
System.out.println("Comment deleted with id: " + id);
database.delete(MySQLiteHelper.TABLE_COMMENTS, MySQLiteHelper.COLUMN_ID
+ " = " + id, null);
}
public List getAllComments() {
List comments = new ArrayList();
Cursor cursor = database.query(MySQLiteHelper.TABLE_COMMENTS,
allColumns, null, null, null, null, null);
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
Comment comment = cursorToComment(cursor);
comments.add(comment);
cursor.moveToNext();
}
// make sure to close the cursor
cursor.close();
return comments;
}
private Comment cursorToComment(Cursor cursor) {
Comment comment = new Comment();
comment.setId(cursor.getLong(0));
comment.setComment(cursor.getString(1));
return comment;
}
}
4.4 用户界面
把res/layout文件夹下面的main.xml文件改成下面这样。这个布局中包含两个按钮分别用于添加和删除评论,和一个用来显示当前所有评论的ListView
。评论内容一会在activity中随机生成。
把TestDatabaseActivity
类改成下面这样:
package de.vogella.android.sqlite.first;
import java.util.List;
import java.util.Random;
import android.app.ListActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
public class TestDatabaseActivity extends ListActivity {
private CommentsDataSource datasource;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
datasource = new CommentsDataSource(this);
datasource.open();
List values = datasource.getAllComments();
// use the SimpleCursorAdapter to show the
// elements in a ListView
ArrayAdapter adapter = new ArrayAdapter(this,
android.R.layout.simple_list_item_1, values);
setListAdapter(adapter);
}
// Will be called via the onClick attribute
// of the buttons in main.xml
public void onClick(View view) {
@SuppressWarnings("unchecked")
ArrayAdapter adapter = (ArrayAdapter) getListAdapter();
Comment comment = null;
switch (view.getId()) {
case R.id.add:
String[] comments = new String[] { "Cool", "Very nice", "Hate it" };
int nextInt = new Random().nextInt(3);
// save the new comment to the database
comment = datasource.createComment(comments[nextInt]);
adapter.add(comment);
break;
case R.id.delete:
if (getListAdapter().getCount() > 0) {
comment = (Comment) getListAdapter().getItem(0);
datasource.deleteComment(comment);
adapter.remove(comment);
}
break;
}
adapter.notifyDataSetChanged();
}
@Override
protected void onResume() {
datasource.open();
super.onResume();
}
@Override
protected void onPause() {
datasource.close();
super.onPause();
}
}
4.5 运行这个应用
安装这个app,并使用Add和Delete按钮。重启你的应用,验证数据是一直存在的。
5.Content Provider 和数据共享
5.1 什么是content provider
如果你想和别的应用共享数据,你可以使用content provider(简称provider)。Provider提供基于URI封装的数据。任何以content://
开头指向资源的URI都以通过provider来访问。通过content provider一个资源URI允许你执行数据基本的CRUD操作(Create, Read, Update, Delete)。
provider允许应用访问数据。这个数据可以存储在数据库中,文件系统中,或者远程服务器上的一个文件。
一般content provider被用在应用中是为了和别的应用分享数据。应用数据通常是默认应用私有的,一个content provider是一个方便的和别的应用分享数据的接口。
一个content provider必须在应用的manifest文件中声明。
5.2 content provider的基本URI
访问content provider的基本URI被定义为content://
和provider命名空间的组合。这个命名空间通过android:authorities
属性定义在manifest文件中。例如content://test/
。
基本URI代表一个数据集合。如果基本URI后跟一个实例标识,例如content://test/2
,则表示单一实例。
5.3 访问content provider
因为访问一个provider需要知道它的URI,所以把provider的URI作为公共常量提供给别的开发者是一个很好的开发习惯。
许多Android数据, 例如联系人, 都是通过content provider访问的。
5.4 自定义content provider
创建自定义content provider,你必须创建继承android.content.ContentProvider
的类。然后将这个类在应用manifest文件中声明。相应的必须声明android:authorities
属性,用来标识这个content provider。这个属性是访问数据的基本URI,所以必须是独一无二的。
你的content provider必须实现几个方法, 例如query()
, insert()
, delete()
, getType()
, onCreate()
。对于不支持的方法最好抛一个异常UnsupportedOperationException()
。
query()方法必须返回Cursor对象。
5.5 content provider和安全
在Android 4.2之前,content provider都是默认对别的应用可用的。Android 4.2以后必须明确地声明为导出的。
在manifest文件中的content provider的声明中,可以使用android:exported=false|true
参数来制定content provider的透明性。
技巧:明确的设置
android:exported
参数来确保在不同的版本间的一致性。
5.6 线程安全
如果直接访问数据库,并且在不同的线程都有写操作,那么你将会陷入并发问题。
一个content provider可以在同一时间被多个程序访问,所以必须实现线程安全性。最简单的方法是在content provider每个方法前加上synchoronized
关键字, 这样同一时间只有一个线程可以访问。
如果你不需要Android同步对provider的访问,在manifest文件中provider的生命中设置参数android:multiprocess=true
。这样就允许在每个客户端进程中创建一个content provider实例,省去了IPC(interprocess communication)。
6.教程:使用ContentProvider
6.1 介绍
下面的内容将会使用Contact(联系人)应用中的ContentProvider
。
6.2 在你的手机上创建联系人
对于这个例子来说,我们需要几个已经存在的联系人内容。选择菜单按钮然后是People(联系人)按钮来创建联系人。
应用会问你是否想登陆,选择登陆或者暂不登陆。选择“创建新联系人”。你可以创建本地联系人。
创建完一个新的联系人,然后可以点击 + 按钮添加更多的联系人。这样app中应该有一些可以使用的联系人数据。
6.3 使用Contact中的ContentProvider
创建一个新的Android项目,名字是de.vogella.android.contentprovider
和一个名字是ContactsActivity
的activity。
更改res/layout中对应的layout文件。把当中的TextView
id改为contactview。删除默认的text内容。
布局文件最后回想下面这样:
访问联系人中的ContentProvider需要一定的权限,因为不是所有应用都应该有访问联系人信息的权限。打开应用的AndroidManifest.xml文件,选择Permission标签。点击添加按钮,选择使用权限。从下拉菜单中选择android.permission.READ_CONTACTS。
将activity改成下面这样:
package de.vogella.android.contentprovider;
import android.app.Activity;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.widget.TextView;
public class ContactsActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contacts);
TextView contactView = (TextView) findViewById(R.id.contactview);
Cursor cursor = getContacts();
while (cursor.moveToNext()) {
String displayName = cursor.getString(cursor
.getColumnIndex(ContactsContract.Data.DISPLAY_NAME));
contactView.append("Name: ");
contactView.append(displayName);
contactView.append("\n");
}
}
private Cursor getContacts() {
// Run query
Uri uri = ContactsContract.Contacts.CONTENT_URI;
String[] projection = new String[] { ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME };
String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP + " = '"
+ ("1") + "'";
String[] selectionArgs = null;
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME
+ " COLLATE LOCALIZED ASC";
return managedQuery(uri, projection, selection, selectionArgs,
sortOrder);
}
}
如果你运行这个应用,那么它将会从联系人应用的ContentProvider
读取数据,并且显示在TextView
中。通常这样的数据应该显示在ListView
中。
7.Loader
7.1 Loader类的作用
Loader
类允许你在activity或者fragment下异步的加载数据。它们可以在数据源发生变化时,将新的数据更新过来。也可以维护配置变化前后数据的一致性。
如果数据是在和activity或者fragment断开后获取的,则缓存这些数据。
Loader类在Android3.0被引入进来,在android1.6有兼容包支持。
7.2 实现一个Loader
你可以将AsyncTaskLoader
作为基类来实现你自己的Loader类。
Activity或者Fragment的LoaderManager
可以管理一或多个Loader
实例Loader的创建是通过下面的调用来实现的:
#start a new loader or re-connect to existing one
getLoaderManager().initLoader(0, null, this)
第一个参数是用来识别的ID,回调类用来标识Loader类。第二个参数用来给回调类额外信息。initLoader()
的第三个参数表示初始化一旦开始就回调用的类(回调类)。这个类必须实现LoaderManager.LoaderCallbacks
接口。推荐用activity或者fragment使用Loader并且实现LoaderManager.LoaderCallbacks
接口。Loader
不是通过LoaderManager.initLoader()
直接创建的,但是必须在 onCreateLoader()
中由回调类来创建。
当Loader
异步获取完数据,回调类的onLoadFinished()
方法会被调用。在这里,你可以更新你的用户接口。
7.3 SQLite 数据库和CursorLoader
Android提供了一个默认Loader
实现CursorLoader
来管理SQLite数据库连接。
对于基于SQLite数据库的ContentProvider,一般需要使用CursorLoader
类。这个类在后台线程执行数据库查询,这样就不会阻塞应用程序。CursorLoader
用来替换已经废弃的使用acivity自己维护cursors的方法。
如果Cursor
失效,回调类的onLoaderReset()
会被调用。
8.Cursor 和 Loaders
访问数据库的问题之一就是访问速度太慢。另一个问题就是应用得正确考虑组件的生命周期,例如当配置改变的时候应该关闭和打开数据库。
为了维护组件的生命周期,在Android3.0之前可以使用managedQuery()
。
但是这个方法在Android3.0之后被弃用了,你应该使用Loader
框架来访问ContentProvider
。SimpleCursorAdapter
可以和ListViews
一起使用,它由swapCursor()
方法。你的Loader可以使用这个方法在onLoadFinished()
方法里面更新Cursor
。CursorLoader
在配置变化之后会重新连接Cursor
。