From 3cadcd3b5d82c2ca3d8cec000f688291b0737dc4 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 4 Jul 2016 21:04:48 -0400 Subject: [PATCH] #335 resolving issues caught by @MKergall for polylines and polygons, after running much longer tests, it revealed that there was still another leak with the ExampleActivity which is now fixed NOJIRA - removing the silly copy sources task in favor of a source redirector (gradle build only) NOJIRA - adding Leak Canary and ACRA for leak testing and crash logging --- OpenStreetMapViewer/build.gradle | 85 +++--------- OpenStreetMapViewer/pom.xml | 4 + .../src/main/AndroidManifest.xml | 3 +- .../java/org/osmdroid/OsmApplication.java | 122 ++++++++++++++++++ .../java/org/osmdroid/StarterMapFragment.java | 12 +- .../samplefragments/FragmentSamples.java | 21 ++- .../samplefragments/SampleAnimateTo.java | 4 +- .../SampleCustomTileSource.java | 3 +- .../samplefragments/SampleGridlines.java | 3 + .../samplefragments/SampleMilitaryIcons.java | 61 +++++---- .../samplefragments/SampleOfflineOnly.java | 6 +- .../samplefragments/SampleSqliteOnly.java | 6 +- .../SampleWhackyColorFilter.java | 2 +- .../{ => models}/USGSTileSource.java | 2 +- .../samplefragments/models/package-info.java | 3 + osmdroid-android-it/pom.xml | 4 + .../org/osmdroid/test/ExtraSamplesTest.java | 109 ++++++---------- .../org/osmdroid/test/MainActivityTest.java | 9 +- .../org/osmdroid/test/MapActivityTest.java | 2 +- .../osmdroid/views/OpenStreetMapViewTest.java | 3 +- .../tileprovider/IRegisterReceiver.java | 2 + .../tileprovider/MapTileProviderArray.java | 9 +- .../modules/OfflineTileProvider.java | 9 ++ .../osmdroid/tileprovider/util/Counters.java | 8 ++ .../util/SimpleRegisterReceiver.java | 7 +- .../views/overlay/ItemizedIconOverlay.java | 2 + .../overlay/ItemizedOverlayWithFocus.java | 3 + .../org/osmdroid/views/overlay/Polygon.java | 7 + .../org/osmdroid/views/overlay/Polyline.java | 6 + pom.xml | 10 ++ 30 files changed, 336 insertions(+), 191 deletions(-) create mode 100644 OpenStreetMapViewer/src/main/java/org/osmdroid/OsmApplication.java rename OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/{ => models}/USGSTileSource.java (97%) create mode 100644 OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/models/package-info.java diff --git a/OpenStreetMapViewer/build.gradle b/OpenStreetMapViewer/build.gradle index c8c0fe303f..d82b1565eb 100644 --- a/OpenStreetMapViewer/build.gradle +++ b/OpenStreetMapViewer/build.gradle @@ -20,6 +20,11 @@ android { lintOptions { abortOnError false } + sourceSets { + androidTest { + java.srcDirs = ['../osmdroid-android-it/src/main/java'] + } + } } apply from: 'https://raw.githubusercontent.com/chrisdoyle/gradle-fury/master/gradle/android-support.gradle' @@ -27,81 +32,23 @@ apply from: 'https://raw.githubusercontent.com/chrisdoyle/gradle-fury/master/gra dependencies { compile 'com.android.support:support-v4:23+' compile project(':osmdroid-android') - androidTestCompile 'com.android.support:support-annotations:23+' - androidTestCompile 'com.android.support.test:runner:0.4.+' - androidTestCompile 'com.android.support.test:rules:0.4.+' -} - -//copy the instrumentation tests from the maven osmdroid-android-it src folder -task copyTestClasses(type: Copy) { - description 'Copies the osmdroid-android-it maven test classes to androidTest folder.' - from ("${rootDir}/osmdroid-android-it/src/main/java") - into "./src/androidTest/java" + //crash logging + compile 'ch.acra:acra:4.7.0' -} - -preBuild.dependsOn copyTestClasses + //memory leak testing + debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2' + releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2' + testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2' -/* -android.applicationVariants.all { variant -> - if (variant.getBuildType().name == "debug") { - variant.connectedInstrumentTest.doFirst { - //task "configDevice${variant.name.capitalize()}" (type: Exec){ - // dependsOn variant.install - - //group = 'setPermissions' - //description = 'Sets API23 style permissions.' - - def adb = android.getAdbExe().toString() - def mypermission = 'android.permission.INTERNET' - exec { - commandLine "echo adb shell pm grant ${variant.applicationId} $mypermission".split(' ') - commandLine "$adb shell pm grant ${variant.applicationId} $mypermission".split(' ') - } - - - mypermission = 'android.permission.ACCESS_FINE_LOCATION' - exec { - commandLine "echo adb shell pm grant ${variant.applicationId} $mypermission".split(' ') - commandLine "$adb shell pm grant ${variant.applicationId} $mypermission".split(' ') - } - - - - mypermission = 'android.permission.WRITE_EXTERNAL_STORAGE' - exec { - commandLine "echo adb shell pm grant ${variant.applicationId} $mypermission".split(' ') - commandLine "$adb shell pm grant ${variant.applicationId} $mypermission".split(' ') - } - - } - //variant.testVariant.connectedInstrumentTest.dependsOn "configDevice${variant.name.capitalize()}" - } -}*/ -/* -def adb = android.getAdbExe().toString() - -task nameofyourtask(type: Exec, dependsOn: 'installDebug') { // or install{productFlavour}{buildType} - group = 'nameofyourtaskgroup' - description = 'Describe your task here.' - def mypermission = 'android.permission.ACCESS_FINE_LOCATION' - commandLine "$adb shell pm grant ${variant.applicationId} $mypermission".split(' ') -} -task nameofyourtask2(type: Exec, dependsOn: 'installDebug') { // or install{productFlavour}{buildType} - group = 'nameofyourtaskgroup' - description = 'Describe your task here.' - def mypermission = 'android.permission.WRITE_EXTERNAL_STORAGE' - commandLine "$adb shell pm grant ${variant.applicationId} $mypermission".split(' ') + //on device testing + androidTestCompile 'com.android.support:support-annotations:23+' + androidTestCompile 'com.android.support.test:runner:0.4.+' + androidTestCompile 'com.android.support.test:rules:0.4.+' } -tasks.whenTaskAdded { task -> - if (task.name.startsWith('connectedDebugAndroidTest')) { // or connected{productFlavour}{buildType}AndroidTest - task.dependsOn nameofyourtask - task.dependsOn nameofyourtask2 - } -}*/ +//the following sets the required permissions for API 23+ devices and AVDs android.applicationVariants.all { variant -> if (variant.getBuildType().name == "debug") { diff --git a/OpenStreetMapViewer/pom.xml b/OpenStreetMapViewer/pom.xml index 5860de8856..b18544ecd6 100644 --- a/OpenStreetMapViewer/pom.xml +++ b/OpenStreetMapViewer/pom.xml @@ -30,6 +30,10 @@ android.support compatibility-v4 + + ch.acra + acra + diff --git a/OpenStreetMapViewer/src/main/AndroidManifest.xml b/OpenStreetMapViewer/src/main/AndroidManifest.xml index 77430db1fb..b47eb7904a 100644 --- a/OpenStreetMapViewer/src/main/AndroidManifest.xml +++ b/OpenStreetMapViewer/src/main/AndroidManifest.xml @@ -37,8 +37,9 @@ android:required="false" /> diff --git a/OpenStreetMapViewer/src/main/java/org/osmdroid/OsmApplication.java b/OpenStreetMapViewer/src/main/java/org/osmdroid/OsmApplication.java new file mode 100644 index 0000000000..19ec13b2dd --- /dev/null +++ b/OpenStreetMapViewer/src/main/java/org/osmdroid/OsmApplication.java @@ -0,0 +1,122 @@ +package org.osmdroid; + +import android.app.Application; +import android.content.Context; +import android.os.Environment; +import android.util.Log; + +import com.squareup.leakcanary.LeakCanary; + +import org.acra.ACRA; +import org.acra.annotation.ReportsCrashes; +import org.acra.collector.CrashReportData; +import org.acra.sender.ReportSender; +import org.acra.sender.ReportSenderException; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +/** + * This is the base application for the sample app. We only use to catch errors during development cycles + * Created by alex on 7/4/16. + */ +@ReportsCrashes( formUri = "") +public class OsmApplication extends Application{ + + @Override + public void onCreate(){ + super.onCreate(); + LeakCanary.install(this); + Thread.currentThread().setUncaughtExceptionHandler(new OsmUncaughtExceptionHandler()); + } + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + + + + // Initialise ACRA + ACRA.init(this); + ACRA.getErrorReporter().setReportSender(new ErrorFileWriter()); + + + + } + + public static class OsmUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { + @Override + public void uncaughtException(Thread thread, Throwable ex) { + Log.e("UncaughtException", "Got an uncaught exception: "+ex.toString()); + if(ex.getClass().equals(OutOfMemoryError.class)) + { + writeHprof(); + } + ex.printStackTrace(); + } + } + + /** + * writes the current heap to the file system at /sdcard/osmdroid/trace-{timestamp}.hprof + */ + public static void writeHprof(){ + try { + android.os.Debug.dumpHprofData(Environment.getExternalStorageDirectory().getAbsolutePath() + "/osmdroid/trace-" + System.currentTimeMillis()+ ".hprof"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + /** + * Writes hard crash stack traces to a file on the SD card. + */ + private static class ErrorFileWriter implements ReportSender { + + @Override + public void send(Context context, CrashReportData crashReportData) throws ReportSenderException { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", + Locale.US); + String timeStamp = sdf.format(new Date()); + String rootDirectory = Environment.getExternalStorageDirectory() + .getAbsolutePath(); + File f = new File(rootDirectory + + File.separatorChar + + "TCE" + + File.separatorChar + + "logs" + + File.separatorChar + + "crash" + + File.separatorChar); + f.mkdirs(); + f = new File(rootDirectory + + File.separatorChar + + "TCE" + + File.separatorChar + + "logs" + + File.separatorChar + + "crash" + + File.separatorChar + + "CRASH_" + + BuildConfig.APPLICATION_ID+ "_" + + timeStamp + ".log"); + f.delete(); + + try { + f.createNewFile(); + PrintWriter pw = new PrintWriter(new FileWriter(f)); + pw.println(crashReportData.toString()); + pw.close(); + } catch (Exception exc) { + exc.printStackTrace(); + } + + + } + } +} diff --git a/OpenStreetMapViewer/src/main/java/org/osmdroid/StarterMapFragment.java b/OpenStreetMapViewer/src/main/java/org/osmdroid/StarterMapFragment.java index ce3874ad2e..2f77e30014 100644 --- a/OpenStreetMapViewer/src/main/java/org/osmdroid/StarterMapFragment.java +++ b/OpenStreetMapViewer/src/main/java/org/osmdroid/StarterMapFragment.java @@ -160,13 +160,19 @@ public void onPause() { this.mLocationOverlay.disableMyLocation(); - //this part terminates all of the overlays and background threads for osmdroid - //only needed when you programmatically create the map - mMapView.onDetach(); super.onPause(); } + @Override + public void onDestroyView(){ + super.onDestroyView(); + //this part terminates all of the overlays and background threads for osmdroid + //only needed when you programmatically create the map + mMapView.onDetach(); + + } + @Override public void onResume() { super.onResume(); diff --git a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/FragmentSamples.java b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/FragmentSamples.java index 712b3a1062..973b040cc4 100644 --- a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/FragmentSamples.java +++ b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/FragmentSamples.java @@ -27,6 +27,17 @@ public static FragmentSamples newInstance() { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + //the following block of code took me an entire weekend to track down the root cause. + //if the code block is in onCreate, it will leak. onActivityCreated = no leak. + //makes no sense, but that's Android for you. + // Generate a ListView with Sample Maps final ArrayList list = new ArrayList<>(); // Put samples next @@ -34,7 +45,10 @@ public void onCreate(Bundle savedInstanceState) { final BaseSampleFragment f = sampleFactory.getSample(a); list.add(f.getSampleTitle()); } - ArrayAdapter adapter = new ArrayAdapter<>(getActivity(), + //changed from getActivity to Application context per http://www.slideshare.net/AliMuzaffar2/android-preventing-common-memory-leaks + //prior to that, we had a memory leak that would only show on long running tests. the issue is that the array adapter + //wasn't being gc'd. + ArrayAdapter adapter = new ArrayAdapter<>(getActivity().getApplicationContext(), android.R.layout.simple_list_item_1, list); setListAdapter(adapter); } @@ -58,4 +72,9 @@ public void onResume(){ fm.popBackStack(); System.gc(); } + + @Override + public void onDestroyView(){ + super.onDestroyView(); + } } diff --git a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleAnimateTo.java b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleAnimateTo.java index 176febcb67..4905f84564 100644 --- a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleAnimateTo.java +++ b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleAnimateTo.java @@ -26,7 +26,7 @@ public String getSampleTitle() { @Override public void addOverlays() { super.addOverlays(); - //this part will recalculate and render the lat/lon gridlines + mMapView.getController().setZoom(10); } @@ -70,7 +70,7 @@ public void run() { double lat = rand.nextDouble() * 180 - 90; double lon = rand.nextDouble() * 360 - 180; mMapView.getController().animateTo(new GeoPoint(lat, lon)); - Toast.makeText(getActivity(), "Animate to " + SampleMapEventListener.df.format(lat) + "," + SampleMapEventListener.df.format(lon), Toast.LENGTH_LONG).show(); + Toast.makeText(getActivity(), "Animate to " + SampleMapEventListener.df.format(lat) + "," + SampleMapEventListener.df.format(lon), Toast.LENGTH_SHORT).show(); } } }); diff --git a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleCustomTileSource.java b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleCustomTileSource.java index 50879a03a6..6baf648dc1 100644 --- a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleCustomTileSource.java +++ b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleCustomTileSource.java @@ -1,7 +1,6 @@ package org.osmdroid.samplefragments; -import org.osmdroid.tileprovider.MapTile; -import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase; +import org.osmdroid.samplefragments.models.USGSTileSource; /** * Simple how to for setting a custom tile source diff --git a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleGridlines.java b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleGridlines.java index 69a921edb6..826076eef3 100644 --- a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleGridlines.java +++ b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleGridlines.java @@ -2,6 +2,7 @@ import android.util.Log; +import org.osmdroid.util.GeoPoint; import org.osmdroid.views.overlay.FolderOverlay; import org.osmdroid.events.MapListener; import org.osmdroid.events.ScrollEvent; @@ -25,6 +26,8 @@ public String getSampleTitle() { @Override protected void addOverlays() { + mMapView.getController().setCenter(new GeoPoint(0d,0d)); + mMapView.getController().setZoom(5); mMapView.setTilesScaledToDpi(true); mMapView.setMapListener(this); mMapView.getController().setZoom(3); diff --git a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleMilitaryIcons.java b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleMilitaryIcons.java index 001034059b..70b2673d3f 100644 --- a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleMilitaryIcons.java +++ b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleMilitaryIcons.java @@ -13,12 +13,10 @@ import org.osmdroid.api.IGeoPoint; import org.osmdroid.tileprovider.BitmapPool; -import org.osmdroid.tileprovider.MapTile; import org.osmdroid.tileprovider.ReusableBitmapDrawable; import org.osmdroid.util.GeoPoint; import org.osmdroid.views.overlay.ItemizedIconOverlay; import org.osmdroid.views.overlay.ItemizedOverlayWithFocus; -import org.osmdroid.views.overlay.MinimapOverlay; import org.osmdroid.views.overlay.OverlayItem; import org.osmdroid.views.overlay.gestures.RotationGestureOverlay; @@ -47,10 +45,10 @@ public class SampleMilitaryIcons extends BaseSampleFragment { // =========================================================== // Fields // =========================================================== - private ItemizedOverlayWithFocus mMyLocationOverlay; + private ItemizedOverlayWithFocus itemOverlay; private RotationGestureOverlay mRotationGestureOverlay; private OverlayItem overlayItem; - List icons = new ArrayList<>(4); + private List icons = new ArrayList<>(4); @Override public String getSampleTitle() { @@ -60,13 +58,6 @@ public String getSampleTitle() { // =========================================================== // Constructors // =========================================================== - /** - * Called when the activity is first created. - */ - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - } @Override protected void addOverlays() { @@ -83,7 +74,7 @@ protected void addOverlays() { /* Itemized Overlay */ { /* OnTapListener for the Markers, shows a simple Toast. */ - mMyLocationOverlay = new ItemizedOverlayWithFocus<>(new ArrayList(), + itemOverlay = new ItemizedOverlayWithFocus<>(new ArrayList(), new ItemizedIconOverlay.OnItemGestureListener() { @Override public boolean onItemSingleTapUp(final int index, final OverlayItem item) { @@ -103,13 +94,13 @@ public boolean onItemLongPress(final int index, final OverlayItem item) { return false; } }, context); - mMyLocationOverlay.setFocusItemsOnTap(true); - mMyLocationOverlay.setFocusedItem(0); + itemOverlay.setFocusItemsOnTap(true); + itemOverlay.setFocusedItem(0); //generates 50 randomized points addIcons(50); - mMapView.getOverlays().add(mMyLocationOverlay); + mMapView.getOverlays().add(itemOverlay); mRotationGestureOverlay = new RotationGestureOverlay(context, mMapView); mRotationGestureOverlay.setEnabled(false); @@ -125,11 +116,11 @@ public boolean onItemLongPress(final int index, final OverlayItem item) { // Zoom and center on the focused item. mMapView.getController().setZoom(5); - IGeoPoint geoPoint = mMyLocationOverlay.getFocusedItem().getPoint(); + IGeoPoint geoPoint = itemOverlay.getFocusedItem().getPoint(); mMapView.getController().animateTo(geoPoint); setHasOptionsMenu(true); - Toast.makeText(context, "Icon selection and location are random!", Toast.LENGTH_LONG).show(); + Toast.makeText(context, "Icon selection and location are random!", Toast.LENGTH_SHORT).show(); } // =========================================================== @@ -194,31 +185,37 @@ private void addIcons(int count) { items.add(overlayItem); } - mMyLocationOverlay.addItems(items); - Toast.makeText(getActivity(), count + " icons added! Current size: " + mMyLocationOverlay.size(), Toast.LENGTH_LONG).show(); + itemOverlay.addItems(items); + mMapView.invalidate(); + Toast.makeText(getActivity(), count + " icons added! Current size: " + itemOverlay.size(), Toast.LENGTH_SHORT).show(); } + @Override public void onDestroyView(){ + //itemOverlay.onDetach(mMapView); super.onDestroyView(); - - for (int i=0; i < icons.size(); i++) { - Drawable drawable = icons.get(i); - // Only recycle if we are running on a project less than 2.3.3 Gingerbread. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { - if (drawable instanceof BitmapDrawable) { - final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); - if (bitmap != null) { - bitmap.recycle(); +/* + synchronized (icons) { + for (int i = 0; i < icons.size(); i++) { + Drawable drawable = icons.get(i); + // Only recycle if we are running on a project less than 2.3.3 Gingerbread. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { + if (drawable instanceof BitmapDrawable) { + final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); + if (bitmap != null) { + bitmap.recycle(); + } } } + if (drawable instanceof ReusableBitmapDrawable) + BitmapPool.getInstance().returnDrawableToPool((ReusableBitmapDrawable) drawable); } - if (drawable instanceof ReusableBitmapDrawable) - BitmapPool.getInstance().returnDrawableToPool((ReusableBitmapDrawable) drawable); + icons.clear(); } - icons.clear(); - System.gc(); + System.gc();*/ + } diff --git a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleOfflineOnly.java b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleOfflineOnly.java index 37d295702c..32a9f222ba 100644 --- a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleOfflineOnly.java +++ b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleOfflineOnly.java @@ -85,7 +85,7 @@ public void addOverlays() { this.mMapView.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE); } - Toast.makeText(getActivity(), "Using " + list[i].getAbsolutePath() + " " + source, Toast.LENGTH_LONG).show(); + Toast.makeText(getActivity(), "Using " + list[i].getAbsolutePath() + " " + source, Toast.LENGTH_SHORT).show(); this.mMapView.invalidate(); return; } catch (Exception ex) { @@ -94,9 +94,9 @@ public void addOverlays() { } } } - Toast.makeText(getActivity(), f.getAbsolutePath() + " did not have any files I can open! Try using MOBAC", Toast.LENGTH_LONG).show(); + Toast.makeText(getActivity(), f.getAbsolutePath() + " did not have any files I can open! Try using MOBAC", Toast.LENGTH_SHORT).show(); } else { - Toast.makeText(getActivity(), f.getAbsolutePath() + " dir not found!", Toast.LENGTH_LONG).show(); + Toast.makeText(getActivity(), f.getAbsolutePath() + " dir not found!", Toast.LENGTH_SHORT).show(); } } diff --git a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleSqliteOnly.java b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleSqliteOnly.java index c42e81edaa..07d0fbbd5a 100644 --- a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleSqliteOnly.java +++ b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleSqliteOnly.java @@ -87,7 +87,7 @@ public void addOverlays() { this.mMapView.setTileSource(TileSourceFactory.DEFAULT_TILE_SOURCE); } - Toast.makeText(getActivity(), "Using " + list[i].getAbsolutePath() + " " + source, Toast.LENGTH_LONG).show(); + Toast.makeText(getActivity(), "Using " + list[i].getAbsolutePath() + " " + source, Toast.LENGTH_SHORT).show(); this.mMapView.invalidate(); return; } catch (Exception ex) { @@ -96,9 +96,9 @@ public void addOverlays() { } } } - Toast.makeText(getActivity(), f.getAbsolutePath() + " did not have any files I can open! Try using MOBAC", Toast.LENGTH_LONG).show(); + Toast.makeText(getActivity(), f.getAbsolutePath() + " did not have any files I can open! Try using MOBAC", Toast.LENGTH_SHORT).show(); } else { - Toast.makeText(getActivity(), f.getAbsolutePath() + " dir not found!", Toast.LENGTH_LONG).show(); + Toast.makeText(getActivity(), f.getAbsolutePath() + " dir not found!", Toast.LENGTH_SHORT).show(); } diff --git a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleWhackyColorFilter.java b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleWhackyColorFilter.java index 7c5b67c561..12384fb6cc 100644 --- a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleWhackyColorFilter.java +++ b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/SampleWhackyColorFilter.java @@ -28,7 +28,7 @@ public void addOverlays() { //this.mMapView.getOverlayManager().getTilesOverlay().setColorFilter(adjustHue(160)); ColorMatrix cm = new ColorMatrix(); - float brightness =.5f; // reduce color's by 50% + float brightness =.5f; // reduce color's by 50%. i.e. just make it darker cm.set(new float[] { brightness, 0, 0, 0, 0, //red 0, brightness, 0, 0, 0, //green diff --git a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/USGSTileSource.java b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/models/USGSTileSource.java similarity index 97% rename from OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/USGSTileSource.java rename to OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/models/USGSTileSource.java index 7263e654de..d3ac5c4204 100644 --- a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/USGSTileSource.java +++ b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/models/USGSTileSource.java @@ -1,4 +1,4 @@ -package org.osmdroid.samplefragments; +package org.osmdroid.samplefragments.models; import org.osmdroid.tileprovider.MapTile; import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase; diff --git a/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/models/package-info.java b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/models/package-info.java new file mode 100644 index 0000000000..527d4b5986 --- /dev/null +++ b/OpenStreetMapViewer/src/main/java/org/osmdroid/samplefragments/models/package-info.java @@ -0,0 +1,3 @@ +/** + * This package's purpose is to contain any support classes, data models, and utilities for the sample fragments + */ \ No newline at end of file diff --git a/osmdroid-android-it/pom.xml b/osmdroid-android-it/pom.xml index 67294616d6..a0892214c6 100644 --- a/osmdroid-android-it/pom.xml +++ b/osmdroid-android-it/pom.xml @@ -43,6 +43,10 @@ aar provided + + com.squareup.leakcanary + leakcanary-android + diff --git a/osmdroid-android-it/src/main/java/org/osmdroid/test/ExtraSamplesTest.java b/osmdroid-android-it/src/main/java/org/osmdroid/test/ExtraSamplesTest.java index 869ff224e7..e40dabfec8 100644 --- a/osmdroid-android-it/src/main/java/org/osmdroid/test/ExtraSamplesTest.java +++ b/osmdroid-android-it/src/main/java/org/osmdroid/test/ExtraSamplesTest.java @@ -18,64 +18,25 @@ import junit.framework.Assert; import org.osmdroid.ExtraSamplesActivity; +import org.osmdroid.OsmApplication; import org.osmdroid.samplefragments.*; import org.osmdroid.tileprovider.util.Counters; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + public class ExtraSamplesTest extends ActivityInstrumentationTestCase2 { public ExtraSamplesTest() { super("org.osmdroid", ExtraSamplesActivity.class); } -/* - public void testActivity() { - //if (Build.VERSION.SDK_INT == 10) - // return; //FIXME dirty fix for travis ci - ExtraSamplesActivity activity = getActivity(); - assertNotNull(activity); - FragmentManager fm = activity.getSupportFragmentManager(); - Fragment frag = (fm.findFragmentByTag(ExtraSamplesActivity.SAMPLES_FRAGMENT_TAG)); - assertNotNull(frag); - - assertTrue(frag instanceof FragmentSamples); - //FragmentSamples samples = (FragmentSamples) frag; - - SampleFactory sampleFactory = SampleFactory.getInstance(); - long startFree= Runtime.getRuntime().freeMemory(); - Log.i(FragmentSamples.TAG, "Memory allocation: INIT Free: " + Runtime.getRuntime().freeMemory() +" Total:" + Runtime.getRuntime().totalMemory() +" Max:"+ Runtime.getRuntime().maxMemory()); - for (int i = 0; i < sampleFactory.count(); i++) { - Log.i(FragmentSamples.TAG, "Memory allocation: Before load: Free: " + Runtime.getRuntime().freeMemory() +" Total:" + Runtime.getRuntime().totalMemory() +" Max:"+ Runtime.getRuntime().maxMemory()); - BaseSampleFragment basefrag = sampleFactory.getSample(i); - Log.i(FragmentSamples.TAG, "loading fragment " + basefrag.getSampleTitle() + ", " + frag.getClass().getCanonicalName()); - if (Build.VERSION.SDK_INT == 10 && basefrag instanceof SampleJumboCache) - continue; - if (Build.VERSION.SDK_INT == 10 && basefrag instanceof SampleOsmPath) - continue; - if (Build.VERSION.SDK_INT == 10 && basefrag instanceof SampleMilitaryIcons) - continue; - if (Build.VERSION.SDK_INT == 10 && basefrag instanceof SampleGridlines) - continue; - if (Build.VERSION.SDK_INT == 10 && basefrag instanceof SampleJumboCache) - continue; - try { - fm.beginTransaction().replace(org.osmdroid.R.id.samples_container, basefrag, ExtraSamplesActivity.SAMPLES_FRAGMENT_TAG) - .addToBackStack(null).commit(); - //this sleep is here to give the fragment enough time to start up and do something - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } catch (java.lang.OutOfMemoryError oom) { - Assert.fail("OOM error! " + sampleFactory.getSample(i).getSampleTitle() + sampleFactory.getSample(i).getClass().getCanonicalName()); - } - System.gc(); - Log.i(FragmentSamples.TAG, "Memory allocation: END Free: " + Runtime.getRuntime().freeMemory() +" Total:" + Runtime.getRuntime().totalMemory() +" Max:"+ Runtime.getRuntime().maxMemory()); - } - } - -*/ + /** + * This tests every sample fragment in the app. See implementation notes on how to increase + * the duration and iteration count for longer running tests and memory leak testing + */ public void testActivity() { + Counters.reset(); final ExtraSamplesActivity activity = getActivity(); assertNotNull(activity); final FragmentManager fm = activity.getSupportFragmentManager(); @@ -86,34 +47,29 @@ public void testActivity() { //FragmentSamples samples = (FragmentSamples) frag; final SampleFactory sampleFactory = SampleFactory.getInstance(); - Log.i(FragmentSamples.TAG, "Memory allocation: INIT Free: " + Runtime.getRuntime().freeMemory() + " Total:" + Runtime.getRuntime().totalMemory() + " Max:" + Runtime.getRuntime().maxMemory()); + //force the fragments to load in a randomized order, why? because it will eventually force + //unforeseen issues via ci + int[] fireOrder = new int[sampleFactory.count()]; + for (int i = 0; i < sampleFactory.count(); i++) { + fireOrder[i]=i; + } + shuffleArray(fireOrder); + + Log.i(FragmentSamples.TAG, "Memory allocation: INIT Free: " + Runtime.getRuntime().freeMemory() + " Total:" + Runtime.getRuntime().totalMemory() + " Max:" + Runtime.getRuntime().maxMemory()); for (int i = 0; i < sampleFactory.count(); i++) { - for (int k = 0; k < 1; k++) { + for (int k = 0; k < 5; k++) { Log.i(FragmentSamples.TAG, k + "Memory allocation: Before load: Free: " + Runtime.getRuntime().freeMemory() + " Total:" + Runtime.getRuntime().totalMemory() + " Max:" + Runtime.getRuntime().maxMemory()); - final BaseSampleFragment basefrag = sampleFactory.getSample(i); - Log.i(FragmentSamples.TAG, "loading fragment " + basefrag.getSampleTitle() + ", " + frag.getClass().getCanonicalName()); + final BaseSampleFragment basefrag = sampleFactory.getSample(fireOrder[i]); + Log.i(FragmentSamples.TAG, "loading fragment ("+i+"/" + sampleFactory.count()+") run " +k +" " + basefrag.getSampleTitle() + ", " + frag.getClass().getCanonicalName()); Counters.printToLogcat(); if (Counters.countOOM > 0 || Counters.fileCacheOOM > 0) { - Assert.fail("OOM Detected, aborting! this test run was " + basefrag.getSampleTitle() + ", " + basefrag.getClass().getCanonicalName()); + OsmApplication.writeHprof(); + Assert.fail("OOM Detected, aborting! this test run was " + basefrag.getSampleTitle() + ", " + basefrag.getClass().getCanonicalName() + " iteration " + k); } - - if (Build.VERSION.SDK_INT <= 10 && basefrag instanceof SampleJumboCache) { - continue; - } - if (Build.VERSION.SDK_INT <= 10 && basefrag instanceof SampleOsmPath) { - continue; - } - if (Build.VERSION.SDK_INT <= 10 && basefrag instanceof SampleMilitaryIcons) { - continue; - } - //if (Build.VERSION.SDK_INT <= 10 && basefrag instanceof SampleGridlines) { - // continue; - //} - activity.runOnUiThread(new Runnable() { @Override public void run() { @@ -151,5 +107,24 @@ public void run() { } } } + + /** + * src http://stackoverflow.com/questions/1519736/random-shuffling-of-an-array + * Implementing Fisher–Yates shuffle + * @param ar + */ + static void shuffleArray(int[] ar) + { + // If running on Java 6 or older, use `new Random()` on RHS here + Random rnd = new Random(); + for (int i = ar.length - 1; i > 0; i--) + { + int index = rnd.nextInt(i + 1); + // Simple swap + int a = ar[index]; + ar[index] = ar[i]; + ar[i] = a; + } + } } diff --git a/osmdroid-android-it/src/main/java/org/osmdroid/test/MainActivityTest.java b/osmdroid-android-it/src/main/java/org/osmdroid/test/MainActivityTest.java index b8ed97276d..7fb7949c02 100644 --- a/osmdroid-android-it/src/main/java/org/osmdroid/test/MainActivityTest.java +++ b/osmdroid-android-it/src/main/java/org/osmdroid/test/MainActivityTest.java @@ -7,8 +7,9 @@ */ package org.osmdroid.test; +import org.junit.Assert; import org.osmdroid.MainActivity; - +import org.osmdroid.tileprovider.util.Counters; import android.test.ActivityInstrumentationTestCase2; public class MainActivityTest extends ActivityInstrumentationTestCase2 { @@ -18,8 +19,14 @@ public MainActivityTest() { } public void testActivity() { + Counters.reset(); MainActivity activity = getActivity(); assertNotNull(activity); + Counters.printToLogcat(); + if (Counters.countOOM > 0 || Counters.fileCacheOOM > 0){ + Assert.fail("OOM Detected, aborting!"); + } + activity.finish(); } } diff --git a/osmdroid-android-it/src/main/java/org/osmdroid/test/MapActivityTest.java b/osmdroid-android-it/src/main/java/org/osmdroid/test/MapActivityTest.java index 6552d8c3fe..e969ccb343 100644 --- a/osmdroid-android-it/src/main/java/org/osmdroid/test/MapActivityTest.java +++ b/osmdroid-android-it/src/main/java/org/osmdroid/test/MapActivityTest.java @@ -9,7 +9,6 @@ import org.osmdroid.StarterMapActivity; import org.osmdroid.tileprovider.util.Counters; - import android.test.ActivityInstrumentationTestCase2; import junit.framework.Assert; @@ -21,6 +20,7 @@ public MapActivityTest() { } public void testActivity() { + Counters.reset(); StarterMapActivity activity = getActivity(); assertNotNull(activity); try { diff --git a/osmdroid-android-it/src/main/java/org/osmdroid/views/OpenStreetMapViewTest.java b/osmdroid-android-it/src/main/java/org/osmdroid/views/OpenStreetMapViewTest.java index 2320d3cea7..3504447b6c 100644 --- a/osmdroid-android-it/src/main/java/org/osmdroid/views/OpenStreetMapViewTest.java +++ b/osmdroid-android-it/src/main/java/org/osmdroid/views/OpenStreetMapViewTest.java @@ -12,7 +12,7 @@ import org.osmdroid.R; import org.osmdroid.util.GeoPoint; import org.osmdroid.views.Projection; - +import org.osmdroid.tileprovider.util.Counters; import android.graphics.Point; import android.test.ActivityInstrumentationTestCase2; import android.test.UiThreadTest; @@ -26,6 +26,7 @@ public class OpenStreetMapViewTest extends ActivityInstrumentationTestCase2 mWorking; - + protected IRegisterReceiver mRegisterReceiver=null; protected final List mTileProviderList; /** @@ -60,7 +60,7 @@ public MapTileProviderArray(final ITileSource pTileSource, super(pTileSource); mWorking = new HashMap(); - + mRegisterReceiver=aRegisterReceiver; mTileProviderList = new ArrayList(); Collections.addAll(mTileProviderList, pTileProviderArray); } @@ -76,6 +76,11 @@ public void detach() { synchronized (mWorking) { mWorking.clear(); } + clearTileCache(); + if (mRegisterReceiver!=null) { + mRegisterReceiver.destroy(); + mRegisterReceiver = null; + } } diff --git a/osmdroid-android/src/main/java/org/osmdroid/tileprovider/modules/OfflineTileProvider.java b/osmdroid-android/src/main/java/org/osmdroid/tileprovider/modules/OfflineTileProvider.java index c346fc6c76..a4ab820e9a 100644 --- a/osmdroid-android/src/main/java/org/osmdroid/tileprovider/modules/OfflineTileProvider.java +++ b/osmdroid-android/src/main/java/org/osmdroid/tileprovider/modules/OfflineTileProvider.java @@ -49,4 +49,13 @@ public OfflineTileProvider(final IRegisterReceiver pRegisterReceiver, File[] sou public IArchiveFile[] getArchives(){ return archives; } + + public void detach() { + if (archives!=null){ + for (int i=0; i < archives.length; i++){ + archives[i].close(); + } + } + super.detach(); + } } diff --git a/osmdroid-android/src/main/java/org/osmdroid/tileprovider/util/Counters.java b/osmdroid-android/src/main/java/org/osmdroid/tileprovider/util/Counters.java index 654bffacf7..3709901ed1 100644 --- a/osmdroid-android/src/main/java/org/osmdroid/tileprovider/util/Counters.java +++ b/osmdroid-android/src/main/java/org/osmdroid/tileprovider/util/Counters.java @@ -31,4 +31,12 @@ public static void printToLogcat() { Log.d(TAG, "fileCacheOOM " + fileCacheOOM); Log.d(TAG, "fileCacheHit " + fileCacheHit); } + public static void reset(){ + countOOM =0; + tileDownloadErrors=0; + fileCacheSaveErrors=0; + fileCacheMiss=0; + fileCacheOOM=0; + fileCacheHit=0; + } } diff --git a/osmdroid-android/src/main/java/org/osmdroid/tileprovider/util/SimpleRegisterReceiver.java b/osmdroid-android/src/main/java/org/osmdroid/tileprovider/util/SimpleRegisterReceiver.java index 72d792c177..a398715304 100644 --- a/osmdroid-android/src/main/java/org/osmdroid/tileprovider/util/SimpleRegisterReceiver.java +++ b/osmdroid-android/src/main/java/org/osmdroid/tileprovider/util/SimpleRegisterReceiver.java @@ -9,7 +9,7 @@ public class SimpleRegisterReceiver implements IRegisterReceiver { - private final Context mContext; + private Context mContext; public SimpleRegisterReceiver(final Context pContext) { super(); @@ -25,4 +25,9 @@ public Intent registerReceiver(final BroadcastReceiver aReceiver, final IntentFi public void unregisterReceiver(final BroadcastReceiver aReceiver) { mContext.unregisterReceiver(aReceiver); } + + @Override + public void destroy() { + mContext=null; + } } diff --git a/osmdroid-android/src/main/java/org/osmdroid/views/overlay/ItemizedIconOverlay.java b/osmdroid-android/src/main/java/org/osmdroid/views/overlay/ItemizedIconOverlay.java index d04d6c0550..734777625f 100644 --- a/osmdroid-android/src/main/java/org/osmdroid/views/overlay/ItemizedIconOverlay.java +++ b/osmdroid-android/src/main/java/org/osmdroid/views/overlay/ItemizedIconOverlay.java @@ -49,6 +49,8 @@ public ItemizedIconOverlay( @Override public void onDetach(MapView mapView){ + if (mItemList!=null) + mItemList.clear(); mItemList=null; mOnItemGestureListener=null; } diff --git a/osmdroid-android/src/main/java/org/osmdroid/views/overlay/ItemizedOverlayWithFocus.java b/osmdroid-android/src/main/java/org/osmdroid/views/overlay/ItemizedOverlayWithFocus.java index b561e7e694..fc55ca716e 100644 --- a/osmdroid-android/src/main/java/org/osmdroid/views/overlay/ItemizedOverlayWithFocus.java +++ b/osmdroid-android/src/main/java/org/osmdroid/views/overlay/ItemizedOverlayWithFocus.java @@ -240,6 +240,9 @@ public void draw(final Canvas c, final MapView osmv, final boolean shadow) { return; } + // this happens during shutdown + if (super.mItemList==null) + return; // get focused item's preferred marker & hotspot final Item focusedItem = super.mItemList.get(this.mFocusedItemIndex); Drawable markerFocusedBase = focusedItem.getMarker(OverlayItem.ITEM_STATE_FOCUSED_MASK); diff --git a/osmdroid-android/src/main/java/org/osmdroid/views/overlay/Polygon.java b/osmdroid-android/src/main/java/org/osmdroid/views/overlay/Polygon.java index b6bf1a1138..3c16861e6b 100644 --- a/osmdroid-android/src/main/java/org/osmdroid/views/overlay/Polygon.java +++ b/osmdroid-android/src/main/java/org/osmdroid/views/overlay/Polygon.java @@ -314,4 +314,11 @@ public boolean contains(MotionEvent event){ return tapped; } + @Override + public void onDetach(MapView mapView) { + mOutline=null; + mHoles.clear(); + onDestroy(); + } + } diff --git a/osmdroid-android/src/main/java/org/osmdroid/views/overlay/Polyline.java b/osmdroid-android/src/main/java/org/osmdroid/views/overlay/Polyline.java index c99feff880..74a8c4b150 100644 --- a/osmdroid-android/src/main/java/org/osmdroid/views/overlay/Polyline.java +++ b/osmdroid-android/src/main/java/org/osmdroid/views/overlay/Polyline.java @@ -491,4 +491,10 @@ protected boolean onClickDefault(Polyline polyline, MapView mapView, GeoPoint ev return true; } + @Override + public void onDetach(MapView mapView) { + mOnClickListener=null; + onDestroy(); + } + } diff --git a/pom.xml b/pom.xml index c17f7b6a51..a6f877829e 100644 --- a/pom.xml +++ b/pom.xml @@ -115,6 +115,16 @@ 0.6 provided + + com.squareup.leakcanary + leakcanary-android + 1.4-beta2 + + + ch.acra + acra + 4.7.0 +