While evaluating using OSMDroid and OSMBonusPack (which has been endorsed by the OSMDroid maintainers) as a replacement for GoogleMaps I came across a deal breaker when using large Polyline paths.

There's a difference in the way Polylines are handled by GoogleMaps and OSMDroid (plus Bonus Pack); GoogleMaps takes care of only drawing the path in the viewport to save memory, while OSMDroid renders the entire texture, for a path with ~9000 points this causes OOM exceptions when using hardware acceleration: OpenGLRenderer﹕ Path too large to be rendered into a texture (see this issue and this one and various others), some StackOverflow posts suggest turning off hardware acceleration for the OSM view:

<org.osmdroid.views.MapView
    android:id="@+id/map"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layerType="software"
    />

but this kills performance and will cause issues on lower powered devices. In order to use OSMDroid with large paths you need to use a different strategy than you'd use with GoogleMaps; only render the points that will appear on screen, and thin the number of points so there's less drawing work to do.

osmMap.setMapListener(new MapListener() {
    @Override
    public boolean onScroll(ScrollEvent event) {
        drawRoute(MapActivity.this, osmMap, pathColor);
        return false;
    }

    @Override
    public boolean onZoom(ZoomEvent event) {
        drawRoute(MapActivity.this, osmMap, pathColor);
        return false;
    }
});
private Thread updateThread = null;
//...
public void drawRoute(final Context context, final MapView osmMap, final int color){
    if(updateThread == null || !updateThread.isAlive()){
        updateRoute(context, osmMap, color);
    }
}
Polyline pathOverlay = null;

private void updateRoute(final Context context, final MapView osmMap, final int color){
    updateThread = new Thread(new Runnable() {
        public void run() {
            final ArrayList<GeoPoint> zoomPoints = new ArrayList<GeoPoint>(routeGeoPoints);

            //Remove any points that are offscreen
            removeHiddenPoints(osmMap, zoomPoints);

            //If there's still too many then thin the array
            if(zoomPoints.size() > MAX_POINTS){
                int stepSize = (int) zoomPoints.size()/MAX_POINTS;
                int count = 1;
                for (Iterator<GeoPoint> iterator = zoomPoints.iterator(); iterator.hasNext();) {
                    iterator.next();

                    if(count != stepSize){
                        iterator.remove();
                    }else{
                        count = 0;
                    }

                    count++;
                }
            }

            //Update the map on the event thread
            osmMap.post(new Runnable() {
                public void run() {
                    //ideally the Polyline construction would happen in the thread but that causes glitches while the event thread
                    //waits for redraw:
                    osmMap.getOverlays().remove(pathOverlay);
                    pathOverlay = new Polyline(context);
                    pathOverlay.setPoints(zoomPoints);
                    pathOverlay.setColor(color);
                    osmMap.getOverlays().add(pathOverlay);
                    osmMap.invalidate();
                }
            });
        }
    });
    updateThread.start();
}

private void removeHiddenPoints(MapView osmMap, ArrayList<GeoPoint> zoomPoints){
    BoundingBoxE6 bounds = osmMap.getBoundingBox();

    for (Iterator<GeoPoint> iterator = zoomPoints.iterator(); iterator.hasNext();) {
        GeoPoint point = iterator.next();

        boolean inLongitude = point.getLatitudeE6() < bounds.getLatNorthE6() && point.getLatitudeE6() > bounds.getLatSouthE6();
        boolean inLatitude = point.getLongitudeE6() > bounds.getLonWestE6() && point.getLongitudeE6() < bounds.getLonEastE6();
        if(!inLongitude || !inLatitude){
            iterator.remove();
        }
    }
}

The result is very smooth but not perfect with occasional glitches along the viewport boundary (this example uses MAX_POINTS = 150):