您的位置:首页 > 移动开发 > Android开发

Android SqlBrite使用介绍和官方demo详解

2017-03-15 15:18 531 查看

一、什么是SqlBrite

对 Android 系统的
SQLiteOpenHelper
ContentResolver
的轻量级封装,配合Rxjava使用。

github地址: https://github.com/square/sqlbrite

ps: 2017年3月15号为止,还不支持Rxjava2,有点遗憾。

二、导包和初始化

在module的builde.gradle依赖加入以下,如果你没导入Rxjava,那么他会自动导入:

compile 'com.squareup.sqlbrite:sqlbrite:1.1.1'


创建一个 SqlBrite 对象,该对象是该库的入口:

SqlBrite sqlBrite = new SqlBrite.Builder().build();


通过SQLiteOpenHelper实例和调度创建BriteDatabase,由于数据库操作是个耗时操作,不建议在 UI 线程中执行的,所以这里可以添加调度器进行线程的控制Scheduler ,一般指定 Schedulers.io() 。

//根据SQLiteOpenHelper和Scheduler来创建BriteDatabase,指定调度器为io操作
BriteDatabase db = sqlBrite.wrapDatabaseHelper(helper, Schedulers.io());


如果是使用 ContentProvider 的话,需要使用
ContentResolver
来创建一个 BriteContentResolver 对象:

BriteContentResolver resolver = sqlBrite.wrapContentProvider(contentResolver, Schedulers.io());
Observable<Query> query = resolver.createQuery(/*...*/);


三、 数据库操作

增删改查基本用法。

查询操作

BriteDatabase.createQuery方法类似于SQLiteDatabase.rawQuery,但是它需要一个附加的表参数来监听更改。

Subscribe 返回的Observable ,它将立即通知Query运行。

Observable<Query> users = db.createQuery("users", "SELECT * FROM users");
users.subscribe(new Action1<Query>() {
@Override public void call(Query query) {
Cursor cursor = query.run();
// TODO parse data...
}
});


与传统的rawQuery不同,对指定表的更新将触发其他通知,只要您保持订阅Observable,

意思就是,当您插入,更新或删除数据时,任何subscribed的查询会立即更新新的数据。

增加插入操作

插入方法调用的就是SqlDataBase的插入方法,这个没什么好说的了。

//根据todo_list表的id,插入数据到todo_item表,
db.insert(TodoItem.TABLE, null, new TodoItem.Builder()
.listId(groceryListId)
.description("Beer")
.build());


修改更新操作

有5个参数

表名

ContentValues

冲突解决方案,一般为0就行

where条件字句

where 可变参数

例如官方栗子里面的:

//更新数据库
db.update(TodoItem.TABLE, new TodoItem.Builder().complete(newValue).build(),
TodoItem.ID + " = ?", String.valueOf(event.id()));
}


删除操作

从表中删除指定的行,有三个参数:

表名

where条件语句

where可变参数

db.delete(TodoItem.TABLE,"");


删除表就执行sql语句了要。

四、其他使用注意

在下面的例子中,重用了BriteDatabase对象“db”作为插入。所有插入,更新或删除操作都必须通过此对象才能正确通知subscribers。

Unsubscribe取消订阅退回对应的subscriber,可以停止获取更新。

final AtomicInteger queries = new AtomicInteger();
Subscription s = users.subscribe(new Action1<Query>() {
@Override public void call(Query query) {
queries.getAndIncrement();
}
});
System.out.println("Queries: " + queries.get()); // Prints 1

db.insert("users", createUser("jw", "Jake Wharton"));
db.insert("users", createUser("mattp", "Matt Precious"));
s.unsubscribe();

db.insert("users", createUser("strong", "Alec Strong"));

System.out.println("Queries: " + queries.get()); // Prints 3


使用transactions可防止数据的大量更改导致subscribers频繁调用。

final AtomicInteger queries = new AtomicInteger();
users.subscribe(new Action1<Query>() {
@Override public void call(Query query) {
queries.getAndIncrement();
}
});
System.out.println("Queries: " + queries.get()); // Prints 1

Transaction transaction = db.newTransaction();
try {
db.insert("users", createUser("jw", "Jake Wharton"));
db.insert("users", createUser("mattp", "Matt Precious"));
db.insert("users", createUser("strong", "Alec Strong"));
transaction.markSuccessful();
} finally {
transaction.end();
}

System.out.println("Queries: " + queries.get()); // Prints 2


ps: 可以用try-with-resources语法使用transaction。可以简化代码,例如:

try (BriteDatabase.Transaction transaction = db.newTransaction()){
db.insert("users", createUser("jw", "Jake Wharton"));
db.insert("users", createUser("mattp", "Matt Precious"));
db.insert("users", createUser("strong", "Alec Strong"));
transaction.markSuccessful();
}


由于查询只是常规的RxJava Observable对象,运算符也可以用于控制向订阅者发送通知的频率。

users.debounce(500, MILLISECONDS).subscribe(new Action1<Query>() {
@Override public void call(Query query) {
// TODO...
}
});


四、官方demo讲解

官方的demo是一个记事本备忘录的功能的一个app,名为todo。 谷歌的栗子也是这个todo。

导入了官方的demo,可以发现,里面有利用的Dagger2、AutoValue、ButterKnife等等这些包。对这些知识还不会用的朋友请先看:

Dagger2:

Android快速依赖注入框架Dagger2使用1

Android快速依赖注入框架Dagger2使用2

AutoValue:

Android AutoValue使用和扩展库

ButterKnife: ButterKnife8.5.1 使用方法教程总结

我们应该怎么分析一个app呢? 我一般的习惯是

程序跑起来

看导包

看manifest

看application

看入口activity

然后就一个页面一个页面地看下去

目录



db目录存放数据库相关的类和bean

ui目录放view: activity和fragment和适配器

外层是Dagger2: Component和Module和Application

整体流程

利用Dagger2在ListsFragmnet、ItemsFragment、NewItemFragment、NewListFragment进行注入,

AppModule提供单例的Application,TodoModule里面包括了DbModule。

@Singleton
@Component(modules = TodoModule.class)
public interface TodoComponent {

void inject(ListsFragment fragment);

void inject(ItemsFragment fragment);

void inject(NewItemFragment fragment);

void inject(NewListFragment fragment);
}


在DbModule中进行了DbOpenHelper、SqlBrite、BriteDatabase的初始化,提供注入。

@Module
public final class DbModule {

//初始化提供SQLiteOpenHelper
@Provides
@Singleton
SQLiteOpenHelper provideOpenHelper(Application application) {
return new DbOpenHelper(application);
}

//初始化提供SqlBrite
@Provides
@Singleton
SqlBrite provideSqlBrite() {
return SqlBrite.create(new SqlBrite.Logger() {
@Override
public void log(String message) {
Timber.tag("Database").v(message);
}
});
}

//初始化提供BriteDatabase
@Provides
@Singleton
BriteDatabase provideDatabase(SqlBrite sqlBrite, SQLiteOpenHelper helper) {
BriteDatabase db = sqlBrite.wrapDatabaseHelper(helper, Schedulers.io());
db.setLoggingEnabled(true);
return db;
}

}


DbOpenHelper进行了表的创建和插入对应的数据:

final class DbOpenHelper extends SQLiteOpenHelper {
private static final int VERSION = 1;

//创建todo_list表的语句
private static final String CREATE_LIST = ""
+ "CREATE TABLE " + TodoList.TABLE + "("
+ TodoList.ID + " INTEGER NOT NULL PRIMARY KEY,"
+ TodoList.NAME + " TEXT NOT NULL,"
+ TodoList.ARCHIVED + " INTEGER NOT NULL DEFAULT 0"
+ ")";

//创建todo_item表的语句
private static final String CREATE_ITEM = ""
+ "CREATE TABLE " + TodoItem.TABLE + "("
+ TodoItem.ID + " INTEGER NOT NULL PRIMARY KEY,"
+ TodoItem.LIST_ID + " INTEGER NOT NULL REFERENCES " + TodoList.TABLE + "(" + TodoList.ID + "),"
+ TodoItem.DESCRIPTION + " TEXT NOT NULL,"
+ TodoItem.COMPLETE + " INTEGER NOT NULL DEFAULT 0"
+ ")";

//创建 单列索引,加速查询
private static final String CREATE_ITEM_LIST_ID_INDEX =
"CREATE INDEX item_list_id ON " + TodoItem.TABLE + " (" + TodoItem.LIST_ID + ")";

//构造器,创建数据库
public DbOpenHelper(Context context) {
super(context, "todo.db", null /* factory */, VERSION);
}

//创建数据库的时候回调
@Override
public void onCreate(SQLiteDatabase db) {
//创建对应的表和索引
db.execSQL(CREATE_LIST);
db.execSQL(CREATE_ITEM);
db.execSQL(CREATE_ITEM_LIST_ID_INDEX);

//插入数据到todo_list表,返回id
long groceryListId = db.insert(TodoList.TABLE, null, new TodoList.Builder()
.name("Grocery List")
.build());

//根据todo_list表的id,插入数据到todo_item表, db.insert(TodoItem.TABLE, null, new TodoItem.Builder() .listId(groceryListId) .description("Beer") .build());
db.insert(TodoItem.TABLE, null, new TodoItem.Builder()
.listId(groceryListId)
.description("Point Break on DVD")
.build());
db.insert(TodoItem.TABLE, null, new TodoItem.Builder()
.listId(groceryListId)
.description("Bad Boys 2 on DVD")
.build());

//下面的三列和上面的套路一样,就不贴代码了

}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}


这时候我就就知道应用创建了两个表: todo_list和todo_item,根据todo_list的id进行关联。就相当于todo_list表存放的是标题,todo_item存放的是所有的数据。



TodoList和TodoItem是bean类,使用了AutoValue进行bean类代码生成。

里面存放了表名、字段对应的常量,还有对应的数据获取。还有使用Builder用来生成ContentValues。

(这里贴出TodoList的代码,TodoItem的套路一样)

@AutoValue
public abstract class TodoList implements Parcelable {

public static final String TABLE = "todo_list";

public static final String ID = "_id";
public static final String NAME = "name";
public static final String ARCHIVED = "archived";

public abstract long id();

public abstract String name();

public abstract boolean archived();

//利用RXjava进行数据的获取,返回bean列表
public static Func1<Cursor, List<TodoList>> MAP = new Func1<Cursor, List<TodoList>>() {
@Override
public List<TodoList> call(final Cursor cursor) {
try {
List<TodoList> values = new ArrayList<>(cursor.getCount());

while (cursor.moveToNext()) {
long id = Db.getLong(cursor, ID);
String name = Db.getString(cursor, NAME);
boolean archived = Db.getBoolean(cursor, ARCHIVED);
values.add(new AutoValue_TodoList(id, name, archived));
}
return values;
} finally {
cursor.close();
}
}
};

//构建器,用来生成ContentValues
public static final class Builder {
private final ContentValues values = new ContentValues();

public Builder id(long id) {
values.put(ID, id);
return this;
}

public Builder name(String name) {
values.put(NAME, name);
return this;
}

public Builder archived(boolean archived) {
values.put(ARCHIVED, archived);
return this;
}

public ContentValues build() {
return values; // TODO defensive copy?
}
}
}


接下来看回到Application初始化TodoComponent。供给Fragment进行注入。

public final class TodoApp extends Application {

private TodoComponent mainComponent;

@Override
public void onCreate() {
super.onCreate();

if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
}

mainComponent = DaggerTodoComponent.builder().todoModule(new TodoModule(this)).build();
}

public static TodoComponent getComponent(Context context) {
return ((TodoApp) context.getApplicationContext()).mainComponent;
}
}


整个app就一个activity,其他都是Fragment。MainActivity利用了系统自带的布局进行初始化显示ListsFragment,

并且回调ListsFragment操作的接口。

public final class MainActivity extends FragmentActivity
implements ListsFragment.Listener, ItemsFragment.Listener {

// 设置内容为Listsfragment
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content, ListsFragment.newInstance())
.commit();
}
}

//ListsFragment的item点击回调
@Override
public void onListClicked(long id) {
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left,
R.anim.slide_out_right)
.replace(android.R.id.content, ItemsFragment.newInstance(id))
.addToBackStack(null)
.commit();
}

//菜单的newList点击回调
@Override
public void onNewListClicked() {
NewListFragment.newInstance().show(getSupportFragmentManager(), "new-list");
}

/**
* 菜单的newItem点击回调
* @param listId 对应todo_list表的列的id
*/
@Override
public void onNewItemClicked(long listId) {
NewItemFragment.newInstance(listId).show(getSupportFragmentManager(), "new-item");
}

}


ListsFragment为单例,是todo_list表的数据,在初始化时候回调onAttach进行Dagger2的注入、初始化ListsAdapter适配器、开启右上角的菜单。

布局主要是一个ListView用来显示TodoList表的数据。界面实现完毕进行设置标题、查询数据库。

public final class ListsFragment extends Fragment {

//回调到MainActivity的接口
interface Listener {
//列表点击
void onListClicked(long id);
//新建列表点击
void onNewListClicked();
}

//单例实现
static ListsFragment newInstance() {
return new ListsFragment();
}

@Inject
BriteDatabase db;   //注入获取BriteDatabase

//使用ButterKnife获取View
@BindView(android.R.id.list)
ListView listView;
@BindView(android.R.id.empty)
View emptyView;

//回调接口
private Listener listener;
//适配器
private ListsAdapter adapter;
//Rxjava的订阅者,在离开界面进行接触订阅
private Subscription subscription;

//初始化时候进行Dagger2的注入
@Override
public void onAttach(Activity activity) {
if (!(activity instanceof Listener)) {
throw new IllegalStateException("Activity must implement fragment Listener.");
}

super.onAttach(activity);

//Dagger2的注入
TodoApp.getComponent(activity).inject(this);

//设置菜单
setHasOptionsMenu(true);

listener = (Listener) activity;
//初始化适配器
adapter = new ListsAdapter(activity);
}

//添加菜单为 NEW LIST
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);

//点击菜单回调
MenuItem item = menu.add(R.string.new_list)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
listener.onNewListClicked();
return true;
}
});
MenuItemCompat.setShowAsAction(item, SHOW_AS_ACTION_IF_ROOM | SHOW_AS_ACTION_WITH_TEXT);
}

//创建Fragment的布局
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.lists, container, false);
}

//view创建完毕,开始绑定Butterknife、适配器
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
listView.setEmptyView(emptyView);
listView.setAdapter(adapter);
}

@OnItemClick(android.R.id.list)
void listClicked(long listId) {
listener.onListClicked(listId);
}

//界面显示完毕设置标题、查询数据库
@Override
public void onResume() {
super.onResume();

getActivity().setTitle("To-Do");

subscription = db.createQuery(ListsItem.TABLES, ListsItem.QUERY)
.mapToList(ListsItem.MAPPER)    //映射到ListItem的MAPPER
.observeOn(AndroidSchedulers.mainThread())//设置订阅者在主线程进行
.subscribe(adapter);
}

@Override
public void onPause() {
super.onPause();
//界面隐藏解除订阅
subscription.unsubscribe();
}
}


ListsAdapter作为数据的适配显示,实现了Rxjava的Action1

final class ListsAdapter extends BaseAdapter implements Action1<List<ListsItem>> {
private final LayoutInflater inflater;

private List<ListsItem> items = Collections.emptyList();

public ListsAdapter(Context context) {
this.inflater = LayoutInflater.from(context);
}

//在ListsFragment里面Rxjava查询数据库完毕,在这里更新适配器
@Override
public void call(List<ListsItem> items) {
this.items = items;
notifyDataSetChanged();
}

....(下面的就省略了)
}


ListsItem是ListsFragment的数据list的bean,里面定义了一个查询语句,这个语句可能比较复杂,意思就是 根据todo_list的_id分组

,选择_id,name,todo_listd _id对应的todo_item的数量(Sql语句不懂的先去补习一下吧)。 还有一个MAPPER映射,Rxjava的处理把Cursor转换为ListsItem。

SELECT list._id, list.name, COUNT(item._id) as item_count
FROM todo_list AS list
LEFT OUTER JOIN todo_item AS item ON list._id = item.todo_list_id
GROUP BY list._id


ItemsFragment是todo_item表的数据,存放的是所有的item。

通过在ListsFragment中点击item的时候传递todo_list表的_id来,然后ItemFragmnet根据list的_id查询得到item显示,和查询数量和todo_list的nane作为标题。

基本套路和ListsFragmnet差不多。

ItemsFragmeng用到了Rxbinding,对listView的item点击进行监听,每点击一次就更新todo_item表对应的数据,就是备忘录的任务完成了的意思。

public final class ItemsFragment extends Fragment {
private static final String KEY_LIST_ID = "list_id";

//根据todo_list_id查询todo_item表的所有的数据
private static final String LIST_QUERY = "SELECT * FROM "
+ TodoItem.TABLE
+ " WHERE "
+ TodoItem.LIST_ID
+ " = ? ORDER BY "
+ TodoItem.COMPLETE
+ " ASC";

//根据todo_list_id查询todo_item表所有的数据的总数
private static final String COUNT_QUERY = "SELECT COUNT(*) FROM "
+ TodoItem.TABLE
+ " WHERE "
+ TodoItem.COMPLETE
+ " = "
+ Db.BOOLEAN_FALSE
+ " AND "
+ TodoItem.LIST_ID
+ " = ?";

//根据_id查询todo_list表的数据的name
private static final String TITLE_QUERY =
"SELECT " + TodoList.NAME + " FROM " + TodoList.TABLE + " WHERE " + TodoList.ID + " = ?";

public interface Listener {
void onNewItemClicked(long listId);
}

public static ItemsFragment newInstance(long listId) {
Bundle arguments = new Bundle();
arguments.putLong(KEY_LIST_ID, listId);

ItemsFragment fragment = new ItemsFragment();
fragment.setArguments(arguments);
return fragment;
}

@Inject
BriteDatabase db;

@BindView(android.R.id.list)
ListView listView;
@BindView(android.R.id.empty)
View emptyView;

private Listener listener;
private ItemsAdapter adapter;
private CompositeSubscription subscriptions; //可以组合多个subscriptions,便于解除订阅

private long getListId() {
return getArguments().getLong(KEY_LIST_ID);
}

@Override
public void onAttach(Activity activity) {
if (!(activity instanceof Listener)) {
throw new IllegalStateException("Activity must implement fragment Listener.");
}

super.onAttach(activity);
TodoApp.getComponent(activity).inject(this);
setHasOptionsMenu(true);

listener = (Listener) activity;
adapter = new ItemsAdapter(activity);
}

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);

MenuItem item = menu.add(R.string.new_item)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
listener.onNewItemClicked(getListId());
return true;
}
});
MenuItemCompat.setShowAsAction(item, SHOW_AS_ACTION_IF_ROOM | SHOW_AS_ACTION_WITH_TEXT);
}

@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.items, container, false);
}

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
listView.setEmptyView(emptyView);
listView.setAdapter(adapter);

//监听Listview的点击,更新todo_item表的状态
RxAdapterView.itemClickEvents(listView) //
.observeOn(Schedulers.io())
.subscribe(new Action1<AdapterViewItemClickEvent>() {
@Override
public void call(AdapterViewItemClickEvent event) {
//取相反值
boolean newValue = !adapter.getItem(event.position()).complete();
//更新数据库 db.update(TodoItem.TABLE, new TodoItem.Builder().complete(newValue).build(), TodoItem.ID + " = ?", String.valueOf(event.id())); }
});
}

@Override
public void onResume() {
super.onResume();
//得到todo_list表对应的_id
String listId = String.valueOf(getListId());

//创建订阅者
subscriptions = new CompositeSubscription();

//查询todo_item表的对应todo_list_id的总数
Observable<Integer> itemCount = db.createQuery(TodoItem.TABLE, COUNT_QUERY, listId) //
.map(new Func1<Query, Integer>() {
@Override
public Integer call(Query query) {
Cursor cursor = query.run();
try {
if (!cursor.moveToNext()) {
throw new AssertionError("No rows");
}
return cursor.getInt(0);
} finally {
cursor.close();
}
}
});

//根据_id查询todo_list表的数据的name
Observable<String> listName =
db.createQuery(TodoList.TABLE, TITLE_QUERY, listId).map(new Func1<Query, String>() {
@Override
public String call(Query query) {
Cursor cursor = query.run();
try {
if (!cursor.moveToNext()) {
throw new AssertionError("No rows");
}
return cursor.getString(0);
} finally {
cursor.close();
}
}
});

//取得对应list的名字和todo_item表对应的数量数量作为标题。
subscriptions.add(
Observable.combineLatest(listName, itemCount, new Func2<String, Integer, String>() {
@Override
public String call(String listName, Integer itemCount) {
return listName + " (" + itemCount + ")";
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<String>() {
@Override
public void call(String title) {
getActivity().setTitle(title);
}
}));

//根据todo_list_id查询todo_item表的所有的数据,更新到适配器
subscriptions.add(db.createQuery(TodoItem.TABLE, LIST_QUERY, listId)
.mapToList(TodoItem.MAPPER)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(adapter));
}

@Override
public void onPause() {
super.onPause();
//解除订阅多个subscriptions
subscriptions.unsubscribe();
}
}


好了,SqlBrite官方的demo基本功能就这样。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐