A big part of the variation you see is Android is caused by a limitation in the way Android allows access signal strength measurements from Bluetooth LE. Unfortunately, there isn't much that can be done about this without changes to Android.
In both iOS CoreLocation and the Android iBeacon Library, the distance estimate is only an estimate, and fluctuations with noise on signal strength measurements cause the estimates to bounce around.
The algorithm in the Android iBeacon Library is not the same as in iOS CoreLocation, because the iOS CoreLocation implementation is closed source. The intention is that they behave in a similar way. The Android iBeacon Library is based on a 10 second running average of 80th percentile measurements (e.g. the top and bottom 10th percentile measurements are thrown away for the average.) You can see the details of the calculation here:
protected static double calculateAccuracy(int txPower, double rssi) {
if (rssi == 0) {
return -1.0; // if we cannot determine accuracy, return -1.
}
double ratio = rssi*1.0/txPower;
if (ratio < 1.0) {
return Math.pow(ratio,10);
}
else {
double accuracy = (0.89976)*Math.pow(ratio,7.7095) + 0.111;
return accuracy;
}
}
On Android, the Bluetooth LE Scan API only allows a single signal strength measurement per scan. On iOS, it is possible to get a different measurement for each advertisement broadcasted. By default, the Android iBeacon Library does one bluetooth scan every 1.1 seconds when it is in the foreground, therefore allowing one measurement every 1.1 seconds. So if you have an iBeacon that is transmitting 30 times per second (as iOS devices acting as iBeacons do), iOS will be able to get 300 samples in a 10 second period, and Android only 9. This explains why the estimate has higher noise on Android. And again, there is very little that can be done about it without operating system changes.
Depending on your use case, you may be able to reduce the noise in the distance estimate on Android by implementing a custom calculation that includes more samples over a longer period of time. This would only be appropriate if your use case does not need rapid updates in the estimate. If you are interested in this, you might open a feature request in the open source library.