相信在Android开发中,很多APP都会用到数据库来保存数据,今天我们来讨论一下它其中用法,用数据库实现离线浏览。数据库类型很多,Android默认是使用SQLite。
首先我们来看一下界面
很简单,经典的图文混排。
要想实现APP离线浏览,我们需要:
- 创建一个类继承SQLiteOpenHelper(该类帮我们封装好了很多数据库操作)
- 将功能抽象出来
- 创建数据库管理类
- 使用我们创建的数据库来实现离线浏览。
首先我们根据模型类来创建SQLiteOpenHelper
模型类
public class News {
private int id;
private String title;
private String postdate;
private String editor;
private String icon;
public News(int id, String title, String postdate, String editor, String icon) {
this.id = id;
this.title = title;
this.postdate = postdate;
this.editor = editor;
this.icon = icon;
}
// getter and setter ...
}
public class NewsSQLiteOpenHelper extends SQLiteOpenHelper {
// 数据库名
private static final String NAME = "learn.db";
// 数据库版本
private static final int VERSION = 1;
// 建表语句
private static final String CREATE_NEWS = "create table if not exists news(" +
"_id integer primary key," +
"title text," +
// 这个时间字段只是单纯的为了后面查询时好排序
"time integer," +
"icon text," +
"editor text," +
"postdate text)";
public NewsSQLiteOpenHelper(Context context) {
super(context, NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_NEWS);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
这里主要要实现的方法是onCreate,在里面完成我们的初始化建表工作,构造函数需要传入的四个参数分别为:上下文、数据库名、游标工厂(默认为null)、版本号。onUpgrade方法 只会在数据库更新才会调用。这里我们不会用到。
将功能抽象出来
public interface INewsDB {
List<News> getAllNews();
void deleteAllNews();
News getNewsById(int id);
void insertAllNews(List<News> newsList);
void insertNews(News news);
}
这里首先说一下为什么要创建这么一个接口,很简单,解耦合,以后维护修改的时候方便。抽象出来的这些功能都是我们后面将用到的,都是基本的增删改查操作。
创建数据库管理类(重点)
public class NewsDBManager implements INewsDB {
private static NewsDBManager sManager;
private NewsSQLiteOpenHelper mHelper;
private NewsDBManager(Context context) {
mHelper = new NewsSQLiteOpenHelper(context);
}
public static NewsDBManager getInstance(Context context) {
if (sManager == null) {
synchronized (NewsDBManager.class) {
if (sManager == null) {
sManager = new NewsDBManager(context);
}
}
}
return sManager;
}
/**
* 查询所有新闻
*
* @return
*/
@Override
public List<News> getAllNews() {
SQLiteDatabase db = mHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("select * from news order by time ASC", null);
List<News> newsList = new ArrayList<>();
while (cursor.moveToNext()) {
int id = cursor.getInt(cursor.getColumnIndex("_id"));
String title = cursor.getString(cursor.getColumnIndex("title"));
String icon = cursor.getString(cursor.getColumnIndex("icon"));
String editor = cursor.getString(cursor.getColumnIndex("editor"));
String postdate = cursor.getString(cursor.getColumnIndex("postdate"));
News news = new News(id, title, postdate, editor, icon);
newsList.add(news);
}
mHelper.close();
return newsList;
}
/**
* 删除所有新闻
*/
@Override
public void deleteAllNews() {
SQLiteDatabase db = mHelper.getWritableDatabase();
db.execSQL("delete from news");
mHelper.close();
}
/**
* 通过给定的id获取新闻
*
* @param id
* @return
*/
@Override
public News getNewsById(int id) {
SQLiteDatabase db = mHelper.getReadableDatabase();
Cursor cursor = db.rawQuery("select * from news where _id = ?", new String[]{String.valueOf(id)});
cursor.moveToNext();
String title = cursor.getString(cursor.getColumnIndex("title"));
String icon = cursor.getString(cursor.getColumnIndex("icon"));
String editor = cursor.getString(cursor.getColumnIndex("editor"));
String postdate = cursor.getString(cursor.getColumnIndex("postdate"));
News news = new News(id, title, postdate, editor, icon);
mHelper.close();
return news;
}
@Override
public void insertAllNews(List<News> newsList) {
for (News news : newsList) {
insertNews(news);
}
}
@Override
public void insertNews(News news) {
SQLiteDatabase db = mHelper.getWritableDatabase();
db.execSQL("insert into news(_id,title,time,icon,editor,postdate) values(?,?,?,?,?,?)", new Object[]{news.getId(), news.getTitle(), System.currentTimeMillis(), news.getIcon(), news.getEditor(), news.getPostdate()});
}
}
解析一下代码:这里我们创建一个News数据库管理类,专门进行数据库的增删改查操作,实现INewsDB抽象的功能。增删改查的代码就不多解释了,这里注意一下delete操作,Android的delete有一个蜜汁bug,本来数据库的删除操作应该是delete * from table,但是这个 * 无法通过编译,所以只能delete from table。
实际运用
好了,终于到最后一步了,有木有很激动。
private void initView() {
mNewsRv = ((RecyclerView) findViewById(R.id.rv_news));
mLoading = findViewById(R.id.pb_loading);
LinearLayoutManager layout = new LinearLayoutManager(this);
mNewsRv.setLayoutManager(layout);
mAdapter = new NewsAdapter(this, null);
mNewsRv.setAdapter(mAdapter);
mLoading.setVisibility(View.VISIBLE);
// 加载数据的时候先检测网络
if (NetworkUtils.checkNetworkState(this)) {
Log.d(TAG, "initView: 从网络获取数据");
getDataFromInternet();
} else {
Log.d(TAG, "initView: 从数据库获取数据");
getDataFromDB();
}
}
private void getDataFromInternet() {
HttpUtils.getAsynString(Urls.URL_NEWS_ITEM_SOFTWARE, new HttpUtils.ResultCallback() {
@Override
public void onFailure(Exception e) {
Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onFinish(String result) {
Gson gson = new Gson();
List<News> data = gson.fromJson(result, new TypeToken<List<News>>() {
}.getType());
mAdapter.update(data);
NewsDBManager manager = NewsDBManager.getInstance(MainActivity.this);
// 删除旧数据,保存最新数据
manager.deleteAllNews();
manager.insertAllNews(data);
mLoading.setVisibility(View.GONE);
}
});
}
private void getDataFromDB() {
NewsDBManager manager = NewsDBManager.getInstance(this);
List<News> data = manager.getAllNews();
if (data.size() > 0) {
mAdapter.update(data);
}
mLoading.setVisibility(View.GONE);
}
最后再来看看调试结果
根据log可知,没网时,最后走了getDataFromDB(),界面数据也与有网时一样正常显示出来。