数据库操作、基本每个android应用都有涉及,有些应用需要对数据库进行加密,不让root用户去访问,有些应用表之间联系紧密要并发处理等,那么怎么建立一个比较好的成熟的框架,方便之后的应用都可以拿来直接用呢。下面介绍下我一直使用的数据库,本人使用的是Android studio 开发工具。
我们都知道android sqlite是存储在我们应用的内部存储data/data/里面的,一般用户是查看不到的、但对于root的手机是可以查看我们的数据库的,为了也防止root用户查看、我们就要对数据库进行加密。我们要使用SQLCipher ,SQLCipher是一SQLite基础之上进行扩展的开源数据库,它主要是在SQLite的基础之上增加了数据加密功能,我们在数据库中使用,可以加大我们数据库的安全性。
这个数据库的下载地址是:https://s3.amazonaws.com/sqlcipher/SQLCipher+for+Android+v2.2.2.zip
接下来介绍SQLCipher的使用
因为我使用的是Android studio 开发工具,结构跟eclipse不同,SQLCipher 需要导入3个lib和一个资源文件,以及3个so文件,主要跟eclipse不同、Android studio的so文件需要再main下面建立一个jniLibs的文件夹,把so文件放在这个文件下。我们把包导好后,开始编写代码了
package com.test.dbencrypt.db;
import android.content.Context;
import net.sqlcipher.database.SQLiteDatabase;
import net.sqlcipher.database.SQLiteOpenHelper;
/**
* Created by fuweiwei on 2015/11/19.
* 数据库访问类,因此数据库使用了加密,所以这里使用的并不是Android API中的SQLiteOpenHelper,而是net.sqlcipher.database包下的SQLiteOpenHelper
*/
public class MyDatabaseHelper extends SQLiteOpenHelper implements TableColumns{
private final static String DB_NAME = "db_demo.db";
private final static int DB_VERSION = 1;
//建表sql语句
public static final String CREATE_TABLE_USER = "CREATE TABLE IF NOT EXISTS " + USER_COLUMNS.TABLE_USER
+"("
+ "_id integer primary key autoincrement,"
+ USER_COLUMNS.NAME +" text,"
+ USER_COLUMNS.PASSWORD +" text"
+")";
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
public MyDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(CREATE_TABLE_USER);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
需要主要的是我们继承的sqliteOpenHelper 不是android Api中的,是net.sqlcipher.database包下的sqlteOpenHelper。有人注意到了我们这里实现了接口TableColumns,那我们看下这个接口是做什么用的
package com.test.dbencrypt.db;
/**
* Created by fuweiwei on 2015/11/19.
* 数据库表、字段(方便以后统一管理)
*/
public interface TableColumns {
public static interface USER_COLUMNS {
public static final String TABLE_USER="user";
public static final String NAME="Name";//名字
public static final String PASSWORD = "Password";// 密码
}
}
其实这个这只是一个统一管理数据库表名和字段的接口,因为在开发中我们时不时会改我们的表名和字段名,放在一起就不需要一处处去改了,这也是跟以前有十几年开发经验老大学的。
上面我们建好了数据打开的辅助类,接下来我们要建一个对数据库进行操作的辅助类
package com.test.dbencrypt.db;
import android.content.Context;
import net.sqlcipher.database.SQLiteDatabase;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by fuweiwei on 2015/11/19.
* 数据库操作基类
*/
public class DBDao {
protected static MyDatabaseHelper dbHelper;
protected SQLiteDatabase db;
//数据库加密的key
protected static String DB_Key ="secret_key";
//数据可打开关闭标记器,可解决重复打开关闭数据库的问题
protected AtomicInteger mOpenCounter = new AtomicInteger();
protected DBDao(Context context) {
if (dbHelper == null) {
synchronized (DBDao.class) {
if (dbHelper == null) {
//将SQLCipher所依赖的so库加载进来
SQLiteDatabase.loadLibs(context);
dbHelper = new MyDatabaseHelper(context);
}
}
}
}
/**
* 获得一个可写数据库
* @return SQLiteDatabase
*/
protected synchronized SQLiteDatabase getWritableDatabase(){
if(mOpenCounter.incrementAndGet() == 1) {
db = dbHelper.getWritableDatabase(DB_Key);
}
return db;
}
/**
* 获得一个可读数据库
* @return SQLiteDatabase
*/
protected synchronized SQLiteDatabase getReadableDatabase(){
if(mOpenCounter.incrementAndGet() == 1) {
db = dbHelper.getReadableDatabase(DB_Key);
}
return db;
}
/**
* 关闭数据库连接
*/
protected synchronized void closeDatabase(){
if(mOpenCounter.decrementAndGet() == 0) {
db.close();
}
}
}
这面这些代码 ,可能有人会不明白,我一一来说,整个这个类我们使用单例的模式,因为我们只需要一个这个的数据库操作辅助类,
SQLiteDatabase.loadLibs(context);这段代码必须要加,相当于初始化,因为我们要对数据库加密,那么必须要有一个秘钥,在数据库解密的时候我们需要这个秘钥,需要注意
的是SQLCipher提供的Api跟android原生的数据库的Api是一模一样的,操作起来几乎一模一样。
有人可能注意到了ATomicInteger,那么这个是做什么用的呢。作用是:每当你使用DBDao的getReadableDatabase()和getWritableDatabase()方法来取得数据库。我们会使用一个引用计数来判断是否要创建数据库对象。如果引用计数为1,则需要创建一个数据库,如果不为1,说明我们已经创建过了。在closeDatabase()方法中我们同样通过判断引用计数的值,如果引用计数降为0,则说明我们需要close数据库。这样做的目的是:因为我们每进行一次数据库操作完毕后是要关闭数据库连接的,这样反复进行数据操作时我们要反复要对数据库进行打开关闭的操作,虽然在StackOverflow上推荐的做法是永远不要关闭数据库。Android会尊重你这种做法,但会给你如下的提示。所以我一点也不推荐这种做法。
Leak found
Caused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed
数据库操作辅助类我们建好了、接下来就是对数据库进行操作了:
package com.test.dbencrypt.db;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import com.test.dbencrypt.bean.UserBean;
import net.sqlcipher.database.SQLiteDatabase;
import java.util.ArrayList;
import java.util.List;
/**
* Created by fuweiwei on 2015/11/19.
* 用户表操作类
*/
public class DBUserDao extends DBDao implements TableColumns.USER_COLUMNS {
private static DBUserDao INSTANCE;
private Context mContext;
protected DBUserDao(Context context) {
super(context);
}
public static DBUserDao getInstance(Context context){
if(INSTANCE==null){
synchronized (DBUserDao.class) {
if(INSTANCE==null){
INSTANCE = new DBUserDao(context);
}
}
}
return INSTANCE;
}
/**
* 添加一条用户
* @param userBean
*/
public void addUser(UserBean userBean){
if(userBean==null){
return;
}
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", userBean.getName());
values.put("password", userBean.getPassword());
db.insert(TABLE_USER,NAME,values);
closeDatabase();
}
/**
* 获取所有用户
* @return
*/
public List<UserBean> getUsers(){
List<UserBean> list = new ArrayList<>();
SQLiteDatabase db = getWritableDatabase();
String sql = "select *from "+TABLE_USER;
Cursor cursor = db.rawQuery(sql,null);
if(cursor != null && cursor.moveToFirst()){
UserBean bean = new UserBean();
bean.setName(cursor.getString(cursor.getColumnIndex(NAME)));
bean.setPassword(cursor.getString(cursor.getColumnIndex(PASSWORD)));
list.add(bean);
}
if(cursor!=null){
cursor.close();
}
closeDatabase();
return list;
}
}
这里我们建一个对User这个表操作的Dao,我提倡一个表一个Dao这样层次清晰,以后我们查看也方便,大家也看到了这里SQLCipher的提供的方法是不是跟原生的一样的,操作还是很简单的。
数据库Bean:
package com.test.dbencrypt.bean;
import java.io.Serializable;
/**
* Created by fuweiwei on 2015/11/19.
*/
public class UserBean implements Serializable{
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
界面
MainActivity:
package com.test.dbencrypt;
import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.test.dbencrypt.bean.UserBean;
import com.test.dbencrypt.db.DBUserDao;
import java.util.List;
public class MainActivity extends FragmentActivity implements View.OnClickListener{
private Button mBtnAdd;
private Button mBtnLook;
private Context mContext = this;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
public void initView(){
mBtnAdd = (Button) findViewById(R.id.btn_add);
mBtnLook = (Button) findViewById(R.id.btn_look);
mBtnAdd.setOnClickListener(this);
mBtnLook.setOnClickListener(this);
}
@Override
public void onClick(View view) {
int id = view.getId();
switch (id){
case R.id.btn_add:
UserBean userBean = new UserBean();
userBean.setName("vgeer");
userBean.setPassword("123456");
DBUserDao.getInstance(mContext).addUser(userBean);
Toast.makeText(mContext,"添加成功",Toast.LENGTH_SHORT).show();
break;
case R.id.btn_look:
List<UserBean> list = DBUserDao.getInstance(mContext).getUsers();
StringBuffer stringBuffer = new StringBuffer();
for (UserBean userBean1:list){
stringBuffer.append("用户:"+userBean1.getName()+",");
stringBuffer.append("密码:"+userBean1.getPassword()+"。");
}
Toast.makeText(mContext,stringBuffer.toString(),Toast.LENGTH_SHORT).show();
break;
}
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加"
android:id="@+id/btn_add"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="查看"
android:id="@+id/btn_look"
android:layout_marginTop="32dp"
android:layout_below="@+id/btn_add"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
</RelativeLayout>
以上就是我经常使用的数据库框架,基本每个应用都可以搬过来直接用,以后也会把我认为好点东西分享给大家。
源码下载