数据库调试工具:Android Debug Database
以前调试数据库的时候需要查看数据库,都是从手机里先用adb pull命令到电脑上,再用DB Browser(SQLite数据库浏览器)查看.db数据库文件。步骤麻烦,而且不能实时的查看手机应用的数据库内容。
最近发现一个非常好用的数据库调试工具:Android Debug Database,实时调试数据库,又将一个开发调试神器收入囊中。此库的作者是大名鼎鼎的amitshekhariitbhu,印度顶尖的Android布道师。写过不少著名的开源库,比如 Fast-Android-Networking 。
GitHub:amitshekhariitbhu/Android-Debug-Database
1、添加依赖
在项目的build.gradle文件中添加Android Debug Database依赖,使用debugImplementation而不是implementation。仅在调试版本中编译,而不在发行版本中编译。免得造成隐私数据泄露!
//Android Debug Database
debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
就是这么简单易用,和项目代码零耦合。只需要添加一下依赖。debug的时候自动带上数据库调试的工具。LeakCanary内存泄漏工具也是如此,借助ContentProvider的注册实现。
2、查看数据库
究竟如何方便的查看数据库呢?来看看。
2.1、获取链接
①查看Debug级别的log,用DebugDB关键字过滤log,可以找到查看数据库的链接地址:
②还可以通过调用DebugDB.getAddressLog()方法获取数据库调试地址。
public class DebugDB {
private static final String TAG = DebugDB.class.getSimpleName();
private static final int DEFAULT_PORT = 8080;
...
public static String getAddressLog() {
Log.d(TAG, addressLog);
return addressLog;
}
...
}
③官方的文档建议用反射获取,不建议直接调用上述方法:由于此库是自动初始化的,因此,如果要获取地址日志,请添加以下方法并调用(我们必须这样做,以避免发布版本中的构建错误,因为此库将不包含在发布版本中)反射。
public static String getDebugDBAddressLog() {
String addressLog = "";
if (BuildConfig.DEBUG) {
try {
Class<?> debugDB = Class.forName("com.amitshekhar.DebugDB");
Method getAddressLog = debugDB.getMethod("getAddressLog");
addressLog = (String) getAddressLog.invoke(null);
} catch (Exception ignore) {
//
}
}
return addressLog;
}
2.2、浏览器查看数据库
在电脑浏览器中打开链接就可以查看手机调试应用的全部数据库文件
2.3、注意
①手机和电脑须在同一个局域网下,192.168.0.*是本地局域网段。
②如果用的是USB连接,没有局域网怎么办?使用adb命令forward,转发PC数据包到手机adbd。然后打开http://localhost:8080
adb forward tcp:8080 tcp:8080
3、配置
Android-Debug-Database使用默认的工具就足够了,但是难免有一些特殊的场景需要额外配置。
3.1、端口配置
默认的端口配置在工具库的代码里
private static final int DEFAULT_PORT = 8080;
①在values/values.xml资源文件中也有定义,获取不到就用上面默认的。
<string name="PORT_NUMBER" translatable="false">8080</string>
<string name="app_name">Debug-DB</string>
所以修改端口也可以重新定义对应的资源,覆盖aar包里的定义。
②在项目的build.gradle文件中添加配置,和上面的道理一样。增加一个资源覆盖默认的。这样做的好处是不会将资源编译到release版本中。
debug {
resValue("string", "PORT_NUMBER", "8081")
}
3.2、数据库账号密码配置
如果调试的数据库需要密码怎么办?只推荐在build.gradle中额外添加debug模式的string资源。千万不要在values.xml中添加,否则release也会将密码编到apk中。
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
resValue("string", "DB_PASSWORD_PERSON", "password")
}
}
3.3、自定义数据库文件
如果要调试自定义数据库文件,请添加以下方法并调用:
public static void setCustomDatabaseFiles(Context context) {
if (BuildConfig.DEBUG) {
try {
Class<?> debugDB = Class.forName("com.amitshekhar.DebugDB");
Class[] argTypes = new Class[]{HashMap.class};
Method setCustomDatabaseFiles = debugDB.getMethod("setCustomDatabaseFiles", argTypes);
HashMap<String, Pair<File, String>> customDatabaseFiles = new HashMap<>();
// set your custom database files
customDatabaseFiles.put(ExtTestDBHelper.DATABASE_NAME,
new Pair<>(new File(context.getFilesDir() + "/" + ExtTestDBHelper.DIR_NAME +
"/" + ExtTestDBHelper.DATABASE_NAME), ""));
setCustomDatabaseFiles.invoke(null, customDatabaseFiles);
} catch (Exception ignore) {
}
}
}
4、部分源码阅读
源码阅读理解部分。主要涉及到Android和服务后端的一些技术,估计开发者amitshekhariitbhu是全栈。
4.1、DebugDB入口类
该类包含一个ClientServer客户端服务对象,除此之外对外暴露一些静态方法,开发者无需了解该库的其它实现细节,快速使用毫无问题。
public class DebugDB {
private static final String TAG = DebugDB.class.getSimpleName();
private static final int DEFAULT_PORT = 8080;
private static ClientServer clientServer;
private static String addressLog = "not available";
//私有构造方法
private DebugDB() {
// This class in not publicly instantiable
}
//初始化服务
public static void initialize(Context context, DBFactory dbFactory) {
int portNumber;
try {
portNumber = Integer.valueOf(context.getString(R.string.PORT_NUMBER));
} catch (NumberFormatException ex) {
Log.e(TAG, "PORT_NUMBER should be integer", ex);
portNumber = DEFAULT_PORT;
Log.i(TAG, "Using Default port : " + DEFAULT_PORT);
}
clientServer = new ClientServer(context, portNumber, dbFactory);
clientServer.start();
addressLog = NetworkUtils.getAddressLog(context, portNumber);
Log.d(TAG, addressLog);
}
//简化显示
public static String getAddressLog()
public static void shutDown()
public static void setCustomDatabaseFiles(HashMap<String, Pair<File, String>> customDatabaseFiles)
public static void setInMemoryRoomDatabases(HashMap<String, SupportSQLiteDatabase> databases)
public static boolean isServerRunning()
}
initialize()方法在DebugDBInitProvider注册onCreate的时候被调用,初始化DebugDB服务,继续向下看
4.2、ClientServer服务
ClientServer继承自Runnable,是一个单线程的服务后端,开启一个线程启动自己,开始服务。
public class ClientServer implements Runnable {
private final int mPort;
private final RequestHandler mRequestHandler;
private boolean mIsRunning;
private ServerSocket mServerSocket;
public ClientServer(Context context, int port, DBFactory dbFactory) {
mRequestHandler = new RequestHandler(context, dbFactory);
mPort = port;
}
public void start() {
mIsRunning = true;
new Thread(this).start();
}
...
}
ServerSocket涉及到网络相关的操作。RequestHandler用来处理PC网页上的页面处理请求。
4.3、RequestHandler
前端的若干类型的页面处理都在RequestHandler这里操作,就不继续往下追代码了。值得一提的是作者用到了GJson,将请求转为轻量级的json数据在后台处理,解析后再进行数据库操作。