您的位置:首页 > 其它

细读百度地图点聚合源码(下)---Renderer类解析

2016-05-13 22:09 246 查看
上一篇文章分析了ClusterMananger的整体结构和核心算法 细读百度地图点聚合源码(上),此文是接着上一篇来的。

在本文中,我们将学习如何在UI线程中做大量的操作,并且不会造成界面卡顿。

上次我们讲到ClusterManager类中的cluster()方法,调用ClusterTask后台线程处理核心算法,既然有doInBackground()后台任务函数,就会有onPostExecute()函数来处理后台线程返回的结果,这一篇我们就分析怎么处理返回的结果。

那么我们就从返回的结果开始吧!

private class ClusterTask extends AsyncTask<Float, Void, Set<? extends Cluster<T>>> {
@Override
protected Set<? extends Cluster<T>> doInBackground(Float... zoom) {
mAlgorithmLock.readLock().lock();
try {
return mAlgorithm.getClusters(zoom[0]);
} finally {
mAlgorithmLock.readLock().unlock();
}
}

@Override
protected void onPostExecute(Set<? extends Cluster<T>> clusters) {
mRenderer.onClustersChanged(clusters);
}
}

上面就是ClusterTask的源码,后台任务处理算法,然后返回数据给主线程,返回的就是一个Set<? extends Cluster<T>>类型的对象,就是一个包含若干个cluster对象的集合,而cluster对象又是一个包含若干MyItem(implements ClusterItem)的集合。
并且,每个cluster中的那些MyItem都是确定可以聚合成一个点的。


那到底要怎么处理这个cluster集合呢?

我们看到是mRenderer来处理的,就是源码中DefaultClusterRenderer类,当然我们也可以继承这个类来实现我们自己的Renderer类,这个等有需求再细说吧。


我们还是先来分析DefaultClusterRenderer这个类究竟做了些什么处理。


一切都是从onClustersChanged(clusters)这个方法开始(该方法从ClusterRenderer接口实现而来)。@Override
public void onClustersChanged(Set<? extends Cluster<T>> clusters) {
mViewModifier.queue(clusters);
}
这个方法很简单,里面只有一句代码,所以我们还得往里面跟踪。
至此我们会有两个疑问:mViewModifier是什么东东?queue()函数又是做什么用的呢?

ViewModifier其实是一个继承于Handler的类,内部只有两个函数:handleMessage()和queue()

我们先来看queue()函数:

public void queue(Set<? extends Cluster<T>> clusters) {
synchronized (this) {
// Overwrite any pending cluster tasks - we don't care about intermediate states.
mNextClusters = new RenderTask(clusters);
}
sendEmptyMessage(RUN_TASK);
}

在函数内部创建一个RenderTask对象,这是一个Runnable接口的实现类,也就是一个未启动的线程。
然后发送一条Message给handleMessage函数。大家应该可以猜到,在handleMessage中肯定会启动这个线程。

下面是handleMessage中的代码:

@Override
public void handleMessage(Message msg) {
if (msg.what == TASK_FINISHED) {//线程执行完成
mViewModificationInProgress = false;//标记线程已经完成
if (mNextClusters != null) {//如果有新任务还未执行,则再次启动线程
// Run the task that was queued up.
sendEmptyMessage(RUN_TASK);
}
return;
}

removeMessages(RUN_TASK);

if (mViewModificationInProgress) {
// Busy - wait for the callback.
return;
}

if (mNextClusters == null) {
// Nothing to do.
return;
}

RenderTask renderTask;
synchronized (this) {
renderTask = mNextClusters;
mNextClusters = null;
mViewModificationInProgress = true;//标记线程正在运行
}

renderTask.setCallback(new Runnable() { //设置线程完成时的回调
@Override
public void run() {
sendEmptyMessage(TASK_FINISHED);//线程完成后,发送消息给自己
}
});
renderTask.setProjection(mMap.getProjection());//无作用
renderTask.setMapZoom(mMap.getMapStatus().zoom);//设置最新的地图级别
new Thread(renderTask).start();//启动线程
}

也不复杂,总的来说就一句话:启动一个线程,这个线程就是RenderTask。

程序执行到这里,我们其实只做了一件事情——启动了一个叫做RenderTask的线程。


那么,RenderTask究竟是何方神圣呢?

从上面的代码中可以看到,在启动线程之前对RenderTask进行了一些设置,所以在分析它的具体功能前,先看看这些设置有什么作用。

首先是setProjection()方法,此方法在现在的这份源码中没有任何作用。

然后是setMapZoom()方法,看一下源码

public void setMapZoom(float zoom) {
this.mMapZoom = zoom;
this.mSphericalMercatorProjection =
new SphericalMercatorProjection(256 * Math.pow(2, Math.min(zoom, mZoom)));
}
可以看到,此方法中会保存当前地图的zoom值,然后创建一个SphericalMercatorProjection对象,它在上一篇核心算法中也出现过,用来实现position(经纬度)和point(二维坐标点)之间的转换。
为了计算点与点之间的距离,我们需要将position转换成point,而为了在地图上绘制marker,我们又需要将point转换成position。

这个转换就公式我就不多分析了,纯数学题。值得一提的是在创建SphericalMercatorProjection对象时传入的参数,其实就是根据zoom计算得到的worldWidth(计算公式:worldWidth = 256 * zoom^2)。至于mZoom,是保存的上一次的zoom值。

其实,zoom值的大小跟最终结果关系不是特别大,唯一的影响就是转换position和point的精度。不知道大家能不能理解,不能理解的话就多看几遍吧。


然后,就是RenderTask最重要的run()方法了。

代码逻辑不算特别复杂,还是采用代码+注释的方式来分析吧!

public void run() {
if (clusters.equals(DefaultClusterRenderer.this.mClusters)) {
mCallback.run();//判断如果新的clusters等于上一次保存的clusters,直接return出去
return;
}

final MarkerModifier markerModifier = new MarkerModifier();//这个类处理显示和动画

final float zoom = mMapZoom;//最新的zoom值
final boolean zoomingIn = zoom > mZoom;//mZoom为上一次保存的zoom值
final float zoomDelta = zoom - mZoom;//zoom变化量级,超过一定量级就不执行动画了

final Set<MarkerWithPosition> markersToRemove = mMarkers;//需呀删除的点。请思考什么样的点需要被删除?
final LatLngBounds visibleBounds = mMap.getMapStatus().bound;//地图在手机屏幕上的可见范围

//1.添加点
// 找出所有屏幕上的原来的cluster中心点,在增加点的时候有些动画需要用到这些点
List<Point> existingClustersOnScreen = null;
if (DefaultClusterRenderer.this.mClusters != null && SHOULD_ANIMATE) {
existingClustersOnScreen = new ArrayList<Point>();
for (Cluster<T> c : DefaultClusterRenderer.this.mClusters) { //迭代上一次保存的clusters
if (shouldRenderAsCluster(c) && visibleBounds.contains(c.getPosition())) {//只有已经聚合了的cluster才可以新增点
Point point = mSphericalMercatorProjection.toPoint(c.getPosition());//position转换成point
existingClustersOnScreen.add(point);//保存屏幕上已经聚合的cluster
}
}
}

// Create the new markers and animate them to their new positions.
final Set<MarkerWithPosition> newMarkers = Collections.newSetFromMap(
new ConcurrentHashMap<MarkerWithPosition, Boolean>());//保存新的clusters中需要显示的点,转成MarkerWithPosition类型
for (Cluster<T> c : clusters) {             //迭代新的clusters
boolean onScreen = visibleBounds.contains(c.getPosition());//是否在屏幕内
if (zoomingIn && onScreen && SHOULD_ANIMATE) { //地图放大 + 此cluster在屏幕内 + 可以动画(SDK版本>11)
Point point = mSphericalMercatorProjection.toPoint(c.getPosition());//position转成point
Point closest = findClosestCluster(existingClustersOnScreen, point);//找出与这个cluster距离最近的原屏幕上的点
if (closest != null) {//存在,则实现动画
LatLng animateTo = mSphericalMercatorProjection.toLatLng(closest);
markerModifier.add(true, new CreateMarkerTask(c, newMarkers, animateTo));
} else {//不存在,则直接添加不生成动画
markerModifier.add(true, new CreateMarkerTask(c, newMarkers, null));
}
} else {//直接添加点,不生成动画
markerModifier.add(onScreen, new CreateMarkerTask(c, newMarkers, null));
}
}

// 2.等待添加点的任务完成
markerModifier.waitUntilFree();

// 把newMarkers中的点从markersToRemove中移除,markersToRemove中的点都是需要从地图上移除的
markersToRemove.removeAll(newMarkers);

//3.移除点
// 找出现在屏幕上显示的cluster中心点,在移除点时需要用到这些点来实现动画
List<Point> newClustersOnScreen = null;
if (SHOULD_ANIMATE) {
newClustersOnScreen = new ArrayList<Point>();
for (Cluster<T> c : clusters) {
if (shouldRenderAsCluster(c) && visibleBounds.contains(c.getPosition())) {
Point p = mSphericalMercatorProjection.toPoint(c.getPosition());
newClustersOnScreen.add(p);
}
}
}

for (final MarkerWithPosition marker : markersToRemove) { //迭代所有需要移除的点
boolean onScreen = visibleBounds.contains(marker.position);

if (!zoomingIn && zoomDelta > -3 && onScreen && SHOULD_ANIMATE) { // 地图缩小 + zoom改变不超过3
final Point point = mSphericalMercatorProjection.toPoint(marker.position);
final Point closest = findClosestCluster(newClustersOnScreen, point);//找出最近的cluster
if (closest != null) {
LatLng animateTo = mSphericalMercatorProjection.toLatLng(closest);//动画移动的终点
markerModifier.animateThenRemove(marker, marker.position, animateTo);
} else {
markerModifier.remove(true, marker.marker);//无动画
}
} else {
markerModifier.remove(onScreen, marker.marker);//无动画
}
}
//等待移除点的任务完成
markerModifier.waitUntilFree();

mMarkers = newMarkers;//保存新的点
DefaultClusterRenderer.this.mClusters = clusters;
mZoom = zoom;//保存最新的zoom

mCallback.run();//执行线程执行完成的回调函数
}


首先,有两个方法需要说明一下。

shouldRenderAsCluster(cluster) ----- 判断cluster是否能聚合,这一步判断的是cluster中包含的ClusterItem的数量。

/**
* Determine whether the cluster should be rendered as individual markers or a cluster.
*/
protected boolean shouldRenderAsCluster(Cluster<T> cluster) {
return cluster.getSize() > MIN_CLUSTER_SIZE;
}
findClosestCluster(existingClustersOnScreen, point) ----- 查找最近的cluster。这个查找过程是设定了一个最小距离minDistSquared,如果没有小于minDistSquared的cluster就返回null。
private static Point findClosestCluster(List<Point> markers, Point point) {
if (markers == null || markers.isEmpty()) {
return null;
}

double minDistSquared = MAX_DISTANCE_AT_ZOOM * MAX_DISTANCE_AT_ZOOM;
Point closest = null;
for (Point candidate : markers) {
double dist = distanceSquared(candidate, point);
if (dist < minDistSquared) {
closest = candidate;
minDistSquared = dist;
}
}
return closest;
}

然后,有个类需要重点介绍----MarkerModifier
MarkerModifier是一个Handler,这也是前篇所说的很精妙的在主线程中做大量工作的方法。
从名称就可以知道,这个是处理地图上Marker的修改。
private MarkerModifier() {
super(Looper.getMainLooper());
}
上面是它的构造函数,注意Looper.getMainLooper()
我们知道Handler必须绑定一个Looper对象,而每个线程有且仅有一个Looper对象。但是MarkerModifier绑定的是MainLooper,也就是说它执行的所有内容,全部都在主线程中操作!!!!
此Handler实现了接口MessageQueue.IdleHandler,在读这份源码之前,楼主并不知道这是个什么东东,所以理解得也不一定对,各位最好自己去查一下。
MessageQueue.IdleHandler就是在系统空闲(Idle)状态时调用接口定义的queueIdle()方法,这个方法在实现接口时必须重写。
放在这个项目中,就是在系统空闲的时候,会自动调用MarkerModifier的handleMessage()方法。这样即可以做大量操作,也不会造成系统卡顿。
每次调用MarkerModifier的add方法或者remove方法,都会发送一个Message给自己,以调用自己的handleMessage方法。
接下来,我们着重介绍handleMessage方法。上代码!
@Override
public void handleMessage(Message msg) {//把所有的新增和删除marker 以及动画的任务全部执行完成

if (!mListenerAdded) {
//添加Idle接口
Looper.myQueue().addIdleHandler(this); //在主线程空闲时,发送BLANK消息执行点聚合动作
mListenerAdded = true;
}
removeMessages(BLANK);
lock.lock();
try {

// 每次执行10个任务
// 分批次执行所有任务(增加,删除,动画),避免系统卡顿
for (int i = 0; i < 10; i++) {
performNextTask();//执行一个任务
}

if (!isBusy()) {//是否执行完所有任务
mListenerAdded = false;
//移除idle接口
Looper.myQueue().removeIdleHandler(this);//所有子线程全部执行完成
// 唤醒所有等待的线程(可以回头去看看RenderTask的run()方法)
busyCondition.signalAll();
} else {
//本来这一句是不必要的,但是百度工程师说,某些情况下系统空闲状态不会成功调用queueIdle()方法
// 所以这里手动延迟10ms再次调用handleMessage
sendEmptyMessageDelayed(BLANK, 10);
}
} finally {
lock.unlock();
}
}

到这里,整个源码就分析完了,其他一些方法都比较简单,就不多说了,大家自己看源码吧!!
ClusterDemo


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: