简介:
通过源码,我们来一步步分析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) { fileSource = std::make_unique<mbgl::DefaultFileSource>( jni::Make<std::string>(_env, _cachePath) + "/mbgl-offline.db", std::make_unique<AssetManagerFileSource>(_env, assetManager)); 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>()) { 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; case 1: break; case 2: migrateToVersion3(); case 3: case 4: migrateToVersion5(); case 5: migrateToVersion6(); case 6: return; default: break; } 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); 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)执行创建表,有关数据库初始化的过程就到这里了