3.数据库_内容提供者
数据库
SqliteOpenHelper
要创建数据库, 首先要写个类, 继承 SqliteOpenHelper, 实现其中两个抽象方法:
public class MyHelper extends SQLiteOpenHelper {// 由于父类没有无参构造函数, 所以子类必须指定调用父类哪个有参的构造函数public MyHelper(Context context) {// context 上下文// name 数据库的名称// factory 数据库查询结果的游标工厂// version 数据库的版本 >=1super(context, "itheima.db", null, 3);}/*** 数据库在 <b>第一次</b> 创建的时候调用的方法 适合做数据库表结构的初始化9* 另外注意, 创建表的时候, 最好指定一个自增长 _ID 作为主键*/@Overridepublic 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(), 但是一般会报错?!*/@Overridepublic 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中)有多少个条目*/@Overridepublic int getCount() {return persons.size();}/*** 返回某个位置要显示的view对象* - position: 当前条目的位置, 从0开始* - convertView: ListView在拖动时,"被拖出去的View", 第一次进入界面时为null* - parent: 就是ListView*/@Overridepublic 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 事件中, 会被监听器调用@Overridepublic Object getItem(int position) {return null;}// 没什么用@Overridepublic 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_MATCHprivate 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);}@Overridepublic boolean onCreate() {helper = new MyHelper(getContext());return false;}@Overridepublic 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销毁时自动关闭dbreturn cursor;case PERSON:System.out.println("尚未创建person表");break;default:throw new IllegalArgumentException("URI非法");}return null;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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 的数据.
-----------
自增长的列也可以自己插入吗
-----------
内容观察者
浙公网安备 33010602011771号