简介:
通过源码,我们来一步步分析Mapbox地图引擎如何进行指定字符串变量解析成url地址加载的,这里是基于5.3.0的版本.
在官方demo中,我们不仅可以加载本地样式文件,已定义样式文件和网络在线文件,它们的格式分别是
- “asset://test.json”
- “https://www.mapbox.com/android-docs/files/mapbox-raster-v8.json“
- “mapbox://styles/mapbox/streets-v10”
这些格式,那么Mapbox如果解析这些字符串去获取到需要的样式数据呢?我们从Mapbox源码分析之样式加载这篇的loadURL()方法开始看起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
| void Style::Impl::loadURL(const std::string& url_) { lastError = nullptr; observer->onStyleLoading(); loaded = false; url = url_; styleRequest = fileSource.request(Resource::style(url), [this](Response res) { if (res.isFresh() || mutated) { styleRequest.reset(); } if (mutated && loaded) { return; } if (res.error) { const std::string message = "loading style failed: " + res.error->message; Log::Error(Event::Setup, message.c_str()); observer->onStyleError(std::make_exception_ptr(util::StyleLoadException(message))); observer->onResourceError(std::make_exception_ptr(std::runtime_error(res.error->message))); } else if (res.notModified || res.noContent) { return; } else { parse(*res.data); } }); }
|
我们在这里看到,样式的数据是通过fileSource.request进行请求加载的,通过调试我们发现这个fileSource是FileSource的子类DefaultFileSource,那么我们先看看这个DefaultFileSource是什么时候传进来的
1 2 3 4 5 6 7 8 9
| Style::Impl::Impl(Scheduler& scheduler_, FileSource& fileSource_, float pixelRatio) : scheduler(scheduler_), fileSource(fileSource_), spriteLoader(std::make_unique<SpriteLoader>(pixelRatio)), light(std::make_unique<Light>()), observer(&nullObserver) { spriteLoader->setObserver(this); light->setObserver(this); }
|
我们在这里看到,是在构造方法时对fileSource变量进行初始化的,那么我们只需要看到Style::Impl对象什么时候构造的,便知道了fileSource的来源,继续往回找
1 2 3
| Style::Style(Scheduler& scheduler, FileSource& fileSource, float pixelRatio) : impl(std::make_unique<Impl>(scheduler, fileSource, pixelRatio)) { }
|
在这里我们发现Impl对象的fileSource是Style对象构造时传进来的,那么我们继续往回找
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
| style::Style& Map::getStyle() { return *impl->style; } Map::Impl::Impl(Map& map_, RendererFrontend& frontend, MapObserver& mapObserver, float pixelRatio_, FileSource& fileSource_, Scheduler& scheduler_, MapMode mode_, ConstrainMode constrainMode_, ViewportMode viewportMode_) : map(map_), observer(mapObserver), rendererFrontend(frontend), fileSource(fileSource_), scheduler(scheduler_), transform(observer, constrainMode_, viewportMode_), mode(mode_), pixelRatio(pixelRatio_), style(std::make_unique<Style>(scheduler, fileSource, pixelRatio)), annotationManager(*style) { style->impl->setObserver(this); rendererFrontend.setObserver(*this); }
|
这里我们看到Style对象是通过map.cpp里的getStyle对象获取的,而style对象是在Map::Impl::Impl构造方法时初始化的,继续往回找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| Map::Map(RendererFrontend& rendererFrontend, MapObserver& mapObserver, const Size size, const float pixelRatio, FileSource& fileSource, Scheduler& scheduler, MapMode mapMode, ConstrainMode constrainMode, ViewportMode viewportMode) : impl(std::make_unique<Impl>(*this, rendererFrontend, mapObserver, pixelRatio, fileSource, scheduler, mapMode, constrainMode, viewportMode)) { impl->transform.resize(size); }
|
这里我们其实也能大概猜出来Map::Impl对象是在Map构造方法时初始化的,那么map对象又是什么时候初始化的,是不是觉得很绕,马上就快到了,我们找到native_map_view.cpp文件,发现在NativeMapView构造方法中构造了map对象
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
| NativeMapView::NativeMapView(jni::JNIEnv& _env, jni::Object<NativeMapView> _obj, jni::Object<FileSource> jFileSource, jni::Object<MapRenderer> jMapRenderer, jni::jfloat _pixelRatio) : javaPeer(_obj.NewWeakGlobalRef(_env)) , mapRenderer(MapRenderer::getNativePeer(_env, jMapRenderer)) , pixelRatio(_pixelRatio) , threadPool(sharedThreadPool()) { if (_env.GetJavaVM(&vm) < 0) { _env.ExceptionDescribe(); return; } mbgl::FileSource& fileSource = mbgl::android::FileSource::getDefaultFileSource(_env, jFileSource); rendererFrontend = std::make_unique<AndroidRendererFrontend>(mapRenderer); map = std::make_unique<mbgl::Map>(*rendererFrontend, *this, mbgl::Size{ static_cast<uint32_t>(width), static_cast<uint32_t>(height) }, pixelRatio, fileSource, *threadPool, MapMode::Continuous, ConstrainMode::HeightOnly, ViewportMode::Default); }
|
到这里我们已经基本清楚fileSource的来源了,是JAVA层NativeMapView对象初始化的时候传下来的,我们继续看到开头,既然我们已经知道fileSource对象是DefaultFileSource,那么它调用的request方法,也就是调用的DefaultFileSource的request方法,这里我们看到default_file_source.cpp文件
1 2 3 4 5 6 7 8 9
| std::unique_ptr<AsyncRequest> DefaultFileSource::request(const Resource& resource, Callback callback) { auto req = std::make_unique<FileSourceRequest>(std::move(callback)); req->onCancel([fs = impl->actor(), req = req.get()] () mutable { fs.invoke(&Impl::cancel, req); }); impl->actor().invoke(&Impl::request, req.get(), resource, req->actor()); return std::move(req); }
|
这里我们看到它转到了它的实现类的request方法
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
| void request(AsyncRequest* req, Resource resource, ActorRef<FileSourceRequest> ref) { auto callback = [ref] (const Response& res) mutable { ref.invoke(&FileSourceRequest::setResponse, res); }; if (isAssetURL(resource.url)) { tasks[req] = assetFileSource->request(resource, callback); } else if (LocalFileSource::acceptsURL(resource.url)) { tasks[req] = localFileSource->request(resource, callback); } else { if (resource.hasLoadingMethod(Resource::LoadingMethod::Cache)) { auto offlineResponse = offlineDatabase->get(resource); if (resource.loadingMethod == Resource::LoadingMethod::CacheOnly) { if (!offlineResponse) { offlineResponse.emplace(); offlineResponse->noContent = true; offlineResponse->error = std::make_unique<Response::Error>( Response::Error::Reason::NotFound, "Not found in offline database"); } else if (!offlineResponse->isUsable()) { offlineResponse->error = std::make_unique<Response::Error>( Response::Error::Reason::NotFound, "Cached resource is unusable"); } callback(*offlineResponse); } else if (offlineResponse) { resource.priorModified = offlineResponse->modified; resource.priorExpires = offlineResponse->expires; resource.priorEtag = offlineResponse->etag; resource.priorData = offlineResponse->data; if (offlineResponse->isUsable()) { callback(*offlineResponse); } } } if (resource.hasLoadingMethod(Resource::LoadingMethod::Network)) { tasks[req] = onlineFileSource.request(resource, [=] (Response onlineResponse) mutable { this->offlineDatabase->put(resource, onlineResponse); callback(onlineResponse); }); } } }
|
这里我们可以看到根据url的不同,和加载方法的不同,将请求分别转给了assetFileSource,localFileSource,onlineFileSource等的request方法,这里我们看onlineFileSource的request方法
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
| std::unique_ptr<AsyncRequest> OnlineFileSource::request(const Resource& resource, Callback callback){ Resource res = resource; switch (resource.kind) { case Resource::Kind::Unknown: case Resource::Kind::Image: break; case Resource::Kind::Style: res.url = mbgl::util::mapbox::normalizeStyleURL(apiBaseURL, resource.url, accessToken); break; case Resource::Kind::Source: res.url = util::mapbox::normalizeSourceURL(apiBaseURL, resource.url, accessToken); break; case Resource::Kind::Glyphs: res.url = util::mapbox::normalizeGlyphsURL(apiBaseURL, resource.url, accessToken); break; case Resource::Kind::SpriteImage: case Resource::Kind::SpriteJSON: res.url = util::mapbox::normalizeSpriteURL(apiBaseURL, resource.url, accessToken); break; case Resource::Kind::Tile: res.url = util::mapbox::normalizeTileURL(apiBaseURL, resource.url, accessToken); break; } return std::make_unique<OnlineFileRequest>(std::move(res), std::move(callback), *impl);}
|
看到这里我们看到根据请求的类型不同,去处理不同的url,在这些参数里我们看下apiBaseURL这个变量,这是一个base url,指定了服务器地址,我们在constants.hpp文件中找到了它
1
| constexpr const char* API_BASE_URL = "https://api.mapbox.com";
|
继续往下看,我们选normalizeStyleURL()方法往下看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| std::string normalizeStyleURL(const std::string& baseURL, const std::string& str, const std::string& accessToken) { if (!isMapboxURL(str)) { return str; } const URL url(str); if (!equals(str, url.domain, "styles")) { Log::Error(Event::ParseStyle, "Invalid style URL"); return str; } const auto tpl = baseURL + "/styles/v1{path}?access_token=" + accessToken; return transformURL(tpl, str, url); }
|
这里我们看到它先验证了一下url,然后将url字符串包装成URL对象,然后进行一个拼接成tpl变量,最后再通过transformURL函数进行一个转换,这里我们先看它如何包装这个URL对象的
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
| URL::URL(const std::string& str) : query([&]() -> Segment { const auto hashPos = str.find('#'); const auto queryPos = str.find('?'); if (queryPos == std::string::npos || hashPos < queryPos) { return { hashPos != std::string::npos ? hashPos : str.size(), 0 }; } return { queryPos, (hashPos != std::string::npos ? hashPos : str.size()) - queryPos }; }()), scheme([&]() -> Segment { if (str.empty() || !isAlphaCharacter(str.front())) return { 0, 0 }; size_t schemeEnd = 0; while (schemeEnd < query.first && isSchemeCharacter(str[schemeEnd])) ++schemeEnd; return { 0, str[schemeEnd] == ':' ? schemeEnd : 0 }; }()), domain([&]() -> Segment { auto domainPos = scheme.first + scheme.second; while (domainPos < query.first && (str[domainPos] == ':' || str[domainPos] == '/')) { ++domainPos; } const bool isData = str.compare(scheme.first, scheme.second, "data") == 0; const auto endPos = str.find(isData ? ',' : '/', domainPos); return { domainPos, std::min(query.first, endPos) - domainPos }; }()), path([&]() -> Segment { auto pathPos = domain.first + domain.second; const bool isData = str.compare(scheme.first, scheme.second, "data") == 0; if (isData) { pathPos++; } return { pathPos, query.first - pathPos }; }()) { }
|
这里我们看到它将字符串分解成query,scheme,domain,path四个变量进行存储,我们再看看transformURL()函数
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
| std::string transformURL(const std::string& tpl, const std::string& str, const URL& url) { auto result = util::replaceTokens(tpl, [&](const std::string& token) -> std::string { if (token == "path") { return str.substr(url.path.first, url.path.second); } else if (token == "domain") { return str.substr(url.domain.first, url.domain.second); } else if (token == "scheme") { return str.substr(url.scheme.first, url.scheme.second); } else if (token == "directory") { const Path path(str, url.path.first, url.path.second); return str.substr(path.directory.first, path.directory.second); } else if (token == "filename") { const Path path(str, url.path.first, url.path.second); return str.substr(path.filename.first, path.filename.second); } else if (token == "extension") { const Path path(str, url.path.first, url.path.second); return str.substr(path.extension.first, path.extension.second); } return ""; }); if (url.query.second > 1) { const auto amp = result.find('?') != std::string::npos ? result.size() : std::string::npos; result.append(str, url.query.first, url.query.second); if (amp < result.size()) { result[amp] = '&'; } } return result; }
|
这里我们看到根据url的不同变量值进行了再次字符串拼接,甚至根据路径的不同,继续拆分成Path对象,最后将拼接结果返回,到这里有关url解析拼接的过程就讲完了.