Mapbox源码分析之数据库初始化

简介:

通过源码,我们来一步步分析Mapbox地图引擎初始化本地数据库的流程,这里是基于5.3.0的版本.

#### 注意
当我写这篇博客的时候,在官方changlog看到后续版本中对FileSource激活部分有做改动,这里暂时以现有版本为主,需要详细了解的同学可以下载新版本的源码了解

Mapbox提供了地图的离线下载存储功能,那么它是什么时候创建本地数据库的呢,我们看到NativeMapView的构造方法

1
2
3
4
5
6
7
8
9
10
public NativeMapView(final MapView mapView, MapRenderer mapRenderer) {
this.mapRenderer = mapRenderer;
this.mapView = mapView;
Context context = mapView.getContext();
fileSource = FileSource.getInstance(context);
pixelRatio = context.getResources().getDisplayMetrics().density;
nativeInitialize(this, fileSource, mapRenderer, pixelRatio);
}

这里我们看到它拿到了FileSource对象的实例,在这个类的注解中,我们知道它是负责激活本地数据源的,那么我们看看它的getInstance()方法

1
2
3
4
5
6
7
8
public static synchronized FileSource getInstance(Context context) {
if (INSTANCE == null) {
String cachePath = getCachePath(context);
INSTANCE = new FileSource(cachePath, context.getResources().getAssets());
}
return INSTANCE;
}

这里我们可以看出这是一个单例模式,我们看到它传入了一个缓存路径,那么这个路径会不会就是数据库存放的路径呢,我们继续往下看

1
2
3
private FileSource(String cachePath, AssetManager assetManager) {
initialize(Mapbox.getAccessToken(), cachePath, assetManager);
}

这里我们看到它调用了底层的初始化方法,那么我们看看和这个对应的底层文件file_source.cpp

1
2
3
4
5
6
7
8
9
10
11
12
FileSource::FileSource(jni::JNIEnv& _env,
jni::String accessToken,
jni::String _cachePath,
jni::Object<AssetManager> assetManager) {
// Create a core default file source
fileSource = std::make_unique<mbgl::DefaultFileSource>(
jni::Make<std::string>(_env, _cachePath) + "/mbgl-offline.db",
std::make_unique<AssetManagerFileSource>(_env, assetManager));
// Set access token
fileSource->setAccessToken(jni::Make<std::string>(_env, accessToken));
}

这里我们看到它在传入的路径后面加了”/mbgl-offline.db”,这个路径果然是数据库路径,我们看到在这个构造方法中实例化了一个DefaultFileSource对象,那么我们看看DefaultFileSource对象的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
DefaultFileSource::DefaultFileSource(const std::string& cachePath,
const std::string& assetRoot,
uint64_t maximumCacheSize)
: DefaultFileSource(cachePath, std::make_unique<AssetFileSource>(assetRoot), maximumCacheSize) {
}
DefaultFileSource::DefaultFileSource(const std::string& cachePath,
std::unique_ptr<FileSource>&& assetFileSource_,
uint64_t maximumCacheSize)
: assetFileSource(std::move(assetFileSource_))
, impl(std::make_unique<util::Thread<Impl>>("DefaultFileSource", assetFileSource, cachePath, maximumCacheSize)) {
}

这里我们发现他又初始化了它的Impl实现类,那么我继续看它的实现类

1
2
3
4
5
6
7
8
9
10
Impl(ActorRef<Impl> self, std::shared_ptr<FileSource> assetFileSource_, const std::string& cachePath, uint64_t maximumCacheSize)
: assetFileSource(assetFileSource_)
, localFileSource(std::make_unique<LocalFileSource>()) {
// Initialize the Database asynchronously so as to not block Actor creation.
self.invoke(&Impl::initializeOfflineDatabase, cachePath, maximumCacheSize);
}
void initializeOfflineDatabase(std::string cachePath, uint64_t maximumCacheSize) {
offlineDatabase = std::make_unique<OfflineDatabase>(cachePath, maximumCacheSize);
}

这里我们看到在它的构造方法中,又执行了initializeOfflineDatabase()方法,从这个方法名可以看出是初始化数据库,我们看到这个方法里面有实例化一个OfflineDatabase对象,我们看看这个对象

1
2
3
4
5
OfflineDatabase::OfflineDatabase(std::string path_, uint64_t maximumCacheSize_)
: path(std::move(path_)),
maximumCacheSize(maximumCacheSize_) {
ensureSchema();
}

这里看出并未做什么,初始化值之后,便调用了ensureSchema()方法,我们看看这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void OfflineDatabase::ensureSchema() {
if (path != ":memory:") {
try {
connect(mapbox::sqlite::ReadWrite);
switch (userVersion()) {
case 0: break; // cache-only database; ok to delete
case 1: break; // cache-only database; ok to delete
case 2: migrateToVersion3(); // fall through
case 3: // no-op and fall through
case 4: migrateToVersion5(); // fall through
case 5: migrateToVersion6(); // fall through
case 6: return;
default: break; // downgrade, delete the database
}
removeExisting();
connect(mapbox::sqlite::ReadWrite | mapbox::sqlite::Create);
} catch (mapbox::sqlite::Exception& ex) {
if (ex.code != mapbox::sqlite::Exception::Code::CANTOPEN && ex.code != mapbox::sqlite::Exception::Code::NOTADB) {
Log::Error(Event::Database, "Unexpected error connecting to database: %s", ex.what());
throw;
}
try {
if (ex.code == mapbox::sqlite::Exception::Code::NOTADB) {
removeExisting();
}
connect(mapbox::sqlite::ReadWrite | mapbox::sqlite::Create);
} catch (...) {
Log::Error(Event::Database, "Unexpected error creating database: %s", util::toString(std::current_exception()).c_str());
throw;
}
}
}
try {
#include "offline_schema.cpp.include"
connect(mapbox::sqlite::ReadWrite | mapbox::sqlite::Create);
// If you change the schema you must write a migration from the previous version.
db->exec("PRAGMA auto_vacuum = INCREMENTAL");
db->exec("PRAGMA journal_mode = DELETE");
db->exec("PRAGMA synchronous = FULL");
db->exec(schema);
db->exec("PRAGMA user_version = 6");
} catch (...) {
Log::Error(Event::Database, "Unexpected error creating database schema: %s", util::toString(std::current_exception()).c_str());
throw;
}
}

到这里基本就可以看出来了首先是通过connect()方法连接数据库,我们先看看connect()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void OfflineDatabase::connect(int flags) {
db = std::make_unique<mapbox::sqlite::Database>(path.c_str(), flags);
db->setBusyTimeout(Milliseconds::max());
db->exec("PRAGMA foreign_keys = ON");
}
Database::Database(const std::string &filename, int flags)
: impl(std::make_unique<DatabaseImpl>(filename.c_str(), flags))
{
}
DatabaseImpl(const char* filename, int flags)
{
const int error = sqlite3_open_v2(filename, &db, flags, nullptr);
if (error != SQLITE_OK) {
const auto message = sqlite3_errmsg(db);
db = nullptr;
throw Exception { error, message };
}
}

这里我就把相关代码放一起了,我们看到connect()方法实例化了一个Database对象,并通过DatabaseImpl类调用sqlite3_open_v2()打开了一个数据库,那么和离线瓦片数据相关的表是怎么创建的呢,这里我们回到上面的ensureSchema()代码处,看到有这么一行

1
#include "offline_schema.cpp.include"

我们找到这个文件看看,我们找到该文件在 platform/default/mbgl/storage目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/* THIS IS A GENERATED FILE; EDIT offline_schema.sql INSTEAD */
static const char * schema =
"CREATE TABLE resources (\n"
" id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n"
" url TEXT NOT NULL,\n"
" kind INTEGER NOT NULL,\n"
" expires INTEGER,\n"
" modified INTEGER,\n"
" etag TEXT,\n"
" data BLOB,\n"
" compressed INTEGER NOT NULL DEFAULT 0,\n"
" accessed INTEGER NOT NULL,\n"
" must_revalidate INTEGER NOT NULL DEFAULT 0,\n"
" UNIQUE (url)\n"
");\n"
"CREATE TABLE tiles (\n"
" id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n"
" url_template TEXT NOT NULL,\n"
" pixel_ratio INTEGER NOT NULL,\n"
" z INTEGER NOT NULL,\n"
" x INTEGER NOT NULL,\n"
" y INTEGER NOT NULL,\n"
" expires INTEGER,\n"
" modified INTEGER,\n"
" etag TEXT,\n"
" data BLOB,\n"
" compressed INTEGER NOT NULL DEFAULT 0,\n"
" accessed INTEGER NOT NULL,\n"
" must_revalidate INTEGER NOT NULL DEFAULT 0,\n"
" UNIQUE (url_template, pixel_ratio, z, x, y)\n"
");\n"
"CREATE TABLE regions (\n"
" id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,\n"
" definition TEXT NOT NULL,\n"
" description BLOB\n"
");\n"
"CREATE TABLE region_resources (\n"
" region_id INTEGER NOT NULL REFERENCES regions(id) ON DELETE CASCADE,\n"
" resource_id INTEGER NOT NULL REFERENCES resources(id),\n"
" UNIQUE (region_id, resource_id)\n"
");\n"
"CREATE TABLE region_tiles (\n"
" region_id INTEGER NOT NULL REFERENCES regions(id) ON DELETE CASCADE,\n"
" tile_id INTEGER NOT NULL REFERENCES tiles(id),\n"
" UNIQUE (region_id, tile_id)\n"
");\n"
"CREATE INDEX resources_accessed\n"
"ON resources (accessed);\n"
"CREATE INDEX tiles_accessed\n"
"ON tiles (accessed);\n"
"CREATE INDEX region_resources_resource_id\n"
"ON region_resources (resource_id);\n"
"CREATE INDEX region_tiles_tile_id\n"
"ON region_tiles (tile_id);\n"
;

看到这里我们明白了,这里将相关创建表的语句定义成schema变量,通过#include引入到代码中,然后通过db->exec(schema)执行创建表,有关数据库初始化的过程就到这里了