SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)

目录

*
一、首先下载、安装
二、配置

+
* (一)、安装Android插件
* (二)、选择安装插件
* (三)、获取jar文件、并配置到项目中
* (四)、在代码中启动SQLiteStudioService
* (五)、运行SQLiteStudio添加数据库
三、从源码分析、到解决找不到数据库问题

+
* (一)、首先看看SQLiteStudioRemote.jar中有哪些代码
* (二)、代码分析
* (三)、总结

一、首先下载、安装

官方下载地址:https://sqlitestudio.pl/
绿色软件,解压直接使用。

二、配置

(一)、安装Android插件

点击 tools -> Open configuration dialog 见图2-1

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-1]
; (二)、选择安装插件

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-2]
(三)、获取jar文件、并配置到项目中

1、点击tools -> Get Android connector JAR file 如图2-3

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-3]

2、得到SQLiteStudioRemote.jar文件,配置到自己的Android项目中。见图2-3-1,图2-3-2

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-3-1]
SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-3-2]
; (四)、在代码中启动SQLiteStudioService
  SQLiteStudioService.instance().start(this)
(五)、运行SQLiteStudio添加数据库

1、如图2-5-1 点击左上角添加数据库按钮

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-5-1]

2、会看到如图2-5-2 的对话框,数据库类型选择Android SQlite

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-5-2]

3、点击Android database URL 右边的文件夹按钮 如图2-5-3

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-5-3]

4、会看到如图2-5-4的对话框,可以看到最低端的选项框已经识别到了数据库,不过无法点击”OK”按钮

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-5-4]

不知道是软件bug还是出于什么考虑,不能点击的原因是在于Database的选项列表必须要大于1,大于1了才能允许点击”ok按钮”

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-5-5]

5、点击最底部Batabase选项 绿色的”+”号按钮,随便命名一个数据库名称点击”OK”创建就可以了,如图2-5-6、2-5-7

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-5-6]
SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-5-7]

6、接着点击”OK”就可以了,我们就可以正常查看和操作数据库了

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-5-8]

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图2-5-9]

; 三、从源码分析、到解决找不到数据库问题

我们在使用的时候可能会遇到找不到数据库的情况,原因可能是因为数据库路径不对,SQLiteStudioService这块代码默认找的是app包名下的databases目录:/data/data/xxx.xxx.xxx/databases,如果我们的项目代码中有自定义存储路径,那么SqliteStudio肯定是识别不到,如果不想更改自己数据库的路径,那就需要分析看看源码,从源码上去修改读取路径

(一)、首先看看SQLiteStudioRemote.jar中有哪些代码

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
可以看出代码并不多。主要原理是app启动Service、Service通过Socket通信与SQLiteStudio进行通信。
; (二)、代码分析

先从代码入口看起

  SQLiteStudioService.instance().start(this)

启动代码就一句话,所以从SQLiteStudioService看起。

SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)
[图3-2-1]

从图3-2-1可以看到SQLiteStudioService是继承Service。这个Service又实现实现了一个单例的方法

  public static SQLiteStudioService instance() {
        if (staticInstance == null) {
            staticInstance = new SQLiteStudioService();
        }

        return staticInstance;
    }

然后再看看start方法如下代码:

    public void start(Context context) {
        if (!this.running) {
            this.listener = new SQLiteStudioListener(context);
            this.listener.setPort(this.port);
            this.listener.setPassword(this.password);
            this.listener.setIpBlackList(this.ipBlackList);
            this.listener.setIpWhiteList(this.ipWhiteList);
            this.listenerThread = new Thread(this.listener);
            this.listenerThread.start();
            this.running = true;
            Log.d(Utils.LOG_TAG, "Started instance on port " + this.port);
        }
    }

从代码中可以理解到start()方法主要是启动了一个线程。SQLiteStudioListener应该是一个实现了Runnable接口的类,新建了一个线程去执行。所以我们接下来就看SQLiteStudioListener类做了什么。(ps:这个代码就很迷,明明是继承了Service,并没有以一个service的启动流程去使用。还要自己去调start。实际上就是当一个单例的辅助类来使用了。)

public class SQLiteStudioListener implements Runnable, ClientJobContainer {

...省略
    public void run() {
        if (this.init()) {
            Log.d(Utils.LOG_TAG, "Listening for clients...");

            while(this.isRunning()) {
                try {
                    Socket clientSocket = this.serverSocket.accept();
                    ClientHandler clientHandler = new ClientHandler(clientSocket, this.context, this, this.authService);
                    this.clientJobs.add(clientHandler);
                    this.threadPool.execute(clientHandler);
                } catch (IOException var3) {
                }
            }

            Log.d(Utils.LOG_TAG, "Listener thread finished.");
        }
    }

    private boolean init() {
        try {
            this.serverSocket = new ServerSocket(this.port, 5);
            this.serverSocket.setSoTimeout(1000);
        } catch (IOException var2) {
            Log.e(Utils.LOG_TAG, "Error while opening listening socket: " + var2.getMessage(), var2);
            return false;
        }

        this.jobsQueue = new LinkedBlockingDeque(1);
        this.clientJobs = new CopyOnWriteArrayList();
        this.threadPool = new ThreadPoolExecutor(20, 20, 10L, TimeUnit.SECONDS, this.jobsQueue);
        this.authService = new AuthServiceImpl(this.password, this.ipBlackList, this.ipWhiteList);
        return true;
    }
...省略
}

主要就看run方法和init方法,从代码上去看run方法主要是去初始化,并返回是否已初始化成功,接着看看init方法都做了些什么,创建了一个Socket服务端serverSocket,存放客户端的数组clientJobs,线程池threadPool,authService是验证身份使用(因为可以设置安全密码)。
然后接着看run方法,就是while循环等待客户端连接,得到socket对象后,又创建了ClientHandler。而this.threadPool.execute(clientHandler);不难看出,是线程池执行一个Runnable对象,所以ClientHandler也是实现了Runnable接口的类。所以接下我们需要看ClientHandler做了什么。

public class ClientHandler implements Runnable {
... 省略
    public void run() {

        String ip = this.clientSocket.getInetAddress().getHostAddress();
        Log.d(Utils.LOG_TAG, "New client from " + ip);
        if (!this.authService.isIpAllowed(ip)) {

            Log.e(Utils.LOG_TAG, "Client's IP address not allowed: " + ip + ", disconnecting.");
            this.cleanUp();
        } else if (!this.init()) {

            Log.e(Utils.LOG_TAG, "Could not initialize handler for the client.");
            this.cleanUp();
        } else {

            while(this.isRunning() && !this.denyAccess) {
                this.readClientChannel();
            }

            this.cleanUp();
            Log.d(Utils.LOG_TAG, "Disconnected client " + ip);
        }
    }
}

ClientHandler的代码得从run方法看起。第一步:获取ip,第二步:判断是否允许连接,第三步:初始化并判断是否初始化成功,第四步:开启循环接受消息。
所以我们主要看看第三步中得init()做了什么?

    private boolean init() {
        try {
            this.inputStream = this.clientSocket.getInputStream();
            this.outputStream = this.clientSocket.getOutputStream();
        } catch (IOException var2) {
            return false;
        }

        this.dataInputStream = new DataInputStream(this.inputStream);
        return true;
    }

init()主要是创建了读写得I\O接口,然后我再看一下readClientChannel()方法做了什么。

    private void readClientChannel() {
        if (!this.clientSocket.isConnected()) {

            this.close();
        } else {
            try {

                switch(this.currentState) {
                case READING_SIZE:
                    this.dataInputStream.readFully(this.sizeBuffer);
                    break;
                case READING_DATA:
                    this.dataInputStream.readFully(this.dataBuffer);
                }
            } catch (EOFException var4) {
                this.close();
                return;
            } catch (IOException var5) {
                Log.e(Utils.LOG_TAG, "Error while reading input from client: " + var5.getMessage(), var5);
                this.sendError(ClientHandler.Error.ERROR_READING_FROM_CLIENT);
                return;
            }

            switch(this.currentState) {
            case READING_SIZE:
                int size = ByteBuffer.wrap(this.sizeBuffer).order(ByteOrder.LITTLE_ENDIAN).getInt();
                if (size > 10485760) {
                    Log.e(Utils.LOG_TAG, "Error while reading input from client: maximum size exceeded: " + size);
                    this.sendError(ClientHandler.Error.ERROR_READING_FROM_CLIENT);
                    return;
                }

                this.currentState = ClientHandler.State.READING_DATA;
                this.dataBuffer = new byte[size];
                break;
            case READING_DATA:
                String str = null;

                try {

                    str = new String(this.dataBuffer, "UTF-8");
                } catch (UnsupportedEncodingException var3) {
                    Log.e(Utils.LOG_TAG, "Error while reading data from client: " + var3.getMessage(), var3);
                    this.sendError(ClientHandler.Error.ERROR_READING_FROM_CLIENT);
                    return;
                }

                this.handleRequest(str);
                this.currentState = ClientHandler.State.READING_SIZE;
            }

        }
    }

从上面的一大长串代码中,找到关键代码this.handleRequest(str);这里是最终处理消息的代码。我们接下来看看handleRequest()做了什么

    private void handleRequest(String data) {
        JSONObject json;
        try {

            json = new JSONObject(data);
        } catch (JSONException var7) {
            this.sendError(ClientHandler.Error.INVALID_FORMAT);
            return;
        }

        HashMap<String, Object> map = (HashMap)JsonConverter.fromJsonValue(json);
        if (!this.authorized) {
            this.authorize(map);
        } else if (!map.containsKey("cmd")) {
            this.sendError(ClientHandler.Error.NO_COMMAND_SPECIFIED);
        } else {
            ClientHandler.Command cmd;
            try {

                cmd = ClientHandler.Command.valueOf("" + map.get("cmd"));
            } catch (IllegalArgumentException var6) {
                this.sendError(ClientHandler.Error.UNKNOWN_COMMAND);
                return;
            }

            switch(cmd) {
            case LIST:

                this.send("list", this.dbService.getDbList());
                break;
            case QUERY:

                this.execAndRespond(map.get("db"), "" + map.get("query"));
                break;
            case DELETE_DB:

                this.deleteDbAndRespond(map.get("db"));
            }

        }
    }

从代码中可以看出,SQLiteStudio的通讯内容是json字符串,以指令的形式执行查询任务。SQLiteStudio如何得知我们app内的数据库,从上面的代码中就可以看出关键代码”this.dbService.getDbList()”。我们看一下this.dbService.getDbList()都做了什么事情。

    public List<String> getDbList() {
        List<String> filteredList = new ArrayList();
        String[] var2 = this.context.databaseList();
        int var3 = var2.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            String dbFile = var2[var4];
            if (!dbFile.endsWith("-journal")) {
                filteredList.add(dbFile);
            }
        }

        return filteredList;
    }

现在一目了然了,SQLiteStudio就是从this.context.databaseList()获取到数据库的。所以我们接下来就只需要修改SQLiteStudioDbService中的getDbList方法,返回自己的数据库路径即可。

(三)、总结

只需要修改SQLiteStudioDbService类中的getDbList方法,返回自己的数据库路径即可。

Original: https://blog.csdn.net/gkhbaby/article/details/120923416
Author: 古baby
Title: SQLiteStudio工具实时操作Android应用中数据库内容(从源码分析找不到数据库问题)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/816215/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球