凌峰创科服务平台

android 从服务器获取文件

核心概念

无论使用哪种方法,基本的网络请求流程都遵循以下步骤:

android 从服务器获取文件-图1
(图片来源网络,侵删)
  1. 添加网络权限:在 AndroidManifest.xml 中声明允许应用使用网络。
  2. 执行网络请求:在后台线程(不能是主线程/UI线程)向服务器发送请求。
  3. 处理响应:读取服务器返回的数据(文件流)。
  4. 保存文件:将读取到的数据写入到设备的存储中。
  5. 更新 UI:在主线程上通知用户操作结果(显示下载进度、成功或失败提示)。

使用 HttpURLConnection (原生 API)

这是 Java 标准库自带的类,无需添加任何第三方依赖,对于简单的下载任务,它完全够用。

添加权限

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 如果需要下载到外部存储,还需要这个权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 对于 Android 11 (API 30) 及以上,建议使用 MANAGE_EXTERNAL_STORAGE -->
<!-- 但最好遵循更现代的分区存储方案 -->

创建下载工具类

这是一个使用 HttpURLConnection 并带有进度回调的完整示例。

import android.content.Context;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class FileDownloader {
    private static final String TAG = "FileDownloader";
    // 下载文件的回调接口
    public interface DownloadCallback {
        void onProgress(int progress); // 更新进度
        void onSuccess(File file);     // 下载成功
        void onFailure(Exception e);   // 下载失败
    }
    public static void downloadFile(Context context, String fileUrl, String fileName, DownloadCallback callback) {
        // 必须在后台线程执行
        new Thread(() -> {
            InputStream inputStream = null;
            FileOutputStream outputStream = null;
            HttpURLConnection connection = null;
            try {
                URL url = new URL(fileUrl);
                connection = (HttpURLConnection) url.openConnection();
                connection.connect();
                // 检查是否是重定向
                if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                    throw new IOException("Server returned HTTP " + connection.getResponseCode()
                            + " " + connection.getResponseMessage());
                }
                // 获取文件总大小
                int fileLength = connection.getContentLength();
                // 创建目标文件
                // 注意:Android 10+ 推荐使用 context.getExternalFilesDir() 获取应用私有目录
                File outputDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
                if (outputDir == null) {
                    throw new IOException("External storage is not available");
                }
                File outputFile = new File(outputDir, fileName);
                inputStream = connection.getInputStream();
                outputStream = new FileOutputStream(outputFile);
                byte[] buffer = new byte[4096];
                int bytesRead;
                int totalBytesRead = 0;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                    totalBytesRead += bytesRead;
                    // 计算并上报进度
                    if (fileLength > 0) { // 避免除以零
                        int progress = (int) ((totalBytesRead * 100) / fileLength);
                        // 切换到主线程更新UI
                        ((MainActivity) context).runOnUiThread(() -> callback.onProgress(progress));
                    }
                }
                // 下载完成
                ((MainActivity) context).runOnUiThread(() -> callback.onSuccess(outputFile));
            } catch (Exception e) {
                // 发生错误
                ((MainActivity) context).runOnUiThread(() -> callback.onFailure(e));
            } finally {
                // 确保流和连接被关闭
                if (outputStream != null) {
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        Log.e(TAG, "Error closing output stream", e);
                    }
                }
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        Log.e(TAG, "Error closing input stream", e);
                    }
                }
                if (connection != null) {
                    connection.disconnect();
                }
            }
        }).start();
    }
}

在 Activity 中使用

// 假设你的 Activity 是 MainActivity
public class MainActivity extends AppCompatActivity {
    private static final String FILE_URL = "http://example.com/path/to/your/file.pdf";
    private static final String FILE_NAME = "downloaded_file.pdf";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button downloadButton = findViewById(R.id.download_button);
        ProgressBar progressBar = findViewById(R.id.progress_bar);
        TextView statusText = findViewById(R.id.status_text);
        downloadButton.setOnClickListener(v -> {
            downloadButton.setEnabled(false);
            progressBar.setProgress(0);
            statusText.setText("下载中...");
            FileDownloader.downloadFile(this, FILE_URL, FILE_NAME, new FileDownloader.DownloadCallback() {
                @Override
                public void onProgress(int progress) {
                    progressBar.setProgress(progress);
                    statusText.setText("下载中... " + progress + "%");
                }
                @Override
                public void onSuccess(File file) {
                    runOnUiThread(() -> {
                        statusText.setText("下载完成: " + file.getAbsolutePath());
                        downloadButton.setEnabled(true);
                        // 可以在这里打开文件,例如使用 Intent
                        openFile(file);
                    });
                }
                @Override
                public void onFailure(Exception e) {
                    runOnUiThread(() -> {
                        statusText.setText("下载失败: " + e.getMessage());
                        downloadButton.setEnabled(true);
                        Log.e("MainActivity", "Download failed", e);
                    });
                }
            });
        });
    }
    private void openFile(File file) {
        Intent intent = new Intent(Intent.ACTION_VIEW);
        Uri uri = FileProvider.getUriForFile(this, "com.your.package.name.fileprovider", file);
        intent.setDataAndType(uri, "application/pdf"); // 根据文件类型设置 MimeType
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        try {
            startActivity(intent);
        } catch (Exception e) {
            Toast.makeText(this, "没有找到可以打开此文件的应用", Toast.LENGTH_SHORT).show();
        }
    }
}

使用 OkHttp (强烈推荐)

OkHttp 是目前 Android 开发中最流行的网络请求库,它更高效、功能更强大,并且能更好地处理现代网络(如 HTTP/2)。

添加依赖

app/build.gradle 文件中添加:

android 从服务器获取文件-图2
(图片来源网络,侵删)
dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.12.0") // 使用最新版本
}

使用 OkHttp 下载文件

OkHttp 的实现方式更简洁,通常结合 ResponseBodybyteStream() 来高效地处理大文件流。

import android.content.Context;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class OkHttpFileDownloader {
    private static final String TAG = "OkHttpDownloader";
    public interface DownloadCallback {
        void onProgress(int progress);
        void onSuccess(File file);
        void onFailure(Exception e);
    }
    public static void downloadFile(Context context, String fileUrl, String fileName, DownloadCallback callback) {
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url(fileUrl).build();
        client.newCall(request).enqueue(new okhttp3.Callback() { // enqueue 使其在后台线程自动执行
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {
                // 在失败回调中,切换到主线程更新UI
                ((MainActivity) context).runOnUiThread(() -> callback.onFailure(e));
            }
            @Override
            public void onResponse(okhttp3.Call call, Response response) throws IOException {
                if (!response.isSuccessful()) {
                    ((MainActivity) context).runOnUiThread(() -> callback.onFailure(new IOException("Unexpected code " + response)));
                    return;
                }
                ResponseBody body = response.body();
                if (body == null) {
                    ((MainActivity) context).runOnUiThread(() -> callback.onFailure(new IOException("Response body is null")));
                    return;
                }
                long contentLength = body.contentLength();
                InputStream inputStream = body.byteStream();
                File outputDir = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
                File outputFile = new File(outputDir, fileName);
                FileOutputStream outputStream = new FileOutputStream(outputFile);
                byte[] buffer = new byte[4096];
                int bytesRead;
                long totalBytesRead = 0;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                    totalBytesRead += bytesRead;
                    if (contentLength > 0) {
                        int progress = (int) ((totalBytesRead * 100) / contentLength);
                        ((MainActivity) context).runOnUiThread(() -> callback.onProgress(progress));
                    }
                }
                outputStream.close();
                inputStream.close();
                // 下载成功,切换到主线程
                ((MainActivity) context).runOnUiThread(() -> callback.onSuccess(outputFile));
            }
        });
    }
}

使用方式和 HttpURLConnection 版本几乎一样,只需替换工具类即可。


使用 Android DownloadManager (系统服务)

对于下载任务,特别是大文件,Android 系统提供了专门的 DownloadManager,它是一个系统服务,可以处理下载、断点续传、通知显示等,非常强大且省电。

添加权限

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 需要一个接收下载完成的广播接收器 -->
<application ...>
    <receiver
        android:name=".DownloadCompleteReceiver"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
        </intent-filter>
    </receiver>
</application>

启动下载

import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
public class SystemDownloader {
    public static void startDownload(Context context, String fileUrl, String fileName) {
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(fileUrl));
        // 设置下载的保存位置
        // 使用外部公共目录,文件可以被其他应用访问
        request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName);
        // 可选:设置网络类型为Wi-Fi
        // request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
        // 可选:显示下载通知
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        // 获取DownloadManager服务并 enqueue 下载任务
        DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
        if (downloadManager != null) {
            downloadManager.enqueue(request);
        }
    }
}

创建广播接收器来处理下载完成事件

import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class DownloadCompleteReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        if (downloadId != -1) {
            DownloadManager.Query query = new DownloadManager.Query();
            query.setFilterById(downloadId);
            DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
            if (downloadManager != null) {
                android.database.Cursor cursor = downloadManager.query(query);
                if (cursor.moveToFirst()) {
                    int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
                    if (status == DownloadManager.STATUS_SUCCESSFUL) {
                        String uriString = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
                        Toast.makeText(context, "下载完成: " + uriString, Toast.LENGTH_LONG).show();
                    } else {
                        Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
                    }
                }
                cursor.close();
            }
        }
    }
}

总结与对比

特性 HttpURLConnection OkHttp DownloadManager
依赖 无需 (Java标准库) 需添加 okhttp 无需 (Android系统服务)
灵活性 高,可精细控制每个请求 非常高,支持拦截器、异步/同步 低,功能固定
大文件处理 需手动管理流和线程 非常优秀,专为流式传输设计 非常优秀,系统级优化,支持断点续传
用户体验 需手动实现进度条和通知 需手动实现进度条和通知 自动,提供系统通知栏进度
后台任务 需手动使用 ThreadAsyncTask enqueue() 自动处理 系统服务,独立于App生命周期
适用场景 简单、小文件下载;不想引入第三方库 推荐,大多数网络请求场景,包括大文件下载 大文件下载;需要系统通知和断点续传;希望降低App电量消耗

如何选择?

android 从服务器获取文件-图3
(图片来源网络,侵删)
  • 新手或简单任务:使用 HttpURLConnection 足够,能帮助你理解网络请求的基本原理。
  • 生产环境开发强烈推荐使用 OkHttp,它是行业标准,功能强大、性能优异、社区活跃。
  • 下载大型文件优先考虑 DownloadManager,它能提供最佳的用户体验(系统通知)和系统级的稳定性、省电优化,如果还需要在下载过程中显示自定义进度条,则可以结合 OkHttp 或 HttpURLConnection 使用。
分享:
扫描分享到社交APP
上一篇
下一篇