【问题标题】:Adding Markers in Background while Looping through ArrayList在 ArrayList 中循环时在后台添加标记
【发布时间】:2014-10-20 10:46:15
【问题描述】:

我将标记从 ArrayList 的对象添加到 googleMap。大约有 250 个标记;我什至必须将它们转换为位图来自定义它们。这是一项相当耗费资源的任务。但它严重阻塞了我的 UI 线程。

这是我的做法:

    final HashMap<Marker, NearLocation> markerIdMap = new HashMap<Marker, NearLocation>();
    for (final NearLocation result : MainActivity.nearLocationList) {

            // Do all the hard work here

    }

如何在地图加载并在生成地图时填充它们之后以某种方式动态地执行此操作?我不确定是否可以通过在后台执行一些工作来做到这一点,然后在标记完成后将其移至 UI 线程进行添加。

我知道如何使用AsyncTask 单独执行此操作。不确定虽然我正在循环...

【问题讨论】:

    标签: android arraylist google-maps-android-api-2 ui-thread


    【解决方案1】:

    据我所知,添加标记无法在 UI 线程之外完成。

    您可以做的是在后台执行所有准备工作(创建标记、转换为位图等)。为了在添加标记时节省一点 UI 线程,您可以放大并使用 https://code.google.com/p/android-maps-extensions/ 仅显示可见标记或集群标记以降低数量,尽管 250 并不多。

    这是关于该主题的 SO 答案:Add markers dynamically on Google Maps v2 for Android

    我有一个应用程序,它有大约 4500 个标记,使用第一种方法运行得相当好(只要它没有快速缩小)。需要注意的是,这里我选择制作一个带有进度条的启动画面,在用户打开地图之前准备好所有的标记。

    如果您想要一个简单的机制来选择周围标记而不使用第三部分库,您可以执行以下操作:Android Maps v2 - animate camera to include most markers

    刚刚想到的一个想法,如果标记的创建真的那么昂贵,那么将 EventBus https://github.com/greenrobot/EventBus 添加到您的项目中。

    使用 EventBus,您可以在 onEventAsync() 方法中对标记进行长时间运行准备。在该方法中,每当标记准备好时,将新标记发布到 UI EventBus.getDefault().post(marker) 并在 onEventMainThread(Marker marker) 中捕获它。在这里,您可以将标记保存在所有准备好的标记列表中,如果地图当前打开,请添加它。

    以下是我在前面提到的应用程序中用于准备标记的一些代码。它用于在消防部门使用的应用程序中显示消火栓。在第一次启动时,所有的消火栓都是从一组 CSV 文件中读取的,并为所有约 4500 个消火栓创建了 MarkerOptions。毫无疑问,很多代码对你没有用,只是留下它以防万一你可以从中受益:

    private List<HydrantHolder> mHydrants;
    private Map<HydrantType, List<MarkerOptions>> mMarkers;
    
    private class ReadHydrantsFiles extends
            AsyncTask<Void, Integer, List<HydrantHolder>> {
    
        File[] hydrantsFiles = new File[0];
    
        // Before running code in separate thread
        @Override protected void onPreExecute() {
    
            loadStarted();
    
            String filepath = paths.PATH_LOCAL_HYDRANTS;
    
            File hydrantsPath = new File(filepath);
            hydrantsFiles = hydrantsPath.listFiles(new FilenameFilter() {
    
                @Override public boolean accept(File dir, String filename) {
                    return filename.toLowerCase(Locale.ENGLISH).contains(
                            Constants.FILETYPE_CSV);
                }
            });
    
            int lineCount = 0;
    
            if (hydrantsFiles == null || hydrantsFiles.length == 0) {
                if (!preferences.isFirstStartUp()) {
                    // TODO notify user
                }
                Log.e(TAG, "Missing hydrants");
                if (moduleCallback != null) {
                    moduleCallback.doneLoadingModule(toString());
                }
                this.cancel(false);
            } else {
    
                for (File file : hydrantsFiles) {
                    // store linecount for visual progress update
                    lineCount += Files.lineCount(file);
    
                }
    
            }
    
        }
    
        // The code to be executed in a background thread.
        @Override protected List<HydrantHolder> doInBackground(Void... args) {
            List<HydrantHolder> all_hydrants = new ArrayList<HydrantHolder>();
            // Directory path here
            int totalLineProgress = 0;
    
            // // required
            int indexLatitude = modulePreferences.indexLatitude();
            int indexLongitude = modulePreferences.indexLongitude();
            // optional
            int indexAddress = modulePreferences.indexAddress();
            int indexAddressNumber = modulePreferences.indexAddressNumber();
            int indexAddressRemark = modulePreferences.indexAddressRemark();
            int indexRemark = modulePreferences.indexRemark();
            // decimals
            int latitude_decimal = modulePreferences.latitude_decimal();
            int longitude_decimal = modulePreferences.longitude_decimal();
    
            if (indexLatitude <= 0 || indexLongitude <= 0)
                return all_hydrants;
    
            for (File file : hydrantsFiles) {
                BufferedReader in = null;
                try {
    
                    String hydrantspath = paths.PATH_LOCAL_HYDRANTS;
                    File hydrantsPath = new File(hydrantspath);
    
                    // Read File Line By Line
                    in = new BufferedReader(new InputStreamReader(
                            new FileInputStream(file), "windows-1252"));
                    String strLine;
                    while ((strLine = in.readLine()) != null) {
    
                        totalLineProgress++;
    
                        String[] hydrantParts = strLine.split(";", -1);
    
                        String errors = "";
                        final String hydrantType = file.getName().replace(
                                Constants.FILETYPE_CSV, "");
    
                        File[] iconFiles = hydrantsPath
                                .listFiles(new FilenameFilter() {
    
                                    @Override public boolean accept(File dir,
                                            String filename) {
                                        if (filename.contains(hydrantType)
                                                && filename
                                                        .contains(Constants.FILETYPE_PNG)) {
                                            return true;
                                        }
                                        return false;
                                    }
                                });
    
                        HydrantHolder.Builder hb = new HydrantHolder.Builder();
                        if (hydrantParts.length >= 5) {
    
                            hb.setHydrantType(hydrantType);
                            if (iconFiles.length != 0) {
                                hb.setIconPath(hydrantspath
                                        + File.separatorChar
                                        + iconFiles[0].getName());
                            }
                            hb.setLatitude(hydrantParts[indexLatitude],
                                    latitude_decimal);
                            hb.setLongitude(hydrantParts[indexLongitude],
                                    longitude_decimal);
    
                            if (indexAddress > 0)
                                hb.setAddress(hydrantParts[indexAddress]);
                            if (indexAddressNumber > 0)
                                hb.setAddressNumber(hydrantParts[indexAddressNumber]);
                            if (indexAddressRemark > 0)
                                hb.setAddressRemark(hydrantParts[indexAddressRemark]);
                            if (indexRemark > 0)
                                hb.setRemark(hydrantParts[indexRemark]);
    
                            if (hb.getErrors().isEmpty()) {
                                HydrantHolder hydrant = hb.build();
                                all_hydrants.add(hydrant);
                            } else {
                                // TODO write error file to Dropbox if possible,
                                // otherwise locally
                                Log.d(TAG, errors);
                            }
                        } else {
                            errors = "Missing information";
                        }
    
                        publishProgress(totalLineProgress);
                    }
                } catch (InvalidPathException e) {
                    Log.e(TAG, e.getMessage(), e);
                } catch (IOException e) {
                    Log.e(TAG, e.getMessage(), e);
                } finally {
                    if (in != null) {
                        try {
                            in.close();
                        } catch (IOException e) {
                        }
                    }
                }
            }
            Log.d(TAG, "" + all_hydrants.size());
            return all_hydrants;
        }
    
        // Update the progress
        @Override protected void onProgressUpdate(Integer... values) {
            // set the current progress of the progress dialog
            // if (progressDialog != null && values != null && values.length >
            // 0) {
            // progressDialog.setProgress(values[0]);
            // }
        }
    
        @Override protected void onPostExecute(List<HydrantHolder> hydrants) {
            // Device.releaseOrientation((Activity) context);
    
            Log.d(TAG, "Saved " + hydrants.size() + " hydrants");
            mHydrants = hydrants;
    
            new PrepareMarkerOptionsTask(hydrants).execute();
    
            super.onPostExecute(hydrants);
        }
    
    }
    
    private class PrepareMarkerOptionsTask extends
            AsyncTask<Void, Integer, Map<HydrantType, List<MarkerOptions>>> {
        // Before running code in separate thread
        List<HydrantHolder> mHydrants;
    
        public PrepareMarkerOptionsTask(List<HydrantHolder> hydrants) {
            this.mHydrants = hydrants;
            mMarkers = new HashMap<HydrantsModule.HydrantType, List<MarkerOptions>>();
        }
    
        @Override protected void onPreExecute() {
    
        }
    
        // The code to be executed in a background thread.
        @Override protected Map<HydrantType, List<MarkerOptions>> doInBackground(
                Void... arg) {
    
            for (HydrantHolder hydrant : mHydrants) {
    
                final String hydrant_type = hydrant.getHydrantType();
                final String hydrant_icon_path = hydrant.getIconPath();
                double latitude = hydrant.getLatitude();
                double longitude = hydrant.getLongitude();
    
                final LatLng position = new LatLng(latitude, longitude);
    
                final String address = hydrant.getAddress();
                final String addressNumber = hydrant.getAddressNumber();
                final String addressremark = hydrant.getAddressRemark();
                final String remark = hydrant.getRemark();
    
                // Log.d(TAG, hydrant.toString());
    
                BitmapDescriptor icon = BitmapDescriptorFactory
                        .defaultMarker(BitmapDescriptorFactory.HUE_RED);
    
                if (!hydrant_icon_path.isEmpty()) {
                    File iconfile = new File(hydrant_icon_path);
                    if (iconfile.exists()) {
                        BitmapDescriptor loaded_icon = BitmapDescriptorFactory
                                .fromPath(hydrant_icon_path);
                        if (loaded_icon != null) {
                            icon = loaded_icon;
                        } else {
                            Log.e(TAG, "loaded_icon was null");
                        }
                    } else {
                        Log.e(TAG, "iconfile did not exist: "
                                + hydrant_icon_path);
                    }
                } else {
                    Log.e(TAG, "iconpath was empty on hydrant type: "
                            + hydrant_type);
                }
    
                StringBuffer snippet = new StringBuffer();
                if (!address.isEmpty())
                    snippet.append("\n" + address + " " + addressNumber);
                if (addressremark.isEmpty())
                    snippet.append("\n" + addressremark);
                if (!remark.isEmpty())
                    snippet.append("\n" + remark);
    
                addHydrantMarker(
                        hydrant_type,
                        new MarkerOptions().position(position)
                                .title(hydrant_type)
                                .snippet(snippet.toString()).icon(icon));
    
                // publishProgress(markers.size());
            }
            return mMarkers;
        }
    
        // Update the progress
        @Override protected void onProgressUpdate(Integer... values) {
        }
    
        @Override protected void onCancelled() {
        }
    
        // after executing the code in the thread
        @Override protected void onPostExecute(
                Map<HydrantType, List<MarkerOptions>> markers) {
    
            Log.d(TAG, "Prepared " + markers.size() + " hydrantMarkerOptions");
            mMarkers = markers;
    
            loadEnded();
    
            super.onPostExecute(markers);
    
        }
    
    }
    
    public Set<HydrantType> getHydrantTypes() {
        return new HashSet<HydrantType>(mMarkers.keySet());
    }
    
    private void addHydrantMarker(String typeName, MarkerOptions marker) {
        HydrantType type = new HydrantType(typeName, marker.getIcon());
        if (mMarkers.containsKey(type)) {
            List<MarkerOptions> markers = mMarkers.get(type);
            markers.add(marker);
        } else {
            List<MarkerOptions> markers = new ArrayList<MarkerOptions>();
            markers.add(marker);
            mMarkers.put(type, markers);
            enableHydrantType(type);
        }
    }
    
    public class HydrantType {
    
        private final String name;
        private final BitmapDescriptor icon;
    
        public HydrantType(String name, BitmapDescriptor icon) {
            this.name = name;
            this.icon = icon;
        }
    
        public String getName() {
            return name;
        }
    
        public BitmapDescriptor getIcon() {
            return icon;
        }
    
        @Override public int hashCode() {
            return name.hashCode();
        }
    
        @Override public boolean equals(Object o) {
            if (o instanceof HydrantType) {
                if (((HydrantType) o).name.equals(name)) {
                    return true;
                }
            }
            return false;
        }
    
    } 
    

    基于 cmets,我添加了更多文本和代码。

    是的,我一次添加了所有 MarkerOptions。不过,因为我在添加所有标记之前放大 GoogleMaps-extensions (第一个链接)只花费 CPU 功率将可见标记添加到地图中。如果用户平移或缩小地图,则会自动添加更多可见标记。

    要使地图延迟加载标记:

    @Override
    public void onMapReady(GoogleMap map) {
        Log.i(TAG, "onMapReady");
        if (map != null) {
            map.setClustering(new ClusteringSettings().enabled(false)
                    .addMarkersDynamically(true));
        }
    }
    

    还有我用来添加消火栓的代码(现在在很多情况下都过于复杂,但如果你仔细阅读,它只是简单地缩放到一个地址,并且只有在缩放完成后才添加所有的消火栓):

    public void addHydrantsNearAddress(final AddressHolder addressHolder,
            final boolean zoomToAddress) {
    
        final GoogleMap map = googlemaps.getMap();
    
    
        final OnCameraChangeListener addHydrantsAfterZoom = new OnCameraChangeListener() {
    
            @Override public void onCameraChange(CameraPosition cameraPosition) {
                Log.d(TAG, cameraPosition.target.toString());
                Log.d(TAG, addressHolder.position.toString());
    
                final GoogleMap map = googlemaps.getMap();
                // if (Location.distanceBetween(cameraPosition.tar,
                // startLongitude, endLatitude, endLongitude, results)) {
                new Handler().postDelayed(new Runnable() {
    
                    @Override public void run() {
                        addAllHydrants();
                    }
                }, 500);
    
                map.setOnCameraChangeListener(null); // unregister
                // }
            }
        };
    
        if (map == null) {
            // wait for the map to be ready before adding hydrants
            googlemaps.setGoogleMapsCallback(new GoogleMapsCallback() {
    
                @Override public void mapReady(GoogleMap map) {
    
                    if (zoomToAddress) {
                        map.setOnCameraChangeListener(addHydrantsAfterZoom);
                        googlemaps.zoomTo(addressHolder);
                    } else {
                        addAllHydrants();
                    }
    
                    googlemaps.setGoogleMapsCallback(null); // unregister
                }
            });
        } else {
    
            if (zoomToAddress) {
                // only setOnCameraChangeListener if cammera needs to move
                LatLng cammeraPos = map.getCameraPosition().target;
                LatLng addressPos = addressHolder.position;
    
                float[] results = new float[1];
                Location.distanceBetween(cammeraPos.latitude,
                        cammeraPos.longitude, addressPos.latitude,
                        addressPos.longitude, results);
    
                // Log.d(TAG, "distance " + results[0]);
    
                if (results[0] > 1) {
                    map.setOnCameraChangeListener(addHydrantsAfterZoom);
                    googlemaps.zoomTo(addressHolder);
                } else {
                    googlemaps.zoomTo(addressHolder);
                    addAllHydrants();
                }
    
            }
    
        }
    
    }
    

    另一个比示例更复杂的原因是我让用户在消火栓类型之间进行过滤。尽管如此,希望你能看到这个想法。

    addAllHydrants() 听起来很简单,迭代 MarkerOptions 并添加它们:

    public void addAllHydrants() {
    
        enableAllHydrants();
    
        GoogleMap map = googlemaps.getMap();
    
        map.setTrafficEnabled(modulePreferences.showTraffic());
        map.setMapType(modulePreferences.getMapType());
    
        addHydrants(map);
    }
    
    private void addHydrants(GoogleMap map) {
    
        Log.d(TAG, "addHydrants");
    
        if (mHydrants == null || mHydrants.isEmpty()) {
            Toast.makeText(context,
                    context.getString(R.string.missing_hydrants),
                    Toast.LENGTH_LONG).show();
            return;
        } else {
            for (MarkerOptions marker : getEnabledHydrantMarkers()) {
                map.addMarker(marker);
            }
        }
    
    }
    

    我真的认为,如果您自己尝试计算、获取和添加可见标记,而不是仅仅如此,那么您将过于复杂化。

    1. 准备 MarkerOptions(第一个代码块中的示例)
    2. 使用 onMapReady 回调等待地图准备就绪(此示例在 Google 的官方示例中)
    3. 在新创建的地图上设置 addMarkersDynamically(true)
    4. 放大感兴趣的地址或区域(例如设备位置)
    5. 添加所有 MarkerOptions
    6. 查看在您在地图上移动时自动添加的标记

    回答关于onMapReady回调的问题

    如果您使用 XML 添加地图,那么我怀疑您可能不需要添加回调。尝试直接在您的活动的onCreate() 中使用地图。只要对getExtendedMap() 的调用不为空,就可以了。

    我正在代码中创建片段,以便能够将控制器代码与地图放在一起。因此,我的 SupportMapFragment 看起来像这样:

    public class GoogleMapFragment extends SupportMapFragment {
    
        private OnGoogleMapFragmentListener mCallback;
    
        public GoogleMapFragment() {
            mCallback = null;
        }
    
        public static interface OnGoogleMapFragmentListener {
            void onMapReady(GoogleMap map);
        }
    
        @Override public void onAttach(Activity activity) {
            super.onAttach(activity);
            try {
                mCallback = (OnGoogleMapFragmentListener) activity;
            } catch (ClassCastException e) {
                throw new ClassCastException(getActivity().getClass().getName()
                        + " must implement OnGoogleMapFragmentListener");
            }
    
        }
    
        @Override public View onCreateView(LayoutInflater inflater,
                ViewGroup container, Bundle savedInstanceState) {
            View view = super.onCreateView(inflater, container, savedInstanceState);
            if (mCallback != null) {
                mCallback.onMapReady(getExtendedMap());
            }
    
            return view;
        }
    
    }
    

    【讨论】:

    • 谢谢!绝对赞成这一点;如果以及当我让它工作时,我会标记为正确的。你有 4,500 个标记?哇!所以听起来你一次加载它们......但是,这可能会改变我需要做的事情。我只想向用户显示 250 关闭,然后当他们滚动时,使用 setOnCameraPositionChange 方法并从我的数据库中获取更多标记。我在所有这些事情上都过得很艰难。应用程序 GasBuddy 做到了我所见过的最好的。
    • 还有一件事;使用setOnCameraChangeListener(上面的错字)很好,但是每次地图更改都会调用它——这意味着大量的数据库调用,无论更改多么小……如果能有一个超时就好了。
    • 已进行编辑。请注意,我在更改后立即调用map.setOnCameraChangeListener(null);,并且通过单击按钮调用addHydrantsNearAddress()。也就是说,代码中的setOnCameraChangeListener 在每次地图加载时只会被调用一次。在 Google-map-extension 接管之后,当可视区域因平移或缩放而发生变化时添加标记。
    • 谢谢!我只是将其标记为正确,因为这正是我想要的。现在由我来实施,再次感谢! (如果还有什么我可能会回来......)
    • 没问题 - 我很乐意在此过程中回答更多问题。我仍然很抱歉我的代码示例没有比这更清楚,但是从现有代码中提取并不容易。快乐的编码和好运:)
    【解决方案2】:

    只是想分享一个我想出的简单解决方案,无需每次都重新加载即可显示所有标记。

    基本上我依靠onCameraChangeListener 来检查用户对地图的视图是否发生了变化,并且只显示该视图中的标记,否则标记将被隐藏......你仍然会有更长的加载时间第一次加载数据的时间,但之后很快。

    首先您需要将所有标记添加到地图中,并将所有标记存储在Map

    // Put it in onCreate or something
    map.clear(); // Clears markers
    markers.clear(); // private Map<Marker, Item> markers = new HashMap<Marker, Item>()
    for (Item item : items) {
        MarkerOptions markerOptions = new MarkerOptions()
            .position(new LatLng(item.getLatitude(), item.getLongitude())
            .title(item.getTitle())
            .snippet(item.getSnippet());
        Marker marker = map.addMarker(marketOptions);
        markers.put(marker, item);
    }
    

    然后你需要设置监听器:

    // Put in onCreate or something
    map.setOnCameraChangeListener(new GoogleMap.OnCameraChangeListener() {
        @Override
        public void onCameraChange(final CameraPosition cameraPosition) {
            showMarkers(cameraPosition.target);
            LatLngBounds bounds = map.getProjection().getVisibleRegion().latLngBounds;
            for (Map.Entry<Marker, Item> entry : markers) {
                Marker marker = entry.getKey();
                if (bounds.contains(marker.getPosition()) {
                    marker.setVisible(true);
                } else {
                    marker.setVisible(false);
                }
            }
        }
    });
    

    这应该在animationMap 和任何形式的相机移动(以编程方式或通过用户拖动)结束时触发

    您可以考虑使用onSaveInstanceState 持久化数据,以使其在应用之间切换时更加灵敏

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-18
      • 2020-06-26
      • 1970-01-01
      • 1970-01-01
      • 2012-04-06
      相关资源
      最近更新 更多