简介
前面分析了Matrix 中 IO Canary 模块,了解了对应的检测原理实现,本文继续分析 SQLite Lint 模块,在分析这块内容的时候最好知道对于SQLite Lint的性能主要检测的内容有哪些,这个可以参考这篇文章
https://mp.weixin.qq.com/s/laUgOmAcMiZIOfM2sWrQgw
文中介绍到他可以在我们开发,测试或者灰度阶段就可以发现隐藏的问题,既然要检测sql语句,那么就设计到了怎么收集App运行时的sql执行信息,包括执行语句、创建的表信息等。其中表相关信息可以通过 pragma 命令得到。对于执行语句,有两种情况
a)DB 框架提供了回调接口。比如微信使用的是 WCDB ,很容易就可以通过MMDataBase.setSQLiteTrace 注册回调拿到这些信息。
b)若使用 Android 默认的 DB 框架,SQLiteLint 提供了一种无侵入的获取到执行的sql语句及耗时等信息的方式。通过hook的技巧,向 SQLite3 C 层的 api sqlite3_profile 方法注册回调,
也能拿到分析所需的信息,从而无需开发者额外的打点统计代码。但是通过我阅读源码发现这个hook在7.0以上存在问题,源码的demo也并没有使用这种方式,而是通过一种通知的方式,也即是开发者将要检测的sql语句直接传递给SQLiteLint
对于检测的选项这里包括:
1.检测索引使用问题
2.检测冗余索引问题
3.检测 select * 问题
4.检测 Autoincrement 问题
5.检测建议使用 prepared statement
至于为什么要检测这些内容,具体可以查看上面的连接,文章中已经介绍的很清楚了,甚至包括了检测的标准
使用
在SQLite Lint页面中包含了下面的检测
源码分析
先看demo 的调用过程
public class MatrixApplication extends Application {
...
private static SQLiteLintConfig initSQLiteLintConfig() {
try {
return new SQLiteLintConfig(SQLiteLint.SqlExecutionCallbackMode.CUSTOM_NOTIFY);
} catch (Throwable t) {
return new SQLiteLintConfig(SQLiteLint.SqlExecutionCallbackMode.CUSTOM_NOTIFY);
}
}
@Override
public void onCreate() {
...
SQLiteLintConfig config = initSQLiteLintConfig();
SQLiteLintPlugin sqLiteLintPlugin = new SQLiteLintPlugin(config);
builder.plugin(sqLiteLintPlugin);
...
}
}
首先是执行了initSQLiteLintConfig ,构建了一个 SQLiteLintConfig对象,这里传递的模式为CUSTOM_NOTIFY,下面看看这俩个模式的选择,可以知道在7.0之后hook就会失败
/**
* The first step of SQLiteLint is to collect the executed sqls. SQL的检测,第一步就是收集Sql的信息
* Two way to achieve this.
*
* #HOOK: We will hook sqlite3_profile, and it will callback to our method when sqls are executed.
* @see com.tencent.sqlitelint.util.SQLite3ProfileHooker
* But hook will fail when tha android version is over Androd N. 但是在 Android N 以上 这个会失败
* TODO try to break through this limitation
*
* #CUSTOM_NOTIFY: It means that the user is responbisable to notify the sql execution. 通过通知的的方式来告知sql的执行
* @see #notifySqlExecution(String, String, int)
* TODO link example
*/
public enum SqlExecutionCallbackMode {
HOOK,
CUSTOM_NOTIFY,
}
//构建一个 SQLiteLintConfig 对象,这里传递进来的模式 CUSTOM_NOTIFY
public SQLiteLintConfig(SQLiteLint.SqlExecutionCallbackMode sqlExecutionCallbackMode) {
SQLiteLint.setSqlExecutionCallbackMode(sqlExecutionCallbackMode);
sConcernDbList = new ArrayList<>();
}
接着执行 SQLiteLint.setSqlExecutionCallbackMode(sqlExecutionCallbackMode);
public class SQLiteLint {
/**
* 首先执行 SqliteLint-lib so 的加载
*/
static {
SQLiteLintNativeBridge.loadLibrary();
}
private SQLiteLint() {
}
/**
* 当前所采用的的Sql的检测方式
*/
private static SqlExecutionCallbackMode sSqlExecutionCallbackMode = null;
public static void setSqlExecutionCallbackMode(SqlExecutionCallbackMode sqlExecutionCallbackMode) {
if (sSqlExecutionCallbackMode != null) {
return;
}
//赋值成员变量,标识当前所采用的模式
sSqlExecutionCallbackMode = sqlExecutionCallbackMode;
//如果为Hook方案
if (sSqlExecutionCallbackMode == SqlExecutionCallbackMode.HOOK) {
// hook must called before open the database
SQLite3ProfileHooker.hook();
}
}
...
}
在执行这个setSqlExecutionCallbackMode()函数之前,先看静态代码块的内容,执行了 SQLiteLintNativeBridge.loadLibrary();
public class SQLiteLintNativeBridge {
/**
* 加载so的操作
*/
public static void loadLibrary() {
System.loadLibrary("SqliteLint-lib");
SLog.nativeSetLogger(android.util.Log.VERBOSE);
}
...
}
可以看出首先调用了 System.loadLibrary("SqliteLint-lib"); 将 SqliteLint-lib 加载进来,这样就会调用的JNI_ONLoad函数的执行,具体的实现是在loader.cc 中
当调用System.loadLibrary 的时候就会调用这个方法
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env;
if (vm->GetEnv((void**) (&env), JNI_VERSION_1_6) != JNI_OK) {
sqlitelint::LOGE( "Initialize GetEnv null");
return -1;
}
//将 g_loaders 集合成员 中 init 为 true 的执行初始化的操作
vector<JNIModule>::iterator e = g_loaders->end();
for (vector<JNIModule>::iterator it = g_loaders->begin(); it != e; ++it)
{
//如果当前为初始化的时候,默认为 1的
if (it->init)
{
sqlitelint::LOGI( "Initialize module '%s'...", it->name);
//执行对应的函数指针
if (it->func(vm, env) != 0)
{
return -1;
}
}
}
return JNI_VERSION_1_6;
}
这里的g_loaders 的定义为
struct JNIModule
{
//注册的名称
const char *name;
//回调函数
ModuleInitializer func;
//是否初始化
bool init;
JNIModule(const char *name_, ModuleInitializer func_, bool init_)
: name(name_), func(func_), init(init_) {}
};
//全局的 g_loaders 集合
static vector<JNIModule> *g_loaders = nullptr;
可以看出 g_loaders 是一个全局的成员集合变量,用来存储JNIModule 的成员,JNIModule的定义就在上面,所以很好奇,难道g_loaders 在执行 JNI_OnLoad的时候就已经有内容了吗,如果有那么是怎么样
添加进来的呢,我们在 sqlite3_profile_hook.cc 中发现了有这样的内容,下面的内容看起来就像是 MODULE_INIT 执行初始化,MODULE_FINI用来执行销毁的操作
MODULE_INIT(sqlite3_proifle_hook){
kInitSuc = false;
kJvm = vm;
jint result = -1;
//找到 SQLiteLintUtil
jclass utilClass = env->FindClass("com/tencent/sqlitelint/util/SQLiteLintUtil");
if (utilClass == nullptr) {
return result;
}
//由于涉及到多线程,所以要变成GlobalRef
kUtilClass = reinterpret_cast<jclass>(env->NewGlobalRef(utilClass));
//找到 SQLiteLintUtil 中的 getThrowableStack 方法
kMethodIDGetThrowableStack = env->GetStaticMethodID(kUtilClass, "getThrowableStack", "()Ljava/lang/String;");
if (kMethodIDGetThrowableStack == nullptr) {
return result;
}
//标识初始化成功
kInitSuc = true;
return 0;
}
MODULE_FINI(sqlite3_proifle_hook) {
//释放全局的 引用
if (kUtilClass) {
env->DeleteGlobalRef(kUtilClass);
}
//重置变量
kInitSuc = false;
kStop = true;
return 0;
}
下面我们看看MODULE_INIT,MODULE_FINI的定义
#define MODULE_INIT(name) \
static int ModuleInit_##name(JavaVM *vm, JNIEnv *env); \
static void __attribute__((constructor)) MODULE_INIT_##name() \
{ \
register_module_func(#name, ModuleInit_##name, 1); \
} \
static int ModuleInit_##name(JavaVM *vm, JNIEnv *env)
#define MODULE_FINI(name) \
static int ModuleFini_##name(JavaVM *vm, JNIEnv *env); \
static void __attribute__((constructor)) MODULE_FINI_##name() \
{ \
register_module_func(#name, ModuleFini_##name, 0); \
} \
这里要注意这俩个宏有一个不同点就是调用register_module_func()第三个参数不一样,这个参数1代表为初始化的时候调用,0代表销毁的时候调用
static int ModuleFini_##name(JavaVM *vm, JNIEnv *env)
可以看出其实他们是一个宏,那么就很简单了,宏在c中只要直接展开就好了,比如我们展开一个内容少一点的 比如 MODULE_FINI(sqlite3_proifle_hook)函数,下面就是展开的内容
static int ModuleFini_sqlite3_proifle_hook(JavaVM *vm, JNIEnv *env);
//声明 __attribute__((constructor)) 会在 main函数执行之前被执行
static void __attribute__((constructor)) MODULE_FINI_sqlite3_proifle_hook()
{
register_module_func("sqlite3_proifle_hook", ModuleFini_sqlite3_proifle_hook, 0);
}
//定义
static int ModuleFini_sqlite3_proifle_hook(JavaVM *vm, JNIEnv *env)
{
if (kUtilClass) {
env->DeleteGlobalRef(kUtilClass);
}
kInitSuc = false;
kStop = true;
return 0;
}
展开之后就很简答了,一个是函数的声明,一个是函数的定义,唯一的这个要解释下
static void __attribute__((constructor)) MODULE_FINI_sqlite3_proifle_hook()
{
register_module_func("sqlite3_proifle_hook", ModuleFini_sqlite3_proifle_hook, 0);
}
这个其实是使用了 __attribute__中constructor和destructor,使用了之后,这个函数被设定为destructor属性,则该函数会在main()函数执行之后或者exit()被调用后被自动的执行
由于我们的为 __attribute__((constructor)) 所以会在main函数执行之前就会调用这个函数,所以会执行
register_module_func("sqlite3_proifle_hook", ModuleFini_sqlite3_proifle_hook, 0);
先看看 register_module_func()函数
SQLITELINT_EXPORT void register_module_func(const char *name, ModuleInitializer func, int init);
void register_module_func(const char *name, ModuleInitializer func, int init)
{
//如果为空,创建一个集合的对象,用来存储 JNIModule
if (!g_loaders)
g_loaders = new vector<JNIModule>();
//!!init 代表取反, 也即是为true 默认值
g_loaders->push_back(JNIModule(name, func, !!init));
}
而其中 ModuleInitializer其实为一个函数指针,下面是定义
typedef int (*ModuleInitializer)(JavaVM *vm, JNIEnv *env);
而我们的ModuleFini_sqlite3_proifle_hook()函数,刚好满足这个条件,那么到了这里就很简单了,当调用到
register_module_func("sqlite3_proifle_hook", ModuleFini_sqlite3_proifle_hook, 0); 对应到register_module_func(),将参数一一对应的化,name就为sqlite3_proifle_hook字符串
所以这里做的内容就是将这些传递过来的参数,构建成一个JNIModule,然后添加到了g_loaders全局变量中,那么对于MODULE_INIT(sqlite3_proifle_hook)也是类似的,只是这里调用
register_module_func()的时候,第三个参数为1,代表在初始化的时候执行,而MODULE_FINI()为在销毁的时候执行,下面我们看看这俩个参数的区别
再次回到JNI_OnLoad函数
//当调用System.loadLibrary 的时候就会调用这个方法
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
JNIEnv* env;
if (vm->GetEnv((void**) (&env), JNI_VERSION_1_6) != JNI_OK) {
sqlitelint::LOGE( "Initialize GetEnv null");
return -1;
}
//将 g_loaders 集合成员 中 init 为 true 的执行初始化的操作
vector<JNIModule>::iterator e = g_loaders->end();
for (vector<JNIModule>::iterator it = g_loaders->begin(); it != e; ++it)
{
//如果当前为初始化的时候,默认为 1的
if (it->init)
{
sqlitelint::LOGI( "Initialize module '%s'...", it->name);
//执行对应的函数指针
if (it->func(vm, env) != 0)
{
return -1;
}
}
}
return JNI_VERSION_1_6;
}
在遍历g_loaders集合的时候,当it->init为真的时候就执行it->func(vm, env),前面已经说过JNIModule,这里的init对应的就为 0或者1, 当使用MODULE_INIT就为1,MODULE_FINI就为0,func为函数指针
所以可以做到在初始化的时候,就可以将所有注册的要初始化的函数都执行初始化,下面再看看销毁的时候调用过程
/**
* 当 so 销毁的时候,会回调这个函数,可以用来执行销毁的操作
*/
extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved)
{
JNIEnv* env;
if (vm->GetEnv((void**) (&env), JNI_VERSION_1_6) != JNI_OK) {
sqlitelint::LOGE( "Finalize GetEnv null");
return;
}
//相应的 将 g_loaders 集合 中的 成员中 init 为false 的,执行对应的函数指针,也即是销毁的操作
vector<JNIModule>::iterator e = g_loaders->end();
for (vector<JNIModule>::iterator it = g_loaders->begin(); it != e; ++it)
{
if (!it->init)
{
sqlitelint::LOGI("Finalize module '%s'...", it->name);
it->func(vm, env);
}
}
}
可以看到这里的调用过程跟JNI_Onload类似,唯一不同的就是 当满足了 if (!it->init)的时候才会调用 it->func(vm, env); 达到销毁的目的,至于为什么要这样写,估计这样写可以将对应模块的初始化
以及销毁的操作交给对应的模块来处理,就不会出现将所有的初始化,销毁操作都写在一个函数里面,比如我们刚刚看的是sqlite3_profile_hook.cc文件,其实这个文件的内容是没有作用的,以为我们并不是
采用hook的方式,在com_tencent_sqlitelint_SqliteLint.cc 中也有对应的MODULE_INIT,MODULE_FINI
MODULE_INIT(sqliteLint) {
kJvm = vm;
assert(env != nullptr);
//找到 SQLiteLintNativeBridge 类
jclass tmp_java_bridge_class = env->FindClass("com/tencent/sqlitelint/SQLiteLintNativeBridge");
if (tmp_java_bridge_class == nullptr) {
LOGE("MODULE_INIT tmpJavaBridgeClass is null");
return -1;
}
//变成全局变量
kJavaBridgeClass = reinterpret_cast<jclass>(env->NewGlobalRef(tmp_java_bridge_class));
...
}
在初始化的时候,找到java对应的类,和方法,属性等字段
MODULE_FINI(sqliteLint) {
if (kExecSqlObj)
env->DeleteLocalRef(kExecSqlObj);
if (kIssueClass)
env->DeleteGlobalRef(kIssueClass);
if (kJavaBridgeClass)
env->DeleteGlobalRef(kJavaBridgeClass);
return 0;
}
在销毁的释放,这些字段,在 com_tencent_sqlitelint_util_SLog.cc 中也有对应的MODULE_INIT,MODULE_FINI的使用,这个文件是Android日志文件,这里做了一层的中转输出,这里就不看了
这样JNI_OnLoad函数执行完毕,继续回调java层,在 TestSQLiteLintActivity 中有这样的使用
Plugin plugin = Matrix.with().getPluginByClass(SQLiteLintPlugin.class);
if (!plugin.isPluginStarted()) {
MatrixLog.i(TAG, "plugin-sqlite-lint start");
plugin.start();
}
public class SQLiteLintPlugin extends Plugin {
...
@Override
public void start() {
...
//设置报告的回调函数
SQLiteLint.setReportDelegate(new IssueReportBehaviour.IReportDelegate() {
@Override
public void report(SQLiteLintIssue issue) {
if (issue == null) {
return;
}
//提交报告
reportMatrixIssue(issue);
}
});
...
}
public void addConcernedDB(SQLiteLintConfig.ConcernDb concernDb) {
if (!isPluginStarted()) {
SLog.i(TAG, "addConcernedDB isPluginStarted not");
return;
}
if (concernDb == null) {
return;
}
mConfig.addConcernDB(concernDb);
//获取到当前要安装的数据库的路径
String concernedDbPath = concernDb.getInstallEnv().getConcernedDbPath();
//执行安装的操作
SQLiteLint.install(mContext, concernDb.getInstallEnv(), concernDb.getOptions());
//设置白名单
SQLiteLint.setWhiteList(concernedDbPath, concernDb.getWhiteListXmlResId());
//设置允许检查的内容
SQLiteLint.enableCheckers(concernedDbPath, concernDb.getEnableCheckerList());
}
...
}
接着看看测试点击按钮执行的逻辑
/**
* 开始数据库的创建测试
*/
private void startDBCreateTest() {
...
plugin.addConcernedDB(new SQLiteLintConfig.ConcernDb(TestDBHelper.get().getWritableDatabase()).enableAllCheckers());
}
首先调用TestDBHelper().get().getWritableDatabase()
public class TestDBHelper extends SQLiteOpenHelper {
...
//单例模式
public static TestDBHelper get() {
if (mHelper == null) {
synchronized (TestDBHelper.class) {
if (mHelper == null) {
mHelper = new TestDBHelper(MatrixApplication.getContext());
}
}
}
return mHelper;
}
public TestDBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
//创建表 testTable
String sql = "create table if not exists " + TABLE_NAME + " (Id integer primary key, name text, age integer)";
sqLiteDatabase.execSQL(sql);
...
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
//升级的化,就直接删除表
String sql = "DROP TABLE IF EXISTS " + TABLE_NAME;
sqLiteDatabase.execSQL(sql);
String sql2 = "DROP TABLE IF EXISTS " + TABLE_NAME_AUTO_INCREMENT;
sqLiteDatabase.execSQL(sql2);
String sql3 = "DROP TABLE IF EXISTS " + TABLE_NAME_WITHOUT_ROWID_BETTER;
sqLiteDatabase.execSQL(sql3);
//表删除之后,重新的执行创建的过程
onCreate(sqLiteDatabase);
}
}
所以这边创建了一个TestDBHelper,本质是SQLiteOpenHelper,在onCreate的时候执行了创建表的操作,在升级的时候,删除表,又直接的创建数据库
接着执行 new SQLiteLintConfig.ConcernDb(),接着调用 enableAllCheckers()函数,下面看看这个函数的定义
public static final class ConcernDb {
//用于检测 索引的问题
private static final String EXPLAIN_QUERY_PLAN_CHECKER_NAME = "ExplainQueryPlanChecker";
//用于检测 Select * 的问题
private static final String AVOID_SELECT_ALL_CHECKER_NAME = "AvoidSelectAllChecker";
//用于检测 WithoutRow 的问题
private static final String WITHOUT_ROWID_BETTER_CHECKER_NAME = "WithoutRowIdBetterChecker";
//用于检测 AutoIncrement 的问题
private static final String AVOID_AUTO_INCREMENT_CHECKER_NAME = "AvoidAutoIncrementChecker";
//用于检测 PreparedStatement 的问题
private static final String PREPARED_STATEMENT_BETTER_CHECKER_NAME = "PreparedStatementBetterChecker";
//用于检测 冗余索引的问题
private static final String REDUNDANT_INDEX_CHECKER_NAME = "RedundantIndexChecker";
//数据库检查的安装环境
private final SQLiteLint.InstallEnv mInstallEnv;
//数据库检查的 配置
private final SQLiteLint.Options mOptions;
//数据库检测的白名单
private int mWhiteListXmlResId;
//当前允许数据库检测的 内容
private final List<String> mEnableCheckerList = new ArrayList<>();
public ConcernDb(SQLiteLint.InstallEnv installEnv, SQLiteLint.Options options) {
mInstallEnv = installEnv;
mOptions = options;
}
//ConcernDb 构造函数 传递一个可写的数据库进来
public ConcernDb(SQLiteDatabase db) {
assert db != null;
//构建一个 InstallEnv 的对象
mInstallEnv = new SQLiteLint.InstallEnv(db.getPath(), new SimpleSQLiteExecutionDelegate(db));
mOptions = SQLiteLint.Options.LAX;
}
/**
* 设置数据库检测的白名单
*/
public ConcernDb setWhiteListXml(final int xmlResId) {
mWhiteListXmlResId = xmlResId;
return this;
}
public SQLiteLint.InstallEnv getInstallEnv() {
return mInstallEnv;
}
public SQLiteLint.Options getOptions() {
return mOptions;
}
//获取到数据库检测的白名单
public int getWhiteListXmlResId() {
return mWhiteListXmlResId;
}
/**
* 执行check的检查,这里是检查所有的情况
* @return
*/
public ConcernDb enableAllCheckers() {
return enableExplainQueryPlanChecker()
.enableAvoidSelectAllChecker()
.enableWithoutRowIdBetterChecker()
.enableAvoidAutoIncrementChecker()
.enablePreparedStatementBetterChecker()
.enableRedundantIndexChecker();
}
...
}
首先在构造函数 中 执行 mInstallEnv = new SQLiteLint.InstallEnv(db.getPath(), new SimpleSQLiteExecutionDelegate(db));
首先将 传递过来的数据库db 传递到了 SimpleSQLiteExecutionDelegate,这个类是更像是一个代理的实现,用来真正的执行数据库的操作,这个会由JNI层来触发
public final class SimpleSQLiteExecutionDelegate implements ISQLiteExecutionDelegate {
private static final String TAG = "SQLiteLint.SimpleSQLiteExecutionDelegate";
private final SQLiteDatabase mDb;
public SimpleSQLiteExecutionDelegate(SQLiteDatabase db) {
assert db != null;
mDb = db;
}
/**
* 执行查询的操作
* @param selection
* @param selectionArgs
* @return
* @throws SQLException
*/
@Override
public Cursor rawQuery(String selection, String... selectionArgs) throws SQLException {
if (!mDb.isOpen()) {
SLog.w(TAG, "rawQuery db close");
return null;
}
//最终调用了 SQLiteDatabase rawQuery 来执行查询
return mDb.rawQuery(selection, selectionArgs);
}
@Override
public void execSQL(String sql) throws SQLException {
if (!mDb.isOpen()) {
SLog.w(TAG, "rawQuery db close");
return;
}
mDb.execSQL(sql);
}
}
接着构建一个InstallEnv 对象,保存了传递过来的内容
/**
* A data struct of some variables which guide the installation and make SQLiteLint work.
* 一些变量的数据结构,指导安装并使SQLiteLint工作。
* Used in {@link #install(Context, InstallEnv, Options)}
*/
public static final class InstallEnv {
//要检查的数据库的路径
private final String mConcernedDbPath;
//数据库执行的委托
private final ISQLiteExecutionDelegate mSQLiteExecutionDelegate;
/**
* @param concernedDbPath the path of the database which will be observed and checked
* @param executionDelegate SQLiteLint will do some sql execution use SQLite, but SQLiteLint will delegate the execution.
* A simple delegate like {@link SimpleSQLiteExecutionDelegate}
*/
public InstallEnv(String concernedDbPath, ISQLiteExecutionDelegate executionDelegate) {
assert concernedDbPath != null;
assert executionDelegate != null;
mConcernedDbPath = concernedDbPath;
mSQLiteExecutionDelegate = executionDelegate;
}
...
}
接着调用 enableAllCheckers()函数,回到前面ConcernDb类的定义,其实这里既是配置要检测的内容,比如ExplainQueryPlanChecker 代表检查索引,AvoidSelectAllChecker代表检查selec*
所以配置检查所有的内容,最终要检查的内容 都是添加到了 mEnableCheckerList 字符串的集合中,后面要传递给JNI层告知当前要检查的种类,接着调用addConcernedDB函数
/**
* 添加concernDb
* @param concernDb
*/
public void addConcernedDB(SQLiteLintConfig.ConcernDb concernDb) {
if (!isPluginStarted()) {
SLog.i(TAG, "addConcernedDB isPluginStarted not");
return;
}
if (concernDb == null) {
return;
}
mConfig.addConcernDB(concernDb);
//获取到当前要安装的数据库的路径
String concernedDbPath = concernDb.getInstallEnv().getConcernedDbPath();
//执行安装的操作
SQLiteLint.install(mContext, concernDb.getInstallEnv(), concernDb.getOptions());
//设置白名单
SQLiteLint.setWhiteList(concernedDbPath, concernDb.getWhiteListXmlResId());
//设置允许检查的内容
SQLiteLint.enableCheckers(concernedDbPath, concernDb.getEnableCheckerList());
}
之后执行 SQLiteLint.install(mContext, concernDb.getInstallEnv(), concernDb.getOptions());
public static void install(Context context, InstallEnv installEnv, Options options) {
...
options = (options == null) ? Options.LAX : options;
//执行安装的操作
SQLiteLintAndroidCoreManager.INSTANCE.install(context, installEnv, options);
}
public enum SQLiteLintAndroidCoreManager {
//单例 ,使用枚举创建单例
INSTANCE;
private static final String TAG = "SQLiteLint.SQLiteLintAndroidCoreManager";
/**
* key: the concerned db path 要检测的数据库的路径
* value: a SQLiteLintAndroidCore to check this db
*/
private ConcurrentHashMap<String, SQLiteLintAndroidCore> mCoresMap = new ConcurrentHashMap<>();
/**
* 执行安装的操作
* @param context
* @param installEnv
* @param options
*/
public void install(Context context, SQLiteLint.InstallEnv installEnv, SQLiteLint.Options options) {
//获取到当前要检查的数据库的路径
String concernedDbPath = installEnv.getConcernedDbPath();
//如果已经存在集合中,忽略
if (mCoresMap.containsKey(concernedDbPath)) {
SLog.w(TAG, "install twice!! ignore");
return;
}
//构建一个 SQLiteLintAndroidCore 对象,然后添加到集合 mCoresMap 中
SQLiteLintAndroidCore core = new SQLiteLintAndroidCore(context, installEnv, options);
mCoresMap.put(concernedDbPath, core);
}
...
}
可以看出这是一个使用枚举的单例写法,接着会构建一个 SQLiteLintAndroidCore 对象
SQLiteLintAndroidCore(Context context, SQLiteLint.InstallEnv installEnv, SQLiteLint.Options options) {
mContext = context;
//初始化 ,创建SqliteOpenHelper
SQLiteLintDbHelper.INSTANCE.initialize(context);
//获取到当前要检测的数据库的路径
mConcernedDbPath = installEnv.getConcernedDbPath();
//获取到数据库执行的委托对象
mSQLiteExecutionDelegate = installEnv.getSQLiteExecutionDelegate();
//如果当前的数据库的检测模式为 Hook
if (SQLiteLint.getSqlExecutionCallbackMode() == SQLiteLint.SqlExecutionCallbackMode.HOOK) {
//执行hook操作
SQLite3ProfileHooker.hook();
}
//native 层的安装
SQLiteLintNativeBridge.nativeInstall(mConcernedDbPath);
//构建一个 mBehaviors 集合 ,添加一个 PersistenceBehaviour 成员
mBehaviors = new ArrayList<>();
/*PersistenceBehaviour is a default pre-behaviour */
mBehaviors.add(new PersistenceBehaviour());
//默认为 BEHAVIOUR_ALERT ,再往 mBehaviors 中添加一个 IssueAlertBehaviour 对象
if (options.isAlertBehaviourEnable()) {
mBehaviors.add(new IssueAlertBehaviour(context, mConcernedDbPath));
}
if (options.isReportBehaviourEnable()) {
mBehaviors.add(new IssueReportBehaviour(SQLiteLint.sReportDelegate));
}
}
首先调用 SQLiteLintDbHelper.INSTANCE.initialize(context);
public enum SQLiteLintDbHelper {
//使用枚举 构建单例
INSTANCE;
...
//单例 ,是一个 SQLiteOpenHelper 对象
private volatile InternalDbHelper mHelper;
public SQLiteDatabase getDatabase() {
if (mHelper == null) {
throw new IllegalStateException("getIssueStorage db not ready");
}
return mHelper.getWritableDatabase();
}
public void initialize(Context context) {
if (mHelper == null) {
synchronized (this) {
if (mHelper == null) {
mHelper = new InternalDbHelper(context);
}
}
}
}
private static final class InternalDbHelper extends SQLiteOpenHelper {
//要打开的数据库名, SQLiteLintInternal.db 是一个用来保存Issure 的数据库
private static final String DB_NAME = "SQLiteLintInternal.db";
private static final int VERSION_1 = 1;
InternalDbHelper(Context context) {
super(context, DB_NAME, null, VERSION_1);
}
@Override
public void onCreate(SQLiteDatabase db) {
SLog.i(TAG, "onCreate");
//执行创建数据库
db.execSQL(IssueStorage.DB_VERSION_1_CREATE_SQL);
//创建索引
for (int i = 0; i < IssueStorage.DB_VERSION_1_CREATE_INDEX.length; i++) {
db.execSQL(IssueStorage.DB_VERSION_1_CREATE_INDEX[i]);
}
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
}
可以看出这也是一个单例,其实这个是内部的数据库,用来存储分析的结果,也即是当分析到了结果,会先经过这个数据库进行保存,继续往下面执行由于我们当前的模式不为hook方式,所以不会执行
SQLite3ProfileHooker.hook();接着就会执行到 SQLiteLintNativeBridge.nativeInstall(mConcernedDbPath); 这是一个native 方法,对应的jni方法的实现为
/**
* 执行native层的安装操作
* @param env
* @param name
*/
void Java_com_tencent_sqlitelint_SQLiteLintNativeBridge_nativeInstall(JNIEnv *env, jobject, jstring name) {
//当前要检测的 数据库的路径
char *filename = jstringToChars(env,name);
//安装 SQLiteLint ,OnIssuePublish 为函数指针,用于进一步的将结果传回java层
InstallSQLiteLint(filename, OnIssuePublish);
//释放操作
free(filename);
//传递SQL 执行的函数指针
SetSqlExecutionDelegate(SqliteLintExecSql);
}
执行 InstallSQLiteLint(filename, OnIssuePublish);
//执行安装的操作 ,issue_callback 为 传递结果的函数指针
void InstallSQLiteLint(const char* db_path, OnPublishIssueCallback issue_callback) {
LintManager::Get()->Install(db_path, issue_callback);
}
这里LintManager其实为一个单例
// A singleton and it manage the lint
class LintManager {
public :
static LintManager* Get();
...
private:
//单例模式,构造函数为 私有
LintManager(){};
~LintManager(){};
private:
...
//单例模式
static LintManager *instance_;
};
//获取到 LintManager 对象,如果是第一次调用,执行创建
LintManager* LintManager::Get() {
if (!instance_){
//加锁操作
std::unique_lock<std::mutex> lock(lints_mutex_);
if (!instance_){
instance_ = new LintManager();
}
//解锁 操作
lock.unlock();
}
return instance_;
}
当第一次调用的时候就会创建一个LintManager 对象,之后都不会进行创建了,接着调用 Install函数
/**
* 执行安装的操作
* @param db_path
* @param issued_callback
*/
void LintManager::Install(const char* db_path, OnPublishIssueCallback issued_callback) {
sInfo("LintManager::Install dbPath:%s", db_path);
//加锁
std::unique_lock<std::mutex> lock(lints_mutex_);
//如果 lints_ 集合中 以及存在 db_path ,直接返回
std::map<const std::string, Lint*>::iterator it = lints_.find(db_path);
if (it != lints_.end()) {
//已经在 集合中存在,解锁
lock.unlock();
sWarn("Install already installed; dbPath: %s", db_path);
return;
}
//如果到了这里说明不存在,构建一个
Lint* lint = new Lint(db_path, issued_callback);
lints_.insert(std::pair<const std::string, Lint*>(db_path, lint));
//解锁
lock.unlock();
}
其中 lints_ 为 LintManager 中的一个集合,用来存储当前已经添加的检测 定义为
std::map<const std::string, Lint*> lints_;
集合的key为当前要添加检测的数据库的路径,这里的逻辑就是先检测当前的数据库路径是否已经添加过,如果已经添加过,直接返回,假设没有,就构建一个Lint 对象,然后存储到集合中
Lint::Lint(const char* db_path, OnPublishIssueCallback issued_callback)
: env_(db_path), checked_sql_cache_(500)//env_(db_path) 会调用 LintEnv(path)的构造函数,构建这样的对象,checked_sql_cache_ 集合的大小为500
, issued_callback_(issued_callback), exit_(false){
//构建一个线程, 线程执行的时候会执行 Check 函数
check_thread_ = new std::thread(&Lint::Check, this);
}
这里要注意在构建这个Lint 对象的时候传递的OnPublishIssueCallback 为函数指针,这个函数指针是用来中转结果的,跟上一篇的检测Io一样,将结果做一个中转,最后转到java层
这里的env_ 为 Lint 的一个成员变量 LintEnv env_;,这里执行 env_(db_path)就会调用对应的构造函数,LintEnv 这个对象用来代表这个数据库检测的环境
// LintEnv 构造函数的执行
LintEnv::LintEnv(std::string db_path) : db_path_(db_path), checked_sql_cnt_(0) {
int pos = db_path.find_last_of('/');
if (pos >= 0) {
db_file_name_ = db_path.substr(pos + 1);
} else {
db_file_name_ = db_path;
}
}
同时将 issued_callback 保存到 成员变量issued_callback_中,用于后面检测结果的中转,同时参加了一个线程check_thread_,线程执行的时候会执行&Lint::Check 函数
/**
* 在 check_thread_ 线程中 执行
*/
void Lint::Check() {
//这边又构建一个 init_check_thread_ 的线程,线程执行的时候,会执行 InitCheck 函数
init_check_thread_ = new std::thread(&Lint::InitCheck, this);
//用来存储要分发结果的集合
std::vector<Issue>* published_issues = new std::vector<Issue>;
std::unique_ptr<SqlInfo> sql_info;
SqlInfo simple_sql_info;
//这下面一般是检查是否有结果,有就分发出去之类的
while (true) {
//从queue 结果队列中 获取到内容,如果 队列中有元素,取出对头的元素,然后辅助给 sql_info ,如果没有元素,则会阻塞在这里
int ret = TakeSqlInfo(sql_info);
if (ret != 0) {
sError("check exit");
break;
}
...
}
}
可以看到当这个子线程执行的时候,又会创建另一个子线程 init_check_thread_ ,函数指针为Lint::InitCheck,这里先不去看这个函数的实现,继续回到nativeInstall 函数
void Java_com_tencent_sqlitelint_SQLiteLintNativeBridge_nativeInstall(JNIEnv *env, jobject, jstring name) {
//当前要检测的 数据库的路径
char *filename = jstringToChars(env,name);
//安装 SQLiteLint ,OnIssuePublish 为函数指针,用于进一步的将结果传回java层
InstallSQLiteLint(filename, OnIssuePublish);
//释放操作
free(filename);
//传递SQL 执行的函数指针
SetSqlExecutionDelegate(SqliteLintExecSql);
}
//传递全局的SQL 执行的函数指针
void SetSqlExecutionDelegate(SqlExecutionDelegate func){
kSqlExecutionDelegate = func;
}
这里的SqliteLintExecSql 也为函数指针,也是一个中转函数的,这个函数主要完成的操作当JNI层要操作数据库的时候,会通过这个函数,中转到Java层,其实真正的查询有java来操作
继续回到 java层的 addConcernedDB()函数,继续往下面执行
//设置白名单
SQLiteLint.setWhiteList(concernedDbPath, concernDb.getWhiteListXmlResId());
public static void setWhiteList(String concernedDbPath, final int xmlResId) {
//如果 INSTANCE 中 存在 concernedDbPath 路径 对应的 SQLiteLintAndroidCore 对象
if (SQLiteLintAndroidCoreManager.INSTANCE.get(concernedDbPath) == null) {
return;
}
//设置白名单
SQLiteLintAndroidCoreManager.INSTANCE.get(concernedDbPath).setWhiteList(xmlResId);
}
/**
* 增加白名单
* @param xmlResId
*/
public void setWhiteList(final int xmlResId) {
CheckerWhiteListLogic.setWhiteList(mContext, mConcernedDbPath, xmlResId);
}
public static void setWhiteList(Context context, final String concernedDbPath, final int xmlResId) {
....
//将解析完白名单之后的结果传递到native 层
addToNative(concernedDbPath, whiteListMap);
}
这里首先会先将xml 白名单使用xml解析器,解析出来,之后调用addToNative 告知白名单的设置
/**
* 将解析白名单之后的结果 传递到native层
* @param concernedDbPath
* @param whiteListMap
*/
private static void addToNative(final String concernedDbPath, Map<String, List<String>> whiteListMap) {
if (whiteListMap == null) {
return;
}
//将map 转成 数组的形式
...
//传递到native 层,设置当前检查数据库的白名单
SQLiteLintNativeBridge.nativeAddToWhiteList(concernedDbPath, checkerArr, whiteListArr);
}
void Java_com_tencent_sqlitelint_SQLiteLintNativeBridge_nativeAddToWhiteList(JNIEnv *env, jobject, jstring db_path, jobjectArray check_name_arr, jobjectArray white_list_arr) {
...
//解析完之后,传递白名单的结果
SetWhiteList(dbPath, whiteList);
free(dbPath);
}
void SetWhiteList(const char* db_path, const std::map<std::string, std::set<std::string>>& white_list) {
LintManager::Get()->SetWhiteList(db_path, white_list);
}
void LintManager::SetWhiteList(const char *db_path, const std::map<std::string, std::set<std::string>> &white_list) {
//加锁
std::unique_lock<std::mutex> lock(lints_mutex_);
//遍历是否能从 lints_ 集合中找到 db_path 对应的 Lint 对象
std::map<const std::string, Lint*>::iterator it = lints_.find(db_path);
//没有找到
if (it == lints_.end()) {
lock.unlock();
sWarn("LintManager::SetWhiteList lint not installed; dbPath: %s", db_path);
return;
}
//找到了,设置白名单
it->second->SetWhiteList(white_list);
//解锁
lock.unlock();
}
void Lint::SetWhiteList(const std::map<std::string, std::set<std::string>> &white_list) {
env_.SetWhiteList(white_list);
}
void LintEnv::SetWhiteList(const std::map<std::string, std::set<std::string>> &white_list) {
white_list_mgr_.SetWhiteList(white_list);
}
这里white_list_mgr_ 为 LintEnv 中的一个成员变量 WhiteListMgr white_list_mgr_,而 WhiteListMgr 中有一个成员变量 std::map<std::string, std::set<std::string>> white_list_;
专门用来存储白名单的内容,继续回到addConcernedDB()函数,执行
SQLiteLint.enableCheckers(concernedDbPath, concernDb.getEnableCheckerList());
public static void enableCheckers(String concernedDbPath, final List<String> enableCheckerList) {
...
SQLiteLintAndroidCoreManager.INSTANCE.get(concernedDbPath).enableCheckers(enableCheckerList);
}
public void enableCheckers(List<String> enableCheckers) {
//将list 结构 转成 数组的形式
String[] enableCheckerArr = new String[enableCheckers.size()];
for (int i = 0; i < enableCheckers.size(); i++) {
enableCheckerArr[i] = enableCheckers.get(i);
}
//将当前 要检测的内容 传递到 native 层
SQLiteLintNativeBridge.nativeEnableCheckers(mConcernedDbPath, enableCheckerArr);
}
void Java_com_tencent_sqlitelint_SQLiteLintNativeBridge_nativeEnableCheckers(JNIEnv *env, jobject, jstring dbPathStr, jobjectArray enableCheckerArr) {
char *dbPath = jstringToChars(env, dbPathStr);
jint j_check_name_arr_count = env->GetArrayLength(enableCheckerArr);
for (jint i = 0 ; i < j_check_name_arr_count ; i++) {
jstring j_checker_name = (jstring) env->GetObjectArrayElement(enableCheckerArr, i);
char *checkerName = jstringToChars(env, j_checker_name);
//告知当前dbPath 路径指定的数据库 检测的内容
EnableChecker(dbPath, checkerName);
free(checkerName);
}
free(dbPath);
}
就会通过EnableChecker()设置当前 数据库 dbPath 对应的检测内容
void EnableChecker(const char* db_path, const std::string& checker_name) {
LintManager::Get()->EnableChecker(db_path, checker_name);
}
void LintManager::EnableChecker(const char *db_path, const std::string &checker_name) {
std::unique_lock<std::mutex> lock(lints_mutex_);
//遍历是否能从 lints_ 集合中找到 db_path 对应的 Lint 对象
std::map<const std::string, Lint*>::iterator it = lints_.find(db_path);
//没有找到
if (it == lints_.end()) {
lock.unlock();
sWarn("LintManager::EnableChecker lint not installed; dbPath: %s", db_path);
return;
}
//找到了,注册检测的内容
it->second->RegisterChecker(checker_name);
lock.unlock();
}
这里就会根据传递过来的字符串匹配,从而注册对应的检测对象
void Lint::RegisterChecker(const std::string &checker_name) {
sDebug("Lint::RegisterChecker check_name: %s", checker_name.c_str());
if (CheckerName::kExplainQueryPlanCheckerName == checker_name) {// 索引检查
RegisterChecker(new ExplainQueryPlanChecker());
} else if (CheckerName::kRedundantIndexCheckerName == checker_name) {//冗余检查
RegisterChecker(new RedundantIndexChecker());
} else if (CheckerName::kAvoidAutoIncrementCheckerName == checker_name) {//AutoIncrement 检查
RegisterChecker(new AvoidAutoIncrementChecker());
} else if (CheckerName::kAvoidSelectAllCheckerName == checker_name) {//select * 检查
RegisterChecker(new AvoidSelectAllChecker());
} else if (CheckerName::kWithoutRowIdBetterCheckerName == checker_name) {//without rowId 检查
RegisterChecker(new WithoutRowIdBetterChecker());
} else if (CheckerName::kPreparedStatementBetterCheckerName == checker_name) {//preparedStatement 检查
RegisterChecker(new PreparedStatementBetterChecker());
}
}
void Lint::RegisterChecker(Checker* checker) {
std::map<CheckScene, std::vector<Checker*>>::iterator iter = checkers_.find(checker->GetCheckScene());
// 根据 checker 分类,如果找到了,就直接添加
if (iter != checkers_.end()) {
iter->second.push_back(checker);
} else {
//如果没有找到,构建一个数组,再添加到总的 checkers_ 数组中
std::vector<Checker*> v;
v.push_back(checker);
checkers_.insert(std::pair<CheckScene, std::vector<Checker*>>(checker->GetCheckScene(), v));
}
}
其中 checkers_ 为 Lint 的一个成员变量,定义为 std::map<CheckScene, std::vector<Checker*>> checkers_; 用来存储要检测的内容,已经检测对应的类,
这样java对应的初始化已经执行完毕
线程的执行
我们知道在构建当前数据库路径对应的Lint对象的时候,创建了俩个线程,其中一个线程的run方法
/**
* 在 check_thread_ 线程中 执行
*/
void Lint::Check() {
//这边又构建一个 init_check_thread_ 的线程,线程执行的时候,会执行 InitCheck 函数
init_check_thread_ = new std::thread(&Lint::InitCheck, this);
//用来存储要分发结果的集合
std::vector<Issue>* published_issues = new std::vector<Issue>;
std::unique_ptr<SqlInfo> sql_info;
SqlInfo simple_sql_info;
//这下面一般是检查是否有结果,有就分发出去之类的
while (true) {
//从queue 结果队列中 获取到内容,如果 队列中有元素,取出对头的元素,然后辅助给 sql_info ,如果没有元素,则会阻塞在这里
int ret = TakeSqlInfo(sql_info);
if (ret != 0) {
sError("check exit");
break;
}
//取出了队列中的头部的元素,下面是执行分发
...
}
}
//虽然用了双端队列,还是得确认线程安全影响大不大
int Lint::TakeSqlInfo(std::unique_ptr<SqlInfo> &sql_info) {
//加锁
std::unique_lock<std::mutex> lock(queue_mutex_);
//如果当前为推出的状态,返回-1
if (exit_) {
return -1;
}
//如果队里为空,利用 queue_cv_ 将线程阻塞
while (queue_.empty()) {
sInfo("Lint::TakeSqlInfo queue empty and wait");
queue_cv_.wait(lock);
if (exit_) {
return -1;
}
}
//如果队列不为空,移除队头的元素,辅助给 sql_info
sql_info = std::move(queue_.front());
queue_.pop_front();
return 0;
}
所以这里在执行TakeSqlInfo()的时候,因为没有queue中没有内容,会导致阻塞,接下来我们再来分析另一个线程
/**
* init_check_thread_ 线程启动的时候,执行
*/
void Lint::InitCheck() {
sVerbose("Lint::Check() init check");
//休眠4秒
std::this_thread::sleep_for(std::chrono::seconds(4));
//构建一个集合用来收集结果
std::vector<Issue>* published_issues = new std::vector<Issue>;
//安排检查, 类型为 kAfterInit 的有 AvoidAutoIncrementChecker , WithoutRowIdBetterChecker ,RedundantIndexChecker
ScheduleCheckers(CheckScene::kAfterInit, SqlInfo(), published_issues);
//如果published_issues 结果 不为空,说明检查到了有不合格的内容
if (!published_issues->empty()) {
sInfo("New check some diagnosis out!");
if (issued_callback_) {
//将结果传递回去
issued_callback_(env_.GetDbPath().c_str(), *published_issues);
}
}
delete published_issues;
}
这里会先调用 ScheduleCheckers(CheckScene::kAfterInit, SqlInfo(), published_issues); 这里注意第一个参数为CheckScene::kAfterInit,这个标识是否为初始化的时候就可以检测,
对于索引的检测,AutoCrenemt等的检测,可以在初始化的时候就检测,这个时机点在检测的每一个类中都有指定,比如
CheckScene AvoidAutoIncrementChecker::GetCheckScene() {
return CheckScene::kAfterInit;
}
/**
* 当前的检查方式为抽样检查
* @return
*/
CheckScene PreparedStatementBetterChecker::GetCheckScene() {
return CheckScene::kSample;
}
再次及时第三个参数为 published_issues 为一个集合用来存储检测的结果
void Lint::ScheduleCheckers(const CheckScene check_scene, const SqlInfo& sql_info, std::vector<Issue> *published_issues) {
std::map<CheckScene, std::vector<Checker*>>::iterator it = checkers_.find(check_scene);
if (it == checkers_.end()) {
return;
}
// 从 checkers_ 得到 满足 check_scene 集合
std::vector<Checker*> scene_checkers = it->second;
size_t scene_checkers_cnt = scene_checkers.size();
//遍历执行对应的 Check 函数
for (size_t i=0;i < scene_checkers_cnt;i++) {
Checker* checker = scene_checkers[i];
//当前比较的类型不能为 kSample的形势,也即是不为抽样检查
if (check_scene != CheckScene::kSample || (env_.GetSqlCnt() % checker->GetSqlCntToSample() == 0)) {
checker->Check(env_, sql_info, published_issues);
}
}
}
由于类型为 kAfterInit 的有 AvoidAutoIncrementChecker , WithoutRowIdBetterChecker ,RedundantIndexChecker,所以就会对应的执行 Check函数
AutoIncrement 检测
void AvoidAutoIncrementChecker::Check(LintEnv &env, const SqlInfo &sql_info, std::vector<Issue> *issues) {
//获取到当前数据库 中表的信息集合
std::vector<TableInfo> tables = env.GetTablesInfo();
sVerbose("AvoidAutoIncrementChecker::Check tables count: %d", tables.size());
std::string create_sql;
//遍历这些表
for (const TableInfo& table_info : tables) {
//检查这个table 是否在白名单中
if (env.IsInWhiteList(kCheckerName, table_info.table_name_)) {
sVerbose("AvoidAutoIncrementChecker::Check in white list: %s", table_info.table_name_.c_str());
continue;
}
//获取到当前创建的sql语句,在 select name, sql from sqlite_master where type='table' 中就能获取到全部表的创建信息
create_sql = table_info.create_sql_;
//转成小写
ToLowerCase(create_sql);
//从创建表的sql 语句中 查找是否有 autoincrement 关键字
if (create_sql.find(kAutoIncrementKeyWord) != std::string::npos) {
//如果找到了,发布一个结果
PublishIssue(env, table_info.table_name_, issues);
}
}
}
既然是提前检测,那么肯定是根据当前数据库的创建表信息,属性等来检测,所以首先要获取到表的信息
std::vector<TableInfo> tables = env.GetTablesInfo();
const std::vector<TableInfo> LintEnv::GetTablesInfo() {
//加锁
std::unique_lock<std::mutex> lock(lints_mutex_);
//如果当前数据库 表信息为空,收集表信息
if (tables_info_.empty()) {
CollectTablesInfo();
}
return tables_info_;
}
由于我们当前第一次访问,所以会执行CollectTablesInfo() 执行收集的操作
/**
* 收集当前数据库表的信息
*/
void LintEnv::CollectTablesInfo() {
...
//用来接受错误的信息
char *err_msg = nullptr;
//查询表的信息
int r = GetQuery(kSelectTablesSql, OnSelectTablesCallback, &tables_info_, &err_msg);
//处理查询的错误信息
if (!DealWithGetQuery(r, err_msg, kSelectTablesSql)) {
return;
}
//用来标记查询数据库列的sql
std::string select_columns_sql;
//用来查询索引的sql
std::string select_index_sql;
std::string select_index_info_sql;
//遍历当前 tables_info_ 集合
for (TableInfo &table_info : tables_info_) {
//查询数据库列的 sql语句
select_columns_sql = "PRAGMA table_info(" + table_info.table_name_ + ")";
//执行查询操作,这里传递的参数 为 table_info,查询完之后填充这个表中列的信息
r = GetQuery(select_columns_sql, OnSelectColumnsCallback, &table_info, &err_msg);
//处理查询列错误的信息
if (!DealWithGetQuery(r, err_msg, select_columns_sql)) {
return;
}
//接下来查询表的 索引信息
select_index_sql = "PRAGMA index_list(" + table_info.table_name_ + ")";
//这里的 接受处理的回调函数为 OnSelectIndexsCallback
r = GetQuery(select_index_sql, OnSelectIndexsCallback, &table_info, &err_msg);
//处理查询错误的信息
if (!DealWithGetQuery(r, err_msg, select_index_sql)) {
return;
}
//遍历当前表中的索引名称,判断是否为组合索引,如果是获取到组合索引中的元素,
for (IndexInfo &index_info : table_info.indexs_) {
select_index_info_sql = "PRAGMA index_info(" + index_info.index_name_ + ")";
r = GetQuery(select_index_info_sql, OnSelectIndexInfoCallback, &index_info, &err_msg);
//处理错误的信息
if (!DealWithGetQuery(r, err_msg, select_index_info_sql)) {
return;
}
}
}
}
首先执行 int r = GetQuery(kSelectTablesSql, OnSelectTablesCallback, &tables_info_, &err_msg);这里 kSelectTablesSql值为,这也即是要查询的内容
static constexpr const char* const kSelectTablesSql = "select name, sql from sqlite_master where type='table'";
第二个参数OnSelectTablesCallback 为 执行了这个查询之后,进行调用的函数指针,而tables_info_ 的定义为,也即是用来填充查询之后表的内容
//用来存储当前数据库 包含表信息的集合
std::vector<TableInfo> tables_info_;
int LintEnv::GetQuery(const std::string &query_sql, const SqlExecutionCallback &callback, void *para, char **errMsg) {
...
//执行查询
return SQLite3ExecSql(db_path_.c_str(), query_sql.c_str(), callback, para, errMsg);
}
int LintEnv::SQLite3ExecSql(const char *db_path, const char *sql, const SqlExecutionCallback &callback, void *para, char **errmsg) {
//如果有设置全局sql 执行的代理,执行这个代理
if (kSqlExecutionDelegate) {
return kSqlExecutionDelegate(db_path, sql, callback, para, errmsg);
} else {
sError("LintEnv::SQLite3ExecSql kSqlExecutionDelegate not set!!!");
return -1;
}
}
由于前面我们有设置kSqlExecutionDelegate 的值,这个值就是用来委托java层执行sql语句的函数指针
int SqliteLintExecSql(const char *file_name, const char *sql, SqlExecutionCallback callback, void *para, char **err_msg) {
//查询之后函数指针,这里将地址转为 整数 传递到java层
int64_t exec_sql_callback_ptr = (int64_t) (intptr_t) callback;
//参数
int64_t para_ptr = (int64_t) (intptr_t) para;
...
//获取到env
JNIEnv *env = nullptr;
bool attached = false;
jint ret = kJvm->GetEnv((void **) &env, JNI_VERSION_1_6);
if (ret == JNI_EDETACHED) {
jint ret = kJvm->AttachCurrentThread(&env, nullptr);
assert(ret == JNI_OK);
(void) ret;
attached = true;
}
//调用 SQLiteLintNativeBridge 中的 sqliteLintExecSql 函数
jstring filename_str = charsToJstring(env,file_name);
jstring sql_str = charsToJstring(env,sql);
jobjectArray retArray = (jobjectArray) env->CallObjectMethod(kExecSqlObj, kExecSqlMethodID,filename_str, sql_str,
callback ? JNI_TRUE : JNI_FALSE, exec_sql_callback_ptr,para_ptr);
env->DeleteLocalRef(filename_str);
env->DeleteLocalRef(sql_str);
if (!retArray) {
LOGE("sqliteLintExecSql retArray is null");
if (attached) kJvm->DetachCurrentThread();
return -1;
}
...
return -1;
}
可以看到这里将这些函数指针转成int64,然后通过调用java层SQLiteLintNativeBridge 的sqliteLintExecSql 函数来执行查询
/**
* JNI 层方法调用 ,native 没有办法查询数据库,只能通知java层来查询,查询完之后将结果传到native 层
* @param dbPath
* @param sql
* @param needCallBack 是否需要回调
* @param execSqlCallbackPtr 底层回调函数的地址
* @param paraPtr 底层参数的地址
* @return
*/
private String[] sqliteLintExecSql(String dbPath, String sql, boolean needCallBack, long execSqlCallbackPtr,long paraPtr) {
String[] retObj = new String[2];
try {
...
ISQLiteExecutionDelegate executionDelegate = null;
//获取到当前 dbPath 对应的 SQLiteLintAndroidCore 对象,然后获取到当前sql 执行的委托对象
SQLiteLintAndroidCore core = SQLiteLintAndroidCoreManager.INSTANCE.get(dbPath);
if (core != null) {
executionDelegate = core.getSQLiteExecutionDelegate();
}
//委托对象为空,直接返回
if (executionDelegate == null) {
SLog.w(TAG, "sqliteLintExecSql mExecSqlImp is null");
return retObj;
}
//不为空
if (needCallBack) {
try {
//执行真正的查询操作
Cursor cu = executionDelegate.rawQuery(sql);
//判断查询的结果
if (cu == null || cu.getCount() < 0) {
//查询结果出错, 返回值为 -1
SLog.w(TAG, "sqliteLintExecSql cu is null");
retObj[0] = "Cursor is null";
} else {
//查询到了结果,这里传递了cursor
doExecSqlCallback(execSqlCallbackPtr,paraPtr, dbPath, cu);
//将最终的结果 变为0,代表查询成功
retObj[1] = "0";
}
//关闭Cursor
if (cu != null) {
cu.close();
}
} catch (Exception e) {
//出现了错误,传递错误的信息
SLog.w(TAG, "sqliteLintExecSql rawQuery exp: %s", e.getMessage());
retObj[0] = e.getMessage();
}
} else {
//如果不需要回调
try {
//那就单纯的执行他,执行的结果也直接设置为0
executionDelegate.execSQL(sql);
retObj[1] = "0";
} catch (SQLException e) {
SLog.w(TAG, "sqliteLintExecSql execSQL exp: %s", e.getMessage());
retObj[0] = e.getMessage();
}
}
} catch (Throwable e) {
SLog.e(TAG, "sqliteLintExecSql ex ", e.getMessage());
}
return retObj;
}
这里的查询又通过 executionDelegate.rawQuery(sql); 来执行,而executionDelegate 前面有提到过 SimpleSQLiteExecutionDelegate对象
public final class SimpleSQLiteExecutionDelegate implements ISQLiteExecutionDelegate {
private static final String TAG = "SQLiteLint.SimpleSQLiteExecutionDelegate";
private final SQLiteDatabase mDb;
public SimpleSQLiteExecutionDelegate(SQLiteDatabase db) {
assert db != null;
mDb = db;
}
@Override
public Cursor rawQuery(String selection, String... selectionArgs) throws SQLException {
if (!mDb.isOpen()) {
SLog.w(TAG, "rawQuery db close");
return null;
}
//最终调用了 SQLiteDatabase rawQuery 来执行查询
return mDb.rawQuery(selection, selectionArgs);
}
...
}
在当前的demo 中 执行 select name, sql from sqlite_master where type=’table’ 获取到结果为
可以看到这里能获取到 创建表的语句,接着当我们获取到结果之后,又会执行 doExecSqlCallback(execSqlCallbackPtr,paraPtr, dbPath, cu);会将返回的内容解析出来,接着会调用
//解析了一条数据,就传递到native层 execSqlCallback(execSqlCallbackPtr, paraPtr, dbName, columnCount, value, name);
extern "C" {
void Java_com_tencent_sqlitelint_SQLiteLintNativeBridge_execSqlCallback(JNIEnv *env, jobject, jlong exec_sql_callback_ptr, jlong para_ptr, jstring name, jint n_column,
jobjectArray column_value, jobjectArray column_name) {
...
//将回调地址直接强转为 SqlExecutionCallback
SqlExecutionCallback exec_sql_callback = (SqlExecutionCallback) (intptr_t) exec_sql_callback_ptr;
if (!exec_sql_callback) {
LOGE("execSqlCallback execSqlCallback is NULL");
return;
}
//参数也是
void* para = (void *) (intptr_t) para_ptr;
if (!para) {
LOGE("execSqlCallback para is NULL");
return;
}
...
//又执行查询结果的回调
exec_sql_callback(para, n_column, p_column_value, p_column_name);
...
}
这里的exec_sql_callback 即为我们前面传递的 OnSelectTablesCallback 函数,所以就会执行到对应的函数
int OnSelectTablesCallback(void *para, int n_column, char **column_value, char **column_name) {
...
//创建一个 TableInfo 对象
TableInfo table_info;
//获取到表明
table_info.table_name_ = (column_value[0] == nullptr ? "" : column_value[0]);
//如果当前获取到的表名 为 "sqlite_master", "sqlite_sequence", "android_metadata" 中的一个 ,直接返回,因为这些不是我们创建的表,是属于系统
if (ReserveSqlManager::IsReservedTable(table_info.table_name_)) {
return 0;
}
//如果到了这里说明表是用户创建的表
table_info.create_sql_ = (column_value[1] == nullptr ? "" : column_value[1]);
//将para 参数转为 std::vector<TableInfo>
std::vector<TableInfo> *infoList = reinterpret_cast<std::vector<TableInfo> *>(para);
//添加到集合中
infoList->push_back(table_info);
return 0;
}
这里我们构建了一个TableInfo 对象,然后从传递过来的结果中赋值 对应的创建表sql语句,之后添加到集合TableInfo中,这样数据库的表信息就收集完毕了
回到 void LintEnv::CollectTablesInfo() 函数,收集了当前数据库中所拥有的表之后,继续往下面执行
//遍历当前 tables_info_ 集合
for (TableInfo &table_info : tables_info_) {
//查询数据库列的 sql语句
select_columns_sql = "PRAGMA table_info(" + table_info.table_name_ + ")";
//执行查询操作,这里传递的参数 为 table_info,查询完之后填充这个表中列的信息
r = GetQuery(select_columns_sql, OnSelectColumnsCallback, &table_info, &err_msg);
//处理查询列错误的信息
if (!DealWithGetQuery(r, err_msg, select_columns_sql)) {
return;
}
//接下来查询表的 索引信息
select_index_sql = "PRAGMA index_list(" + table_info.table_name_ + ")";
//这里的 接受处理的回调函数为 OnSelectIndexsCallback
r = GetQuery(select_index_sql, OnSelectIndexsCallback, &table_info, &err_msg);
//处理查询错误的信息
if (!DealWithGetQuery(r, err_msg, select_index_sql)) {
return;
}
//遍历当前表中的索引名称,判断是否为组合索引,如果是获取到组合索引中的元素,
for (IndexInfo &index_info : table_info.indexs_) {
select_index_info_sql = "PRAGMA index_info(" + index_info.index_name_ + ")";
r = GetQuery(select_index_info_sql, OnSelectIndexInfoCallback, &index_info, &err_msg);
//处理错误的信息
if (!DealWithGetQuery(r, err_msg, select_index_info_sql)) {
return;
}
}
}
之后执行查询
select_columns_sql = "PRAGMA table_info(" + table_info.table_name_ + ")";
//执行查询操作,这里传递的参数 为 table_info,查询完之后填充这个表中列的信息
r = GetQuery(select_columns_sql, OnSelectColumnsCallback, &table_info, &err_msg);
PRAGMA table_info(testTable) 查询的结果 ,从下面的几个可以看出,这个语句可以查询出当前表的列,type等
至于查询的过程就不重复了,查询成功之后,就会回调执行 OnSelectColumnsCallback()函数,进一步的填充 代表当前表信息的 table_info 对象
int OnSelectColumnsCallback(void *para, int n_column, char **column_value, char **column_name) {
if (para == nullptr) {
sError("OnSelectColumnsCallback para is null");
return -1;
}
//将参数转转为 TableInfo
TableInfo *table_info = reinterpret_cast<TableInfo *>(para);
//接下来填充 table_info中 columns_ 成员的值
ColumnInfo column_info;
int columns_assigned = 0;
for (int i = 0; i < n_column; i++) {
if (strcmp("name", column_name[i]) == 0) {//填充name
column_info.name_ = (nullptr != column_value[i] ? column_value[i] : "");
columns_assigned++;
} else if (strcmp("type", column_name[i]) == 0) {//填充type
column_info.type_ = (nullptr != column_value[i] ? column_value[i] : "");
columns_assigned++;
} else if (strcmp("pk", column_name[i]) == 0) {//填充 primary_key
column_info.is_primary_key_ = column_value[i][0] != '0';
columns_assigned++;
}
if (columns_assigned == 3) {
break;
}
}
table_info->columns_.push_back(column_info);
return 0;
}
接着又执行
select_index_sql = "PRAGMA index_list(" + table_info.table_name_ + ")";
//这里的 接受处理的回调函数为 OnSelectIndexsCallback
r = GetQuery(select_index_sql, OnSelectIndexsCallback, &table_info, &err_msg);
“PRAGMA index_list(“ + table_info.tablename + “)”;
查询完之后,就会执行 OnSelectIndexsCallback 函数指针,进一步的填充 table_info对象
int OnSelectIndexsCallback(void *para, int n_column, char **column_value, char **column_name) {
if (para == nullptr) {
sError("OnSelectIndexsCallback para is null");
return -1;
}
IndexInfo index_info;
int columns_assigned = 0;
for (int i = 0; i < n_column; i++) {
if (strcmp("name", column_name[i]) == 0) {//索引的名称
index_info.index_name_ = (nullptr != column_value[i] ? column_value[i] : "");
columns_assigned++;
} else if (strcmp("seq", column_name[i]) == 0) {//索引的顺序
index_info.seq_ = atoi(column_value[i]);
columns_assigned++;
} else if (strcmp("unique", column_name[i]) == 0) {//索引是否唯一
index_info.is_unique_ = column_value[i][0] != '0';
columns_assigned++;
}
if (columns_assigned == 3) {
break;
}
}
//将参数转转为 TableInfo
TableInfo* table_info = reinterpret_cast<TableInfo *>(para);
sVerbose("OnSelectIndexsCallback index : %s", index_info.index_name_.c_str());
//填充当前表创建的索引信息
table_info->indexs_.push_back(index_info);
return 0;
}
接着遍历获取到当前表的索引,判断是否为组合索引
select_index_info_sql = "PRAGMA index_info(" + index_info.index_name_ + ")";
r = GetQuery(select_index_info_sql, OnSelectIndexInfoCallback, &index_info, &err_msg);
“PRAGMA index_info(“ + index_info.indexname + “)”;
查询完之后,回调执行 OnSelectIndexInfoCallback 函数,进一步的填充 table_info对象
int OnSelectIndexInfoCallback(void* para, int n_column, char** column_value, char** column_name) {
if (para == nullptr) {
sError("OnSelectIndexsCallback para is null");
return -1;
}
IndexElement index_element;
int columns_assigned = 0;
for (int i = 0; i < n_column; i++) {
if (strcmp("seqno", column_name[i]) == 0) {//位置
index_element.pos_ = atoi(column_value[i]);
columns_assigned ++;
}
else if (strcmp("cid", column_name[i]) == 0) {//对应列的索引
index_element.column_index_ = atoi(column_value[i]);
columns_assigned ++;
}
else if (strcmp("name", column_name[i]) == 0) {//列的名称
index_element.column_name_ = (nullptr != column_value[i] ? column_value[i] : "");
columns_assigned ++;
}
if (columns_assigned == 3) {
break;
}
}
//添加到TableInfo 中的 IndexInfo 数组中
IndexInfo* index_info = reinterpret_cast<IndexInfo*>(para);
if(index_info) {
index_info->AddIndexElement(index_element);
} else{
sError("onCollectIndexInfoCallback indexInfo is null");
}
return 0;
}
在这些信息都收集完之后,就可以执行分析了,继续回到前面
检查 AutoIncrement
void AvoidAutoIncrementChecker::Check(LintEnv &env, const SqlInfo &sql_info, std::vector<Issue> *issues) {
//获取到当前数据库 中表的信息集合
std::vector<TableInfo> tables = env.GetTablesInfo();
std::string create_sql;
//遍历这些表
for (const TableInfo& table_info : tables) {
//检查这个table 是否在白名单中
if (env.IsInWhiteList(kCheckerName, table_info.table_name_)) {
sVerbose("AvoidAutoIncrementChecker::Check in white list: %s", table_info.table_name_.c_str());
continue;
}
//获取到当前创建的sql语句,在 select name, sql from sqlite_master where type='table' 中就能获取到全部表的创建信息
create_sql = table_info.create_sql_;
//转成小写
ToLowerCase(create_sql);
//从创建表的sql 语句中 查找是否有 autoincrement 关键字
if (create_sql.find(kAutoIncrementKeyWord) != std::string::npos) {
//如果找到了,发布一个结果
PublishIssue(env, table_info.table_name_, issues);
}
}
}
对于检测的内容也明了,就是通过你的创建表的sql中判断是否有autoincrement 关键字,如果有就执行 PublishIssue(env, table_info.table_name_, issues);
void AvoidAutoIncrementChecker::PublishIssue(const LintEnv& env, const std::string &table_name, std::vector<Issue> *issues) {
std::string desc = "Table(" + table_name + ") has a column which is AutoIncrement." + "It's not really recommended.";
Issue issue;
issue.id = GenIssueId(env.GetDbFileName(), kCheckerName, table_name);
issue.db_path = env.GetDbPath();
//当前创建的时间
issue.create_time = GetSysTimeMillisecond();
//等级
issue.level = IssueLevel::kTips;
//当前的类型
issue.type = IssueType::kAvoidAutoIncrement;
//数据库名
issue.table = table_name;
//详细的描述信息
issue.desc = desc;
//添加到集合中
issues->push_back(issue);
}
这里的 issues 为我们传递进来的,所以这里就会往这个集合添加元素,继续回到我们的线程执行函数
void Lint::InitCheck() {
sVerbose("Lint::Check() init check");
//休眠4秒
std::this_thread::sleep_for(std::chrono::seconds(4));
//构建一个集合用来收集结果
std::vector<Issue>* published_issues = new std::vector<Issue>;
//安排检查, 类型为 kAfterInit 的有 AvoidAutoIncrementChecker , WithoutRowIdBetterChecker ,RedundantIndexChecker
ScheduleCheckers(CheckScene::kAfterInit, SqlInfo(), published_issues);
//如果published_issues 结果 不为空,说明检查到了有不合格的内容
if (!published_issues->empty()) {
sInfo("New check some diagnosis out!");
if (issued_callback_) {
//将结果传递回去
issued_callback_(env_.GetDbPath().c_str(), *published_issues);
}
}
delete published_issues;
}
这样published_issues 集合中就有内容了,下面就可以将结果传递出去了 issued_callback_(env_.GetDbPath().c_str(), *published_issues);
/**
* 函数指针,用于进一步的中转结果
* @param db_path
* @param published_issues
*/
void OnIssuePublish(const char* db_path, std::vector<Issue> published_issues) {
...
//获取到Env
bool attached = false;
JNIEnv* env = nullptr;
jint jRet = kJvm->GetEnv((void **) &env, JNI_VERSION_1_6);
if (jRet == JNI_EDETACHED) {
LOGD("OnIssuePublish GetEnv JNI_EDETACHED");
jint jAttachRet = kJvm->AttachCurrentThread(&env, nullptr);
if (jAttachRet != JNI_OK) {
LOGE("OnIssuePublish AttachCurrentThread !JNI_OK");
return;
} else {
attached = true;
}
} else if (jRet != JNI_OK) {
LOGE("OnIssuePublish GetEnv !JNI_OK");
return;
}
LOGV("OnIssuePublish issue size %d", published_issues.size());
//构建一个ArrayList
jclass listCls = env->FindClass("java/util/ArrayList");
jobject jIssueList = env->NewObject(listCls, kListConstruct);
//将published_issues 集合中的每一个Issure 的信息,添加到 ArrayList中
for (std::vector<Issue>::iterator it = published_issues.begin(); it != published_issues.end(); it++) {
jstring id = charsToJstring(env,it->id.c_str());
jstring dbPath = charsToJstring(env,it->db_path.c_str());
jint level = it->level;
jint type = it->type;
jstring sql = charsToJstring(env,it->sql.c_str());
jstring table = charsToJstring(env,it->table.c_str());
jstring desc = charsToJstring(env,it->desc.c_str());
jstring detail = charsToJstring(env,it->detail.c_str());
jstring advice = charsToJstring(env,it->advice.c_str());
jlong createTime = it->create_time;
jstring extInfo = charsToJstring(env, it->ext_info.c_str());
jlong sqlTimeCost = it->sql_time_cost;
jboolean isInMainThread = it->is_in_main_thread;
//构建以java层 SQLiteLintIssue对象
jobject diagnosisObj = env->NewObject(kIssueClass, kMethodIDIssueConstruct, id, dbPath,
level, type, sql, table, desc, detail, advice, createTime, extInfo, sqlTimeCost, isInMainThread);
LOGV("OnIssuePublish id=%s", it->id.c_str());
//执行 ArrayList 的 Add 函数,添加到 ArrayList 集合中
env->CallBooleanMethod(jIssueList, kListAdd, diagnosisObj);
env->DeleteLocalRef(id);
env->DeleteLocalRef(dbPath);
env->DeleteLocalRef(sql);
env->DeleteLocalRef(table);
env->DeleteLocalRef(desc);
env->DeleteLocalRef(detail);
env->DeleteLocalRef(advice);
env->DeleteLocalRef(extInfo);
}
//调用 SQLiteLintNativeBridge 的 onPublishIssue 函数,传递结果
jstring jDbPath = charsToJstring(env, db_path);
env->CallStaticVoidMethod(kJavaBridgeClass, kMethodIDOnPublishIssueCallback, jDbPath, jIssueList);
env->DeleteLocalRef(jDbPath);
if (attached) kJvm->DetachCurrentThread();
}
最终会通过JNI的调用java 到 SQLiteLintNativeBridge 中的 onPublishIssue 函数
private static void onPublishIssue(final String dbName, final ArrayList<SQLiteLintIssue> publishedIssues) {
try {
SQLiteLintAndroidCoreManager.INSTANCE.get(dbName).onPublish(publishedIssues);
}catch (Throwable e) {
SLog.e(TAG, "onPublishIssue ex ", e.getMessage());
}
}
之后的逻辑就不看了,大致就是会先存储到内部的数据库,然后将这些结果显示出来,包括冗余检查,Without RowId 这里就不检测了
对于我们事实的sql 语句检查是怎么样做到的呢
在demo 中 有这样的方法,会按钮按下的时候触发
private void testParser() {
//获取到 要测试的 sql 数组
String[] list = TestSQLiteLintHelper.getTestParserList();
//分别执行
for (String sql : list) {
MatrixLog.i(TAG, "testParser, sql = " + sql);
SQLiteLint.notifySqlExecution(TestDBHelper.get().getWritableDatabase().getPath(), sql, 10);
}
}
//获取到要测试的sql 语句
public static String[] getTestParserList() {
String[] list = new String[]{
"select a,b,c from test where id > 100 and id < 200 or id between 300 and 400 or id = 1000 ORDER BY c1,c2,c3 desc limit 10 offset 2;",
"select t1.a ,t2.b from table1 as t1,table2 as t2 where t1.tid = t2.tid and t1.tid='aaa' or t2.tid=12;",
"select count(*) from test union select id from test where id not IN (select id from test1);",
"select title, year from film where starring like 'Jodie%' and year >= 1985 order by year desc limit 10;",
"SELECT * FROM table1 WHERE column2 not LIKE 'rt' OR column3 LIKE 'rc' AND column1 = 'yy' GROUP BY column2 ORDER BY column2;",
"SELECT * FROM nosharding_test WHERE id = 1 AND(id < 50 OR id > 200);",
"SELECT * FROM test WHERE name NOT LIKE '/Hello%' ESCAPE '/';",
....
};
return list;
}
遍历每一条语句,分别执行
SQLiteLint.notifySqlExecution(TestDBHelper.get().getWritableDatabase().getPath(), sql, 10);
public static void notifySqlExecution(String concernedDbPath, String sql, int timeCost) {
//获取到当前 数据库的路径 对应的 SQLiteLintAndroidCore
if (SQLiteLintAndroidCoreManager.INSTANCE.get(concernedDbPath) == null) {
return;
}
//notifySqlExecution
SQLiteLintAndroidCoreManager.INSTANCE.get(concernedDbPath).notifySqlExecution(concernedDbPath, sql, timeCost);
}
public void notifySqlExecution(String dbPath, String sql, long timeCost) {
String extInfoStack = "null"; //get stack only when cost > 8
//timeCost 默认传递的参数为 10,所以大于8
if (timeCost >= 8) {
//这个是获取到当前的堆栈信息
extInfoStack = SQLiteLintUtil.getThrowableStack(new Throwable());
}
//传递到native 层
SQLiteLintNativeBridge.nativeNotifySqlExecute(dbPath, sql, timeCost, extInfoStack);
}
可以看到这里首先获取到java层对应的堆栈信息,然后传递给native层
void Java_com_tencent_sqlitelint_SQLiteLintNativeBridge_nativeNotifySqlExecute(JNIEnv *env, jobject, jstring dbPath
, jstring sql, jlong executeTime, jstring extInfo) {
char *filename = jstringToChars(env, dbPath);
char *ext_info = jstringToChars(env, extInfo);
char *jsql = jstringToChars(env, sql);
NotifySqlExecution(filename, jsql, executeTime, ext_info);
free(jsql);
free(ext_info);
free(filename);
}
void NotifySqlExecution(const char* db_path, const char* sql, long time_cost, const char* ext_info) {
LintManager::Get()->NotifySqlExecution(db_path, sql, time_cost, ext_info);
}
void LintManager::NotifySqlExecution(const char *db_path, const char *sql, long time_cost, const char* ext_info) {
//加锁
std::unique_lock<std::mutex> lock(lints_mutex_);
//找到当前数据库路径对应的 Lint 对象
std::map<const std::string, Lint*>::iterator it = lints_.find(db_path);
if (it == lints_.end()) {
lock.unlock();
sWarn("LintManager::NotifySqlExecution lint not installed; dbPath: %s", db_path);
return;
}
//执行测试
it->second->NotifySqlExecution(sql, time_cost, ext_info);
lock.unlock();
}
void Lint::NotifySqlExecution(const char *sql, const long time_cost, const char* ext_info) {
//参数检查
if (sql == nullptr){
sError("Lint::NotifySqlExecution sql NULL");
return;
}
//判断当前sql 是否在 reserve_sql_mgr_ 集合中 找得到,并且找到的时候 时间不能超过一秒
if (env_.IsReserveSql(sql)) {
sDebug("Lint::NotifySqlExecution a reserved sql");
return;
}
//构建一个SqlInfo 对象
SqlInfo *sql_info = new SqlInfo();
sql_info->sql_ = sql;
//对应的执行的时间
sql_info->execution_time_ = GetSysTimeMillisecond();
//对应的java 的堆栈信息
sql_info->ext_info_ = ext_info;
//对应的花费的时间
sql_info->time_cost_ = time_cost;
//标识当前的状态是否在主线程
sql_info->is_in_main_thread_ = IsInMainThread();
//加锁
std::unique_lock<std::mutex> lock(queue_mutex_);
//将这个 sql_info 添加到了 queue_ 队列中,同时执行了 notify_one ,这样检查的线程就会唤醒了
queue_.push_back(std::unique_ptr<SqlInfo>(sql_info));
queue_cv_.notify_one();
//解锁
lock.unlock();
}
这边会将传递参数,构建一个SqlInfo 对象,然后添加到 queue_ 中,前面说个还有一个线程因为queue中的内容为空,导致处于阻塞的状态,这里因为添加进去了,所以就不会阻塞了,继续回到
线程函数
void Lint::Check() {
//这边又构建一个 init_check_thread_ 的线程,线程执行的时候,会执行 InitCheck 函数
init_check_thread_ = new std::thread(&Lint::InitCheck, this);
//用来存储要分发结果的集合
std::vector<Issue>* published_issues = new std::vector<Issue>;
std::unique_ptr<SqlInfo> sql_info;
SqlInfo simple_sql_info;
//这下面一般是检查是否有结果,有就分发出去之类的
while (true) {
//从queue 结果队列中 获取到内容,如果 队列中有元素,取出对头的元素,然后辅助给 sql_info ,如果没有元素,则会阻塞在这里
int ret = TakeSqlInfo(sql_info);
if (ret != 0) {
sError("check exit");
break;
}
//取出了队列中的头部的元素,下面是执行分发
//标识当前检测的sql 语句加一
env_.IncSqlCnt();
...
ScheduleCheckers(CheckScene::kSample, *sql_info, published_issues);
const std::string& wildcard_sql = sql_info->wildcard_sql_.empty() ? sql_info->sql_ : sql_info->wildcard_sql_;
bool checked = false;
if (!checked_sql_cache_.Get(wildcard_sql, checked)) {
...
ScheduleCheckers(CheckScene::kUncheckedSql, *sql_info, published_issues);
checked_sql_cache_.Put(wildcard_sql, true);
} else {
sVerbose("Lint::Check() already checked recently");
}
if (!published_issues->empty()) {
sInfo("New check some diagnosis out!, sql=%s", sql_info->sql_.c_str());
if (issued_callback_) {
issued_callback_(env_.GetDbPath().c_str(), *published_issues);
}
}
sql_info = nullptr;
env_.CheckReleaseHistory();
}
sError("check break");
delete published_issues;
}
由于TakeSqlInfo() 返回了元素,所以会往下执行,分别执行 ScheduleCheckers(CheckScene::kSample, *sql_info, published_issues);,以及
ScheduleCheckers(CheckScene::kUncheckedSql, *sql_info, published_issues);至于检测的内容就不细看了,这里比较复杂,检测到了结果之后,就会将检测的结果通过issued_callback_
传递到java层
总结
SQLiteLint 中检测 ,索引使用问题,冗余索引问题,Autoincrement 问题,without rowid 特性不需要事实的sql语句,只需要通过读取数据库创建的表信息就可以检测,其中检测是通过JNI的方式将参数传递到java层,由java层来真正的操作数据库,查询完之后又将结果传递给Native层,对于 Select * ,prepared statement 需要事实的sql语句来分析,由于没有采用hook sqlite3_profile 所以对于要检测的sql 语句需要java层传递到native层