上一篇文章分析了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
时间: 2024-10-20 12:49:34