perf(scanner): stop redoing everything on every advertisement, plus map point events
Yes, the scanner was duplicating. refreshCategoryOptions(), updateRadar() and pairedAdapter.notifyDataSetChanged() ran from acceptSignal on every advertisement AND from pruneTask once a second - the same three calls either way. Worse, the whole persist path ran per advertisement, and ScanCallback is delivered on the main looper: a hex dump of the advertisement, lookupCompany(), get(), lookupOui(), then telemetry(), setDetectedCategory(), recordObservation() and setVendor(). Eight database operations on the UI thread per beacon, at SCAN_MODE_LOW_LATENCY, times every transmitter in range. The radar sweep re-posts itself every 33ms and was competing for that thread, which is why it dragged. A device already on the radar now only gets its RSSI and last-seen stamp refreshed until PERSIST_INTERVAL_MS (3s) is up. A device's first sighting still takes the full path and refreshes at once. The radar view also allocated per frame at 30fps: a six-stop RadialGradient plus two arrays, and a fresh ArrayList and Comparator for the target sort. Both reused now. setTargets no longer invalidates while scanning, since onDraw already re-posts itself. Left setLayerType(SOFTWARE) alone - setShadowLayer only works on text under hardware acceleration, so removing it would drop the ring glow. Separately, history points now say why they exist. location_history had no event column, so schema 16 adds one; connected() and disconnected() stamp it and a plain position refresh leaves it null. The popup shows a linked or broken-link line for the two states and stays quiet for older rows that predate the column. History points also render as the device's own marker at half size rather than a plain circle, sharing one divIcon per device so a long trail does not build a DOM node per point. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0f01b836c6
commit
f16efce66e
@@ -39,6 +39,18 @@ public class BluetoothRadarView extends View {
|
||||
private float zoom = 1.0f;
|
||||
private boolean suppressTap;
|
||||
private double sweepAngle = -90.0;
|
||||
|
||||
/* Draw-time scratch, reused every frame -- see onDraw. */
|
||||
private android.graphics.RadialGradient glow;
|
||||
private float glowCx = -1f, glowCy = -1f, glowR = -1f;
|
||||
private final ArrayList<Target> ordered = new ArrayList<Target>();
|
||||
private final Comparator<Target> byAngle = new Comparator<Target>() {
|
||||
public int compare(Target first, Target second) {
|
||||
float a = angleFor(first.address);
|
||||
float b = angleFor(second.address);
|
||||
return a < b ? -1 : (a > b ? 1 : 0);
|
||||
}
|
||||
};
|
||||
public BluetoothRadarView(Context context) {
|
||||
super(context);
|
||||
scaleDetector = new ScaleGestureDetector(context, new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
@@ -68,7 +80,10 @@ public class BluetoothRadarView extends View {
|
||||
if (!targetAngles.containsKey(target.address))
|
||||
targetAngles.put(target.address, Float.valueOf(findOpenAngle(target.address)));
|
||||
}
|
||||
targets.clear(); targets.addAll(values); invalidate();
|
||||
targets.clear(); targets.addAll(values);
|
||||
// While scanning, onDraw already re-posts itself every 33ms; an
|
||||
// extra invalidate per update just forces redundant frames.
|
||||
if (!scanning) invalidate();
|
||||
}
|
||||
|
||||
protected synchronized void onDraw(Canvas canvas) {
|
||||
@@ -81,9 +96,15 @@ public class BluetoothRadarView extends View {
|
||||
float glowRadius = radius + 34f;
|
||||
float edgeStop = radius / glowRadius;
|
||||
paint.setStyle(Paint.Style.FILL);
|
||||
paint.setShader(new android.graphics.RadialGradient(cx, cy, glowRadius,
|
||||
new int[] { Color.TRANSPARENT, Color.argb(5, 35, 180, 110), Color.argb(24, 45, 225, 140), Color.argb(48, 55, 235, 150), Color.argb(18, 35, 190, 120), Color.TRANSPARENT },
|
||||
new float[] { 0f, 0.55f, edgeStop - 0.05f, edgeStop, edgeStop + 0.10f, 1f }, android.graphics.Shader.TileMode.CLAMP));
|
||||
// Rebuilt only when the geometry moves. This used to allocate a
|
||||
// six-stop gradient and two arrays on every frame of a 30fps sweep.
|
||||
if (glow == null || glowCx != cx || glowCy != cy || glowR != glowRadius) {
|
||||
glow = new android.graphics.RadialGradient(cx, cy, glowRadius,
|
||||
new int[] { Color.TRANSPARENT, Color.argb(5, 35, 180, 110), Color.argb(24, 45, 225, 140), Color.argb(48, 55, 235, 150), Color.argb(18, 35, 190, 120), Color.TRANSPARENT },
|
||||
new float[] { 0f, 0.55f, edgeStop - 0.05f, edgeStop, edgeStop + 0.10f, 1f }, android.graphics.Shader.TileMode.CLAMP);
|
||||
glowCx = cx; glowCy = cy; glowR = glowRadius;
|
||||
}
|
||||
paint.setShader(glow);
|
||||
canvas.drawCircle(cx, cy, glowRadius, paint);
|
||||
paint.setShader(null);
|
||||
paint.setStyle(Paint.Style.STROKE);
|
||||
@@ -123,14 +144,11 @@ public class BluetoothRadarView extends View {
|
||||
paint.setTextAlign(Paint.Align.CENTER);
|
||||
paint.setTextSize(12 * getResources().getDisplayMetrics().scaledDensity);
|
||||
canvas.drawText("YOU", cx, cy + 24, paint);
|
||||
ArrayList<Target> ordered = new ArrayList<Target>(targets);
|
||||
Collections.sort(ordered, new Comparator<Target>() {
|
||||
public int compare(Target first, Target second) {
|
||||
float a = angleFor(first.address);
|
||||
float b = angleFor(second.address);
|
||||
return a < b ? -1 : (a > b ? 1 : 0);
|
||||
}
|
||||
});
|
||||
// Reused across frames; a fresh list and comparator per frame is
|
||||
// pure garbage at 30fps.
|
||||
ordered.clear();
|
||||
ordered.addAll(targets);
|
||||
Collections.sort(ordered, byAngle);
|
||||
float density = getResources().getDisplayMetrics().scaledDensity;
|
||||
for (int i = 0; i < ordered.size(); i++) {
|
||||
Target target = ordered.get(i);
|
||||
|
||||
Reference in New Issue
Block a user