凌峰创科服务平台

Android如何高效获取服务器文件?

核心概念:网络请求

无论使用哪种方法,其本质都是通过 HTTP/HTTPS 协议向服务器发送一个请求,然后接收服务器返回的文件数据,在 Android 中,网络操作不能在主线程(UI线程)中执行,否则会抛出 NetworkOnMainThreadException 异常,我们必须使用异步任务或线程来处理网络请求。

Android如何高效获取服务器文件?-图1
(图片来源网络,侵删)

使用 HttpURLConnection (Java 原生方式)

这是 Android SDK 自带的方式,无需添加第三方依赖,适合处理简单的下载任务,但对于复杂的网络交互,代码会显得比较繁琐。

适用场景:

  • 简单的文件下载。
  • 不想引入第三方库的项目。

实现步骤:

  1. 获取网络权限:在 AndroidManifest.xml 中添加。
  2. 在后台线程中发起请求:可以使用 AsyncTask(已废弃,但适合理解)、Thread + Handler,或者现代的 ExecutorService
  3. 处理输入流和输出流:将服务器返回的文件流写入到本地文件。

代码示例 (使用 ExecutorServiceFileOutputStream):

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FileDownloader {
    private static final String FILE_URL = "http://example.com/path/to/your/file.pdf";
    private static final String FILE_NAME = "downloaded_file.pdf";
    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static final String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };
    private ExecutorService executor = Executors.newSingleThreadExecutor();
    public void downloadFile() {
        // 检查权限
        if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            // 请求权限
            ActivityCompat.requestPermissions(thisActivity, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
            return;
        }
        executor.execute(() -> {
            HttpURLConnection connection = null;
            InputStream inputStream = null;
            FileOutputStream outputStream = null;
            File file = null;
            try {
                URL url = new URL(FILE_URL);
                connection = (HttpURLConnection) url.openConnection();
                connection.connect();
                // 检查HTTP响应码
                if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                    // 获取外部存储的公共下载目录
                    File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
                    if (!directory.exists()) {
                        directory.mkdirs();
                    }
                    file = new File(directory, FILE_NAME);
                    inputStream = connection.getInputStream();
                    outputStream = new FileOutputStream(file);
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    while ((bytesRead = inputStream.read(buffer)) != -1) {
                        outputStream.write(buffer, 0, bytesRead);
                    }
                    // 下载完成,可以在主线程更新UI
                    runOnUiThread(() -> {
                        Toast.makeText(getApplicationContext(), "文件下载成功: " + file.getAbsolutePath(), Toast.LENGTH_LONG).show();
                    });
                } else {
                    Log.e("FileDownload", "Server returned HTTP " + connection.getResponseCode() + " " + connection.getResponseMessage());
                }
            } catch (IOException e) {
                Log.e("FileDownload", "Error downloading file", e);
            } finally {
                // 关闭流和连接
                if (outputStream != null) {
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (connection != null) {
                    connection.disconnect();
                }
            }
        });
    }
}

使用 OkHttp (强烈推荐)

OkHttp 是目前 Android 开发中最流行的网络请求库,它更高效、更简洁,并且内置了对现代网络协议(如 HTTP/2)的支持,处理文件下载非常方便。

适用场景:

  • 几乎所有需要网络请求的 Android 应用。
  • 需要处理复杂的网络逻辑(如缓存、拦截器)。

实现步骤:

  1. 添加依赖:在 app/build.gradle 文件中添加。
  2. 获取网络权限:同上。
  3. 创建 OkHttp 客户端
  4. 构建请求并执行
  5. 使用 ResponseBody 将字节流写入文件

代码示例:

添加依赖 (build.gradle):

dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.12.0") // 使用最新版本
}

下载代码:

Android如何高效获取服务器文件?-图2
(图片来源网络,侵删)
import android.Manifest;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
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;
public class OkHttpFileDownloader {
    private static final String FILE_URL = "http://example.com/path/to/your/image.jpg";
    private static final String FILE_NAME = "downloaded_image.jpg";
    private OkHttpClient client = new OkHttpClient();
    public void downloadFile() {
        // 检查权限 (同上,省略...)
        if (ContextCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            // 请求权限
            return;
        }
        Request request = new Request.Builder()
                .url(FILE_URL)
                .build();
        client.newCall(request).enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {
                // 在主线程显示错误信息
                runOnUiThread(() -> {
                    Toast.makeText(getApplicationContext(), "下载失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
                });
            }
            @Override
            public void onResponse(okhttp3.Call call, Response response) throws IOException {
                if (!response.isSuccessful()) {
                    throw new IOException("Unexpected code " + response);
                }
                // 获取响应体
                InputStream inputStream = response.body().byteStream();
                File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
                File file = new File(directory, FILE_NAME);
                FileOutputStream outputStream = new FileOutputStream(file);
                byte[] buffer = new new byte[4096];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, bytesRead);
                }
                outputStream.close();
                inputStream.close();
                // 在主线程显示成功信息
                runOnUiThread(() -> {
                    Toast.makeText(getApplicationContext(), "文件下载成功: " + file.getAbsolutePath(), Toast.LENGTH_LONG).show();
                });
            }
        });
    }
}

优点

  • 代码更简洁、易读。
  • enqueue 方法内部已经处理了线程切换,回调在主线程执行。
  • 功能强大,支持拦截器、缓存等。

使用 Retrofit (RESTful API 的首选)

如果你的服务器提供的是 RESTful API,并且你下载的文件是 API 响应的一部分(通过一个 GET /files/{id} 接口获取),Retrofit 是最佳选择。

适用场景:

  • 下载与 API 请求紧密相关的文件。
  • 项目中已经大量使用 Retrofit 进行数据交互。

实现步骤:

  1. 添加依赖retrofitconverter-gson (或其他)。
  2. 定义 API 接口
  3. 创建 Retrofit 实例
  4. 调用接口方法并处理响应

代码示例:

添加依赖 (build.gradle):

dependencies {
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0") // 如果你需要解析JSON
}

定义 API 接口和 Service:

Android如何高效获取服务器文件?-图3
(图片来源网络,侵删)
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
public interface FileApiService {
    @GET("files/{file_id}")
    Call<ResponseBody> downloadFile(@Path("file_id") String fileId);
}

执行下载:

import okhttp3.ResponseBody;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitFileDownloader {
    public void downloadFile(String fileId) {
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://example.com/api/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        FileApiService service = retrofit.create(FileApiService.class);
        Call<ResponseBody> call = service.downloadFile(fileId);
        call.enqueue(new retrofit2.Callback<ResponseBody>() {
            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                // 处理失败
            }
            @Override
            public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
                if (response.isSuccessful()) {
                    ResponseBody body = response.body();
                    if (body != null) {
                        saveFile(body, "downloaded_from_api.pdf");
                    }
                } else {
                    // 处理非 2xx 响应
                }
            }
        });
    }
    private void saveFile(ResponseBody body, String fileName) {
        // 保存文件的逻辑与 OkHttp 中的完全相同
        // ...
    }
}

优点

  • 与 RESTful API 完美集成,代码结构清晰。
  • 类型安全,编译时检查。
  • 同样支持异步和同步。

进阶:使用 DownloadManager (系统下载服务)

对于大文件下载,或者希望利用系统自带的下载管理器(如显示在通知栏、支持断点续传、可以被用户管理),可以使用 DownloadManager

适用场景:

  • 下载大文件。
  • 希望下载任务在后台持久化,即使应用关闭也能继续。
  • 需要标准的下载进度和通知。

实现步骤:

  1. 获取网络权限
  2. 创建 DownloadManager.Request 对象,设置下载 URL 和保存路径。
  3. 获取 DownloadManager 系统服务
  4. 调用 enqueue() 方法开始下载,会返回一个下载ID。
  5. 通过 DownloadManager.Query 查询下载状态

代码示例:

import android.app.DownloadManager;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
public class DownloadManagerActivity extends AppCompatActivity {
    private DownloadManager downloadManager;
    private long downloadId;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ... 初始化UI ...
        downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
        startDownload();
    }
    private void startDownload() {
        Uri uri = Uri.parse("http://example.com/path/to/your/large_file.zip");
        DownloadManager.Request request = new DownloadManager.Request(uri);
        // 设置允许在移动网络下下载
        request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI);
        // 设置通知的标题和描述
        request.setTitle("大文件下载");
        request.setDescription("正在下载一个大文件...");
        // 设置保存路径和文件名
        request.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, "large_file.zip");
        // 开始下载,获取唯一的下载ID
        downloadId = downloadManager.enqueue(request);
    }
    // 你可以通过 BroadcastReceiver 来监听下载完成事件
    // 或者通过 query() 方法主动查询状态
}

总结与对比

方法 优点 缺点 适用场景
HttpURLConnection 无需依赖,Java标准库 代码繁琐,功能有限,线程处理麻烦 简单任务,不想引入第三方库
OkHttp 强烈推荐,高效、简洁、功能强大 需要引入第三方库 绝大多数网络请求场景,特别是文件下载
Retrofit 与API完美集成,类型安全,代码优雅 主要用于RESTful API,不直接用于普通URL下载 下载作为API一部分的项目
DownloadManager 系统级,支持断点续传、后台持久化、标准通知 只能下载HTTP/HTTPS URL,控制粒度较粗 大文件下载,需要系统级下载体验

给你的建议:

  • 如果是新项目任何非简单的下载需求,直接使用 OkHttpRetrofit,它们是现代 Android 开发的标准。
  • 如果是下载非常大的文件,并且希望有系统级别的下载管理体验,使用 DownloadManager
  • 除非有特殊要求,否则尽量避免使用原生且过时的 HttpURLConnection
分享:
扫描分享到社交APP
上一篇
下一篇