3.数据库_内容提供者

来源:互联网 发布:method(i,2))用法Java 编辑:程序博客网 时间:2024/04/30 10:40
数据库

SqliteOpenHelper

要创建数据库, 首先要写个类, 继承 SqliteOpenHelper, 实现其中两个抽象方法:
  1. public class MyHelper extends SQLiteOpenHelper {
  2. // 由于父类没有无参构造函数, 所以子类必须指定调用父类哪个有参的构造函数
  3. public MyHelper(Context context) {
  4. // context 上下文
  5. // name 数据库的名称
  6. // factory 数据库查询结果的游标工厂
  7. // version 数据库的版本 >=1
  8. super(context, "itheima.db", null, 3);
  9. }
  10. /**
  11. * 数据库在 <b>第一次</b> 创建的时候调用的方法 适合做数据库表结构的初始化9
  12. * 另外注意, 创建表的时候, 最好指定一个自增长 _ID 作为主键
  13. */
  14. @Override
  15. public void onCreate(SQLiteDatabase db) {
  16. System.out.println("onCreate");
  17. db.execSQL("CREATE TABLE account(_ID INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20))");
  18. }

  1. /**
  2. 当数据库版本改变时调用
  3. 1.数据库不存在: 创建数据库, 执行onCreate()
  4. 2.数据库存在:
  5. a.版本号没变: 什么都不做
  6. b.版本号提升: onUpgrade()
  7. c.版本号降低: onDowngrade(), 但是一般会报错?!
  8. */
  9. @Override
  10. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  11. System.out.println("onUpgrade");
  12. db.execSQL("ALTER TABLE account ADD balance INTEGER");
  13. }
  14. }
  15. SQ

LiteDatabase
得到 SQLiteDataBase:
    - helper.getReadableDatabase()
    - helper.getWritableDatabase()
ReadableDatabase 和 WritableDatabase : 其实都可以用, 但是该咋用就咋用吧, 文档中中说, 一般都会返回可写数据库, 除非磁盘写满或其他特殊情况, 才会返回只读的数据库.
 

经典版CRUD

sql语句最好先在外面的工具中写好, 然后在往代码里写
  1. {{{class="brush:java"
  2. public class AccountDao {
  3. private MyHelper helper;
  4. public AccountDao(Context context) {
  5. helper = new MyHelper(context); // 创建Dao时, 创建Helper
  6. }
  7. public void insert(Account account) {
  8. SQLiteDatabase db = helper.getWritableDatabase(); // 获取数据库对象
  9. db.execSQL("INSERT INTO account(name, balance) VALUES(?, ?)",
  10. new Object[] { account.getName(), account.getBalance() }); // 执行插入操作
  11. db.close(); // 关闭数据库
  12. }
  13. public void delete(int id) {
  14. SQLiteDatabase db = helper.getWritableDatabase();
  15. db.execSQL("DELETE FROM account WHERE _id=?", new Object[] { id });
  16. db.close();
  17. }
  18. public void update(Account account) {
  19. SQLiteDatabase db = helper.getWritableDatabase();
  20. db.execSQL(
  21. "UPDATE account SET name=?, balance=? WHERE _id=?",
  22. new Object[] { account.getName(), account.getBalance(),
  23. account.getId() });
  24. db.close();
  25. }
  26. public Account query(int id) {
  27. SQLiteDatabase db = helper.getReadableDatabase(); // 获取数据库对象
  28. Cursor c = db.rawQuery("SELECT name, balance FROM account WHERE _id=?",
  29. new String[] { id + "" }); // 执行查询操作, 得到Cursor对象
  30. Account a = null;
  31. if (c.moveToNext()) { // 从Cursor中获取数据, 封装成Account对象
  32. String name = c.getString(0);
  33. int balance = c.getInt(1);
  34. a = new Account(id, name, balance);
  35. }
  36. c.close(); // 关闭结果集
  37. db.close(); // 关闭数据库
  38. return a; // 返回对象
  39. }
  40. public List<Account> queryAll() {
  41. SQLiteDatabase db = helper.getReadableDatabase();
  42. Cursor c = db.rawQuery("SELECT _id, name, balance FROM account", null);
  43. List<Account> list = new ArrayList<Account>();
  44. while (c.moveToNext()) {
  45. int id = c.getInt(c.getColumnIndex("_id")); // 可以根据列名获取索引
  46. String name = c.getString(1);
  47. int balance = c.getInt(2);
  48. list.add(new Account(id, name, balance));
  49. }
  50. c.close();
  51. db.close();
  52. return list;
  53. }
  54. public List<Account> queryPage(int pageNum, int pageSize) {
  55. SQLiteDatabase db = helper.getReadableDatabase();
  56. // sqlite 的分页和mysql一样
  57. Cursor c = db.rawQuery(
  58. "SELECT _id, name, balance FROM account LIMIT ?,?",
  59. new String[] { (pageNum - 1) * pageSize + "", pageSize + "" });
  60. List<Account> list = new ArrayList<Account>();
  61. while (c.moveToNext()) {
  62. int id = c.getInt(c.getColumnIndex("_id")); // 可以根据列名获取索引
  63. String name = c.getString(1);
  64. int balance = c.getInt(2);
  65. list.add(new Account(id, name, balance));
  66. }
  67. c.close();
  68. db.close();
  69. return list;
  70. }
  71. }


系统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 类型
 
  1. == 事务 ==
  2. - db.beginTransaction : 开始事务
  3. - db.setTransactionSuccessful : 设置成功标记
  4. - db.endTransaction : 结束事务, 提交最后一个成功标记之前的操作
  5. {{{class="brush:java"
  6. public void remit(int fromId, int toId, int amount) {
  7. SQLiteDatabase db = helper.getWritableDatabase();
  8. try {
  9. db.beginTransaction(); // 开启事务
  10. db.execSQL("UPDATE account SET balance=balance-? WHERE _id=?",
  11. new Object[] { amount, fromId });
  12. db.execSQL("UPDATE account SET balance=balance+? WHERE _id=?",
  13. new Object[] { amount, toId });
  14. db.setTransactionSuccessful(); // 设置事务成功
  15. } finally {
  16. db.endTransaction(); // 结束事务, 会提交最后一个成功标记之前的代码
  17. db.close();
  18. }
  19. }
  20. }}}

 

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"
   
  1. private class MyAdapter extends BaseAdapter {
  2. /**
  3. * 返回整个数据适配器列表中(或者说ListView中)有多少个条目
  4. */
  5. @Override
  6. public int getCount() {
  7. return persons.size();
  8. }
  9. /**
  10. * 返回某个位置要显示的view对象
  11. * - position: 当前条目的位置, 从0开始
  12. * - convertView: ListView在拖动时,"被拖出去的View", 第一次进入界面时为null
  13. * - parent: 就是ListView
  14. */
  15. @Override
  16. public View getView(int position, View convertView, ViewGroup parent) {
  17. /* 根据layout目录下 list_item的布局为模板 创建一个view对象.
  18. * 第三个参数表示要将创建的 View 对象挂在哪个对象上, 由于我们这里
  19. * 要将创建的 View对象返回出去, 所以这里写 null ,其实写 parent也行
  20. * 只是 ListView 会自动帮我们将返回的 View 挂在自己身上.
  21. */
  22. View view = View.inflate(getApplicationContext(), R.layout.list_item, null);
  23. TextView tv_name = (TextView) view.findViewById(R.id.tv_name);
  24. TextView tv_id = (TextView) view.findViewById(R.id.tv_id);
  25. TextView tv_phone = (TextView) view.findViewById(R.id.tv_phone);
  26. Person person = persons.get(position);
  27. tv_id.setText("联系人id:"+person.getId());
  28. tv_name.setText(person.getName());
  29. tv_phone.setText(person.getPhone());
  30. return view;
  31. }
  32. // 获取指定位置上的View对象, 在ListView的 onItemClick 事件中, 会被监听器调用
  33. @Override
  34. public Object getItem(int position) {
  35. return null;
  36. }
  37. // 没什么用
  38. @Override
  39. public long getItemId(int position) {
  40. return 0;
  41. }
  42. }
  43. }}}


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.
 
  1. public class AccountProvider extends ContentProvider {
  2. // http://www.baidu.com/news/add
  3. // content://com.gaoyuan.sqlitedemo.provider.AccountProvider/account/insert
  4. // 定义一个URI匹配器, 用于匹配URI, 如果没有匹配的路径, 返回UriMatcher.NO_MATCH
  5. private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
  6. private static final int ACCOUNT = 1;
  7. private static final int PERSON = 2;
  8. private MyHelper helper;
  9. static {
  10. matcher.addURI("com.gaoyuan.sqlitedemo.provider.AccountProvider", "account",
  11. ACCOUNT);
  12. matcher.addURI("com.gaoyuan.sqlitedemo.provider.AccountProvider", "person",
  13. PERSON);
  14. }
  15. @Override
  16. public boolean onCreate() {
  17. helper = new MyHelper(getContext());
  18. return false;
  19. }
  20. @Override
  21. public Cursor query(Uri uri, String[] projection, String selection,
  22. String[] selectionArgs, String sortOrder) {
  23. switch (matcher.match(uri)) {
  24. case ACCOUNT:
  25. SQLiteDatabase db = helper.getReadableDatabase();
  26. Cursor cursor = db.query("account", projection, selection, selectionArgs,
  27. null, null, sortOrder);
  28. // 注意查询操作结束要一定不要关闭数据库, 否则 cursor 就无法获取数据了.
  29. // 系统会在cursor销毁时自动关闭db
  30. return cursor;
  31. case PERSON:
  32. System.out.println("尚未创建person表");
  33. break;
  34. default:
  35. throw new IllegalArgumentException("URI非法");
  36. }
  37. return null;
  38. }
  39. @Override
  40. public Uri insert(Uri uri, ContentValues values) {
  41. switch (matcher.match(uri)) {
  42. case ACCOUNT:
  43. SQLiteDatabase db = helper.getWritableDatabase();
  44. long id = db.insert("account", null, values);
  45. db.close();
  46. return ContentUris.withAppendedId(uri, id);
  47. case PERSON:
  48. System.out.println("尚未创建person表");
  49. break;
  50. default:
  51. throw new IllegalArgumentException("URI非法");
  52. }
  53. return null;
  54. }
  55. @Override
  56. public int delete(Uri uri, String selection, String[] selectionArgs) {
  57. switch (matcher.match(uri)) {
  58. case ACCOUNT:
  59. SQLiteDatabase db = helper.getReadableDatabase();
  60. int delete = db.delete("account", selection, selectionArgs);
  61. db.close();
  62. return delete;
  63. case PERSON:
  64. System.out.println("尚未创建person表");
  65. break;
  66. default:
  67. throw new IllegalArgumentException("URI非法");
  68. }
  69. return 0;
  70. }
  71. @Override
  72. public int update(Uri uri, ContentValues values, String selection,
  73. String[] selectionArgs) {
  74. switch (matcher.match(uri)) {
  75. case ACCOUNT:
  76. SQLiteDatabase db = helper.getWritableDatabase();
  77. int update = db.update("account", values, selection, selectionArgs);
  78. db.close();
  79. return update;
  80. case PERSON:
  81. System.out.println("尚未创建person表");
  82. break;
  83. default:
  84. throw new IllegalArgumentException("URI非法");
  85. }
  86. return 0;
  87. }
  88. @Override
  89. public String getType(Uri uri) {
  90. return null;
  91. }
  92. }

 
注意, 在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 的数据.
 
 
 
-----------
自增长的列也可以自己插入吗
-----------

 
内容观察者
 
 


来自为知笔记(Wiz)


0 0
原创粉丝点击