前言
前面的文章中《实现Android本地Sqlite数据库网络传输到PC端》中制作的将本地Sqlite数据库通过网络通讯传到PC端后进行数据的查看,为便运维时使用的,但是如果发现问题后需要对数据库的数据进行修改时,只能通过改了本地数据库再覆盖Android的数据库,这样操作起来非常麻烦,所以本章就是在当时的程序基础上实现了一个针对Android Sqlite数据库进行Sql操作的运维小工具。
实现效果
http://mpvideo.qpic.cn/0bc3iuacqaaahman6zevf5qvarodfbcqakaa.f10002.mp4?dis_k=5dcce405cc86200000c186e5f011c2a2&dis_t=1637631475&vid=wxv_2141207347430555648&format_id=10002&support_redirect=0&mmversion=false
Android本地的数据库操作我们还是用的Room框架,只不过网上大部分Room的教程都是类的查询,做运维时是需要自己写Sql的,所以是用了Sqlite里面对应的query和execsql这两个方法(查询和执行脚本用到)
# | 思路 |
---|---|
1 | 区分查询还是执行,通过脚本开头是不是select来判断 |
2 | select开头的脚本返回Cursor后动态生成字符串后通讯到PC端 |
3 | 不是select开头的使用execsql直接执行脚本 |
4 | 通讯方式还是用前篇一样的NanoMsg |
核心函数
使用Room返回的对象下面,有一个openHelper.writableDatabase,在这下面就可以找到query和execsql两个方法,用于执行脚本
其实execSQL执行脚本这个比较简单,通讯过来的脚本是什么样,直接执行就完成了。关键是用query查询的怎么样展示出来。
Query的数据呈现
Query返回Cursor
点击Query的方法后可以看到方法中直接就是返回的Cursor、
因为手动写的Sql,并不能知道要返回的对应类,所以在返回数据的时候需要对Cursor进行动态数据的处理。
Cursor中有columncount和columnNames,通过这两个可以得到当前的游标返回的列数和列名。
代码语言:javascript复制 val sb = StringBuilder()
//生成对应列名
val columnqty = it.columnCount
for (i in 0 until columnqty) {
sb.append("[${it.columnNames[i]}]").append(",")
}
sb.deleteCharAt(sb.lastIndexOf(","))
sb.append("rn")
而Cursor中获取数据时,都是用的getString、getInt、getFloat等方式,所以在获取数据前,首先需要判断当前列是什么数据类型,然后根据对应的数据类型使用相应的函数获取到数据。Cursor中有个getType的函数,通过这个方法可以获取到对应的数据类型,核心代码如下:
代码语言:javascript复制//生成对应数据
it.moveToFirst()
do {
for (i in 0 until columnqty) {
when (it.getType(i)) {
Cursor.FIELD_TYPE_STRING -> {
sb.append(it.getString(i))
}
Cursor.FIELD_TYPE_INTEGER -> {
sb.append(it.getInt(i))
}
Cursor.FIELD_TYPE_FLOAT -> {
sb.append(it.getFloat(i))
}
Cursor.FIELD_TYPE_BLOB -> {
sb.append(it.getBlob(i))
}
else -> {
sb.append(it.getString(i))
}
}
sb.append(",")
}
sb.deleteCharAt(sb.lastIndexOf(","))
sb.append("rn")
} while (it.moveToNext())
上面代码中使用了do while,主要是一开始用的while发现第一条数据会忽略掉了,所以用Do while实现后问题解决。
封装好的ExecSql方法
代码语言:javascript复制 private fun ExecSql(sql: String) {
val exectype = if (sql.trimStart().startsWith("select")) {
1
} else {
2
}
//加载AppDataBase
val db = DbUtil().getDatabase(this);
val execsqlScope = CoroutineScope(Job())
execsqlScope.launch(Dispatchers.IO) {
try {
when (exectype) {
1 -> {
val cursor = db.openHelper.writableDatabase.query(sql)
cursor?.let {
val sb = StringBuilder()
//生成对应列名
val columnqty = it.columnCount
for (i in 0 until columnqty) {
sb.append("[${it.columnNames[i]}]").append(",")
}
sb.deleteCharAt(sb.lastIndexOf(","))
sb.append("rn")
//生成对应数据
it.moveToFirst()
do {
for (i in 0 until columnqty) {
when (it.getType(i)) {
Cursor.FIELD_TYPE_STRING -> {
sb.append(it.getString(i))
}
Cursor.FIELD_TYPE_INTEGER -> {
sb.append(it.getInt(i))
}
Cursor.FIELD_TYPE_FLOAT -> {
sb.append(it.getFloat(i))
}
Cursor.FIELD_TYPE_BLOB -> {
sb.append(it.getBlob(i))
}
else -> {
sb.append(it.getString(i))
}
}
sb.append(",")
}
sb.deleteCharAt(sb.lastIndexOf(","))
sb.append("rn")
} while (it.moveToNext())
//传输数据
VNanoNNPairUtils.getInstance().Send(sb.toString().toByteArray())
withContext(Dispatchers.Main) {
tvshow.append("查询数据完成发送rn")
}
}
}
else -> {
db.runInTransaction {
db.openHelper.writableDatabase.execSQL(sql)
}
//传输数据
VNanoNNPairUtils.getInstance().Send("更新完成".toByteArray())
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
tvshow.append(e.message "rn")
}
}
}
}
TIPS
如上图,我这里返回的显示格式是第一行为列名,然后每个是对应的数据,其实掌握了动态生成的方法后,完全也可以自己拼装成Json的方法实现,我这主要自己通讯,用Json的方式每一条数据都要加一个列表,通讯的数据流太大,为了节省点资源还是改为了上面的方式。
顺便说一下,我又重新下了VS2022,C#这块直接用的VS2022编译的,新的编译器中智能提示实现在比原来强大太多了,看上图红框中就知道了。
后来找了个OpenCV的Demo直接在VS2022下打开升级编译后,也是一切正常,暂时看不出什么问题,并且鼠标指针悬停时的提示参数显示也比VS2019详细了好多,里面还有热重载的功能,等有时间也测试下,感觉项目整体升级到VS2022的日期越来越近了。
关于数据库的通讯,及通讯的方式,可以看《实现Android本地Sqlite数据库网络传输到PC端》这篇文中,最后这个Demo的源码地址如下,GitHub上不去的可以点击文末的原文链接,上面是码云的源码地址。
源码地址:
https://github.com/Vaccae/TransAndroidSqliteDBDemo.git
完