As far as I know, adding the markers cannot be done outside the UI thread.
What you can do is perform all the preparations in background (create marker, convertion to bitmap, etc..). To spare the UI thread a bit when adding markers you could zoom in and use https://code.google.com/p/android-maps-extensions/ to only show the visible markers or cluster markers to bring down the amount, though 250 is not alot imo.
Here is a SO answer on the topic: Add markers dynamically on Google Maps v2 for Android
I have an app that has around ~4500 markers running fairly well using the first method (as long as it is not rapidly zoomed out that is). It should be noted that here I have chosen to make a splash screen with a progress bar, preparing all the markers before the user has any chance of opening the map.
If you want a simple mechanism for selecting surrounding markers without using 3rd part library you can do something like this: Android Maps v2 - animate camera to include most markers
An idea that comes to mind just now, if the creation of markers is really that expensive, is to add EventBus https://github.com/greenrobot/EventBus to your project.
Using EventBus you could do a long running preparation of markers inside an onEventAsync()
method. Inside that method, whenever a marker is ready, post the new marker to UI EventBus.getDefault().post(marker)
and catch it in onEventMainThread(Marker marker)
. Here you could save the marker in a list of all the prepared markers and if the map is currently open, add it.
Here is some of the code I use to prepare the markers in the app I mentioned earlier. It is used to show hydrants in an app used by the fire dept. At first startup all the hydrants are read from a set of CSV files and MarkerOptions for all ~4500 hydrants are created. Undoubtedly a lot of the code is of no use to you, just leaving it in case there is something that you can benefit from:
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("
" + address + " " + addressNumber);
if (addressremark.isEmpty())
snippet.append("
" + addressremark);
if (!remark.isEmpty())
snippet.app