zl程序教程

您现在的位置是:首页 >  数据库

当前栏目

制作一个Android Sqlite远程运维小工具

2023-03-15 22:07:33 时间

前言

前面的文章中《实现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,通过这两个可以得到当前的游标返回的列数和列名。

              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("
")

而Cursor中获取数据时,都是用的getString、getInt、getFloat等方式,所以在获取数据前,首先需要判断当前列是什么数据类型,然后根据对应的数据类型使用相应的函数获取到数据。Cursor中有个getType的函数,通过这个方法可以获取到对应的数据类型,核心代码如下:

//生成对应数据
    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("
")
    } while (it.moveToNext())

上面代码中使用了do while,主要是一开始用的while发现第一条数据会忽略掉了,所以用Do while实现后问题解决。

封装好的ExecSql方法

   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("
")

                            //生成对应数据
                            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("
")
                            } while (it.moveToNext())

                            //传输数据
                            VNanoNNPairUtils.getInstance().Send(sb.toString().toByteArray())
                            withContext(Dispatchers.Main) {
                                tvshow.append("查询数据完成发送
")
                            }
                        }
                    }
                    else -> {
                        db.runInTransaction {
                            db.openHelper.writableDatabase.execSQL(sql)
                        }
                        //传输数据
                        VNanoNNPairUtils.getInstance().Send("更新完成".toByteArray())
                    }
                }
            } catch (e: Exception) {
                withContext(Dispatchers.Main) {
                    tvshow.append(e.message + "
")
                }
            }
        }
    }

TIPS

如上图,我这里返回的显示格式是第一行为列名,然后每个是对应的数据,其实掌握了动态生成的方法后,完全也可以自己拼装成Json的方法实现,我这主要自己通讯,用Json的方式每一条数据都要加一个列表,通讯的数据流太大,为了节省点资源还是改为了上面的方式。

顺便说一下,我又重新下了VS2022,C#这块直接用的VS2022编译的,新的编译器中智能提示实现在比原来强大太多了,看上图红框中就知道了。

后来找了个OpenCV的Demo直接在VS2022下打开升级编译后,也是一切正常,暂时看不出什么问题,并且鼠标指针悬停时的提示参数显示也比VS2019详细了好多,里面还有热重载的功能,等有时间也测试下,感觉项目整体升级到VS2022的日期越来越近了。

关于数据库的通讯,及通讯的方式,可以看《实现Android本地Sqlite数据库网络传输到PC端》这篇文中,最后这个Demo的源码地址如下,GitHub上不去的可以点击文末的原文链接,上面是码云的源码地址。

源码地址:

https://github.com/Vaccae/TransAndroidSqliteDBDemo.git