第九个项目——新闻列表2

实验目的

  • 掌握SQLite存储关系型数据的方法;

  • 掌握SQLiteOpenHelperSQLiteDatabase等类的常用方法;

  • 掌握Cursor进行数据集遍历的方法;

  • 掌握CursorAdapter适配器类的使用方法;

实验要求

  • 使用SQLiteOpenHelper进行数据库创建及管理;

  • 使用SQLiteDatabase进行数据库操作;

  • 使用Cursor、CursorAdapter**进行数据查询及展示;

实验内容

步骤一,打开Code07项目

使用Android Studio打开Code07项目。

本次项目的目的是将MainActivityListView的数据源转换为从SQLite数据库中读取。

步骤二,创建News的合约类——NewsContract

新建名为NewsContract类,该类包含了News表的所有原数据信息,代码如下所示。

NewsContract类的构造方法访问符设为private,避免其他开发人员实例化该类。 在NesContract类中定义了名为NewsEntry的内部类,该类实现了BaseColumns接口,主要作用是为NewsEntry类新增一个名为_ID的字段名。

public final class NewsContract {
    private NewsContract() {}

    public static class NewsEntry implements BaseColumns {
        public static final String TABLE_NAME = "tbl_news";
        public static final String COLUMN_NAME_TITLE = "title";
        public static final String COLUMN_NAME_AUTHOR = "author";
        public static final String COLUMN_NAME_CONTENT = "content";
        public static final String COLUMN_NAME_IMAGE = "image";
    }
}

步骤三,自定义SQLiteOpenHelper子类

1. 自定义SQLiteOpenHelper子类

定义名为MySQLiteOpenHelper类,该类继承于SQLiteOpenHelper,主要用于对数据库的创建及版本管理。 在该类中首先定义两个字符串常量表示News数据库表的创建和删除SQL语句,如代码\ref{lst:code07_sql_statement}所示。

MySQLiteOpenHelper类中还定义了两个常量DATABASE_VERSIONDATABASE_NAME分别表示数据库的版本及数据库名。

private static final String SQL_CREATE_ENTRIES =
    "CREATE TABLE " + NewsContract.NewsEntry.TABLE_NAME + " (" +
    NewsContract.NewsEntry._ID + " INTEGER PRIMARY KEY, " +
    NewsContract.NewsEntry.COLUMN_NAME_TITLE + " VARCHAR(200), " +
    NewsContract.NewsEntry.COLUMN_NAME_AUTHOR + " VARCHAR(100), " +
    NewsContract.NewsEntry.COLUMN_NAME_CONTENT + " TEXT, " +
    NewsContract.NewsEntry.COLUMN_NAME_IMAGE + " VARCHAR(100) " +
    ")" ;

private static final String SQL_DELETE_ENTRIES =
    "DROP TABLE IF EXISTS " + NewsContract.NewsEntry.TABLE_NAME;

public static final int DATABASE_VERSION = 1;
public static final String DATABASE_NAME = "news.db";

private Context mContext;

2. 定义构造函数及重载onCreate()方法

SQLiteOpenHelper基类构造函数接收四个参数:

  • Context context,上下文对象;

  • String name,数据库名;

  • SQLiteDatabase.CursorFactory factory,创建Cursor的工厂类,默认传递null即可;

  • int version,数据库版本号;

如果数据库结构发生了变化,则递增version即可。

onCreate()方法在数据库文件不存在时将被执行,通常只执行一次,因此在该方法中应该完成数据库表的创建以及数据初始化的操作,代码如下所示。

public class MyDbOpenHelper extends SQLiteOpenHelper {
    ...
    public MyDbOpenHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        mContext = context;
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(SQL_CREATE_ENTRIES);
        initDb(sqLiteDatabase);
    }

3. 构造数据库初始数据

在实际开发中,数据库的初始数据可能通过网络进行获取或者通过第三方途径获取。 在本项目中为了简便起见,将原来从string.xml构造的数据放入数据库中。 把初始化数据的功能代码放在initDb()方法中。

initDb()方法的参数是SQLiteDatabase对象,在调用该方法时确保SQLiteDatabase对象已经被初始化。 常见的做法是将initDb()方法放入MySQLiteOpenHelper类的onCreate()方法中。

private void initDb(SQLiteDatabase sqLiteDatabase) {
    Resources resources = mContext.getResources();
    String[] titles = resources.getStringArray(R.array.titles);
    String[] authors = resources.getStringArray(R.array.authors);
    String[] contents = resources.getStringArray(R.array.contents);
    TypedArray images = resources.obtainTypedArray(R.array.images);

    int length = 0;
    length = Math.min(titles.length, authors.length);
    length = Math.min(length, contents.length);
    length = Math.min(length, images.length());

    for (int i = 0; i < length; i++) {
        ContentValues values = new ContentValues();
        values.put(NewsContract.NewsEntry.COLUMN_NAME_TITLE,
                        titles[i]);
        values.put(NewsContract.NewsEntry.COLUMN_NAME_AUTHOR, 
                        authors[i]);
        values.put(NewsContract.NewsEntry.COLUMN_NAME_CONTENT,
                        contents[i]);
        values.put(NewsContract.NewsEntry.COLUMN_NAME_IMAGE, 
                        images.getString(i));

        long r = sqLiteDatabase.insert(
                        NewsContract.NewsEntry.TABLE_NAME,
                        null,
                        values);
    }
}

4. 重载onUpgrade()方法

由于数据库文件存储于App的私有目录内,当数据库结构发生变化时,通常意味着该数据库中的表结构需要进行调整。正确的做法是将DATABASE_VERSION值递增,并重载onUpdate()方法,在该方法中实现调整数据库表结构的逻辑。

在本项目中进将数据库表进行删除,并重新调用onCreate()方法创建数据库表。

public class MyDbOpenHelper extends SQLiteOpenHelper {
    ...
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, 
                           int oldVersion, int newVersion) {
        sqLiteDatabase.execSQL(SQL_DELETE_ENTRIES);
        onCreate(sqLiteDatabase);
    }
}

5. 读取数据库数据

通过SQLiteOpenHelper类的getReadableDatabase()方法获得SQLiteDatabase对象,并通过该对象执行query()方法查询News表中的数据。

通常数据库的查询操作比较耗时,需要结合Loader一起使用,在本项目中为了简便起见,直接在MainActivityonCreate()方法中完成查询操作。

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    myDbHelper = new MyDbOpenHelper(MainActivity.this);
    db = myDbHelper.getReadableDatabase();
    Cursor cursor1 = db.query(
            NewsContract.NewsEntry.TABLE_NAME,
            null, null, null, null, null, null);
    ...

获得Cursor对象后,可以通过循环,借助于moveToNext()getString()等方法读取每一条记录的字段值。 getString()等方法需要的参数是列编号,因此需要使用getColumnIndex()方法将列名转换为Cursor对象指向结果集中的列编号。

    ...

    List<News> newsList = new ArrayList<>();

    int titleIndex = cursor.getColumnIndex(
            NewsContract.NewsEntry.COLUMN_NAME_TITLE);
    int authorIndex = cursor.getColumnIndex(
            NewsContract.NewsEntry.COLUMN_NAME_AUTHOR);
    int imageIndex = cursor.getColumnIndex(
            NewsContract.NewsEntry.COLUMN_NAME_IMAGE);

    while (cursor.moveToNext()) {
        News news = new News();

        String title = cursor.getString(titleIndex);
        String author = cursor.getString(authorIndex);
        String image = cursor.getString(imageIndex);

        Bitmap bitmap = BitmapFactory.decodeStream(
            getClass().getResourceAsStream("/" + image));

        news.setTitle(title);
        news.setAuthor(author);
        news.setImage(bitmap);
        list.add(news);
    }

    NewsAdapter newsAdapter = new NewsAdapter(
            MainActivity.this,
            R.layout.list_item,
            newsList);
    lvNewsList.setAdapter(newsAdapter);

步骤四,编译、部署APK

编译并部署Code07项目的APK,运行效果应与之前的相同,但所有数据均存储与SQLite数据库中。

实验小结

通过本次实验,你应该掌握了如下知识内容:

  • 使用SQLiteOpenHelper对数据库进行管理;

  • 使用SQLiteDatabase进行数据操作;

  • 使用Cursor进行数据集遍历;

Last updated

Was this helpful?