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

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

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

目录
    • 一、首先下载、安装
    • 二、配置
        • (一)、安装Android插件
        • (二)、选择安装插件
        • (三)、获取jar文件、并配置到项目中
        • (四)、在代码中启动SQLiteStudioService
        • (五)、运行SQLiteStudio添加数据库
    • 三、从源码分析、到解决找不到数据库问题
        • (一)、首先看看SQLiteStudioRemote.jar中有哪些代码
        • (二)、代码分析
        • (三)、总结

一、首先下载、安装

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

二、配置 (一)、安装Android插件

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

(二)、选择安装插件

(三)、获取jar文件、并配置到项目中

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

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

(四)、在代码中启动SQLiteStudioService
  SQLiteStudioService.instance().start(this)
(五)、运行SQLiteStudio添加数据库

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

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

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

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

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

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

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

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

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

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


可以看出代码并不多。主要原理是app启动Service、Service通过Socket通信与SQLiteStudio进行通信。

(二)、代码分析

先从代码入口看起

  SQLiteStudioService.instance().start(this)//启动代码

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

从图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() {
        //1、获取ip
        String ip = this.clientSocket.getInetAddress().getHostAddress();
        Log.d(Utils.LOG_TAG, "New client from " + ip);
        if (!this.authService.isIpAllowed(ip)) {
            //2、判断是否可以连接
            Log.e(Utils.LOG_TAG, "Client's IP address not allowed: " + ip + ", disconnecting.");
            this.cleanUp();
        } else if (!this.init()) {
            //3、初始化、并判断初始化是否成功
            Log.e(Utils.LOG_TAG, "Could not initialize handler for the client.");
            this.cleanUp();
        } else {
            //4、开启循环接受消息
            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()主要是创建了读写得IO接口,然后我再看一下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
            json = new JSONObject(data);
        } catch (JSONException var7) {
            this.sendError(ClientHandler.Error.INVALID_FORMAT);
            return;
        }

        HashMap 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 getDbList() {
        List 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方法,返回自己的数据库路径即可。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4744136.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-08
下一篇 2022-11-08

发表评论

登录后才能评论

评论列表(0条)

保存