Android Picasso 网络请求模块深度剖析(四)

Android Picasso 网络请求模块深度剖析

本人掘金号,欢迎点击关注:掘金号地址

本人公众号,欢迎点击关注:公众号地址

一、引言

在 Android 开发领域,图片加载是极为常见的功能需求。用户在浏览各类应用时,会频繁地遇到图片展示的场景,如社交媒体应用中的用户头像、商品图片,新闻资讯应用中的配图等。高效、稳定地加载图片对于提升应用的用户体验至关重要。Picasso 作为一款知名的 Android 图片加载库,凭借其简洁的 API 和出色的性能,在开发者群体中广泛应用。其中,网络请求模块是 Picasso 实现图片从网络加载到本地展示的核心部分,它负责与远程服务器进行通信,获取图片资源。深入剖析 Picasso 的网络请求模块,有助于开发者更好地理解其工作原理,进而优化应用的图片加载功能,提升应用的整体性能。

二、Picasso 网络请求模块概述

2.1 模块功能概述

Picasso 的网络请求模块主要承担从网络服务器获取图片数据的任务。当开发者调用 Picasso 的 API 加载一张网络图片时,该模块会根据图片的 URL 发起网络请求,与服务器建立连接,获取图片的二进制数据,并将其传递给后续的解码和显示模块。同时,该模块还具备缓存处理、错误处理等功能,以提高图片加载的效率和稳定性。

2.2 主要类和接口介绍

在 Picasso 的网络请求模块中,涉及多个关键的类和接口,下面对它们进行简要介绍:

  • Downloader 接口:这是网络请求的核心接口,定义了下载图片的基本方法。任何实现该接口的类都可以作为 Picasso 的网络请求器,负责从网络或其他数据源下载图片。
  • OkHttp3Downloader 类 :Downloader 接口的具体实现类,使用 OkHttp3 库进行网络请求。OkHttp3 是一个高效的 HTTP 客户端库,具有连接池管理、缓存支持、拦截器等特性,能够提升网络请求的性能和稳定性。
  • NetworkRequestHandler 类 :负责处理网络请求的逻辑,它会调用 Downloader 进行实际的下载操作。当 Picasso 接收到一个网络图片请求时,会将请求传递给 NetworkRequestHandler,由它来决定是否可以处理该请求,并执行下载流程。
  • Response 类:表示网络请求的响应结果,包含了图片数据和相关的元信息,如是否来自缓存、响应的状态码等。

三、Downloader 接口分析

3.1 接口定义

java 复制代码
// Downloader.java
package com.squareup.picasso;

import java.io.IOException;

/**
 * 定义了下载图片的基本方法,是网络请求的核心接口。
 */
public interface Downloader {
    /**
     * 从指定的 URI 下载图片,并返回响应结果。
     * @param uri 图片的 URI
     * @param networkPolicy 网络策略,用于控制缓存和请求行为
     * @return 网络请求的响应结果
     * @throws IOException 如果下载过程中出现 I/O 错误
     */
    Response load(Uri uri, int networkPolicy) throws IOException;

    /**
     * 关闭下载器,释放相关资源。
     */
    void shutdown();
}

3.2 接口方法详解

3.2.1 load 方法
  • 参数 :
    • uri:表示要下载图片的统一资源标识符,通常是一个网络 URL。
    • networkPolicy:网络策略参数,用于控制请求的缓存行为。例如,可以设置为不使用缓存、仅从缓存获取等。
  • 返回值 :返回一个 Response 对象,包含了下载的图片数据和相关元信息。
  • 异常处理 :如果在下载过程中出现 I/O 错误,会抛出 IOException 异常。
3.2.2 shutdown 方法

该方法用于关闭下载器,释放相关的资源,如网络连接、线程池等。在应用退出或不再需要进行网络请求时,调用该方法可以避免资源泄漏。

3.3 接口的作用

Downloader 接口为 Picasso 的网络请求提供了统一的抽象,使得 Picasso 可以使用不同的网络请求库来实现图片的下载。开发者可以根据自己的需求,实现该接口并使用自定义的下载器,或者选择使用 Picasso 提供的默认实现,如 OkHttp3Downloader。

四、OkHttp3Downloader 类分析

4.1 类的初始化

java 复制代码
// OkHttp3Downloader.java
package com.squareup.picasso;

import android.content.Context;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import java.io.File;
import java.io.IOException;

/**
 * 使用 OkHttp3 库实现的 Downloader 接口。
 */
public class OkHttp3Downloader implements Downloader {
    private final OkHttpClient client;
    private final Cache cache;

    /**
     * 构造函数,使用默认的 OkHttpClient 实例。
     * @param context 应用的上下文对象
     */
    public OkHttp3Downloader(final Context context) {
        // 获取应用的缓存目录
        this(Utils.createDefaultCacheDir(context));
    }

    /**
     * 构造函数,指定缓存目录。
     * @param cacheDir 缓存目录
     */
    public OkHttp3Downloader(final File cacheDir) {
        // 创建默认的缓存大小为 50MB
        this(cacheDir, Utils.calculateDiskCacheSize(cacheDir));
    }

    /**
     * 构造函数,指定缓存目录和缓存大小。
     * @param cacheDir 缓存目录
     * @param maxSize 缓存的最大大小
     */
    public OkHttp3Downloader(final File cacheDir, final long maxSize) {
        // 创建 OkHttp 的缓存对象
        this.cache = new Cache(cacheDir, maxSize);
        // 创建 OkHttp 客户端实例,并设置缓存
        this.client = new OkHttpClient.Builder().cache(cache).build();
    }

    /**
     * 构造函数,使用自定义的 OkHttpClient 实例。
     * @param client 自定义的 OkHttpClient 实例
     */
    public OkHttp3Downloader(OkHttpClient client) {
        this.client = client;
        this.cache = client.cache();
    }
}
4.1.1 构造函数详解
  • OkHttp3Downloader(Context context) :该构造函数接收一个 Context 对象,通过 Utils.createDefaultCacheDir(context) 方法获取应用的默认缓存目录,然后调用另一个构造函数进行初始化。
  • OkHttp3Downloader(File cacheDir) :该构造函数接收一个 File 对象,表示缓存目录。它会调用 Utils.calculateDiskCacheSize(cacheDir) 方法计算默认的缓存大小(通常为 50MB),然后创建 Cache 对象和 OkHttpClient 实例。
  • OkHttp3Downloader(File cacheDir, long maxSize) :该构造函数接收缓存目录和最大缓存大小作为参数,创建 Cache 对象并将其设置到 OkHttpClient 中。
  • OkHttp3Downloader(OkHttpClient client) :该构造函数接收一个自定义的 OkHttpClient 实例,直接使用该实例进行网络请求,并获取其缓存对象。

4.2 load 方法实现

java 复制代码
// OkHttp3Downloader.java
@Override
public Response load(Uri uri, int networkPolicy) throws IOException {
    // 创建 OkHttp 的请求构建器
    Request.Builder requestBuilder = new Request.Builder().url(uri.toString());

    // 根据网络策略设置请求头
    if (NetworkPolicy.isOfflineOnly(networkPolicy)) {
        requestBuilder.cacheControl(ONLY_IF_CACHED);
    } else {
        CacheControl cacheControl = null;
        if (networkPolicy != 0) {
            if (NetworkPolicy.shouldReadFromDiskCache(networkPolicy)) {
                if (!NetworkPolicy.shouldWriteToDiskCache(networkPolicy)) {
                    cacheControl = NO_CACHE;
                }
            } else {
                cacheControl = NO_STORE;
            }
        }
        if (cacheControl != null) {
            requestBuilder.cacheControl(cacheControl);
        }
    }

    // 构建 OkHttp 的请求对象
    Request request = requestBuilder.build();
    // 执行 OkHttp 的请求
    okhttp3.Response response = client.newCall(request).execute();
    // 获取响应的状态码
    int responseCode = response.code();
    // 检查响应状态码是否为 200(成功)
    if (responseCode >= 300) {
        // 关闭响应体
        response.body().close();
        throw new ResponseException(responseCode + " " + response.message(), networkPolicy, responseCode);
    }

    // 检查响应是否来自缓存
    boolean fromCache = response.cacheResponse() != null;
    // 返回 Picasso 的响应对象
    return new Response(response.body().byteStream(), fromCache);
}
4.2.1 构建请求
  • 首先,使用 Request.Builder 创建一个 OkHttp 的请求构建器,并设置请求的 URL 为图片的 URI。
  • 然后,根据 networkPolicy 参数设置请求头的缓存控制信息。如果是离线模式(NetworkPolicy.isOfflineOnly(networkPolicy) 为 true),则设置缓存控制为 ONLY_IF_CACHED,表示只从缓存中获取数据。否则,根据是否需要从磁盘缓存读取和写入,设置不同的缓存控制策略。
4.2.2 执行请求
  • 调用 requestBuilder.build() 方法构建 OkHttp 的请求对象。
  • 使用 client.newCall(request).execute() 方法执行请求,并获取响应对象。
4.2.3 处理响应
  • 检查响应的状态码,如果状态码大于等于 300,表示请求失败,关闭响应体并抛出 ResponseException 异常。
  • 检查响应是否来自缓存,通过 response.cacheResponse() != null 判断。
  • 最后,返回一个 Response 对象,包含响应的输入流和是否来自缓存的信息。

4.3 shutdown 方法实现

java 复制代码
// OkHttp3Downloader.java
@Override
public void shutdown() {
    // 获取缓存对象
    Cache cache = client.cache();
    if (cache != null) {
        try {
            // 关闭缓存
            cache.close();
        } catch (IOException ignored) {
        }
    }
}

shutdown 方法用于关闭下载器,释放相关资源。它会获取 OkHttpClient 的缓存对象,并尝试关闭该缓存。如果关闭过程中出现 IOException 异常,会忽略该异常。

五、NetworkRequestHandler 类分析

5.1 canHandleRequest 方法

java 复制代码
// NetworkRequestHandler.java
package com.squareup.picasso;

import android.net.Uri;
import java.io.IOException;

/**
 * 负责处理网络请求的逻辑,调用 Downloader 进行实际的下载操作。
 */
public class NetworkRequestHandler extends RequestHandler {
    private final Downloader downloader;
    private final Stats stats;

    /**
     * 构造函数,初始化下载器和统计信息对象。
     * @param downloader 下载器实例
     * @param stats 统计信息对象
     */
    public NetworkRequestHandler(Downloader downloader, Stats stats) {
        this.downloader = downloader;
        this.stats = stats;
    }

    @Override
    public boolean canHandleRequest(Request data) {
        // 获取请求的 URI
        String scheme = data.uri.getScheme();
        // 检查 URI 的协议是否为 http 或 https
        return (SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme));
    }
}
5.1.1 方法功能

canHandleRequest 方法用于判断当前的 RequestHandler 是否能够处理指定的请求。在 NetworkRequestHandler 类中,它会检查请求的 URI 的协议是否为 http 或 https,如果是,则返回 true,表示可以处理该请求;否则返回 false。

5.1.2 代码详解
  • data.uri.getScheme():获取请求的 URI 的协议部分,如 http 或 https。
  • SCHEME_HTTP.equals(scheme) || SCHEME_HTTPS.equals(scheme):判断协议是否为 http 或 https。

5.2 load 方法

java 复制代码
// NetworkRequestHandler.java
@Override
public Result load(Request request, int networkPolicy) throws IOException {
    // 记录开始时间
    long started = System.nanoTime();
    // 调用下载器的 load 方法进行下载
    Response response = downloader.load(request.uri, networkPolicy);
    if (response == null) {
        return null;
    }
    // 检查响应是否来自缓存
    boolean fromCache = response.cached;
    // 获取响应的输入流
    InputStream is = response.getInputStream();
    if (is == null) {
        return null;
    }
    // 记录结束时间
    long size = is instanceof ContentLengthInputStream
           ? ((ContentLengthInputStream) is).getContentLength() : -1;
    // 统计下载信息
    stats.dispatchDownloadFinished(System.nanoTime() - started, size, fromCache);
    // 返回加载结果
    return new Result(is, fromCache? DISK : NETWORK);
}
5.2.1 下载流程
  • 记录下载开始时间,使用 System.nanoTime() 获取当前时间。
  • 调用 downloader.load(request.uri, networkPolicy) 方法进行实际的下载操作,获取 Response 对象。
  • 检查 Response 对象是否为空,如果为空则返回 null。
5.2.2 处理响应
  • 检查响应是否来自缓存,通过 response.cached 判断。
  • 获取响应的输入流,如果输入流为空则返回 null。
5.2.3 统计信息
  • 记录下载结束时间,计算下载耗时。
  • 如果输入流是 ContentLengthInputStream 类型,则获取其内容长度。
  • 调用 stats.dispatchDownloadFinished 方法统计下载信息,包括下载耗时、下载大小和是否来自缓存。
5.2.4 返回结果

返回一个 Result 对象,包含输入流和数据来源(磁盘缓存或网络)。

六、Response 类分析

6.1 类的定义

java 复制代码
// Response.java
package com.squareup.picasso;

import java.io.InputStream;

/**
 * 表示网络请求的响应结果,包含了图片数据和相关的元信息。
 */
public class Response {
    final InputStream stream;
    final boolean cached;

    /**
     * 构造函数,初始化响应结果。
     * @param stream 响应的输入流
     * @param cached 响应是否来自缓存
     */
    public Response(InputStream stream, boolean cached) {
        this.stream = stream;
        this.cached = cached;
    }

    /**
     * 获取响应的输入流。
     * @return 响应的输入流
     */
    public InputStream getInputStream() {
        return stream;
    }

    /**
     * 判断响应是否来自缓存。
     * @return 如果响应来自缓存则返回 true,否则返回 false
     */
    public boolean isCached() {
        return cached;
    }
}

6.2 类的属性和方法详解

6.2.1 属性
  • stream:响应的输入流,用于读取图片的二进制数据。
  • cached:表示响应是否来自缓存的布尔值。
6.2.2 构造函数

Response(InputStream stream, boolean cached):用于初始化 Response 对象,接收输入流和是否来自缓存的信息。

6.2.3 方法
  • getInputStream():返回响应的输入流。
  • isCached():返回响应是否来自缓存的布尔值。

6.3 类的作用

Response 类封装了网络请求的响应结果,为后续的图片解码和处理提供了统一的接口。通过 getInputStream 方法可以获取响应的输入流,通过 isCached 方法可以判断响应是否来自缓存,方便开发者进行不同的处理。

七、网络请求模块的工作流程

7.1 请求的发起

当开发者调用 Picasso 的 load 方法加载一张网络图片时,Picasso 会创建一个 Request 对象,该对象包含了图片的 URI、尺寸、变换等信息。然后,Picasso 会遍历所有的 RequestHandler,调用它们的 canHandleRequest 方法,判断哪个 RequestHandler 可以处理该请求。如果 NetworkRequestHandler 的 canHandleRequest 方法返回 true,则表示该请求是一个网络请求,将由 NetworkRequestHandler 进行处理。

7.2 请求的处理

NetworkRequestHandler 接收到请求后,会调用 load 方法进行实际的下载操作。在 load 方法中,它会调用 Downloader 的 load 方法,将请求的 URI 和网络策略传递给 Downloader。

7.3 网络请求的执行

Downloader(通常是 OkHttp3Downloader)会根据请求的 URI 和网络策略,使用 OkHttp3 库发起网络请求。它会构建 OkHttp 的请求对象,设置请求头的缓存控制信息,然后执行请求并获取响应。

7.4 响应的处理

Downloader 获取到响应后,会检查响应的状态码,如果状态码大于等于 300,表示请求失败,会抛出异常。如果请求成功,会检查响应是否来自缓存,并返回一个 Response 对象。NetworkRequestHandler 接收到 Response 对象后,会进行统计信息的记录,并返回一个 Result 对象。

7.5 结果的返回

NetworkRequestHandler 将 Result 对象返回给 Picasso,Picasso 会根据 Result 对象中的输入流进行图片解码和显示。如果响应来自缓存,则可以直接从缓存中获取图片数据,避免了网络请求,提高了加载速度。

八、网络请求模块的优化策略

8.1 缓存策略优化

8.1.1 合理设置缓存大小

在初始化 OkHttp3Downloader 时,可以根据应用的需求合理设置磁盘缓存的大小。如果缓存大小设置过小,可能会导致频繁的网络请求;如果设置过大,会占用过多的磁盘空间。可以根据应用的使用场景和用户设备的存储情况进行调整。

java 复制代码
// 设置磁盘缓存大小为 100MB
File cacheDir = new File(context.getCacheDir(), "picasso_cache");
long cacheSize = 100 * 1024 * 1024; // 100MB
OkHttp3Downloader downloader = new OkHttp3Downloader(cacheDir, cacheSize);
8.1.2 灵活使用网络策略

Picasso 提供了多种网络策略,可以根据不同的情况灵活使用。例如,在网络不稳定的情况下,可以优先从缓存中获取图片;在需要更新图片时,可以强制从网络获取。

java 复制代码
// 仅从缓存获取图片
Picasso.get().load("http://example.com/image.jpg")
       .networkPolicy(NetworkPolicy.OFFLINE)
       .into(imageView);

// 强制从网络获取图片
Picasso.get().load("http://example.com/image.jpg")
       .networkPolicy(NetworkPolicy.NO_CACHE)
       .into(imageView);

8.2 超时设置优化

在使用 OkHttp3Downloader 时,可以通过自定义 OkHttpClient 实例来设置超时时间,避免长时间的网络请求导致应用卡顿。可以设置连接超时、读取超时和写入超时时间。

java 复制代码
// 创建自定义的 OkHttpClient 实例,并设置超时时间
OkHttpClient client = new OkHttpClient.Builder()
       .connectTimeout(10, TimeUnit.SECONDS)
       .readTimeout(10, TimeUnit.SECONDS)
       .writeTimeout(10, TimeUnit.SECONDS)
       .build();

// 使用自定义的 OkHttpClient 实例初始化 OkHttp3Downloader
OkHttp3Downloader downloader = new OkHttp3Downloader(client);

// 创建 Picasso 实例,并设置下载器
Picasso picasso = new Picasso.Builder(context)
       .downloader(downloader)
       .build();

// 使用自定义的 Picasso 实例加载图片
picasso.load("http://example.com/image.jpg").into(imageView);

8.3 错误处理优化

在网络请求过程中,可能会出现各种错误,如网络连接失败、服务器返回错误等。可以通过 Picasso 的 error 方法设置错误处理逻辑,当网络请求失败时显示错误图片或提示信息。

java 复制代码
Picasso.get().load("http://example.com/image.jpg")
       .error(R.drawable.error_image)
       .into(imageView);

同时,可以通过捕获 IOException 异常,进行更详细的错误处理和日志记录。

java 复制代码
try {
    Picasso.get().load("http://example.com/image.jpg").into(imageView);
} catch (IOException e) {
    // 记录错误日志
    Log.e("Picasso", "Network request error: " + e.getMessage());
    // 显示错误提示信息
    Toast.makeText(context, "Failed to load image", Toast.LENGTH_SHORT).show();
}

8.4 并发请求优化

在处理多个图片请求时,可以通过调整 OkHttpClient 的线程池配置,优化并发请求的性能。例如,可以增加线程池的最大线程数,提高并发处理能力。

java 复制代码
// 创建自定义的 OkHttpClient 实例,并设置线程池配置
OkHttpClient client = new OkHttpClient.Builder()
       .dispatcher(new Dispatcher())
       .build();
Dispatcher dispatcher = client.dispatcher();
dispatcher.setMaxRequests(10); // 设置最大并发请求数
dispatcher.setMaxRequestsPerHost(5); // 设置每个主机的最大并发请求数

// 使用自定义的 OkHttpClient 实例初始化 OkHttp3Downloader
OkHttp3Downloader downloader = new OkHttp3Downloader(client);

// 创建 Picasso 实例,并设置下载器
Picasso picasso = new Picasso.Builder(context)
       .downloader(downloader)
       .build();

九、与其他网络请求库的对比

9.1 与 Volley 的对比

9.1.1 功能特点
  • Picasso:专注于图片加载,提供了简洁的 API 来加载、转换和显示图片。它集成了网络请求和缓存功能,能够自动处理图片的解码和缩放,适用于简单的图片加载场景。
  • Volley:是一个通用的网络请求库,支持多种类型的请求,如 JSON 请求、图片请求等。它提供了请求队列管理、缓存、重试机制等功能,适用于复杂的网络请求场景。
9.1.2 性能表现
  • Picasso:在图片加载方面性能较好,尤其是在处理大量图片时,能够有效利用缓存,减少网络请求。它的图片解码和缩放算法经过优化,能够快速加载和显示图片。
  • Volley:在处理小型 JSON 请求时性能较高,因为它的请求队列管理和缓存机制能够提高请求的效率。但在处理大型图片请求时,可能会因为内存管理和图片解码的问题导致性能下降。
9.1.3 使用场景
  • Picasso:适用于以图片展示为主的应用,如社交媒体应用、图片浏览器等。
  • Volley:适用于需要处理多种类型请求的应用,如新闻资讯应用、电商应用等。

9.2 与 Retrofit 的对比

9.2.1 功能特点
  • Picasso:主要用于图片加载,提供了简单易用的 API 来加载和显示图片。它的网络请求模块基于 OkHttp,能够自动处理图片的缓存和下载。
  • Retrofit:是一个类型安全的 HTTP 客户端,用于与 RESTful API 进行交互。它通过注解的方式定义接口,将网络请求转换为 Java 方法调用,方便开发者进行网络请求的管理。
9.2.2 性能表现
  • Picasso:在图片加载方面性能稳定,能够快速加载和显示图片。它的缓存机制能够有效减少网络请求,提高图片加载的效率。
  • Retrofit:在处理 RESTful API 请求时性能较高,因为它使用了动态代理和注解处理器,能够自动生成网络请求代码。但在处理图片请求时,需要结合其他图片加载库使用。
9.2.3 使用场景
  • Picasso:适用于需要大量图片加载的应用,如图片社交应用、图库应用等。
  • Retrofit:适用于与 RESTful API 进行交互的应用,如电商应用、金融应用等。

十、总结与展望

10.1 总结

通过对 Android Picasso 网络请求模块的深入分析,我们了解到该模块是 Picasso 实现图片从网络加载到本地展示的核心部分。它通过 Downloader 接口提供了统一的网络请求抽象,使用 OkHttp3Downloader 实现了具体的网络请求逻辑,通过 NetworkRequestHandler 处理网络请求的流程,最终通过 Response 类封装了网络请求的响应结果。该模块具备缓存处理、错误处理等功能,能够提高图片加载的效率和稳定性。同时,我们还介绍了一些优化策略,如缓存策略优化、超时设置优化、错误处理优化等,这些策略可以帮助开发者进一步提升应用的图片加载性能。

10.2 展望

未来,Android Picasso 的网络请求模块可以在以下几个方面进行改进和扩展:

  • 支持更多的网络协议 :除了 http 和 https,可以考虑支持其他网络协议,如 ftp、sftp 等,以满足不同场景下的图片加载需求。
  • 优化缓存算法:可以进一步优化缓存算法,提高缓存的命中率和空间利用率。例如,采用更智能的缓存淘汰策略,根据图片的使用频率、大小等因素进行缓存管理。
  • 增强错误处理机制:提供更详细的错误信息和更灵活的错误处理方式,方便开发者进行调试和优化。例如,在网络请求失败时,能够返回具体的错误原因和建议解决方案。
  • 支持并发请求优化:进一步优化并发请求的性能,提高多图片加载的效率。可以通过调整线程池配置、优化请求队列管理等方式,减少请求的等待时间。
  • 集成更多的网络请求库:除了 OkHttp3,还可以支持其他流行的网络请求库,如 Volley、Retrofit 等,让开发者可以根据自己的需求选择合适的网络请求库。

总之,Android Picasso 的网络请求模块是一个功能强大、灵活可扩展的模块,通过不断的改进和优化,将为 Android 开发者提供更好的图片加载体验。

相关推荐
浅念-14 分钟前
动态规划专题:斐波那契数列模型 + 网格路径DP(LeetCode例题全解析)
笔记·算法·leetcode·面试·职场和发展·动态规划·推荐算法
落魄大学生之流水线上谋生计17 分钟前
接口测试实习指南:从基础方法到面试与现场难题
面试·职场和发展
事圆则缓19 分钟前
Android 视频压缩上传实战:从 content Uri、转码到断点续传
android
程序员清风1 小时前
AI时代建议学点自己真正感兴趣的!
java·后端·面试
(Charon)2 小时前
【C++面试】线程同步机制:互斥锁、条件变量、原子操作与读写锁
开发语言·c++·算法·面试
花间相见3 小时前
【计算基础|网络06】HTTPS(上):加密体系与 RSA 握手
后端·面试
JAVA面经实录9173 小时前
Java高级后端 · 全套面试通关手册(Nginx)
java·nginx·面试
冉冉同学3 小时前
AI Agent 开始操作真实手机:移动端自动化的 3 个新考点
android·ai编程
考研保研资料分享4 小时前
211工商管理保研北大光华:夏令营笔试、科研面试与人力资源复习经验
面试·职场和发展
自然 醒4 小时前
【AI面试题】刷面试题发现了蝌蚪智汇小程序
人工智能·面试·职场和发展