数据库
SqliteOpenHelper
要创建数据库, 首先要写个类, 继承 SqliteOpenHelper, 实现其中两个抽象方法:
public class MyHelper extends SQLiteOpenHelper {
// 由于父类没有无参构造函数, 所以子类必须指定调用父类哪个有参的构造函数
public MyHelper(Context context) {
// context 上下文
// name 数据库的名称
// factory 数据库查询结果的游标工厂
// version 数据库的版本 >=1
super(context, "itheima.db", null, 3);
}
/**
* 数据库在 <b>第一次</b> 创建的时候调用的方法 适合做数据库表结构的初始化9
* 另外注意, 创建表的时候, 最好指定一个自增长 _ID 作为主键
*/
@Override
public void onCreate(SQLiteDatabase db) {
System.out.println("onCreate");
db.execSQL("CREATE TABLE account(_ID INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20))");
}
/**
当数据库版本改变时调用
1.数据库不存在: 创建数据库, 执行onCreate()
2.数据库存在:
a.版本号没变: 什么都不做
b.版本号提升: onUpgrade()
c.版本号降低: onDowngrade(), 但是一般会报错?!
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
System.out.println("onUpgrade");
db.execSQL("ALTER TABLE account ADD balance INTEGER");
}
}
SQ
LiteDatabase
得到 SQLiteDataBase:
- helper.getReadableDatabase()
- helper.getWritableDatabase()
ReadableDatabase 和 WritableDatabase : 其实都可以用, 但是该咋用就咋用吧, 文档中中说, 一般都会返回可写数据库, 除非磁盘写满或其他特殊情况, 才会返回只读的数据库.
经典版CRUD
sql语句最好先在外面的工具中写好, 然后在往代码里写
{{{class="brush:java"
public class AccountDao {
private MyHelper helper;
public AccountDao(Context context) {
helper = new MyHelper(context); // 创建Dao时, 创建Helper
}
public void insert(Account account) {
SQLiteDatabase db = helper.getWritableDatabase(); // 获取数据库对象
db.execSQL("INSERT INTO account(name, balance) VALUES(?, ?)",
new Object[] { account.getName(), account.getBalance() }); // 执行插入操作
db.close(); // 关闭数据库
}
public void delete(int id) {
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL("DELETE FROM account WHERE _id=?", new Object[] { id });
db.close();
}
public void update(Account account) {
SQLiteDatabase db = helper.getWritableDatabase();
db.execSQL(
"UPDATE account SET name=?, balance=? WHERE _id=?",
new Object[] { account.getName(), account.getBalance(),
account.getId() });
db.close();
}
public Account query(int id) {
SQLiteDatabase db = helper.getReadableDatabase(); // 获取数据库对象
Cursor c = db.rawQuery("SELECT name, balance FROM account WHERE _id=?",
new String[] { id + "" }); // 执行查询操作, 得到Cursor对象
Account a = null;
if (c.moveToNext()) { // 从Cursor中获取数据, 封装成Account对象
String name = c.getString(0);
int balance = c.getInt(1);
a = new Account(id, name, balance);
}
c.close(); // 关闭结果集
db.close(); // 关闭数据库
return a; // 返回对象
}
public List<Account> queryAll() {
SQLiteDatabase db = helper.getReadableDatabase();
Cursor c = db.rawQuery("SELECT _id, name, balance FROM account", null);
List<Account> list = new ArrayList<Account>();
while (c.moveToNext()) {
int id = c.getInt(c.getColumnIndex("_id")); // 可以根据列名获取索引
String name = c.getString(1);
int balance = c.getInt(2);
list.add(new Account(id, name, balance));
}
c.close();
db.close();
return list;
}
public List<Account> queryPage(int pageNum, int pageSize) {
SQLiteDatabase db = helper.getReadableDatabase();
// sqlite 的分页和mysql一样
Cursor c = db.rawQuery(
"SELECT _id, name, balance FROM account LIMIT ?,?",
new String[] { (pageNum - 1) * pageSize + "", pageSize + "" });
List<Account> list = new ArrayList<Account>();
while (c.moveToNext()) {
int id = c.getInt(c.getColumnIndex("_id")); // 可以根据列名获取索引
String name = c.getString(1);
int balance = c.getInt(2);
list.add(new Account(id, name, balance));
}
c.close();
db.close();
return list;
}
}
系统API版CRUD
一下的方法都是SQLiteDatabase对象的:
- insert(tablename, null, ContentValues); 返回自增长id
ContentValues 是个 map, 里面存放的键值对就是列名和值
- delete(tablename, "_id=?", new String[]{"xx"}); 返回删除行数
- update(tablename, ContentValues, "_id=?", new String[]{"xx"}); 返回更新行数
- query(tablename, 列名数组, 条件语句, 参数字符串数组, group by, having, order by); 返回Cursor
后面讲 ContentProvider 时会给出详细的代码
关于 insert 方法返回的 id
insert 方法返回的是一个自增长的, long 类型的id, 这个id和我们创建表时指定的 _ID 没关系.
但是, 这个id是自增长的, 而我们一般情况下定义的主键 _ID 也是自增长的, 二者的值相同,
所以二者可以混用.
另外, 对于我们指定的 INTEGER 类型的 自增长 _ID, 也是可以存放 long 类型的.
但是javabean就不行. 所以在创建 javabean 时, 最好对应的属性也制定为 long 类型
== 事务 ==
- db.beginTransaction : 开始事务
- db.setTransactionSuccessful : 设置成功标记
- db.endTransaction : 结束事务, 提交最后一个成功标记之前的操作
{{{class="brush:java"
public void remit(int fromId, int toId, int amount) {
SQLiteDatabase db = helper.getWritableDatabase();
try {
db.beginTransaction(); // 开启事务
db.execSQL("UPDATE account SET balance=balance-? WHERE _id=?",
new Object[] { amount, fromId });
db.execSQL("UPDATE account SET balance=balance+? WHERE _id=?",
new Object[] { amount, toId });
db.setTransactionSuccessful(); // 设置事务成功
} finally {
db.endTransaction(); // 结束事务, 会提交最后一个成功标记之前的代码
db.close();
}
}
}}}
Sqlite3工具
adb -s xxx shell (sqlite需要在shell环境下运行)
cd data/data/xx.xx.xx
sqlite3 xx.db
.exit
ListView
是个符合MVC结构的控件. 要使用 ListView, 需要三个组件.
- 1. 在布局文件中定义一个 ListView 控件 (View), 一般为了方便,
会再写一个布局文件定义 ListView 中每个View的布局.
- 2. 准备需要展示在 ListView 中的数据. (Model)
- 3. 在 Activity 中为 ListView 控件设置适配器, 将数据填充到 ListView中.
适配器可以自己写一个或者使用系统提供的
{{{class="brush:java"
private class MyAdapter extends BaseAdapter {
/**
* 返回整个数据适配器列表中(或者说ListView中)有多少个条目
*/
@Override
public int getCount() {
return persons.size();
}
/**
* 返回某个位置要显示的view对象
* - position: 当前条目的位置, 从0开始
* - convertView: ListView在拖动时,"被拖出去的View", 第一次进入界面时为null
* - parent: 就是ListView
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
/* 根据layout目录下 list_item的布局为模板 创建一个view对象.
* 第三个参数表示要将创建的 View 对象挂在哪个对象上, 由于我们这里
* 要将创建的 View对象返回出去, 所以这里写 null ,其实写 parent也行
* 只是 ListView 会自动帮我们将返回的 View 挂在自己身上.
*/
View view = View.inflate(getApplicationContext(), R.layout.list_item, null);
TextView tv_name = (TextView) view.findViewById(R.id.tv_name);
TextView tv_id = (TextView) view.findViewById(R.id.tv_id);
TextView tv_phone = (TextView) view.findViewById(R.id.tv_phone);
Person person = persons.get(position);
tv_id.setText("联系人id:"+person.getId());
tv_name.setText(person.getName());
tv_phone.setText(person.getPhone());
return view;
}
// 获取指定位置上的View对象, 在ListView的 onItemClick 事件中, 会被监听器调用
@Override
public Object getItem(int position) {
return null;
}
// 没什么用
@Override
public long getItemId(int position) {
return 0;
}
}
}}}
listview 的 item 布局最外层指定高度无效, 要设置 minHeight.
重用convertView
当条目过多, 拖动地很快时, 程序异常终止, 这是因为被拖出屏幕的View对象会被
系统回收掉, 当拖动的太快时, 系统来不及回收, 就会产生问题. 解决方法是:
重用 convertView, 在 getView 方法中, 这个参数表示被拖出去的控件对象, 重用这个对象
就可以避免垃圾回收慢的问题.
当ListView第一次展示时, convertView对象为空, 因为此时并没有控件被拖出去.
{{{class="brush:java"
View view = convertView != null ? convertView
: View.inflate(MainActivity.this, R.layout.item, null);
}}}
ListView点击事件写法
- 在 ListView 上绑定 onItemClickListener, 推荐使用
- 创建 View 时, 在 View 上绑定
lv.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Account a = list.get(position);
Toast.makeText(getApplicationContext(), a.toString(), Toast.LENGTH_SHORT).show();
}
});
常见Adapter
ArrayAdapter
SimpleAdapter
CursorAdapter
SimpleCursorAdapter
ContentProvider
一个应用想要把自己的数据库提供给别的应用, 需要通过内容提供者ContentProvider.
步骤如下:
- 1. 写一个类, 继承 ContentProvider
- 2. 在清单文件中配置.
开发的时候注意: *改完 CP 要记得重新发布一次.*
Note: A provider isn't required to have a primary key, and it isn't required to use _ID as the column name of a primary key if one is present. However, if you want to bind data from a provider to a ListView, one of the column names has to be _ID. This requirement is explained in more detail in the section Displaying query results.
public class AccountProvider extends ContentProvider {
// http://www.baidu.com/news/add
// content://com.gaoyuan.sqlitedemo.provider.AccountProvider/account/insert
// 定义一个URI匹配器, 用于匹配URI, 如果没有匹配的路径, 返回UriMatcher.NO_MATCH
private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final int ACCOUNT = 1;
private static final int PERSON = 2;
private MyHelper helper;
static {
matcher.addURI("com.gaoyuan.sqlitedemo.provider.AccountProvider", "account",
ACCOUNT);
matcher.addURI("com.gaoyuan.sqlitedemo.provider.AccountProvider", "person",
PERSON);
}
@Override
public boolean onCreate() {
helper = new MyHelper(getContext());
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
switch (matcher.match(uri)) {
case ACCOUNT:
SQLiteDatabase db = helper.getReadableDatabase();
Cursor cursor = db.query("account", projection, selection, selectionArgs,
null, null, sortOrder);
// 注意查询操作结束要一定不要关闭数据库, 否则 cursor 就无法获取数据了.
// 系统会在cursor销毁时自动关闭db
return cursor;
case PERSON:
System.out.println("尚未创建person表");
break;
default:
throw new IllegalArgumentException("URI非法");
}
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
switch (matcher.match(uri)) {
case ACCOUNT:
SQLiteDatabase db = helper.getWritableDatabase();
long id = db.insert("account", null, values);
db.close();
return ContentUris.withAppendedId(uri, id);
case PERSON:
System.out.println("尚未创建person表");
break;
default:
throw new IllegalArgumentException("URI非法");
}
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
switch (matcher.match(uri)) {
case ACCOUNT:
SQLiteDatabase db = helper.getReadableDatabase();
int delete = db.delete("account", selection, selectionArgs);
db.close();
return delete;
case PERSON:
System.out.println("尚未创建person表");
break;
default:
throw new IllegalArgumentException("URI非法");
}
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
switch (matcher.match(uri)) {
case ACCOUNT:
SQLiteDatabase db = helper.getWritableDatabase();
int update = db.update("account", values, selection, selectionArgs);
db.close();
return update;
case PERSON:
System.out.println("尚未创建person表");
break;
default:
throw new IllegalArgumentException("URI非法");
}
return 0;
}
@Override
public String getType(Uri uri) {
return null;
}
}
注意,
在4.2 以后,
清单文件里配置 ContentProvider 时
需要加一个属性:
android:exported="true"
ContentResolver
在一个应用中要使用别的应用的ContentProvider, 需要通过ContentResolver
如果调用一个应用的ContentProvider, ContentProvider所在程序还没有启动, 访问时会启动它,
并调用他的 onCreate 方法, 再次访问时就不会再调用onCreate了.
{{{class="brush:java"
public class AccountDao {
private ContentResolver r;
private Uri uri = Uri
.parse("content://com.gaoyuan.sqlitedemo.provider.AccountProvider/account");
public AccountDao(Context context) {
// 首先获得一个 中间人 ContentResolver, 需要通过上下文获取
this.r = context.getContentResolver();
}
public int add(Account account) {
ContentValues values = new ContentValues();
values.put("name", account.getName());
values.put("balance", account.getBalance());
Uri adduri = r.insert(uri, values);
return (int) ContentUris.parseId(adduri);
}
public int delete(int id) {
int delete = r.delete(uri, "_id=?", new String[] { id + "" });
return delete;
}
public int update(Account account) {
ContentValues values = new ContentValues();
values.put("name", account.getName());
values.put("balance", account.getBalance());
int update = r.update(uri, values, "_id=?", new String[] { account.getId() + "" });
return update;
}
public List<Account> findAll() {
List<Account> list = new ArrayList<Account>();
Account a = null;
Cursor cursor = r.query(uri, null, null, null, null);
while (cursor.moveToNext()) {
int id1 = cursor.getInt(cursor.getColumnIndex("_id"));
String name = cursor.getString(cursor.getColumnIndex("name"));
int balance = cursor.getInt(cursor.getColumnIndex("balance"));
a = new Account(id1, name, balance);
list.add(a);
}
return list;
}
}
}}}
ContentResolver 提供的数据库操作很有限
ContentObserver
如果想知道某个数据库何时发生变化了, 可以使用ContentObserver. 这里有一个例子: 当联系人数据改变时, 获取是哪一条改变了
操作系统的短信
content://sms
date, address, type, body
操作系统的联系人
raw_contacts: 只需要关心 id
mimetypes: 数据的类型, 1表示邮箱, 7表示姓名, 5表示电话等等
data: 引用 raw_contacts 中的id 和mimetype中的id, 存放联系人的信息, 如姓名, 电话, 邮箱等等.
在使用 ContentResolver 进行查询时, 要使用 mimetype, 而不是 mimetype_id.
查到的值也不是序号, 而是String. 这是因为 ContentProvider内部帮我们做了一个表连接
raw_contact_id, mimetype, data1
保存联系人: 先往 raw_contacts 中插入一条新纪录, 获取返回值Uri, 从中解析出 _id,
使用这个_id, 往 data 表中插入对应 mimetype 的数据.
-----------
自增长的列也可以自己插入吗
-----------
内容观察者