前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实现Android本地Sqlite数据库网络传输到PC端

实现Android本地Sqlite数据库网络传输到PC端

作者头像
Vaccae
发布2021-10-12 15:47:00
1.1K0
发布2021-10-12 15:47:00
举报
文章被收录于专栏:微卡智享微卡智享

学更好的别人,

做更好的自己。

——《微卡智享》

本文长度为4578,预计阅读7分钟

前言

在开发初期,当Android端嵌入在硬件中,并且本地数据库单机业务逻辑挺多,往往要分析数据是否处理正常,需要直接从数据库中查看,这时我们一般都是将数据库拷贝到PC端后查看分析,在虚拟机中可以实现直接拷贝,但是真机无法直接访问Android端data/data/包名/databases的数据库路径,所以做了一个小Demo,通过网络将本地数据库文件传到PC端。

实现效果

Q1

为什么要做这个东西?

文章开头也说过,开发初期做业务测试的时候,往往查询功能还没做完,需要看数据库中业务逻辑做的是否对,数据是否正常,所以需要在数据库中查询。

主要我最近开发的是在硬件设备,装的Android平板控制,要求在断网情况下单机也能运行,所以基本的业务逻辑包括数据的保存都在本地处理,后台定时通讯上传数据,除了文章开头说的开发初期可以方便传上来数据库来分析,后期也是想通过这个方式实现本地的数据库备份。于是就有了这篇文章和Demo,文章最后还是会列出源码地址,想研究的小伙伴也可直接下载。

实现方式

微卡智享

流程设计

上图做了一个简单的流程设计图,还是很简单的,中间的数据库文件传输采用NanoMsg通讯,C#端用用的Nuget包中的NNanoMsg,Android端采用的我自己封装的VNanoMsg。有关NanoMsg相关的文章可以看《NanoMsg框架|NanoMsg的简介》及相关的一系列文章。

Demo中使用的库

Android:Room+LiveEventBus+VNanoMsg

代码语言:javascript
复制
allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://jitpack.io' }
    }
}

    //VNanoMsg通讯库
    implementation 'com.github.Vaccae:VNanoMsg:1.0.4'
    //LiveEventBus
    api 'io.github.jeremyliao:live-event-bus-x:1.8.0'
    //Room
    def room_version = "2.2.5"
    kapt "androidx.room:room-compiler:$room_version"
    implementation "androidx.room:room-runtime:$room_version"
    implementation "androidx.room:room-ktx:$room_version"

C#:NNanoMsg

要在Nuget包中添加NNanoMsg

Android端Demo

上图中是Android端Demo的所有类文件,主要多是的Room的类,像实体的创建,Dao的使用,还有数据库的创建等。

01

Room数据库创建

BaseDao

首先定义了一个BaseDao,这样创建Dao时直接继承自BaseDao不用再写Insert,update,delete的函数了。

代码语言:javascript
复制
package com.vaccae.roomdemo.bean

import androidx.room.*

@Dao
interface BaseDao<T> {
    @Transaction
    @Insert
    fun add(vararg arr:T)
    @Transaction
    @Insert
    fun add(arr:ArrayList<T>)

    @Transaction
    @Update
    fun upd(vararg arr:T)
    @Transaction
    @Update
    fun upd(arr:ArrayList<T>)

    @Transaction
    @Delete
    fun del(vararg arr:T)
    @Transaction
    @Delete
    fun del(arr:ArrayList<T>)
}

Productitem

这里直接用productitem这个文件,里面@Entity和@Dao都创建在一起了

代码语言:javascript
复制
package com.vaccae.roomdemo.bean

import androidx.room.ColumnInfo
import androidx.room.Dao
import androidx.room.Entity
import androidx.room.Query

@Entity(tableName = "Body", primaryKeys = ["Code", "BarCode"])
class ProductItem {
    @ColumnInfo(name = "Code")
    lateinit var code: String

    @ColumnInfo(name = "BarCode")
    lateinit var barcode: String

    @ColumnInfo(name = "Qty")
    var qty = 0
}

@Dao
interface ProductItemDao : BaseDao<ProductItem> {
    @Query("select * from Body")
    fun getAll(): List<ProductItem>
}

AppDataBase

AppDataBase是数据库的整个创建,数据库升级都在里面,其中可以通过DbUtil类调用实现,里面的testdb是数据库名,可以外部直接定义。

代码语言:javascript
复制
package com.vaccae.roomdemo.bean

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase


/**
 * 作者:Vaccae
 * 邮箱:3657447@qq.com
 * 创建时间:2020-04-14 14:29
 * 功能模块说明:
 */
@Database(entities = [Product::class,ProductItem::class], version = 2)
abstract class AppDataBase : RoomDatabase() {
    abstract fun ProductDao(): ProductDao

    abstract fun ProductItemDao():ProductItemDao
}

class DbUtil {

    //数据库升级
    var migration1_2 = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {
            val sql="CREATE TABLE if not exists Body(Code TEXT NOT NULL ," +
                    "BarCode TEXT NOT NULL,Qty INTEGER NOT NULL,PRIMARY KEY(Code,BarCode))"
            database.execSQL(sql)
        }
    }

    //创建单例
    private var INSTANCE: AppDataBase? = null

    fun getDatabase(context: Context): AppDataBase {
        if (INSTANCE == null) {
            synchronized(lock = AppDataBase::class) {
                if (INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(
                        context.applicationContext,
                        AppDataBase::class.java, "testdb"
                    )
                        .allowMainThreadQueries()//允许在主线程查询数据
                        .addMigrations(migration1_2)//数据库升级时执行
                        .fallbackToDestructiveMigration()
                        .build()
                }
            }
        }
        return INSTANCE!!
    }
}

外部的调用方式

代码语言:javascript
复制
    private fun CreateProductItem() {
        //定义明细列表
        val itemlist = ArrayList<ProductItem>()
        //加载AppDataBase
        val db = DbUtil().getDatabase(this);
        //显示所有Product的明细
        val list = db.ProductDao().getAll()

        list.forEach {
            for (i in 1..3) {
                val item = ProductItem()
                item.code = it.code
                item.barcode = it.code + i.toString()
                item.qty = 1
                itemlist.add(item)
            }
        }
        db.ProductItemDao().add(itemlist)

        //显示明细
        val getlist = db.ProductItemDao().getAll()
        tvshow.text = ""
        getlist.forEach {
            tvshow.append(
                it.code + " " + it.barcode
                        + " " + it.qty + "\r\n"
            )
        }
    }

02

获取本机IP地址

Demo是用Android端作为通讯的服务器,所以需要获取到本机的IP地址,用于VNanoMsg绑定服务端口,所以写了一个获取本地IP地址的类PhoneAdrUtil。

代码语言:javascript
复制
package com.vaccae.roomdemo

import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.net.wifi.WifiManager
import android.os.Build
import androidx.annotation.RequiresApi
import java.net.Inet4Address
import java.net.InetAddress
import java.net.NetworkInterface
import java.util.*

/**
 * 作者:Vaccae
 * 邮箱:3657447@qq.com
 * 创建时间:13:14
 * 功能模块说明:
 */
class PhoneAdrUtil {

    companion object {

        fun getIpAdr(context: Context): String? {
            val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

            if (Build.VERSION.SDK_INT < 23) {
                val networiinfo = cm.activeNetworkInfo
                networiinfo?.let {
                    if (it.type == ConnectivityManager.TYPE_WIFI) {
                        return getWIfiIpAdr(context)
                    } else if (it.type == ConnectivityManager.TYPE_MOBILE) {
                        return getMobileIpAdr()
                    }
                }
            } else {
                val network = cm.activeNetwork
                network?.let { it ->
                    val networkCapabilities = cm.getNetworkCapabilities(it)
                    networkCapabilities?.let { item ->
                        if (item.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
                            return getWIfiIpAdr(context)
                        }else if (item.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
                            return getMobileIpAdr()
                        }
                    }
                }
            }

            return null
        }

        private fun getMobileIpAdr():String {
            var ipstr = ""
            val en: Enumeration<NetworkInterface> = NetworkInterface.getNetworkInterfaces()
            while (en.hasMoreElements()) {
                val intf: NetworkInterface = en.nextElement()
                val enumIpAddr: Enumeration<InetAddress> = intf.inetAddresses
                while (enumIpAddr.hasMoreElements()) {
                    val inetAddress: InetAddress = enumIpAddr.nextElement()
                    if (!inetAddress.isLoopbackAddress && inetAddress is Inet4Address) {
                        ipstr = inetAddress.hostAddress.toString()
                        return ipstr
                    }
                }
            }
            return ipstr
        }

        private fun getWIfiIpAdr(context: Context):String{
            val wifiManager =
                context.getSystemService(Context.WIFI_SERVICE) as WifiManager
            val wifiinfo = wifiManager.connectionInfo
            return ChangeIP2String(wifiinfo.ipAddress)
        }

        private fun ChangeIP2String(ip: Int): String {
            return "" + (ip and 0xFF) + "." +
                    ((ip shr 8) and 0xFF) + "." +
                    ((ip shr 16) and 0xFF) + "." +
                    (ip shr 24 and 0xFF);
        }
    }



}

03

VNanoMsg数据通讯

不用VNanoMsg可以自己写Socket通讯或是别的,我自己用NanoMsg主要原因是轻量,方便,也有多种模式,像订阅,消息队列等。这次用的Pair模式是是一对一的,服务端和客户端谁先启动都可以,不用像传统的Socket必须服务端先启动,客户端再连接,而且Pair模式下send是不阻塞,recv是阻塞的,并且通讯时多大的包recv可以一次性全部接收完,Demo中我就是把文件整个读完后一起send的,然后一个Recv全部接收完了,完全不用自己去写循环读取和判断是否接收完等。

代码语言:javascript
复制
package com.vaccae.roomdemo

import android.R.attr
import com.jeremyliao.liveeventbus.LiveEventBus
import com.vaccae.vnanomsg.NNPAIR
import kotlinx.coroutines.*
import java.io.File
import java.io.FileInputStream
import java.lang.Exception
import android.R.attr.path

import android.R.string.no


/**
 * 作者:Vaccae
 * 邮箱:3657447@qq.com
 * 创建时间:09:31
 * 功能模块说明:
 */
object VNanoNNPairUtils {

    private var mNNPAIR: NNPAIR? = null

    private var isOpenListen = false;

    fun IsRecvListen(): Boolean {
        return isOpenListen
    }

    fun getInstance(): VNanoNNPairUtils {
        mNNPAIR ?: run {
            synchronized(VNanoNNPairUtils::class.java) {
                mNNPAIR = NNPAIR()
            }
        }
        return VNanoNNPairUtils
    }

    fun Bind(ipadr: String): VNanoNNPairUtils {
        mNNPAIR?.let {
            //var ipstr = "tcp://192.168.10.155:8157"
            it.bind(ipadr)
        }
        return VNanoNNPairUtils
    }

    fun UnBind() {
        mNNPAIR?.let {
            it.shutdownbind()
        }
    }

    private fun byteMerger(bt1: ByteArray, bt2: ByteArray): ByteArray {
        val bt3 = ByteArray(bt1.size + bt2.size)
        System.arraycopy(bt1, 0, bt3, 0, bt1.size)
        System.arraycopy(bt2, 0, bt3, bt1.size, bt2.size)
        return bt3
    }

    fun Send(file: File) {
        mNNPAIR?.let {
            var filebytearray = ByteArray(0)
            var len = 0;
            var byteArray = ByteArray(1024)
            val inputStream: FileInputStream = FileInputStream(file)

            //判断是否读到文件末尾
            while (inputStream.read(byteArray).also { len = it } != -1) {
                //将文件循环写入fielbytearray
                filebytearray = byteMerger(filebytearray, byteArray)
            }

            it.send(filebytearray)
        }
    }

    fun Send(byte: ByteArray) {
        mNNPAIR?.let { it.send(byte) }
    }


    fun StartRecvListen() {
        mNNPAIR?.let {
            isOpenListen = true;
            val recvScope = CoroutineScope(Job())
            recvScope.launch {
                try {
                    withContext(Dispatchers.IO) {
                        while (isOpenListen) {
                            delay(50)
                            val recvstr = it.recv()
                            recvstr?.let {
                                LiveEventBus.get("NNPair", String::class.java)
                                    .postOrderly(it)
                            }
                        }
                    }
                } catch (e: Exception) {
                    throw e
                }
            }
        }
    }


    fun StopRecvListen() {
        isOpenListen = false;
    }


}

C#桌面端Demo

客户端比较简单,就是输入IP地址进行发送接收,然后保存到本地即可。

01

FileHelper文件保存

代码语言:javascript
复制
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace nanomsgclient
{
    public class FileHelper
    {
        /// <summary>
        /// 将文件转换成byte[]数组
        /// </summary>
        /// <param name="fileUrl">文件路径文件名称</param>
        /// <returns>byte[]数组</returns>
        public static byte[] FileToByte(string fileUrl)
        {
            try
            {
                using (FileStream fs = new FileStream(fileUrl, FileMode.Open, FileAccess.Read))
                {
                    byte[] byteArray = new byte[fs.Length];
                    fs.Read(byteArray, 0, byteArray.Length);
                    return byteArray;
                }
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// 将byte[]数组保存成文件
        /// </summary>
        /// <param name="byteArray">byte[]数组</param>
        /// <param name="fileName">保存至硬盘的文件路径</param>
        /// <returns></returns>
        public static bool ByteToFile(byte[] byteArray, string fileName)
        {
            bool result = false;
            try
            {
                using (FileStream fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write))
                {
                    fs.Write(byteArray, 0, byteArray.Length);
                    result = true;
                }
            }
            catch
            {
                result = false;
            }
            return result;
        }

    }
}

02

Form界面

简单的窗体布局,整个代码也写到了一起

代码语言:javascript
复制
using nanomsgclient;
using NNanomsg.Protocols;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace NanoRecvDataBase
{
    public partial class Form1 : Form
    {

        private PairSocket pairSocket = null;

        public Form1()
        {
            InitializeComponent();

            CheckForIllegalCrossThreadCalls = false;

            _tbMsg = tbMsg;
        }

        #region 文本框操作

        //定义文本框
        private static TextBox _tbMsg;

        //定义Action
        private Action<string> TextShowAction = new Action<string>(TextShow);

        //定义更新UI函数
        private static void TextShow(string sMsg)
        {
            //当文本行数大于500后清空
            if (_tbMsg.Lines.Length > 500)
            {
                _tbMsg.Clear();
            }

            string ShowMsg = DateTime.Now + "  " + sMsg + "\r\n";
            _tbMsg.AppendText(ShowMsg);

            //让文本框获取焦点 
            _tbMsg.Focus();
            //设置光标的位置到文本尾 
            _tbMsg.Select(_tbMsg.TextLength, 0);
            //滚动到控件光标处 
            _tbMsg.ScrollToCaret();
        }

        #endregion

        private void btnRecv_Click(object sender, EventArgs e)
        {
            try
            {
                if (pairSocket == null)
                {
                    pairSocket = new PairSocket();
                    var ipadr = tbipadr.Text;
                    TextShow("要连接的IP地址为:" + ipadr);
                    pairSocket.Connect(ipadr);
                }


                var res = new Task<string>(() =>
                {
                    pairSocket.Send(Encoding.UTF8.GetBytes("getdbnames"));

                    while (true)
                    {
                        Thread.Sleep(50);
                        //接收数据
                        byte[] buffer = pairSocket.Receive();
                        if (buffer != null)
                        {
                            string recvstr = Encoding.UTF8.GetString(buffer);
                            return recvstr;
                        }
                    }
                });
                res.Start();

                var getdbnum = res.Result;
                var dbnames = getdbnum.Split('#');
                TextShow("接收到数据库文件个数:" + dbnames.Length);

                var resfile = new Task<String>(() =>
                {
                    for (int i = 0; i < dbnames.Length; ++i)
                    {
                        string filename = dbnames[i];
                        pairSocket.Send(Encoding.UTF8.GetBytes("#" + filename));

                        while (true)
                        {
                            Thread.Sleep(50);
                            //接收数据
                            byte[] buffer = pairSocket.Receive();
                            if (buffer != null)
                            {
                                var pathfile = "D:\\DataBase\\" + filename;
                                FileHelper.ByteToFile(buffer, pathfile);
                                TextShow(pathfile + "文件传输成功");
                                break;
                            }
                        }
                    }
                    return "传输完成";
                });
                resfile.Start();

                TextShow(resfile.Result);


            }
            catch (Exception ex)
            {
                TextShow(ex.Message);

            }
        }
    }
}

以上就是一个简单的Android将本地Sqlite数据库传输到PC端的程序就实现了。

源码地址

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-09-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微卡智享 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 流程设计
  • Demo中使用的库
  • Android端Demo
  • C#桌面端Demo
  • 源码地址
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档