MapView是整个MapBox的核心,有必要先把MapView研究一下。
- MapView的initialize方法其中调用了setWillNotDraw(false):自定义View中如果重写了onDraw()即自定义了绘制,那么就应该在构造函数中调用view的setWillNotDraw(false),设置该flag标志。其实默认该标志就是false。
- MapView的getMapAsync(final OnMapReadyCallback callback) 方法,如果不调用这个方法那么默认世界地图会显示出来,调用该方法,回调实现为空,默认世界地图也会显示出来,如果参数传一个null,默认世界地图也会显示出来,也就是说getMapAsync方法与原始地图数据的加载没有关系,此方法主要是当地图数据准备好后或者说是MapboxMap准备好后,回调添加的接口。
- 经过几天的断断续续的研读源码,终于发现,MapView默认加载的地图的设置是这一句话:
private String styleUrl = Style.MAPBOX_STREETS;
请求网络是HTTPRequest类(Java C++代码中也有相应的类),下面分析一下,整个加载流程:- Mapbox请求网络用的是OKHttp, HTTPRequest类实现了okhttp3.Callback接口,然后实现了该接口的两个方法
void onFailure(Call call, IOException e);
其中调用了private native void nativeOnFailure(int type, String message)这个C++实现的方法;
void onResponse(Call call, Response response) throws IOException;
其中调用了private native void nativeOnResponse(int code, String etag, String modified, String cacheControl, String expires, String retryAfter, String xRateLimitReset, byte[] body) 这个C++实现的方法。
需要注意的是这个类的构造方法是私有的,(并且此构造函数中是进行了网络请求的,下面会贴代码)不可能通过new来构造这个类,那么八成是通过反射来构造这个类。下面看C++的代码:
http_file_source.cpp这个类有这么一行代码:
static constexpr auto Name() { return “com/mapbox/mapboxsdk/http/HTTPRequest”; }; 可以猜测,这极有可能是通过这个类名来反射的。
注意,使用JNI目前有两种方式,一种是遵守JNI规范的函数命名规范(应用层多采用,这层也是大多数人一开始学习JNI接触到的),一种是采用函数注册的方式(应用框架层多采用)。Mapbox目前我发现的都是采用第二种函数注册的方式。
继续,前面说到HTTPRequest极有可能是通过反射来实例化的,
在http_file_source.cpp这个类中有一个类HTTPRequest(注意此类继承自AsyncRequest) 还有一个函数void RegisterNativeHTTPRequest(jni::JNIEnv& env) 现在只需要知道,这个函数是用来关联Java层与C/C++层函数的,在main.cpp中extern “C” JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved)方法调用的,当在系统中调用System.loadLibrary函数时,该函数会找到对应的动态库,然后首先试图找到”JNI_OnLoad”函数,如果该函数存在,则调用它。JNI_OnLoad可以和JNIEnv的registerNatives函数结合起来,实现动态的函数替换。这不是本文的重点,本文不在分析。
- Mapbox请求网络用的是OKHttp, HTTPRequest类实现了okhttp3.Callback接口,然后实现了该接口的两个方法
void RegisterNativeHTTPRequest(jni::JNIEnv& env) {
1. HTTPRequest::javaClass = *jni::Class<HTTPRequest>::Find(env).NewGlobalRef(env).release();//release()方法是智能指针的控制权的释放,有兴趣的同学可以看一下C++的智能指针。
2. #define METHOD(MethodPtr, name) jni::MakeNativePeerMethod<decltype(MethodPtr), (MethodPtr)>(name)
3. jni::RegisterNativePeer<HTTPRequest>(env, HTTPRequest::javaClass, "mNativePtr",
METHOD(&HTTPRequest::onFailure, "nativeOnFailure"),
METHOD(&HTTPRequest::onResponse, "nativeOnResponse"));
}
上面是RegisterNativeHTTPRequest方法,第1部分代码其实就是C++代码根据类名找到Java层对应的类(反射),这一行代码依次调用顺序为:
class.hpp中
static Class Find(JNIEnv& env)
{
return Class(FindClass(env, TagType::Name()));
}
//TagType::Name() 其实调用的就是上面说到的http_file_source.cpp中的方法static constexpr auto Name() { return "com/mapbox/mapboxsdk/http/HTTPRequest"; };最终结果HTTPRequest::javaClass指向了Java类HTTPRequest
functions.hpp中
inline jclass& FindClass(JNIEnv& env, const char* name)
{
return *CheckJavaException(env, Wrap<jclass*>(env.FindClass(name)));
}
template < class T >
UniqueGlobalRef<T> NewGlobalRef(JNIEnv& env, T* t)
{
jobject* obj = Wrap<jobject*>(env.NewGlobalRef(Unwrap(t)));
CheckJavaException(env);
if (t && !obj)
throw std::bad_alloc();
return UniqueGlobalRef<T>(reinterpret_cast<T*>(obj), GlobalRefDeleter(env));
}
owenrship.hpp中
template < class T >
using UniqueGlobalRef = std::unique_ptr< T, GlobalRefDeleter >;
//注意,Mapbox的C++层代码中使用了大量的智能指针,像上面一样,如果阅读有困难,可能先了解一下C++智能指针的相关语法知识。
第2部分的代码就是一个define宏定义,
第3部分的代码也很简单,就是将java层HTTPRequest.java中的native方法nativeOnFailure、nativeOnResponse对应至C++中的HTTPRequest::onFailure、HTTPRequest::onResponse方法,变量mNativePtr代表的是Java层中HTTPRequest对应C++层HTTPRequest对象的地址,下面是方法体:
native_method.hpp中:
template < class Peer, class TagType, class... Methods >
void RegisterNativePeer(JNIEnv& env, const Class<TagType>& clazz, const char* fieldName, Methods&&... methods)
{
static Field<TagType, jni::jlong> field { env, clazz, fieldName };
RegisterNatives(env, clazz, methods.template operator()<Peer>(field)...);
}
functions.hpp中
template < class... Methods >
inline void RegisterNatives(JNIEnv& env, jclass& clazz, const Methods&... methods)
{
::JNINativeMethod unwrapped[sizeof...(methods)] = { Unwrap(methods)... };
CheckJavaExceptionThenErrorCode(env,
env.RegisterNatives(Unwrap(clazz), unwrapped, sizeof...(methods)));
}
分析到这里,大家应该能明白,当调用System.loadLibrary函数时,java层与C++层各种方法字段就关联起来了。
下面来看http_file_source.cpp中的HTTPRequest类的构造函数:
HTTPRequest::HTTPRequest(jni::JNIEnv& env, const Resource& resource_, FileSource::Callback callback_)
: resource(resource_),
callback(callback_) {
std::string etagStr;
std::string modifiedStr;
if (resource.priorEtag) {
etagStr = *resource.priorEtag;
} else if (resource.priorModified) {
modifiedStr = util::rfc1123(*resource.priorModified);
}
jni::UniqueLocalFrame frame = jni::PushLocalFrame(env, 10);
static auto constructor =
javaClass.GetConstructor<jni::jlong, jni::String, jni::String, jni::String>(env);//取得Java类HTTPRequest的构造函数
javaRequest = javaClass.New(env, constructor,reinterpret_cast<jlong>(this),
jni::Make<jni::String>(env, resource.url),
jni::Make<jni::String>(env, etagStr),
jni::Make<jni::String>(env,modifiedStr)).NewGlobalRef(env);//实例化Java层中的HTTRequest对象。
}
至此java层HTTRequest对象已经构造成功,验证了前面说的通过反射来构造这个类的假设。注意,java层HTTPRequest类的构造函数中作了网络请求,看代码:
java层HTTPRequest类中:
private HTTPRequest(long nativePtr, String resourceUrl, String etag, String modified) {
mNativePtr = nativePtr;
try {
// Don't try a request if we aren't connected
if (!MapboxAccountManager.getInstance().isConnected()) {
throw new NoRouteToHostException("No Internet connection available.");
}
HttpUrl httpUrl = HttpUrl.parse(resourceUrl);
System.out.println("----------ssa----------"+httpUrl+"-------------");
final String host = httpUrl.host().toLowerCase(MapboxConstants.MAPBOX_LOCALE);
if (host.equals("mapbox.com") || host.endsWith(".mapbox.com") || host.equals("mapbox.cn") || host.endsWith(".mapbox.cn")) {
if (httpUrl.querySize() == 0) {
resourceUrl = resourceUrl + "?";
} else {
resourceUrl = resourceUrl + "&";
}
resourceUrl = resourceUrl + "events=true";
}
System.out.println("---------ssa-----------"+resourceUrl+"-------------");
Request.Builder builder = new Request.Builder()
.url(resourceUrl)
.tag(resourceUrl.toLowerCase(MapboxConstants.MAPBOX_LOCALE))
.addHeader("User-Agent", getUserAgent());
if (etag.length() > 0) {
builder = builder.addHeader("If-None-Match", etag);
} else if (modified.length() > 0) {
builder = builder.addHeader("If-Modified-Since", modified);
}
mRequest = builder.build();
mCall = mClient.newCall(mRequest);
mCall.enqueue(this);
} catch (Exception e) {
onFailure(e);
}
}
- 上面主要是介绍了一些细节点,但是倒底是什么时候创建了C++层的HTTPRequest对象呢?现在按顺序分析一下整个请求网络,拉取矢量数据的过程,希望能从中找到答案:
- 上面说到MapView默认加载的地图的设置是这一句话:
private String styleUrl = Style.MAPBOX_STREETS;
下面看一下MapView.java中设置styleUrl的代码:
- 上面说到MapView默认加载的地图的设置是这一句话:
public void setStyleUrl(@NonNull String url) {
if (destroyed) {
return;
}
// stopgap for https://github.com/mapbox/mapbox-gl-native/issues/6242
if (TextUtils.isEmpty(nativeMapView.getAccessToken())) {
setAccessToken(MapboxAccountManager.getInstance().getAccessToken());
}
styleUrl = url;
nativeMapView.setStyleUrl(url);//调用了NativeMapView.java中的setSytleUrl方法。
styleWasSet = true;
}
NativeMapView.java中:
public void setStyleUrl(String url) {
nativeSetStyleUrl(nativeMapViewPtr, url);
}
private native void nativeSetStyleUrl(long nativeMapViewPtr, String url);
//又到了一个native方法。。。
- 追踪nativeSetStyleUrl方法
jni.cpp中
void nativeSetStyleUrl(JNIEnv *env, jni::jobject* obj, jlong nativeMapViewPtr, jni::jstring* url) {
assert(nativeMapViewPtr != 0);
NativeMapView *nativeMapView = reinterpret_cast<NativeMapView *>(nativeMapViewPtr);
nativeMapView->getMap().setStyleURL(std_string_from_jstring(env, url));//nativeMapView->getMap()得到的是Map,此处调用的是map.cpp中的setStyleURL方法。其中native_map_view.cpp中有相关代码,见下面:
}
native_map_view.cpp相关代码:
在NativeMapView的构造函数中有以下代码
fileSource = std::make_unique<mbgl::DefaultFileSource>(
mbgl::android::cachePath + "/mbgl-offline.db",
mbgl::android::apkPath);
map = std::make_unique<mbgl::Map>(
*this, mbgl::Size{ static_cast<uint32_t>(width), static_cast<uint32_t>(height) },
pixelRatio, *fileSource, threadPool, MapMode::Continuous);
//上面的代码相当于初始化了map与fileSource(为mbgl::DefaultFileSource)
map.cpp中:
void Map::setStyleURL(const std::string& url) {
if (impl->styleURL == url) {
return;
}
impl->loading = true;
impl->backend.notifyMapChange(MapChangeWillStartLoadingMap);
impl->styleRequest = nullptr;
impl->styleURL = url;
impl->styleJSON.clear();
impl->styleMutated = false;
impl->style = std::make_unique<Style>(impl->fileSource, impl->pixelRatio);//make_unique也是智能指针中的内容
//下面的impl->fileSource即是上面提到的mbgl::DefaultFileSource,在default_file_source.cpp中
impl->styleRequest = impl->fileSource.request(Resource::style(impl->styleURL), [this](Response res) {
// Once we get a fresh style, or the style is mutated, stop revalidating.
if (res.isFresh() || impl->styleMutated) {
impl->styleRequest.reset();
}
// Don't allow a loaded, mutated style to be overwritten with a new version.
if (impl->styleMutated && impl->style->loaded) {
return;
}
if (res.error) {
if (res.error->reason == Response::Error::Reason::NotFound &&
util::mapbox::isMapboxURL(impl->styleURL)) {
Log::Error(Event::Setup, "style %s could not be found or is an incompatible legacy map or style", impl->styleURL.c_str());
} else {
Log::Error(Event::Setup, "loading style failed: %s", res.error->message.c_str());
}
impl->onStyleError();
impl->onResourceError(std::make_exception_ptr(std::runtime_error(res.error->message)));
} else if (res.notModified || res.noContent) {
return;
} else {
impl->loadStyleJSON(*res.data);
}
});//上面很明显做了一个网络请求
}
default_file_source.cpp中request方法
std::unique_ptr<AsyncRequest> DefaultFileSource::request(const Resource& resource, Callback callback) {
class DefaultFileRequest : public AsyncRequest {
public:
DefaultFileRequest(Resource resource_, FileSource::Callback callback_, util::Thread<DefaultFileSource::Impl>& thread_)
: thread(thread_),
workRequest(thread.invokeWithCallback(&DefaultFileSource::Impl::request, this, resource_, callback_)) {
}
~DefaultFileRequest() override {
thread.invoke(&DefaultFileSource::Impl::cancel, this);
}
util::Thread<DefaultFileSource::Impl>& thread;
std::unique_ptr<AsyncRequest> workRequest;
};
if (isAssetURL(resource.url)) {
return assetFileSource->request(resource, callback);
} else if (LocalFileSource::acceptsURL(resource.url)) {
return localFileSource->request(resource, callback);
} else {
return std::make_unique<DefaultFileRequest>(resource, callback, *thread);
}
}
- 上面已经说到了impl->fileSource即是mbgl::DefaultFileSource,所以现在我们需要看DefaultFileSource的request方法:
default_file_source.cpp中:
std::unique_ptr<AsyncRequest> DefaultFileSource::request(const Resource& resource, Callback callback) {
class DefaultFileRequest : public AsyncRequest {
public:
DefaultFileRequest(Resource resource_, FileSource::Callback callback_, util::Thread<DefaultFileSource::Impl>& thread_)
: thread(thread_),
workRequest(thread.invokeWithCallback(&DefaultFileSource::Impl::request, this, resource_, callback_)) {
}
~DefaultFileRequest() override {
thread.invoke(&DefaultFileSource::Impl::cancel, this);
}
util::Thread<DefaultFileSource::Impl>& thread;
std::unique_ptr<AsyncRequest> workRequest;
};
if (isAssetURL(resource.url)) {
return assetFileSource->request(resource, callback);
} else if (LocalFileSource::acceptsURL(resource.url)) {
return localFileSource->request(resource, callback);
} else {
Log::Info(Event::General, "----sunshanai---DefaultFileRequest::resource.url: %s----", resource.url.c_str());
return std::make_unique<DefaultFileRequest>(resource, callback, *thread);
}
}
该request方法返回了一个指向DefaultFileRequest的智能指针。然后,它会新起一个线程继续调用DefaultFileRequest::impl的request方法
void request(AsyncRequest* req, Resource resource, Callback callback) {
Resource revalidation = resource;
const bool hasPrior = resource.priorEtag || resource.priorModified || resource.priorExpires;
if (!hasPrior || resource.necessity == Resource::Optional) {
auto offlineResponse = offlineDatabase.get(resource);
if (resource.necessity == Resource::Optional && !offlineResponse) {
// Ensure there's always a response that we can send, so the caller knows that
// there's no optional data available in the cache.
offlineResponse.emplace();
offlineResponse->noContent = true;
offlineResponse->error = std::make_unique<Response::Error>(
Response::Error::Reason::NotFound, "Not found in offline database");
}
if (offlineResponse) {
revalidation.priorModified = offlineResponse->modified;
revalidation.priorExpires = offlineResponse->expires;
revalidation.priorEtag = offlineResponse->etag;
callback(*offlineResponse);
}
}
if (resource.necessity == Resource::Required) {
Log::Info(Event::General, "----sunshanai---onlineFileSource.request: %s----", resource.url.c_str());//log测试用,下面调用了onlineFileSource的request方法
tasks[req] = onlineFileSource.request(revalidation, [=] (Response onlineResponse) {
this->offlineDatabase.put(revalidation, onlineResponse);Log::Info(Event::General, "----sunshanai---onlineResponse: %s----", resource.url.c_str());
callback(onlineResponse);
});
}
}
下面看onlineFileSource的request方法
online_file_source.cpp中
std::unique_ptr<AsyncRequest> OnlineFileSource::request(const Resource& resource, Callback callback) {
Resource res = resource;
switch (resource.kind) {
case Resource::Kind::Unknown:
break;
case Resource::Kind::Style:
res.url = mbgl::util::mapbox::normalizeStyleURL(apiBaseURL, resource.url, accessToken);
Log::Info(Event::General, "----sunshanai---normalizeStyleURL: %s----", res.url.c_str());
break;
case Resource::Kind::Source:
res.url = util::mapbox::normalizeSourceURL(apiBaseURL, resource.url, accessToken);
Log::Info(Event::General, "----sunshanai---normalizeSourceURL: %s----", res.url.c_str());
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);//返回了一个指向OnlineFileRequest的智能指针,此时会调用OnlineFileRequest的构造函数见下面:
}
OnlineFileRequest::OnlineFileRequest(Resource resource_, Callback callback_, OnlineFileSource::Impl& impl_)
: impl(impl_),
resource(std::move(resource_)),
callback(std::move(callback_)) {
impl.add(this);
// Force an immediate first request if we don't have an expiration time.
if (resource.priorExpires) {
schedule(resource.priorExpires);
} else {
std::string abc = "sunshanai_OnlineFileRequest_schedule";
Log::Info(Event::General, "----sunshanai---OnlineFileRequest_schedule: %s----", abc.c_str());
schedule(util::now()); //会执行这个schedule方法,见下面:
}
}
void OnlineFileRequest::schedule(optional<Timestamp> expires) {
if (impl.isPending(this) || impl.isActive(this)) {
// There's already a request in progress; don't start another one.
return;
}
// If we're not being asked for a forced refresh, calculate a timeout that depends on how many
// consecutive errors we've encountered, and on the expiration time, if present.
Duration timeout = std::min(
http::errorRetryTimeout(failedRequestReason, failedRequests, retryAfter),
http::expirationTimeout(expires, expiredRequests));
if (timeout == Duration::max()) {
return;
}
// Emulate a Connection error when the Offline mode is forced with
// a really long timeout. The request will get re-triggered when
// the NetworkStatus is set back to Online.
if (NetworkStatus::Get() == NetworkStatus::Status::Offline) {
failedRequestReason = Response::Error::Reason::Connection;
failedRequests = 1;
timeout = Duration::max();
}
timer.start(timeout, Duration::zero(), [&] {
impl.activateOrQueueRequest(this);//接下来执行这个activateOrQueueRequest,方法体见下面:
});
}
void activateOrQueueRequest(OnlineFileRequest* request) {
assert(allRequests.find(request) != allRequests.end());
assert(activeRequests.find(request) == activeRequests.end());
assert(!request->request);
if (activeRequests.size() >= HTTPFileSource::maximumConcurrentRequests()) {
queueRequest(request);
} else {
activateRequest(request);//接下来执行activateRequest方法,方法体见下面:
}
}
void activateRequest(OnlineFileRequest* request) {
activeRequests.insert(request);
std::string abc = "sunshanai_activateRequest";
Log::Info(Event::General, "----sunshanai---activateRequest: %s----", abc.c_str());
//注意下面调用了httpFileSource.request方法,这里httpFileSource在文件http_file_source.cpp中
request->request = httpFileSource.request(request->resource, [=] (Response response) {
activeRequests.erase(request);
activatePendingRequest();
request->request.reset();
request->completed(response);
});
assert(pendingRequestsMap.size() == pendingRequestsList.size());
}
http_file_source.cpp中
std::unique_ptr<AsyncRequest> HTTPFileSource::request(const Resource& resource, Callback callback) {
return std::make_unique<HTTPRequest>(*impl->env, resource, callback);
}
生成了一个指向HTTPRequest(C++)的智能指针,至此,与前面的对应起来,其构造函数构造了Java类HTTPRequest类,进行了网络请求。
最后,又回到了开始的地方,map.cpp中setStyleURL方法,一切从这里开始,又到这里结束,网络请求成功后,回调,然后最终调用方法impl->loadStyleJSON(*res.data);
这个方法就是数据的处理了,下一篇博客继续分析(还有关于SourceURL的的分析)。。。