一、举个例子
1、开发
1、配置
工程配置文件中添加:
buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0
//greendao配置
classpath 'org.greenrobot:greendao-gradle-plugin:3.0.0'
}
}
module配置文件中添加:
apply plugin: 'com.android.application'
apply plugin: 'com.antfortune.freeline'
apply plugin: 'org.greenrobot.greendao'
android {
compileSdkVersion 26
buildToolsVersion '26.0.2'
defaultConfig {
applicationId "com.zxcn.test"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
//android里添加下面代码
greendao {
schemaVersion 1
daoPackage 'com.hy.AndroidBase.gen' //一般为app包名+生成文件的文件夹名
targetGenDir 'src/main/java' //生成文件路径;这个目录必须这样写,否则自动编译的会出错
}
}
repositories {
// ...
maven { url "https://jitpack.io" }
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:26.+'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
compile 'io.reactivex:rxjava:1.1.5'
compile project(':mylib')
compile'org.greenrobot:greendao:3.0.1'
compile'org.greenrobot:greendao-generator:3.0.0'
}
2、创建实体类User
package com.hy.base.androidbase.androidbasesave.greendao;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Generated;
@Entity
public class User {
@Id
private Long id;
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
@Generated(hash = 873297011)
public User(Long id, String name) {
this.id = id;
this.name = name;
}
@Generated(hash = 586692638)
public User() {
}
@Override
public String toString(){
return "UserInfo1{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
在AndroidStudio编译,自动生成:
3、在Application里配置数据库:
public class MyApplication extends Application {
String tag = "060_MyApp";
private static MyApplication sInstance;
private MyActivityManager activityManager = null; // activity管理类
//单例模式
public static synchronized MyApplication getInstance() {
return sInstance;
}
/**
* 提供全局获取 Context
*/
private static Context context;
public static Context getContext() {
return context;
}
/**
* 重写Application生命周期
*/
@Override
public void onCreate() {
super.onCreate();
Log.e(tag,"MyApplication onCreate");
context = getApplicationContext();
initCrashHandler();
initGlobalManager();
initGlobalService();
initDatabase();
//配置数据库
setupGreenDaoDatabase();
}
@Override
public void onTerminate() {
// 程序终止的时候执行
Log.e(tag, "onTerminate");
super.onTerminate();
}
@Override
public void onLowMemory() {
// 低内存的时候执行
Log.e(tag, "onLowMemory");
super.onLowMemory();
}
@Override
public void onTrimMemory(int level) {
// 程序在内存清理的时候执行
Log.e(tag, "onTrimMemory");
super.onTrimMemory(level);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
Log.e(tag, "onConfigurationChanged");
super.onConfigurationChanged(newConfig);
}
public MyActivityManager getActivityManager() {
return activityManager;
}
public void initCrashHandler() {
}
public void initGlobalManager() {
// 获得activity管理类实例
activityManager = MyActivityManager.getInstance();
}
public void initGlobalService() {
//启动应用级的service
// startService(new Intent(getApplicationContext(), MyService.class));
// startService(new Intent(getApplicationContext(), MyForegroundService.class));
}
public void initDatabase() {
DatabaseHelper.getHelper(getApplicationContext());
}
private void setupGreenDaoDatabase(){
//创建数据库shop.db
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "shop.db", null);
//获取可写数据库
SQLiteDatabase db = helper.getWritableDatabase();
//获取数据库对象
DaoMaster daoMaster = new DaoMaster(db);
//获取dao对象管理者
daoSession = daoMaster.newSession();
}
private static DaoSession daoSession;
public static DaoSession getDaoInstant() {
return daoSession;
}
}
4、创建增删改查的类
public class UserUtils {
private static UserUtils mUserUtils;
public UserUtils(){
}
public static UserUtils getUserUtils(){
if (mUserUtils == null) {
mUserUtils = new UserUtils();
}
return mUserUtils;
}
/**
* 添加数据,如果有重复则覆盖
*
*/
public void insert(User mUser) {
MyApplication.getDaoInstant().getUserDao().insertOrReplace(mUser);
}
/**
* 删除数据
*
* @param id
*/
public void delete(long id) {
MyApplication.getDaoInstant().getUserDao().deleteByKey(id);
}
/**
* 更新数据
*/
public void update(User mUser) {
MyApplication.getDaoInstant().getUserDao().update(mUser);
}
/**
* 查询Type为1的所有数据
*
* @return
*/
public List<User> queryUser(String name) {
return MyApplication.getDaoInstant().getUserDao().queryBuilder().where(UserDao.Properties.Name.eq(name)).list();
}
/**
* 查询所有数据
*
* @return
*/
public List<User> queryAll() {
return MyApplication.getDaoInstant().getUserDao().loadAll();
}
}
5、调用:
insertg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
UserUtils.getUserUtils().insert(new User(1L,"zhangsan"));
UserUtils.getUserUtils().insert(new User(2L,"lisi"));
UserUtils.getUserUtils().insert(new User(1L,"wangwu"));
UserUtils.getUserUtils().insert(new User(1L,"zhaoliu"));
}
});
deleteg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
UserUtils.getUserUtils().delete(1);
}
});
updateg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
UserUtils.getUserUtils().update(new User(2L,"hahahha"));
}
});
queryg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
UserUtils.getUserUtils().queryUser("zhaoliu");
}
});
queryallg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
List<User> all = UserUtils.getUserUtils().queryAll();
for (int i = 0; i < all.size(); i++) {
Log.e("060", "all[" + i + "] :" + all.get(i).toString());
}
}
});
6、采用RecyclerView显示。
(1)给实体User创建对应的Adapter类。
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.ViewHolder> {
private LayoutInflater mInflater;
private List<User> mDatas;
public UserAdapter(Context context, List<User> mDataBean) {
Log.e("060 DataBeanAdapter:","0");
mInflater = LayoutInflater.from(context);
mDatas = mDataBean;
}
/**
* 创建ViewHolder,每个ViewHolder管理一个item。因为ViewHolder会重复利用,
* 所以如果Recyclerview有12个item,可能只创建7个ViewHolder
*/
int count_create = 0;
int count_bind = 0;
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
count_create++;
Log.e("060 onCreateViewHolder:",count_create + "");
View view = mInflater.inflate(R.layout.item_user, viewGroup, false);
ViewHolder viewHolder = new ViewHolder(view);
viewHolder.mid = (TextView) view.findViewById(R.id.id);
viewHolder.mname = (TextView) view.findViewById(R.id.name);
viewHolder.mage = (TextView) view.findViewById(R.id.age);
return viewHolder;
}
/**
* 绑定ViewHolder到Recyclerview
*/
@Override
public void onBindViewHolder(final ViewHolder viewHolder, final int i) {
count_bind++;
viewHolder.mid.setText(mDatas.get(i).getId()+"");
viewHolder.mname.setText(mDatas.get(i).getName());
viewHolder.mage.setText(mDatas.get(i).getAge());
//设置点击事件对应的回调
if (mOnItemClickLitener != null) {
viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mOnItemClickLitener.onItemClick(viewHolder.itemView, i);
}
});
}
}
/**
* ************************* 定义ViewHolder ******************************
*/
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View arg0) {
super(arg0);
}
TextView mid;
TextView mname;
TextView mage;
}
/**
* ************************* 重写Recyclerview Adapter的接口 ******************************
*/
@Override
public int getItemCount() {
int m = 0;
if(mDatas!=null){
m = mDatas.size();
}
return m;
}
/**
* ************************* Recyclerview item对应点击事件******************************
*/
/**
* RecyclerView Item 的点击事件回调接口
*/
public interface OnItemClickLitener {
void onItemClick(View view, int position);
}
private OnItemClickLitener mOnItemClickLitener;
public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener) {
this.mOnItemClickLitener = mOnItemClickLitener;
}
/**
* RecyclerView Item 的滚动事件回调接口
*/
public interface OnScrollListener {
// 状态为0时:当前屏幕停止滚动;
// 状态为1时:屏幕在滚动 且 用户仍在触碰或手指还在屏幕上;
// 状态为2时:随用户的操作,屏幕上产生的惯性滑动;
// void onScrollStateChanged(RecyclerView recyclerView, int newState);
void onScrolled(RecyclerView recyclerView, int dx, int dy);
}
private OnScrollListener mOnScrollListener;
public void setOnScrollListener(OnScrollListener mOnScrollListener) {
this.mOnScrollListener = mOnScrollListener;
}
}
R.layout.item_user对应的资源文件放到layout目录:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="60dp"
android:orientation="vertical">
<LinearLayout
android:id="@+id/layout1"
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal">
<TextView
android:id="@+id/id"
android:layout_width="0dp"
android:layout_height="25dp"
android:layout_marginTop="15dp"
android:layout_weight="1"
android:gravity="center_horizontal"
android:text="some info"
android:textSize="15sp"/>
<TextView
android:id="@+id/name"
android:layout_width="0dp"
android:layout_height="25dp"
android:layout_marginTop="15dp"
android:layout_weight="1"
android:gravity="center_horizontal"
android:text="some info"
android:textSize="15sp"/>
<TextView
android:id="@+id/age"
android:layout_width="0dp"
android:layout_height="25dp"
android:layout_marginTop="15dp"
android:layout_weight="1"
android:gravity="center_horizontal"
android:text="some info"
android:textSize="15sp"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_below="@+id/layout1"
android:background="#e1f1f9"/>
</RelativeLayout>
(2)创建RecyclerView所位于的Activity类。
public class GreenDaoQureyActivity extends Activity {
String tag = "060_GreenDaoQureyActivity";
List<User> mDatas;
UserAdapter mAdapter;
RecyclerView mbphistory;
Button bt1;
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_gdquery);
mbphistory = findViewById(R.id.mbphistory);
bt1 = findViewById(R.id.bt1);
bt1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
queryall();
}
});
}
private void queryall() {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Log.e(tag,"time1 : "+ DateUtils.getDateStr(System.currentTimeMillis()));
for(long i= 0; i<1000;i++){
UserUtils.getUserUtils().insert(new User(i,"aa","20",""));
}
Log.e(tag,"time2 : "+ DateUtils.getDateStr(System.currentTimeMillis()));
mDatas = UserUtils.getUserUtils().queryAll();
Log.e(tag,"time3 : "+ DateUtils.getDateStr(System.currentTimeMillis()));
handler.sendEmptyMessage(0);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
private void overrideCallback() {
mAdapter.setOnItemClickLitener(new UserAdapter.OnItemClickLitener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(GreenDaoQureyActivity.this, position + "", Toast.LENGTH_SHORT).show();
switch (position) {
case 0:
break;
default:
}
}
});
}
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
mbphistory.setLayoutManager(new LinearLayoutManager(GreenDaoQureyActivity.this, LinearLayoutManager.VERTICAL, false));
mAdapter = new UserAdapter(GreenDaoQureyActivity.this, mDatas);
mbphistory.setAdapter(mAdapter);
//override click callback
overrideCallback();
break;
case 2:
break;
}
}
};
}
其对应的layout文件夹:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/bt1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="query all"/>
<LinearLayout
android:layout_below="@+id/bt1"
android:id="@+id/layout1"
android:layout_width="match_parent"
android:layout_height="55dp"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="25dp"
android:layout_marginTop="15dp"
android:layout_weight="1"
android:text="序号"
android:gravity="center_horizontal"
android:textSize="20sp"/>
<TextView
android:layout_width="0dp"
android:layout_height="25dp"
android:layout_marginTop="15dp"
android:layout_weight="1"
android:text="姓名"
android:gravity="center_horizontal"
android:textSize="20sp"/>
<TextView
android:layout_width="0dp"
android:layout_height="25dp"
android:layout_marginTop="15dp"
android:layout_weight="1"
android:text="年龄"
android:gravity="center_horizontal"
android:textSize="20sp"/>
</LinearLayout>
<View
android:id="@+id/l1"
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_below="@+id/layout1"
android:background="#e1f1f9"/>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/l1"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/mbphistory"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:scrollbars="none"/>
</RelativeLayout>
</RelativeLayout>
2、安卓mainthread数据库操作
数据库的操作很重,一次读写操作花费 10~20ms 是很常见的,这样的耗时很容易造成界面的卡顿。所以通常情况下,如果可以的话一定要避免在主线程中处理数据库。
方案:开线程方位数据库。
3、调试
1、数据库直观查看:
采用Facebook Stetho工具查看。
参考:
Android调式工具 --Facebook Stetho --无需root手机设备即可查看data目录下数据
https://blog.csdn.net/x767914233/article/details/70233471
2、APK应用显示查看:
采用RecyclerView显示。
bug1: RecyclerView显示为空。
https://blog.csdn.net/the_spring/article/details/54236996
二、数据库加密
工具:
SQLCipher
查看:
Android 数据库 greenDao 3(包括加密) 封装使用 https://www.jianshu.com/p/427e0e9f396a
2、调试
使用SQLCipher加密数据库后,使用 Facebook Stetho数据库查看工具,确实不能查看数据库的内部数据了,说明加密起作用了。
三、数据库升级
目的:
不删除原有表,在表上增加字段 ,且保持原来字段的数据不被改变。
参考:
greendao3.0以上使用步骤(二):数据库到底该怎么升级
https://blog.csdn.net/huangxiaoguo1/article/details/54574713
数据库升级代码:
升级前效果
升级后效果
代码:
1、首先我们引入MigrationHelper类,内容如下:
package com.hy.base.androidbase.androidbasesave.greendao;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import com.hy.AndroidBase.gen.DaoMaster;
import org.greenrobot.greendao.AbstractDao;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.internal.DaoConfig;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class MigrationHelper {
private static final String CONVERSION_CLASS_NOT_FOUND_EXCEPTION = "MIGRATION HELPER - CLASS DOESN'T MATCH WITH THE CURRENT PARAMETERS";
private static MigrationHelper instance;
public static MigrationHelper getInstance() {
if (instance == null) {
instance = new MigrationHelper();
}
return instance;
}
public void migrate(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
generateTempTables(db, daoClasses);
DaoMaster.dropAllTables(db, true);
DaoMaster.createAllTables(db, false);
restoreData(db, daoClasses);
}
/**
* 生成临时列表
*
* @param db
* @param daoClasses
*/
private void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
for (int i = 0; i < daoClasses.length; i++) {
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String divider = "";
String tableName = daoConfig.tablename;
String tempTableName = daoConfig.tablename.concat("_TEMP");
ArrayList<String> properties = new ArrayList<>();
StringBuilder createTableStringBuilder = new StringBuilder();
createTableStringBuilder.append("CREATE TABLE ").append(tempTableName).append(" (");
for (int j = 0; j < daoConfig.properties.length; j++) {
String columnName = daoConfig.properties[j].columnName;
if (getColumns(db, tableName).contains(columnName)) {
properties.add(columnName);
String type = null;
try {
type = getTypeByClass(daoConfig.properties[j].type);
} catch (Exception exception) {
exception.printStackTrace();
}
createTableStringBuilder.append(divider).append(columnName).append(" ").append(type);
if (daoConfig.properties[j].primaryKey) {
createTableStringBuilder.append(" PRIMARY KEY");
}
divider = ",";
}
}
createTableStringBuilder.append(");");
db.execSQL(createTableStringBuilder.toString());
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("INSERT INTO ").append(tempTableName).append(" (");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(" FROM ").append(tableName).append(";");
db.execSQL(insertTableStringBuilder.toString());
}
}
/**
* 存储新的数据库表 以及数据
*
* @param db
* @param daoClasses
*/
private void restoreData(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
for (int i = 0; i < daoClasses.length; i++) {
DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
String tableName = daoConfig.tablename;
String tempTableName = daoConfig.tablename.concat("_TEMP");
ArrayList<String> properties = new ArrayList();
for (int j = 0; j < daoConfig.properties.length; j++) {
String columnName = daoConfig.properties[j].columnName;
if (getColumns(db, tempTableName).contains(columnName)) {
properties.add(columnName);
}
}
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" (");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(TextUtils.join(",", properties));
insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");
StringBuilder dropTableStringBuilder = new StringBuilder();
dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
db.execSQL(insertTableStringBuilder.toString());
db.execSQL(dropTableStringBuilder.toString());
}
}
private String getTypeByClass(Class<?> type) throws Exception {
if (type.equals(String.class)) {
return "TEXT";
}
if (type.equals(Long.class) || type.equals(Integer.class) || type.equals(long.class)) {
return "INTEGER";
}
if (type.equals(Boolean.class)) {
return "BOOLEAN";
}
Exception exception = new Exception(CONVERSION_CLASS_NOT_FOUND_EXCEPTION.concat(" - Class: ").concat(type.toString()));
exception.printStackTrace();
throw exception;
}
private List<String> getColumns(Database db, String tableName) {
List<String> columns = new ArrayList<>();
Cursor cursor = null;
try {
cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 1", null);
if (cursor != null) {
columns = new ArrayList<>(Arrays.asList(cursor.getColumnNames()));
}
} catch (Exception e) {
Log.v(tableName, e.getMessage(), e);
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
}
return columns;
}
}
2、重写DaoMaster.OpenHelper的onUpgrade()方法。
创建MyOpenHelper:
public class MyOpenHelper extends DaoMaster.OpenHelper {
public MyOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
/**
* 数据库升级
* @param db
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
//操作数据库的更新 有几个表升级都可以传入到下面
MigrationHelper.getInstance().migrate(db, UserDao.class);
}
}
3、初始化DaoMaster时,注意引进MyOpenHelper。
/**
* 初始化不加密的数据库
*/
private void setupGreenDaoDatabase() {
//有数据库升级前,初始化DaoMaster
if (null == daoMaster) {
synchronized (GreenDaoApplication.class) {
if (null == daoMaster) {
MyOpenHelper helper = new MyOpenHelper(this, "user.db", null);
daoMaster = new DaoMaster(helper.getWritableDatabase());
}
}
}
//获取dao对象管理者
daoSession = daoMaster.newSession();
}
5、修改module gradle的greendao配置:
schemaVersion 加1
//android里添加下面代码
greendao {
schemaVersion 2
daoPackage 'com.hy.AndroidBase.gen' //一般为app包名+生成文件的文件夹名
targetGenDir 'src/main/java' //生成文件路径;这个目录必须这样写,否则自动编译的会出错
}
四、特点
开发的角度
优点:
1、使用简单,无需从头开始写数据库。普通需求基本直用重写两个类,一个是数据bean类,一个是数据库操作类。
性能的角度
优点:
GitHub demo源码地址:
https://github.com/yuanhhyuan/AndroidDataSave