开发测速App遇下载速度偏差问题,求优化方案及上传测速实现
Hey there! Let's tackle your speed test app issues step by step—both fixing the download speed discrepancy with Ookla Speedtest and adding upload testing, plus clearing up that TrafficStats confusion.
优化下载测速方案
Your current approach has a few key flaws that are causing the big gap with Ookla's results:
- Bitmap decoding skews timing:
BitmapFactory.decodeStream()doesn’t just download data—it processes it into a bitmap, which adds extra CPU time that shouldn’t count toward download speed. - Inaccurate data size:
getContentLength()can return compressed file sizes if the server uses gzip, which means you’re calculating speed based on the wrong byte count. - Single small file test: Ookla uses multi-threaded downloads of large files over a sustained period (5-10 seconds) to saturate your connection. A single small bitmap download is way too volatile to reflect real bandwidth.
- Including connection setup time: Your timer starts when you create the URL, but TCP handshake and connection setup delays aren’t part of actual download speed.
Here’s how to fix it:
1. Skip Bitmap Processing & Count Actual Bytes
Download raw bytes instead of decoding them, and track exactly how many bytes you receive. Also, start timing only after the connection is established:
public class OptimizedDownloadTest extends AsyncTask<String, Void, Long> { private long startTime; private long downloadDuration; @Override protected Long doInBackground(String... urls) { HttpURLConnection connection = null; InputStream inputStream = null; long totalBytes = 0; try { URL url = new URL(urls[0]); connection = (HttpURLConnection) url.openConnection(); // Disable caching to avoid cached results skewing tests connection.setRequestProperty("Cache-Control", "no-cache, no-store, must-revalidate"); connection.setRequestProperty("Pragma", "no-cache"); connection.setRequestProperty("Expires", "0"); // Connect first, then start timing when data starts flowing connection.connect(); if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { return null; } inputStream = connection.getInputStream(); byte[] buffer = new byte[4096]; // 4KB buffer for efficient reading int bytesRead; startTime = System.currentTimeMillis(); // Run test for 5 seconds (matches Ookla's typical duration) while ((bytesRead = inputStream.read(buffer)) != -1 && System.currentTimeMillis() - startTime < 5000) { totalBytes += bytesRead; } downloadDuration = System.currentTimeMillis() - startTime; } catch (Exception e) { e.printStackTrace(); } finally { try { if (inputStream != null) inputStream.close(); if (connection != null) connection.disconnect(); } catch (Exception e) { e.printStackTrace(); } } return totalBytes; } @Override protected void onPostExecute(Long totalBytes) { if (totalBytes != null && downloadDuration > 0) { double seconds = downloadDuration / 1000.0; double speedMbps = (totalBytes * 8) / (1024 * 1024 * seconds); // Convert bytes to Mbps // Update your UI elements fileSpeed.setText(String.format("%.2f Mbps", speedMbps)); duration.setText(String.valueOf(downloadDuration) + " ms"); filesize.setText(String.valueOf(totalBytes / 1024) + " KB"); } } }
2. Add Multi-Threaded Downloads
To mimic Ookla’s behavior, run multiple download tasks in parallel to saturate your connection. Use an ExecutorService to manage threads:
ExecutorService executor = Executors.newFixedThreadPool(3); // 3 concurrent downloads executor.execute(new OptimizedDownloadTest()); executor.execute(new OptimizedDownloadTest()); executor.execute(new OptimizedDownloadTest()); executor.shutdown();
Average the results across all threads for a more accurate, stable reading.
实现上传测速
Upload testing works by sending large amounts of data to a server and timing the process. Here’s a basic implementation:
1. Create an Upload Test Task
public class UploadSpeedTest extends AsyncTask<String, Void, Long> { private long startTime; private long uploadDuration; @Override protected Long doInBackground(String... uploadUrls) { HttpURLConnection connection = null; OutputStream outputStream = null; long totalBytesSent = 0; try { // Generate a 10MB random payload (adjust size based on your needs) byte[] uploadData = new byte[10 * 1024 * 1024]; new Random().nextBytes(uploadData); URL url = new URL(uploadUrls[0]); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setRequestProperty("Content-Type", "application/octet-stream"); connection.setRequestProperty("Content-Length", String.valueOf(uploadData.length)); // Connect first, then start timing when we begin sending data connection.connect(); outputStream = connection.getOutputStream(); startTime = System.currentTimeMillis(); outputStream.write(uploadData); outputStream.flush(); uploadDuration = System.currentTimeMillis() - startTime; // Verify server received the data if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { totalBytesSent = uploadData.length; } } catch (Exception e) { e.printStackTrace(); } finally { try { if (outputStream != null) outputStream.close(); if (connection != null) connection.disconnect(); } catch (Exception e) { e.printStackTrace(); } } return totalBytesSent; } @Override protected void onPostExecute(Long totalBytesSent) { if (totalBytesSent != null && uploadDuration > 0) { double seconds = uploadDuration / 1000.0; double speedMbps = (totalBytesSent * 8) / (1024 * 1024 * seconds); // Update UI with upload speed uploadSpeed.setText(String.format("%.2f Mbps", speedMbps)); } } }
Note: You’ll need a server endpoint that accepts large POST requests. For testing, you can set up a simple Node.js/PHP server that reads and discards incoming data, or use free public test endpoints (be mindful of rate limits).
关于TrafficStats的疑问
You’re partially right—TrafficStats doesn’t give real-time speed out of the box, but you can calculate it by polling traffic counts at intervals. Here’s how to track app-specific real-time speed:
private Handler speedUpdateHandler = new Handler(); private long previousRxBytes = 0; private long previousTxBytes = 0; private Runnable speedUpdateRunnable = new Runnable() { @Override public void run() { int appUid = getApplicationInfo().uid; long currentRxBytes = TrafficStats.getUidRxBytes(appUid); long currentTxBytes = TrafficStats.getUidTxBytes(appUid); // Calculate real-time download speed (Kbps) if (previousRxBytes != 0) { long rxDelta = currentRxBytes - previousRxBytes; double downloadSpeedKbps = (rxDelta * 8) / 1024.0; realtimeDownloadSpeed.setText(String.format("%.2f Kbps", downloadSpeedKbps)); } // Calculate real-time upload speed if (previousTxBytes != 0) { long txDelta = currentTxBytes - previousTxBytes; double uploadSpeedKbps = (txDelta * 8) / 1024.0; realtimeUploadSpeed.setText(String.format("%.2f Kbps", uploadSpeedKbps)); } previousRxBytes = currentRxBytes; previousTxBytes = currentTxBytes; // Update every 1 second speedUpdateHandler.postDelayed(this, 1000); } }; // Start tracking when your test begins speedUpdateHandler.post(speedUpdateRunnable); // Stop tracking when done speedUpdateHandler.removeCallbacks(speedUpdateRunnable);
This compares traffic counts every second to calculate real-time speed. Note: Some older devices don’t support per-UID traffic tracking, so handle cases where getUidRxBytes() returns -1.
内容的提问来源于stack exchange,提问作者Kaustubh Bhatter

