- 一、首先下载、安装
- 二、配置
- (一)、安装Android插件
- (二)、选择安装插件
- (三)、获取jar文件、并配置到项目中
- (四)、在代码中启动SQLiteStudioService
- (五)、运行SQLiteStudio添加数据库
- 三、从源码分析、到解决找不到数据库问题
- (一)、首先看看SQLiteStudioRemote.jar中有哪些代码
- (二)、代码分析
- (三)、总结
官方下载地址:https://sqlitestudio.pl/
绿色软件,解压直接使用。
点击 tools -> Open configuration dialog 见图2-1
1、点击tools -> Get Android connector JAR file 如图2-3
2、得到SQLiteStudioRemote.jar文件,配置到自己的Android项目中。见图2-3-1,图2-3-2
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”就可以了,我们就可以正常查看和 *** 作数据库了
三、从源码分析、到解决找不到数据库问题(一)、首先看看SQLiteStudioRemote.jar中有哪些代码我们在使用的时候可能会遇到找不到数据库的情况,原因可能是因为数据库路径不对,SQLiteStudioService这块代码默认找的是app包名下的databases目录:/data/data/xxx.xxx.xxx/databases,如果我们的项目代码中有自定义存储路径,那么SqliteStudio肯定是识别不到,如果不想更改自己数据库的路径,那就需要分析看看源码,从源码上去修改读取路径
可以看出代码并不多。主要原理是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; } HashMapmap = (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 ListgetDbList() { 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方法,返回自己的数据库路径即可。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)