凌峰创科服务平台

Android如何从服务器高效下载图片?

  1. 基础方案:使用 HttpURLConnectionAsyncTask(适用于较旧的 Android 版本,有助于理解原理)。
  2. 现代方案:使用现代库(如 Kotlin Coroutines + Retrofit + Glide),这是目前 Android 开发的主流和推荐方式。
  3. 权限:讨论网络权限。
  4. 错误处理与最佳实践:包括线程安全、内存优化、错误处理等。

基础实现(HttpURLConnection + AsyncTask)

这种方式不依赖第三方库,适合理解底层原理,但在新项目中不推荐。

Android如何从服务器高效下载图片?-图1
(图片来源网络,侵删)

添加网络权限

AndroidManifest.xml 文件中,必须添加访问网络的权限:

<manifest ...>
    <!-- 允许应用访问网络 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- Android 9 (API 28) 及以上默认禁止 HTTP,允许 HTTPS -->
    <application
        android:usesCleartextTraffic="true"
        ...>
        ...
    </application>
</manifest>

创建下载任务

AsyncTask 可以在后台线程执行网络请求,并在完成后在主线程更新 UI。

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.widget.ImageView;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
    // 用于显示下载的图片的 ImageView
    private final ImageView imageView;
    public DownloadImageTask(ImageView imageView) {
        this.imageView = imageView;
    }
    // 在后台线程执行,执行耗时操作
    @Override
    protected Bitmap doInBackground(String... urls) {
        String imageUrl = urls[0];
        Bitmap bitmap = null;
        try {
            URL url = new URL(imageUrl);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setDoInput(true);
            connection.connect();
            InputStream inputStream = connection.getInputStream();
            bitmap = BitmapFactory.decodeStream(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap; // 返回结果给 onPostExecute
    }
    // 在主线程执行,用于更新 UI
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageView != null && bitmap != null) {
            imageView.setImageBitmap(bitmap);
        }
    }
}

在 Activity 或 Fragment 中使用

ImageView myImageView = findViewById(R.id.my_image_view);
String imageUrl = "https://example.com/path/to/your/image.jpg";
// 创建并执行 AsyncTask
new DownloadImageTask(myImageView).execute(imageUrl);

缺点

  • AsyncTask 在 Android 11 (API 30) 中已被标记为过时。
  • 处理并发、取消任务等场景比较复杂。
  • 容易发生内存泄漏,Activity 在任务完成前销毁。

现代实现(Kotlin Coroutines + Retrofit + Glide)

这是目前 Android 开发最推荐、最简洁、最强大的方式,我们将使用 Kotlin 来演示。

Android如何从服务器高效下载图片?-图2
(图片来源网络,侵删)

添加依赖

app/build.gradle.kts (或 build.gradle) 文件中添加必要的库:

// 对于 Kotlin 项目
dependencies {
    // Kotlin Coroutines (用于异步操作)
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
    // Retrofit (用于进行网络请求)
    implementation("com.squareup.retrofit2:retrofit:2.9.0")
    implementation("com.squareup.retrofit2:converter-gson:2.9.0") // 如果你还需要解析 JSON
    // Glide (用于加载和显示图片,支持缓存、生命周期管理等)
    implementation("com.github.bumptech.glide:glide:4.16.0")
    kapt("com.github.bumptech.glide:compiler:4.16.0") // 用于注解处理器
}

添加网络权限

同方案一,确保 AndroidManifest.xml 中有 INTERNET 权限。

使用 Glide 直接加载(最简单的方式)

Glide 内部已经处理了所有复杂的操作:后台下载、缓存、线程管理、生命周期绑定等,你只需要一行代码。

ActivityFragment 中:

Android如何从服务器高效下载图片?-图3
(图片来源网络,侵删)
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val imageUrl = "https://example.com/path/to/your/image.jpg"
        // 使用 Glide 加载图片
        Glide.with(this) // 上下文
            .load(imageUrl) // 图片 URL
            .into(myImageView) // 目标 ImageView
    }
}

就这么简单! Glide 会自动:

  • 在后台线程下载图片。
  • 将图片显示在 myImageView 上。
  • 处理图片的缓存(内存和磁盘),避免重复下载。
  • ActivityFragment 销毁时,自动取消加载任务,防止内存泄漏。

使用 Retrofit + Coroutines 获取图片 URL

如果你的图片 URL 是从 API 的 JSON 响应中获取的,那么使用 Retrofit 来获取这个 URL,然后再用 Glide 显示。

a. 定义数据模型

// 假设 API 返回的 JSON 是这样的:
// { "imageUrl": "https://example.com/image.jpg", "title": "My Image" }
data class ImageResponse(
    val imageUrl: String,
    val title: String
)

b. 创建 Retrofit API 接口

import retrofit2.http.GET
interface ApiService {
    @GET("api/get_image_info") // 你的 API 端点
    suspend fun getImageInfo(): ImageResponse // 使用 suspend 关键字
}

c. 在 ViewModel 中调用

ViewModel 是管理 UI 数据和业务逻辑的最佳场所。

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class MyViewModel : ViewModel() {
    private val apiService: ApiService by lazy {
        // 创建 Retrofit 实例
        Retrofit.Builder()
            .baseUrl("https://your-api-base-url.com/") // 你的 API 基础 URL
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
    fun fetchAndDisplayImage(imageView: ImageView) {
        viewModelScope.launch { // viewModelScope 会自动在 ViewModel 销毁时取消协程
            try {
                val imageResponse = apiService.getImageInfo()
                // 获取到 URL 后,用 Glide 显示
                Glide.with(imageView.context)
                    .load(imageResponse.imageUrl)
                    .into(imageView)
            } catch (e: Exception) {
                // 处理网络错误或解析错误
                e.printStackTrace()
                // 可以在这里更新 UI 显示错误信息
            }
        }
    }
}

d. 在 Activity/Fragment 中使用 ViewModel

class MainActivity : AppCompatActivity() {
    private lateinit var myViewModel: MyViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        myViewModel = ViewModelProvider(this).get(MyViewModel::class.java)
        myViewModel.fetchAndDisplayImage(myImageView)
    }
}

核心概念与最佳实践

权限(Permissions)

  • INTERNET:从 Android 9 (API 28) 开始,默认禁止使用 HTTP 协议,只允许 HTTPS,如果你的服务器使用 HTTP,需要在 application 标签中添加 android:usesCleartextTraffic="true"
  • ACCESS_NETWORK_STATE:可选,但推荐,用于检查网络连接状态,避免在没有网络时发起请求。

内存优化

  • 图片大小:直接下载原图可能会导致内存占用过高,特别是对于大图或高分辨率屏幕,最佳实践是让服务器提供不同尺寸的图片(通过 URL 参数指定宽度/高度,如 .../image.jpg?width=300)。
  • Glide 的配置:Glide 默认会进行内存缓存和磁盘缓存,大大提高了性能和减少了流量消耗,你可以根据需要配置其缓存策略。

错误处理

  • 网络错误:捕获 IOExceptionHttpException
  • 空指针:确保在请求完成前,ImageView 或其他 UI 组件没有被销毁。
  • 用户反馈:当下载失败时,向用户显示错误信息(如 Toast、Snackbar)或一个默认的占位图。

生命周期管理

  • 现代库的优势:Retrofit + Coroutines + Glide 的组合天然地与 Android 的生命周期绑定,当 ActivityFragment 进入 Destroy 状态时,协程和 Glide 的加载任务会自动取消,不会导致内存泄漏,这是使用现代方案的最大优势之一。
特性 基础方案 (HttpURLConnection) 现代方案 (Retrofit + Glide)
依赖 无需第三方库 需要添加 Retrofit, Glide, Coroutines 库
代码量 较多,需要手动管理线程 极少,代码简洁
线程管理 手动使用 AsyncTaskHandler 自动,由 Coroutines 和 Glide 处理
生命周期 容易内存泄漏,需手动处理 自动绑定,安全可靠
功能 仅能下载,无缓存等功能 内置缓存、转换、生命周期管理等强大功能
推荐度 仅用于学习原理,不用于新项目 强烈推荐,是当前 Android 开发的行业标准

对于任何新的 Android 项目,强烈建议你采用 方案二 的现代技术栈,它能让你更专注于业务逻辑,而不是处理底层的、繁琐的技术细节。

分享:
扫描分享到社交APP
上一篇
下一篇