Android中检测未完成下载的music.mp3并重新下载的技术问询
解决Android下载文件完整性检测与重新下载问题
我来帮你搞定这个下载后文件不完整的问题~你的现有代码只判断了文件是否存在,没检查它是否和服务器上的原始文件一致。下面我会给你调整代码,实现自动检测文件完整性+不完整时重新下载的功能。
核心思路
判断文件完整最直接的方式就是对比本地文件大小和服务器上的原始文件大小:
- 下载前先获取服务器目标文件的大小(用HEAD请求,只拿响应头不下载内容,省流量)
- 对比本地文件(如果存在)的大小与服务器大小
- 若大小不一致(或本地文件不存在),删除不完整文件并重新下载
修改后的完整代码示例
首先添加工具方法,用来获取服务器文件大小和检查本地文件完整性,同时用SharedPreferences缓存服务器文件大小(避免重复请求服务器):
// 缓存服务器文件大小的SharedPreferences配置 private static final String PREF_NAME = "DownloadCache"; private static final String PREF_KEY_MUSIC_LENGTH = "music_mp3_length"; // 获取服务器文件大小(仅请求响应头,不下载文件内容) private long getServerFileSize(String urlStr) { HttpURLConnection connection = null; try { URL url = new URL(urlStr.trim()); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("HEAD"); connection.setConnectTimeout(5000); connection.setReadTimeout(5000); connection.connect(); if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { // 注意:如果服务器返回gzip压缩内容,这里的长度是压缩后的大小,需要额外处理 return connection.getContentLength(); } } catch (IOException e) { e.printStackTrace(); } finally { if (connection != null) { connection.disconnect(); } } return -1; } // 检查本地文件是否完整 private boolean isLocalFileComplete(File localFile) { if (!localFile.exists() || !localFile.isFile()) { return false; } // 先从缓存读取服务器文件大小 SharedPreferences sp = getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); long serverFileSize = sp.getLong(PREF_KEY_MUSIC_LENGTH, -1); // 缓存没有的话,请求服务器获取并缓存 if (serverFileSize == -1) { serverFileSize = getServerFileSize("http://dl.music.ml/1398/03/16/music.mp3"); if (serverFileSize != -1) { sp.edit().putLong(PREF_KEY_MUSIC_LENGTH, serverFileSize).apply(); } else { // 无法获取服务器大小,默认认为文件不完整 return false; } } // 对比本地文件和服务器文件大小 return localFile.length() == serverFileSize; }
然后修改你的下载触发逻辑:
String musicFilePath = android.os.Environment.getExternalStorageDirectory() + "/folderName/music.mp3"; File musicFile = new File(musicFilePath); // 检查文件是否完整 if (!isLocalFileComplete(musicFile)) { // 删除不完整的旧文件(如果存在) if (musicFile.exists()) { musicFile.delete(); } // 初始化进度对话框 mProgressDialog = new ProgressDialog(TV_LG.this); mProgressDialog.setMessage("downloading..."); mProgressDialog.setIndeterminate(false); mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); mProgressDialog.setCancelable(true); // 启动下载任务 final DownloadTask downloadTask = new DownloadTask(TV_LG.this); downloadTask.execute("http://dl.music.ml/1398/03/16/music.mp3"); mProgressDialog.setOnCancelListener(dialog -> downloadTask.cancel(true)); } else { // 文件已经完整,无需下载 Toast.makeText(this, "文件已存在且完整", Toast.LENGTH_SHORT).show(); }
最后调整DownloadTask,在下载完成后自动缓存服务器文件大小(如果之前没缓存):
private class DownloadTask extends AsyncTask<String, Integer, String> { private Context context; public DownloadTask(Context context) { this.context = context; } @Override protected String doInBackground(String... sUrl) { PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName()); wl.acquire(); try { InputStream input = null; OutputStream output = null; HttpURLConnection connection = null; int serverFileLength = -1; try { URL url = new URL(sUrl[0].trim()); connection = (HttpURLConnection) url.openConnection(); connection.connect(); if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { return "Server returned HTTP " + connection.getResponseCode() + " " + connection.getResponseMessage(); } serverFileLength = connection.getContentLength(); input = connection.getInputStream(); // 确保存储目录存在 File dir = new File(android.os.Environment.getExternalStorageDirectory() + "/folderName"); if (!dir.exists()) { dir.mkdirs(); } output = new FileOutputStream(musicFilePath); byte data[] = new byte[4096]; long totalDownloaded = 0; int count; while ((count = input.read(data)) != -1) { if (isCancelled()) { return null; } totalDownloaded += count; if (serverFileLength > 0) { publishProgress((int) (totalDownloaded * 100 / serverFileLength)); } output.write(data, 0, count); } // 下载完成后缓存服务器文件大小(如果之前没缓存) if (serverFileLength > 0) { SharedPreferences sp = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); if (sp.getLong(PREF_KEY_MUSIC_LENGTH, -1) == -1) { sp.edit().putLong(PREF_KEY_MUSIC_LENGTH, serverFileLength).apply(); } } } catch (Exception e) { return e.toString(); } finally { try { if (output != null) output.close(); if (input != null) input.close(); } catch (IOException ignored) {} if (connection != null) connection.disconnect(); } } finally { wl.release(); } return null; } @Override protected void onProgressUpdate(Integer... progress) { super.onProgressUpdate(progress); mProgressDialog.setIndeterminate(false); mProgressDialog.setMax(100); mProgressDialog.setProgress(progress[0]); } @Override protected void onPostExecute(String result) { mProgressDialog.dismiss(); if (result != null) { Toast.makeText(context, "下载失败: " + result, Toast.LENGTH_LONG).show(); } else { Toast.makeText(context, "下载完成", Toast.LENGTH_SHORT).show(); } } }
额外注意事项
- 如果服务器返回的是gzip压缩内容,
getContentLength()会返回压缩后的大小,这时候本地文件大小(解压后)会和服务器长度不一致。这种情况可以在请求头中添加Accept-Encoding: identity,告诉服务器不要压缩内容。 - 别忘了在
AndroidManifest.xml添加必要权限:<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Android 10+ 需要添加这个权限 --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
内容的提问来源于stack exchange,提问作者android remote control
相关产品推荐
相关产品推荐

