深入剖析Android Volley响应数据格式转换(15)

深入剖析Android Volley响应数据格式转换:从源码到实战的全面解析

一、引言

在Android开发中,网络请求是必不可少的一部分。而Volley作为Android官方推荐的网络请求库,凭借其简洁的API和高效的性能,受到了广大开发者的喜爱。当我们使用Volley发送网络请求后,服务器返回的数据通常是JSON、XML、二进制数据等格式,而我们在应用中需要使用的是Java对象。因此,如何将服务器返回的数据转换为我们需要的格式,就成为了一个关键问题。

本文将深入剖析Android Volley库中响应数据格式转换的实现原理,从源码级别详细分析数据格式转换的各个关键环节。通过本文的学习,你将全面掌握Volley数据格式转换的核心机制,学会如何自定义数据转换器,如何处理不同格式的数据,以及如何优化数据转换的性能。

二、Volley响应数据格式转换基础

2.1 响应数据格式转换的基本概念

响应数据格式转换是指将服务器返回的数据(如JSON、XML、二进制数据等)转换为应用程序可以直接使用的Java对象的过程。在Volley中,这个过程主要由ResponseParser接口及其实现类来完成。

2.2 Volley中数据转换的核心接口和类

Volley中数据转换的核心接口和类包括:

  1. ResponseParser:数据解析器接口,定义了解析网络响应的方法
  2. Request:请求基类,包含了数据解析的相关方法
  3. StringRequest:字符串请求类,将响应数据转换为字符串
  4. JsonRequest:JSON请求类,将响应数据转换为JSON对象或数组
  5. ImageRequest:图片请求类,将响应数据转换为Bitmap对象
  6. GsonRequest:自定义请求类,使用Gson库将响应数据转换为Java对象

下面我们将详细分析这些接口和类的源码实现。

三、ResponseParser接口源码分析

3.1 ResponseParser接口定义

ResponseParser接口是Volley中数据解析的核心接口,定义了将网络响应数据解析为特定类型对象的方法:

/**
 * 响应数据解析器接口,定义了解析网络响应的方法
 */
public interface ResponseParser<T> {
    /**
     * 解析网络响应数据
     * @param response 网络响应
     * @return 解析后的数据对象
     */
    Response<T> parseNetworkResponse(NetworkResponse response);
}

从上面的源码可以看出,ResponseParser接口只有一个方法parseNetworkResponse,该方法接收一个NetworkResponse对象,并返回一个Response<T>对象。

3.2 Response类源码分析

Response类是Volley中表示响应结果的类,包含了响应数据和可能的错误信息:

/**
 * 响应结果类,包含了响应数据和可能的错误信息
 */
public class Response<T> {
    /** 响应成功的监听器 */
    public interface Listener<T> {
        /**
         * 当响应成功时调用
         */
        void onResponse(T response);
    }

    /** 响应错误的监听器 */
    public interface ErrorListener {
        /**
         * 当响应错误时调用
         */
        void onErrorResponse(VolleyError error);
    }

    /** 响应数据 */
    public final T result;
    
    /** 缓存条目 */
    public final Cache.Entry cacheEntry;
    
    /** 错误信息 */
    public final VolleyError error;
    
    /** 响应是否成功 */
    public final boolean isSuccess;

    /**
     * 创建一个成功的响应
     */
    private Response(T result, Cache.Entry cacheEntry) {
        this.isSuccess = true;
        this.result = result;
        this.cacheEntry = cacheEntry;
        this.error = null;
    }

    /**
     * 创建一个错误的响应
     */
    private Response(VolleyError error) {
        this.isSuccess = false;
        this.result = null;
        this.cacheEntry = null;
        this.error = error;
    }

    /**
     * 创建一个成功的响应实例
     */
    public static <T> Response<T> success(T result, Cache.Entry cacheEntry) {
        return new Response<T>(result, cacheEntry);
    }

    /**
     * 创建一个错误的响应实例
     */
    public static <T> Response<T> error(VolleyError error) {
        return new Response<T>(error);
    }
}

从上面的源码可以看出,Response类提供了两个静态工厂方法successerror,分别用于创建成功的响应和错误的响应。成功的响应包含解析后的数据和缓存条目,而错误的响应包含错误信息。

四、Request类中的数据转换方法

4.1 parseNetworkResponse方法

Request类是所有请求的基类,其中定义了数据解析的核心方法parseNetworkResponse

/**
 * 请求的基类
 */
public abstract class Request<T> implements Comparable<Request<T>> {
    // 其他成员变量和方法...

    /**
     * 解析网络响应数据
     * 子类必须实现此方法来解析网络响应
     * @param response 网络响应
     * @return 解析后的响应
     */
    abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

    // 其他方法...
}

从上面的源码可以看出,parseNetworkResponse是一个抽象方法,子类必须实现该方法来完成具体的数据解析工作。

4.2 deliverResponse方法

deliverResponse方法用于将解析后的数据传递给响应监听器:

/**
 * 将解析后的响应传递给监听器
 */
void deliverResponse(T response) {
    mListener.onResponse(response);
}

这个方法很简单,就是调用我们设置的响应监听器的onResponse方法,将解析后的数据传递给它。

五、内置数据转换器实现分析

5.1 StringRequest源码分析

StringRequest是Volley中最简单的请求类,它将响应数据转换为字符串:

/**
 * 字符串请求,将响应数据转换为字符串
 */
public class StringRequest extends Request<String> {
    private final Listener<String> mListener;

    /**
     * 创建一个新的字符串请求
     */
    public StringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }

    /**
     * 创建一个新的GET请求
     */
    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

    @Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            // 根据响应头中的字符集解析响应数据
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            // 如果字符集不支持,使用默认字符集
            parsed = new String(response.data);
        }
        // 返回成功的响应,包含解析后的字符串和缓存条目
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }
}

从上面的源码可以看出,StringRequestparseNetworkResponse方法将响应数据转换为字符串。它首先尝试根据响应头中的字符集解析数据,如果字符集不支持,则使用默认字符集。

5.2 JsonRequest源码分析

JsonRequest是JSON请求的基类,它将响应数据转换为JSON对象或数组。下面是其源码分析:

/**
 * JSON请求的基类,将响应数据转换为JSON对象或数组
 */
public abstract class JsonRequest<T> extends Request<T> {
    /** 默认字符集 */
    private static final String PROTOCOL_CHARSET = "utf-8";

    /** Content-Type头 */
    private static final String PROTOCOL_CONTENT_TYPE =
        String.format("application/json; charset=%s", PROTOCOL_CHARSET);

    private final Listener<T> mListener;
    private final String mRequestBody;

    /**
     * 创建一个新的JSON请求
     */
    public JsonRequest(int method, String url, String requestBody,
            Listener<T> listener, ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
        mRequestBody = requestBody;
    }

    @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
    }

    @Override
    abstract protected Response<T> parseNetworkResponse(NetworkResponse response);

    @Override
    public String getBodyContentType() {
        return PROTOCOL_CONTENT_TYPE;
    }

    @Override
    public byte[] getBody() {
        try {
            return mRequestBody == null ? null : mRequestBody.getBytes(PROTOCOL_CHARSET);
        } catch (UnsupportedEncodingException uee) {
            VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s",
                    mRequestBody, PROTOCOL_CHARSET);
            return null;
        }
    }
}

从上面的源码可以看出,JsonRequest是一个抽象类,它定义了JSON请求的基本结构,但具体的解析工作由其子类完成。

5.3 JsonObjectRequest源码分析

JsonObjectRequestJsonRequest的子类,用于处理JSON对象响应:

/**
 * JSON对象请求,将响应数据转换为JSONObject
 */
public class JsonObjectRequest extends JsonRequest<JSONObject> {

    /**
     * 创建一个新的GET请求,用于获取JSON对象
     */
    public JsonObjectRequest(String url, Listener<JSONObject> listener,
            ErrorListener errorListener) {
        this(Method.GET, url, null, listener, errorListener);
    }

    /**
     * 创建一个新的JSON对象请求
     */
    public JsonObjectRequest(int method, String url, JSONObject jsonRequest,
            Listener<JSONObject> listener, ErrorListener errorListener) {
        super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(),
                listener, errorListener);
    }

    @Override
    protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
        try {
            // 将响应数据转换为字符串
            String jsonString = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
            
            // 将字符串解析为JSONObject
            return Response.success(new JSONObject(jsonString),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            // 返回解析错误
            return Response.error(new ParseError(e));
        } catch (JSONException je) {
            // 返回解析错误
            return Response.error(new ParseError(je));
        }
    }
}

从上面的源码可以看出,JsonObjectRequestparseNetworkResponse方法首先将响应数据转换为字符串,然后将字符串解析为JSONObject对象。如果解析过程中出现异常,会返回一个包含解析错误的响应。

5.4 JsonArrayRequest源码分析

JsonArrayRequestJsonRequest的另一个子类,用于处理JSON数组响应:

/**
 * JSON数组请求,将响应数据转换为JSONArray
 */
public class JsonArrayRequest extends JsonRequest<JSONArray> {

    /**
     * 创建一个新的JSON数组请求
     */
    public JsonArrayRequest(String url, Listener<JSONArray> listener, ErrorListener errorListener) {
        super(Method.GET, url, null, listener, errorListener);
    }

    /**
     * 创建一个新的JSON数组请求
     */
    public JsonArrayRequest(int method, String url, String requestBody,
            Listener<JSONArray> listener, ErrorListener errorListener) {
        super(method, url, requestBody, listener, errorListener);
    }

    @Override
    protected Response<JSONArray> parseNetworkResponse(NetworkResponse response) {
        try {
            // 将响应数据转换为字符串
            String jsonString = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers, PROTOCOL_CHARSET));
            
            // 将字符串解析为JSONArray
            return Response.success(new JSONArray(jsonString),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            // 返回解析错误
            return Response.error(new ParseError(e));
        } catch (JSONException je) {
            // 返回解析错误
            return Response.error(new ParseError(je));
        }
    }
}

从上面的源码可以看出,JsonArrayRequestparseNetworkResponse方法与JsonObjectRequest类似,只是将字符串解析为JSONArray对象。

5.5 ImageRequest源码分析

ImageRequest用于处理图片响应,将响应数据转换为Bitmap对象:

/**
 * 图片请求,将响应数据转换为Bitmap
 */
public class ImageRequest extends Request<Bitmap> {
    /** 最大图片宽度 */
    private final int mMaxWidth;
    /** 最大图片高度 */
    private final int mMaxHeight;
    /** 图片配置 */
    private final Bitmap.Config mDecodeConfig;
    /** 响应监听器 */
    private final Listener<Bitmap> mListener;
    /** 图片缩放类型 */
    private ScaleType mScaleType = ScaleType.CENTER_INSIDE;

    /** 图片缩放类型 */
    public enum ScaleType {
        CENTER_INSIDE,
        FIT_CENTER
    }

    /**
     * 创建一个新的图片请求
     */
    public ImageRequest(String url, Listener<Bitmap> listener, int maxWidth, int maxHeight,
            ScaleType scaleType, Bitmap.Config decodeConfig, ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        setRetryPolicy(new DefaultRetryPolicy(DefaultRetryPolicy.DEFAULT_TIMEOUT_MS, 2,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        mListener = listener;
        mDecodeConfig = decodeConfig;
        mMaxWidth = maxWidth;
        mMaxHeight = maxHeight;
        mScaleType = scaleType;
    }

    @Override
    public Priority getPriority() {
        return Priority.LOW;
    }

    @Override
    protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
        // 对响应数据进行同步处理
        synchronized (ImageRequest.class) {
            try {
                // 解码图片
                return doParse(response);
            } catch (OutOfMemoryError e) {
                VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
                return Response.error(new ParseError(e));
            }
        }
    }

    /**
     * 实际执行图片解码的方法
     */
    private Response<Bitmap> doParse(NetworkResponse response) {
        byte[] data = response.data;
        // 首先尝试获取图片的尺寸信息,而不加载整个图片
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeByteArray(data, 0, data.length, options);
        
        // 获取图片的原始宽度和高度
        int actualWidth = options.outWidth;
        int actualHeight = options.outHeight;

        // 计算需要的宽度和高度
        int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
                actualWidth, actualHeight, mScaleType);
        int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
                actualHeight, actualWidth, mScaleType);

        // 计算缩放比例
        options.inJustDecodeBounds = false;
        options.inSampleSize = findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
        Bitmap tempBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);

        // 如果需要,对图片进行缩放
        if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
                tempBitmap.getHeight() > desiredHeight)) {
            Bitmap bitmap = Bitmap.createScaledBitmap(tempBitmap,
                    desiredWidth, desiredHeight, true);
            tempBitmap.recycle();
            return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
        } else {
            return Response.success(tempBitmap, HttpHeaderParser.parseCacheHeaders(response));
        }
    }

    /**
     * 计算调整后的尺寸
     */
    private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
            int actualSecondary, ScaleType scaleType) {
        // 如果没有设置最大尺寸,返回实际尺寸
        if (maxPrimary == 0 && maxSecondary == 0) {
            return actualPrimary;
        }

        // 如果只有一个尺寸设置了最大值
        if (maxPrimary == 0) {
            // 高度优先,宽度按比例缩放
            double ratio = (double) maxSecondary / (double) actualSecondary;
            return (int) (actualPrimary * ratio);
        }

        if (maxSecondary == 0) {
            // 宽度优先,高度按比例缩放
            return maxPrimary;
        }

        // 如果两个尺寸都设置了最大值,根据缩放类型计算
        double ratio = (double) actualSecondary / (double) actualPrimary;
        int resized = maxPrimary;

        // 如果图片需要完全包含在指定区域内
        if (scaleType == ScaleType.CENTER_INSIDE) {
            if ((double) resized * ratio > maxSecondary) {
                resized = (int) (maxSecondary / ratio);
            }
            return resized;
        }

        // 如果图片需要适应指定区域
        if ((double) resized * ratio < maxSecondary) {
            resized = (int) (maxSecondary / ratio);
        }
        return resized;
    }

    /**
     * 找到最佳的采样率
     */
    static int findBestSampleSize(
            int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
        double wr = (double) actualWidth / desiredWidth;
        double hr = (double) actualHeight / desiredHeight;
        double ratio = Math.min(wr, hr);
        float n = 1.0f;
        while ((n * 2) <= ratio) {
            n *= 2;
        }

        return (int) n;
    }

    @Override
    protected void deliverResponse(Bitmap response) {
        mListener.onResponse(response);
    }
}

从上面的源码可以看出,ImageRequestparseNetworkResponse方法首先尝试获取图片的尺寸信息,然后根据需要的尺寸计算缩放比例,最后解码并缩放图片,返回Bitmap对象。

六、自定义数据转换器

6.1 为什么需要自定义数据转换器

虽然Volley提供了几种内置的数据转换器,但在实际开发中,我们可能会遇到以下情况:

  1. 服务器返回的数据格式不是JSON、XML或图片,而是其他格式
  2. 我们需要使用特定的JSON解析库,如Gson、Jackson等
  3. 我们需要对响应数据进行复杂的处理和转换
  4. 我们需要支持自定义的响应格式,如Protobuf

在这些情况下,我们就需要自定义数据转换器来满足我们的需求。

6.2 使用Gson库实现自定义数据转换器

下面我们以Gson库为例,演示如何实现一个自定义的数据转换器:

/**
 * 使用Gson库的自定义请求类,将响应数据转换为Java对象
 */
public class GsonRequest<T> extends Request<T> {
    private final Gson mGson;
    private final Class<T> mClazz;
    private final Map<String, String> mHeaders;
    private final Listener<T> mListener;

    /**
     * 创建一个新的Gson请求
     */
    public GsonRequest(int method, String url, Class<T> clazz, Map<String, String> headers,
            Listener<T> listener, ErrorListener errorListener) {
        super(method, url, errorListener);
        mGson = new Gson();
        mClazz = clazz;
        mHeaders = headers;
        mListener = listener;
    }

    @Override
    public Map<String, String> getHeaders() throws AuthFailureError {
        return mHeaders != null ? mHeaders : super.getHeaders();
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            // 将响应数据转换为字符串
            String json = new String(
                    response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            
            // 使用Gson将JSON字符串解析为Java对象
            return Response.success(
                    mGson.fromJson(json, mClazz),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            // 返回解析错误
            return Response.error(new ParseError(e));
        } catch (JsonSyntaxException e) {
            // 返回解析错误
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
    }
}

6.3 使用GsonRequest的示例

下面是一个使用GsonRequest的示例,展示了如何将JSON响应转换为Java对象:

// 定义数据模型类
class User {
    private String name;
    private int age;
    private String email;
    
    // getters and setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
}

// 创建Gson请求
GsonRequest<User> request = new GsonRequest<User>(
        Request.Method.GET,
        "https://api.example.com/user/123",
        User.class,
        null,  // 请求头
        new Response.Listener<User>() {
            @Override
            public void onResponse(User user) {
                // 处理解析后的用户对象
                Log.d("MyApp", "User name: " + user.getName());
                Log.d("MyApp", "User age: " + user.getAge());
                Log.d("MyApp", "User email: " + user.getEmail());
            }
        },
        new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // 处理错误
                Log.e("MyApp", "Error: " + error.getMessage());
            }
        }
);

// 将请求添加到请求队列
requestQueue.add(request);

6.4 处理复杂JSON结构

对于复杂的JSON结构,我们可以使用Gson的TypeToken来处理:

// 定义数据模型类
class UserList {
    private List<User> users;
    
    // getters and setters
    public List<User> getUsers() { return users; }
    public void setUsers(List<User> users) { this.users = users; }
}

// 创建Gson请求处理复杂JSON结构
GsonRequest<UserList> request = new GsonRequest<UserList>(
        Request.Method.GET,
        "https://api.example.com/users",
        UserList.class,
        null,
        new Response.Listener<UserList>() {
            @Override
            public void onResponse(UserList userList) {
                // 处理用户列表
                for (User user : userList.getUsers()) {
                    Log.d("MyApp", "User name: " + user.getName());
                }
            }
        },
        new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // 处理错误
                Log.e("MyApp", "Error: " + error.getMessage());
            }
        }
) {
    @Override
    protected Response<UserList> parseNetworkResponse(NetworkResponse response) {
        try {
            String json = new String(
                    response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            
            // 使用TypeToken处理复杂类型
            Type type = new TypeToken<UserList>() {}.getType();
            UserList userList = mGson.fromJson(json, type);
            
            return Response.success(
                    userList,
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JsonSyntaxException e) {
            return Response.error(new ParseError(e));
        }
    }
};

// 将请求添加到请求队列
requestQueue.add(request);

七、数据转换的性能优化

7.1 数据转换性能问题分析

在Android应用中,数据转换可能会成为性能瓶颈,特别是在处理大量数据或复杂JSON结构时。主要的性能问题包括:

  1. 字符串解析开销:将字节数组转换为字符串需要分配内存和处理字符编码
  2. JSON解析开销:解析JSON数据需要遍历整个JSON结构,创建大量对象
  3. 内存分配和垃圾回收:频繁的内存分配和垃圾回收会导致应用卡顿
  4. 数据复制:在数据转换过程中可能会发生多次数据复制

7.2 性能优化策略

针对上述性能问题,我们可以采取以下优化策略:

  1. 减少字符串解析:尽量直接处理字节数组,避免不必要的字符串转换
  2. 使用流式解析:对于大型JSON数据,使用流式解析而不是一次性解析整个JSON
  3. 优化数据模型:避免创建过多的中间对象,使用更高效的数据结构
  4. 缓存解析器实例:避免重复创建解析器实例,特别是Gson、Jackson等解析库的实例
  5. 使用Protobuf等高效格式:对于对性能要求极高的场景,考虑使用Protobuf等更高效的数据格式

7.3 使用Jackson替代Gson

Jackson是另一个流行的JSON解析库,相比Gson,它在性能上有一定优势。下面是一个使用Jackson的自定义请求类:

/**
 * 使用Jackson库的自定义请求类,将响应数据转换为Java对象
 */
public class JacksonRequest<T> extends Request<T> {
    private static final ObjectMapper sObjectMapper = new ObjectMapper();
    private final Class<T> mClazz;
    private final Listener<T> mListener;

    /**
     * 创建一个新的Jackson请求
     */
    public JacksonRequest(int method, String url, Class<T> clazz,
            Listener<T> listener, ErrorListener errorListener) {
        super(method, url, errorListener);
        mClazz = clazz;
        mListener = listener;
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            // 使用Jackson直接从字节数组解析
            T result = sObjectMapper.readValue(response.data, mClazz);
            return Response.success(
                    result,
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (IOException e) {
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
    }
}

7.4 使用Protobuf进行数据转换

对于对性能要求极高的场景,可以考虑使用Protobuf进行数据转换。下面是一个使用Protobuf的示例:

/**
 * 使用Protobuf的自定义请求类
 */
public class ProtobufRequest<T extends Message> extends Request<T> {
    private final Class<T> mClazz;
    private final Listener<T> mListener;
    private final Message.Builder mBuilder;

    /**
     * 创建一个新的Protobuf请求
     */
    public ProtobufRequest(int method, String url, Class<T> clazz, Message.Builder builder,
            Listener<T> listener, ErrorListener errorListener) {
        super(method, url, errorListener);
        mClazz = clazz;
        mListener = listener;
        mBuilder = builder;
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            // 使用Protobuf解析字节数组
            T result = (T) mBuilder.mergeFrom(response.data).build();
            return Response.success(
                    result,
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (InvalidProtocolBufferException e) {
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
    }
}

7.5 数据转换性能测试

为了验证不同数据转换方法的性能差异,我们可以进行性能测试。下面是一个简单的性能测试框架:

/**
 * 数据转换性能测试类
 */
public class DataConversionPerformanceTest {
    private static final int TEST_TIMES = 1000;
    private static final String JSON_DATA = "{\"name\":\"John\",\"age\":30,\"email\":\"john@example.com\"}";
    private static final byte[] JSON_BYTES = JSON_DATA.getBytes(StandardCharsets.UTF_8);
    
    private static final Gson GSON = new Gson();
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    public static void main(String[] args) {
        // 测试Gson解析性能
        testGsonParsing();
        
        // 测试Jackson解析性能
        testJacksonParsing();
        
        // 测试Protobuf解析性能(需要先定义Protobuf消息类型)
        // testProtobufParsing();
    }

    /**
     * 测试Gson解析性能
     */
    private static void testGsonParsing() {
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < TEST_TIMES; i++) {
            User user = GSON.fromJson(new String(JSON_BYTES), User.class);
        }
        
        long endTime = System.currentTimeMillis();
        System.out.println("Gson parsing time: " + (endTime - startTime) + "ms");
    }

    /**
     * 测试Jackson解析性能
     */
    private static void testJacksonParsing() {
        long startTime = System.currentTimeMillis();
        
        for (int i = 0; i < TEST_TIMES; i++) {
            try {
                User user = OBJECT_MAPPER.readValue(JSON_BYTES, User.class);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        long endTime = System.currentTimeMillis();
        System.out.println("Jackson parsing time: " + (endTime - startTime) + "ms");
    }

    /**
     * 测试Protobuf解析性能
     */
    private static void testProtobufParsing() {
        // 这里需要先定义Protobuf消息类型并生成Java类
        // 然后进行性能测试
    }

    /**
     * 用户数据模型类
     */
    private static class User {
        private String name;
        private int age;
        private String email;
        
        // getters and setters
        public String getName() { return name; }
        public void setName(String name) { this.name = name; }
        
        public int getAge() { return age; }
        public void setAge(int age) { this.age = age; }
        
        public String getEmail() { return email; }
        public void setEmail(String email) { this.email = email; }
    }
}

八、处理特殊格式的响应数据

8.1 处理XML响应数据

虽然Volley主要用于处理JSON数据,但我们也可以自定义请求类来处理XML响应数据。下面是一个使用XmlPullParser处理XML响应的示例:

/**
 * XML请求类,将响应数据转换为自定义对象
 */
public class XmlRequest<T> extends Request<T> {
    private final Listener<T> mListener;
    private final XmlParser<T> mParser;

    /**
     * 创建一个新的XML请求
     */
    public XmlRequest(int method, String url, XmlParser<T> parser,
            Listener<T> listener, ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
        mParser = parser;
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            // 将响应数据转换为InputStream
            ByteArrayInputStream inputStream = new ByteArrayInputStream(response.data);
            
            // 使用XmlPullParser解析XML
            T result = mParser.parse(inputStream);
            
            return Response.success(
                    result,
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (Exception e) {
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
    }

    /**
     * XML解析器接口
     */
    public interface XmlParser<T> {
        /**
         * 解析XML输入流
         */
        T parse(InputStream inputStream) throws Exception;
    }
}

8.2 使用示例

下面是一个使用XmlRequest处理XML响应的示例:

// 定义数据模型类
class Book {
    private String title;
    private String author;
    private double price;
    
    // getters and setters
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    
    public String getAuthor() { return author; }
    public void setAuthor(String author) { this.author = author; }
    
    public double getPrice() { return price; }
    public void setPrice(double price) { this.price = price; }
}

// 创建XML解析器
XmlRequest.XmlParser<Book> parser = new XmlRequest.XmlParser<Book>() {
    @Override
    public Book parse(InputStream inputStream) throws Exception {
        Book book = new Book();
        XmlPullParser parser = Xml.newPullParser();
        parser.setInput(inputStream, "UTF-8");
        
        int eventType = parser.getEventType();
        while (eventType != XmlPullParser.END_DOCUMENT) {
            if (eventType == XmlPullParser.START_TAG) {
                String tagName = parser.getName();
                if (tagName.equals("title")) {
                    book.setTitle(parser.nextText());
                } else if (tagName.equals("author")) {
                    book.setAuthor(parser.nextText());
                } else if (tagName.equals("price")) {
                    book.setPrice(Double.parseDouble(parser.nextText()));
                }
            }
            eventType = parser.next();
        }
        
        return book;
    }
};

// 创建XML请求
XmlRequest<Book> request = new XmlRequest<Book>(
        Request.Method.GET,
        "https://api.example.com/book/123",
        parser,
        new Response.Listener<Book>() {
            @Override
            public void onResponse(Book book) {
                // 处理解析后的书籍对象
                Log.d("MyApp", "Book title: " + book.getTitle());
                Log.d("MyApp", "Book author: " + book.getAuthor());
                Log.d("MyApp", "Book price: " + book.getPrice());
            }
        },
        new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // 处理错误
                Log.e("MyApp", "Error: " + error.getMessage());
            }
        }
);

// 将请求添加到请求队列
requestQueue.add(request);

8.3 处理二进制响应数据

对于二进制响应数据,如文件下载,我们可以创建一个自定义请求类来处理:

/**
 * 二进制请求类,用于下载文件
 */
public class BinaryRequest extends Request<byte[]> {
    private final Listener<byte[]> mListener;

    /**
     * 创建一个新的二进制请求
     */
    public BinaryRequest(String url, Listener<byte[]> listener, ErrorListener errorListener) {
        super(Method.GET, url, errorListener);
        mListener = listener;
    }

    @Override
    protected Response<byte[]> parseNetworkResponse(NetworkResponse response) {
        // 直接返回响应数据的字节数组
        return Response.success(
                response.data,
                HttpHeaderParser.parseCacheHeaders(response));
    }

    @Override
    protected void deliverResponse(byte[] response) {
        mListener.onResponse(response);
    }
}

8.4 处理二进制响应的示例

下面是一个使用BinaryRequest下载文件的示例:

// 创建二进制请求
BinaryRequest request = new BinaryRequest(
        "https://example.com/file.zip",
        new Response.Listener<byte[]>() {
            @Override
            public void onResponse(byte[] response) {
                // 处理下载的文件数据
                try {
                    // 将数据写入文件
                    FileOutputStream fos = new FileOutputStream("/sdcard/downloaded_file.zip");
                    fos.write(response);
                    fos.close();
                    Log.d("MyApp", "File downloaded successfully");
                } catch (IOException e) {
                    Log.e("MyApp", "Error writing file: " + e.getMessage());
                }
            }
        },
        new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // 处理错误
                Log.e("MyApp", "Error downloading file: " + error.getMessage());
            }
        }
);

// 将请求添加到请求队列
requestQueue.add(request);

九、数据转换的最佳实践

9.1 统一数据转换框架

为了更好地管理和使用数据转换器,建议创建一个统一的数据转换框架。这个框架可以包含以下几个组件:

  1. 数据转换器接口:定义数据转换的方法
  2. 具体数据转换器实现:实现不同格式的数据转换
  3. 数据转换器工厂:根据需要创建合适的数据转换器
  4. 统一的请求基类:集成数据转换逻辑

下面是一个简单的统一数据转换框架的实现示例:

/**
 * 数据转换器接口
 */
public interface DataParser<T> {
    /**
     * 解析网络响应数据
     */
    Response<T> parseNetworkResponse(NetworkResponse response);
}

/**
 * Gson数据转换器
 */
public class GsonParser<T> implements DataParser<T> {
    private final Gson mGson;
    private final Class<T> mClazz;

    public GsonParser(Class<T> clazz) {
        mGson = new Gson();
        mClazz = clazz;
    }

    @Override
    public Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            String json = new String(
                    response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            
            T result = mGson.fromJson(json, mClazz);
            
            return Response.success(
                    result,
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JsonSyntaxException e) {
            return Response.error(new ParseError(e));
        }
    }
}

/**
 * Jackson数据转换器
 */
public class JacksonParser<T> implements DataParser<T> {
    private static final ObjectMapper sObjectMapper = new ObjectMapper();
    private final Class<T> mClazz;

    public JacksonParser(Class<T> clazz) {
        mClazz = clazz;
    }

    @Override
    public Response<T> parseNetworkResponse(NetworkResponse response) {
        try {
            T result = sObjectMapper.readValue(response.data, mClazz);
            
            return Response.success(
                    result,
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (IOException e) {
            return Response.error(new ParseError(e));
        }
    }
}

/**
 * 数据转换器工厂
 */
public class ParserFactory {
    /**
     * 创建Gson数据转换器
     */
    public static <T> DataParser<T> createGsonParser(Class<T> clazz) {
        return new GsonParser<>(clazz);
    }

    /**
     * 创建Jackson数据转换器
     */
    public static <T> DataParser<T> createJacksonParser(Class<T> clazz) {
        return new JacksonParser<>(clazz);
    }

    /**
     * 根据格式创建数据转换器
     */
    public static <T> DataParser<T> createParser(String format, Class<T> clazz) {
        if ("json".equalsIgnoreCase(format)) {
            // 默认使用Gson
            return new GsonParser<>(clazz);
        } else if ("jackson".equalsIgnoreCase(format)) {
            return new JacksonParser<>(clazz);
        }
        
        // 其他格式的解析器...
        
        throw new IllegalArgumentException("Unsupported format: " + format);
    }
}

/**
 * 统一的请求基类
 */
public class GenericRequest<T> extends Request<T> {
    private final Listener<T> mListener;
    private final DataParser<T> mParser;

    public GenericRequest(int method, String url, DataParser<T> parser,
            Listener<T> listener, ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
        mParser = parser;
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        return mParser.parseNetworkResponse(response);
    }

    @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
    }
}

9.2 使用统一框架的示例

下面是一个使用统一数据转换框架的示例:

// 创建Gson数据转换器
DataParser<User> parser = ParserFactory.createGsonParser(User.class);

// 创建通用请求
GenericRequest<User> request = new GenericRequest<>(
        Request.Method.GET,
        "https://api.example.com/user/123",
        parser,
        new Response.Listener<User>() {
            @Override
            public void onResponse(User user) {
                // 处理用户数据
                Log.d("MyApp", "User name: " + user.getName());
            }
        },
        new Response.ErrorListener() {
            @Override
            public void onErrorResponse(VolleyError error) {
                // 处理错误
                Log.e("MyApp", "Error: " + error.getMessage());
            }
        }
);

// 将请求添加到请求队列
requestQueue.add(request);

9.3 其他最佳实践建议

  1. 合理选择解析库:根据项目需求和性能要求,选择合适的JSON解析库,如Gson、Jackson等
  2. 优化数据模型:设计简洁、高效的数据模型,避免过度嵌套和冗余字段
  3. 处理空值和异常情况:在数据转换过程中,处理好空值和异常情况,避免应用崩溃
  4. 使用线程池处理大数据:对于大型数据的解析,考虑使用线程池来避免阻塞主线程
  5. 添加适当的缓存:对于频繁请求的数据,添加适当的缓存机制,减少数据转换的次数

十、Volley响应数据格式转换源码深度解析

10.1 NetworkDispatcher中的数据转换流程

NetworkDispatcher是Volley中负责执行网络请求的核心类之一,它在获取网络响应后,会调用请求的parseNetworkResponse方法进行数据转换:

/**
 * 网络调度器,负责从请求队列中取出请求并执行
 */
public class NetworkDispatcher extends Thread {
    // 其他成员变量...

    @Override
    public void run() {
        // 设置线程优先级
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        
        // 循环处理请求
        while (true) {
            long startTime
/**
 * 网络调度器,负责从请求队列中取出请求并执行
 */
public class NetworkDispatcher extends Thread {
    // 其他成员变量...

    @Override
    public void run() {
        // 设置线程优先级
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        
        // 循环处理请求
        while (true) {
            long startTimeMs = SystemClock.elapsedRealtime();
            Request<?> request;
            try {
                // 从队列中取出请求(阻塞操作)
                request = mQueue.take();
            } catch (InterruptedException e) {
                // 如果被中断,检查是否应该退出
                if (mQuit) {
                    return;
                }
                continue;
            }

            try {
                // 标记请求开始
                request.addMarker("network-queue-take");

                // 如果请求已经被取消,跳过处理
                if (request.isCanceled()) {
                    request.finish("network-discard-cancelled");
                    continue;
                }

                // 设置请求的重试策略
                request.addMarker("network-http-attempt");
                
                // 执行网络请求
                NetworkResponse networkResponse = mNetwork.performRequest(request);
                request.addMarker("network-http-complete");

                // 如果服务器返回304(未修改)且请求已经有缓存响应,使用缓存响应
                if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                    request.finish("not-modified");
                    continue;
                }

                // 调用请求的parseNetworkResponse方法进行数据转换
                Response<?> response = request.parseNetworkResponse(networkResponse);
                request.addMarker("network-parse-complete");

                // 如果请求需要缓存,将响应放入缓存
                if (request.shouldCache() && response.cacheEntry != null) {
                    mCache.put(request.getCacheKey(), response.cacheEntry);
                    request.addMarker("network-cache-written");
                }

                // 标记请求已交付响应
                request.markDelivered();
                
                // 分发响应到主线程
                mDelivery.postResponse(request, response);
            } catch (VolleyError volleyError) {
                // 处理网络错误
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                
                // 解析网络错误
                parseAndDeliverNetworkError(request, volleyError);
            } catch (Exception e) {
                // 处理其他异常
                VolleyLog.e(e, "Unhandled exception %s", e.toString());
                VolleyError volleyError = new VolleyError(e);
                volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
                
                // 分发错误到主线程
                mDelivery.postError(request, volleyError);
            }
        }
    }

    // 其他方法...
}

从上述代码可知,NetworkDispatcher线程不断从请求队列mQueue中取出请求。在获取到网络响应networkResponse后,直接调用请求对象的parseNetworkResponse方法,将networkResponse作为参数传入,从而触发数据格式转换流程。转换完成后,根据请求是否需要缓存进行相应操作,最后将响应分发到主线程。若在此过程中出现错误,则进入错误处理流程。

10.2 Request类中数据转换核心方法的调用链

Request类中,parseNetworkResponse是数据转换的核心抽象方法,其调用链涉及多个关键方法。以StringRequest为例,以下是详细的调用过程:

  1. 请求执行:当NetworkDispatcher获取到请求并得到网络响应后,调用request.parseNetworkResponse(networkResponse)
// 在NetworkDispatcher类中
Response<?> response = request.parseNetworkResponse(networkResponse);
  1. 子类实现StringRequest继承自Request,并重写了parseNetworkResponse方法。
/**
 * 字符串请求,将响应数据转换为字符串
 */
public class StringRequest extends Request<String> {
    // 其他代码...
    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            // 根据响应头中的字符集解析响应数据
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            // 如果字符集不支持,使用默认字符集
            parsed = new String(response.data);
        }
        // 返回成功的响应,包含解析后的字符串和缓存条目
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }
    // 其他代码...
}
  1. 字符集解析:在parseNetworkResponse方法中,调用HttpHeaderParser.parseCharset方法解析响应头中的字符集。
// 在StringRequest的parseNetworkResponse方法中
parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
/**
 * HttpHeaderParser类中解析字符集的方法
 */
public class HttpHeaderParser {
    // 其他代码...
    public static String parseCharset(Map<String, String> headers) {
        String charset = headers.get("Content-Type");
        if (charset != null) {
            String[] parts = charset.split(";");
            for (String part : parts) {
                part = part.trim();
                if (part.startsWith("charset=")) {
                    return part.substring("charset=".length());
                }
            }
        }
        return "ISO-8859-1";
    }
    // 其他代码...
}
  1. 缓存头解析:最后调用HttpHeaderParser.parseCacheHeaders方法解析缓存头信息,并将解析后的数据和缓存头信息封装成Response对象返回。
// 在StringRequest的parseNetworkResponse方法中
return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
/**
 * HttpHeaderParser类中解析缓存头的方法
 */
public class HttpHeaderParser {
    // 其他代码...
    public static Cache.Entry parseCacheHeaders(NetworkResponse response) {
        long now = System.currentTimeMillis();
        Map<String, String> headers = response.headers;
        long serverDate = 0;
        long serverExpires = 0;
        long softExpire = 0;
        long maxAge = 0;
        boolean hasCacheControl = false;
        boolean mustRevalidate = false;

        String serverEtag;
        String headerValue;

        headerValue = headers.get("Date");
        if (headerValue != null) {
            serverDate = parseDateAsEpoch(headerValue);
        }

        headerValue = headers.get("Cache-Control");
        if (headerValue != null) {
            hasCacheControl = true;
            String[] tokens = headerValue.split(",");
            for (String token : tokens) {
                token = token.trim();
                if (token.equals("no-cache") || token.equals("no-store")) {
                    return null;
                } else if (token.startsWith("max-age=")) {
                    try {
                        maxAge = Long.parseLong(token.substring("max-age=".length()));
                    } catch (NumberFormatException e) {
                        // Ignore
                    }
                } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) {
                    mustRevalidate = true;
                }
            }
        }

        headerValue = headers.get("Expires");
        if (headerValue != null) {
            serverExpires = parseDateAsEpoch(headerValue);
        }

        serverEtag = headers.get("ETag");

        // Cache-Control takes precedence over an Expires header
        long cacheHitButRefreshed = now + maxAge * 1000;
        long cacheExpired = (serverDate > 0) ? (serverDate + serverExpires * 1000) : 0;
        long softExpire = (serverDate > 0) ? (serverDate + maxAge * 1000) : 0;

        Cache.Entry entry = new Cache.Entry();
        entry.data = response.data;
        entry.etag = serverEtag;
        entry.softTtl = softExpire;
        entry.ttl = cacheExpired;
        entry.serverDate = serverDate;
        entry.responseHeaders = headers;
        entry.hasCacheControl = hasCacheControl;
        entry.mustRevalidate = mustRevalidate;

        return entry;
    }
    // 其他代码...
}

10.3 ResponseDelivery的响应分发与数据转换结果处理

ResponseDelivery接口负责将响应(包括数据转换后的结果)和错误分发到主线程,其默认实现类ExecutorDelivery的具体工作流程如下:

/**
 * 使用Executor将响应分发到主线程的实现类
 */
public class ExecutorDelivery implements ResponseDelivery {
    // 主线程的Executor
    private final Executor mResponsePoster;

    // 构造函数
    public ExecutorDelivery(final Handler handler) {
        // 创建一个在主线程执行的Executor
        mResponsePoster = new Executor() {
            @Override
            public void execute(Runnable command) {
                handler.post(command);
            }
        };
    }

    @Override
    public void postResponse(Request<?> request, Response<?> response) {
        postResponse(request, response, null);
    }

    @Override
    public void postResponse(Request<?> request, Response<?> response, Runnable runnable) {
        // 标记请求已交付响应
        request.markDelivered();
        request.addMarker("post-response");
        
        // 创建并执行分发响应的Runnable
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, response, runnable));
    }

    @Override
    public void postError(Request<?> request, VolleyError error) {
        request.addMarker("post-error");
        
        // 设置错误的请求
        error.setRequest(request);
        
        // 创建并执行分发错误的Runnable
        mResponsePoster.execute(new ResponseDeliveryRunnable(request, error));
    }

    @Override
    public void postRetry(Request<?> request) {
        request.addMarker("post-retry");
        
        // 创建并执行重试的Runnable
        mResponsePoster.execute(new ResponseDeliveryRunnable(request));
    }

    /**
     * 响应分发的Runnable
     */
    @SuppressWarnings("rawtypes")
    private class ResponseDeliveryRunnable implements Runnable {
        private final Request mRequest;
        private final Response mResponse;
        private final VolleyError mError;
        private final Runnable mRunnable;
        private final boolean mIsRetry;

        // 不同参数的构造函数
        public ResponseDeliveryRunnable(Request request, Response response, Runnable runnable) {
            mRequest = request;
            mResponse = response;
            mError = null;
            mRunnable = runnable;
            mIsRetry = false;
        }

        public ResponseDeliveryRunnable(Request request, VolleyError error) {
            mRequest = request;
            mResponse = null;
            mError = error;
            mRunnable = null;
            mIsRetry = false;
        }

        public ResponseDeliveryRunnable(Request request) {
            mRequest = request;
            mResponse = null;
            mError = null;
            mRunnable = null;
            mIsRetry = true;
        }

        @Override
        public void run() {
            // 如果请求已被取消,不执行任何操作
            if (mRequest.isCanceled()) {
                mRequest.finish("canceled-at-delivery");
                return;
            }

            if (mIsRetry) {
                // 处理重试请求
                mRequestQueue.add(mRequest);
                return;
            }

            if (mResponse != null) {
                // 处理成功响应
                mRequest.deliverResponse(mResponse.result);
                
                // 如果有回调,执行回调
                if (mRunnable != null) {
                    mRunnable.run();
                }
            } else {
                // 处理错误响应
                mRequest.deliverError(mError);
            }

            // 如果请求应该被缓存,执行缓存相关操作
            if (mResponse != null && mResponse.isSuccess()) {
                mRequest.finish("done");
            }
        }
    }
}

NetworkDispatcher完成数据转换得到Response对象后,调用mDelivery.postResponse(request, response)将响应分发到主线程。ExecutorDeliverypostResponse方法会创建一个ResponseDeliveryRunnable对象,并通过mResponsePoster在主线程执行。在ResponseDeliveryRunnablerun方法中,如果是成功响应,会调用mRequest.deliverResponse(mResponse.result),将数据转换后的结果传递给请求的响应监听器;如果是错误响应,则调用mRequest.deliverError(mError),将错误信息传递给错误监听器 ,从而完成整个响应数据格式转换结果的处理与分发流程。

如果还想了解Volley响应数据格式转换在其他方面的细节,比如与不同网络协议结合的处理,或是更复杂数据结构的转换优化,欢迎和我说说。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android 小码蜂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值