游戏SDK架构设计之代码实现

您所在的位置:网站首页 文本文档小游戏代码 游戏SDK架构设计之代码实现

游戏SDK架构设计之代码实现

2023-03-17 01:38| 来源: 网络整理| 查看: 265

OKHttp 源码解析(一) OKHttp 源码解析(二)拦截器

前言

前篇介绍了游戏SDK的基本架构设计,其中一个模块是基础工具库,基础工具库的内容包括:统一封装的网络框架,可以使用okhttp、volley、retrofit,或自行写的异步任务也可以。还有存储工具类、文件工具类、热修复工具类等。这个篇幅先介绍一下网络框架。

由于游戏SDK是提供给CP使用的,为避免不必要的第三方库冲突,也同时减少包体,游戏SDK的应尽量减少引入第三方库。

这里的网络框架从一开始由游戏SDK自行封装的异步任务后改为封装 OKHttp,考虑的原因有以下几点:

自行封装的网络框架无法满足需求某个任务的需求,比如有业务场景,当用户取消登录时,需要立即取消登录请求,不再继续登录。目前 OKHttp 非常普遍,Android 原生的HttpClient在 android5.0被废弃,6.0逐渐删除。HttpURLConnection 是一个轻量级的http客户端,api 较少,用得不是很方便。而OKHttp支持 2.3 及以上版本,支持 JDK 1.7 及以上版本。 网络框架设计的需求分析

我们写的代码就是为了满足需求的,如何设计一个框架也是从需求出发,设计一个最适合项目的网络框架。游戏SDK的网络请求需求分析:

正常的 post/get 请求;多文件上传文件、下载文件;统一的回调处理是否需要提示统一错误;统一处理返回值。请求失败时自动重试。 OKHttp 二次封装

这里只简单说一下整个流程,引出后面对 OKHttp 的理解分析。

以下只说使用过程,源码详解见:

1、初始化及发送请求

创建单例管理类,单例实例使用 volatile 关键词修饰,关于 volatile 的作用:https://blog.csdn.net/MarinaTsang/article/details/84306990 // 初始化 okhttp ,单例管理 public class OkHttpManager { private static final String TAG = "[OkHttpManager.requestServerData]"; private static int TIME_OUT = 10; private static int READ_TIME_OUT = 15; private static int MAX_RETRY = 3; // 设置最长下载时间 30s private static int MAX_DOWNLOAD_TIME = 30; private static volatile OkHttpManager mManager; private OkHttpClient mOkHttpClient; public OkHttpManager() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); builder.hostnameVerifier((hostname, session) -> true); // builder.sslSocketFactory(SSL.getSSLSocketFactory(), SSL.getTrustAllCert()); // 尝试解决:okhttp3.internal.framed.StreamResetException: stream was reset: CANCEL // https://github.com/square/okhttp/issues/3955 // builder.protocols(Collections.singletonList(Protocol.HTTP_1_1)); // 这个默认只重试一次; 关闭默认重连机制 builder.retryOnConnectionFailure(false); builder.connectTimeout(TIME_OUT, TimeUnit.SECONDS); builder.readTimeout(READ_TIME_OUT, TimeUnit.SECONDS); builder.writeTimeout(READ_TIME_OUT, TimeUnit.SECONDS); // 允许重定向 builder.followRedirects(true); // 自动重试拦截器 builder.addInterceptor(new RetryInterceptor(MAX_RETRY)); // 解决 java.io.IOException: unexpected end of stream 问题。 builder.addInterceptor(new NetInterceptor()); mOkHttpClient = builder.build(); // 设置最大请求并发数 // mOkHttpClient.dispatcher().setMaxRequestsPerHost(15); } /** * 单例 */ public static OkHttpManager getInstance() { if (mManager == null) { synchronized (OkHttpManager.class) { if (mManager == null) { mManager = new OkHttpManager(); } } } return mManager; } // 之后的就是 get 请求 和 post 请求 /** * 网络请求统一分发 * * @param method * @param url * @param params * @param tag */ public void requestServerData(String method, String url, Map params, Object tag, String urlType, DisposeDataHandler handler) { // 先检测是否有网 if (isNetworkAvailable(handler)) { return; } // 拼接 url,不同的跟域名,使用一个 HttpConfig 来统一管理 if (HttpConfig.URL_TYPE_SAFE_API.equals(urlType)) { url = PlatInfo.getSdkRootApi() + url; } else if (HttpConfig.URL_TYPE_DATA_COLLECT.equals(urlType)) { url = PlatInfo.getDataCollectorApi() + url; } RequestParams requestParams = new RequestParams(params); if (params != null) { LogUtil.d(TAG, "params===>", params.toString()); } // CommonRequest 根据不同类型,构建不同的请求体 Request request = null; if (UrlUtils.METHOD_GET.equals(method)) { request = CommonRequest.createGetRequest(url, tag, requestParams); } else if (UrlUtils.METHOD_POST.equals(method)) { request = CommonRequest.createPostRequest(url, tag, requestParams); } else if (UrlUtils.METHOD_POST_JSON.equals(method)) { request = CommonRequest.createPostJsonRequest(url, tag, requestParams); } if (request != null) { // CommonCallback 统一处理回调 mOkHttpClient.newCall(request).enqueue(new CommonCallback(handler)); } } // 上传文件和下载文件类似 }

2、构建请求体

public class CommonRequest { private static final String TAG = "[CommonRequest]"; public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); private static final MediaType FILE_TYPE = MediaType.parse("multipart/form-data; charset=utf-8"); /** * 发送 get 请求 * * @param url * @param params * @return */ public static Request createGetRequest(@NotNull String url, Object tag, @Nullable RequestParams params) { StringBuilder urlBuilder = new StringBuilder(url).append("?"); if (params != null) { for (Map.Entry entry : params.getParams().entrySet()) { urlBuilder.append(entry.getKey()).append("=").append(UrlUtils.URLEncode((String) entry.getValue())).append("&"); } } return getBuilderAddTag(tag).get().url(urlBuilder.substring(0, urlBuilder.length() - 1)).build(); } private static Request.Builder getBuilderAddTag(Object tag) { Request.Builder builder = new Request.Builder(); if (tag != null) { builder.tag(tag); } return builder; } /** * 发送 post 表单请求 * * @param url * @param params * @return */ public static Request createPostRequest(@NotNull String url, Object tag, @NotNull RequestParams params) { FormBody.Builder builder = new FormBody.Builder(); for (Map.Entry entry : params.getParams().entrySet()) { builder.add(entry.getKey(), (String) entry.getValue()); } FormBody formBody = builder.build(); return getBuilderAddTag(tag).url(url).post(formBody).build(); } /** * 发送post JSON 请求 * * @param url * @param params * @return */ public static Request createPostJsonRequest(@NotNull String url, Object tag, @NotNull RequestParams params) { RequestBody body = RequestBody.create(JSON, JsonUtils.map2jsonObject(params.getParams()).toString()); return getBuilderAddTag(tag).post(body).url(url).build(); } /** * 创建文件上传请求类 * * @param url * @param tag * @param params * @param fileParams * @return */ public static Request createFilePost(@NotNull String url, Object tag, @Nullable Map params, @NotNull Map fileParams) { MultipartBody.Builder builder = addFileRegulaParams(params); // 添加多文件 Iterator iterator = fileParams.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry next = iterator.next(); String key = next.getKey(); List fileList = next.getValue(); if (fileList == null) { continue; } // 循环添加文件 for (File file : fileList) { if (file != null) { builder.addFormDataPart(key, file.getName(), RequestBody.create(FILE_TYPE, file)); } } } RequestBody build = builder.build(); return getBuilderAddTag(tag).post(build).url(url).build(); } /** * 创建文件上传请求类 * 文件需要根据分类重命名 * * @param url * @param tag * @param params * @param fileParams 需要分类重命名的文件集合 * Map 外层是的key 是请求key ,比如服务端规定:logFiles, * Map 是文件列表集合,key 是分类,比如CP的日志文件对应一个 list file; * SDK 日志对应一个list file * 需要根据不同的 file 类型,传文件时的名字需要拼接名字 * @return */ public static Request createNeedRenameFilePost(@NotNull String url, Object tag, @Nullable Map params, @NotNull Map fileParams) { // 添加常规参数 MultipartBody.Builder builder = addFileRegulaParams(params); // 添加多文件 Iterator iterator = fileParams.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry next = iterator.next(); String key = next.getKey(); Map fileMap = next.getValue(); if (fileMap == null) { continue; } Iterator iterator1 = fileMap.entrySet().iterator(); while (iterator1.hasNext()) { Map.Entry typeMap = iterator1.next(); String typeMapKey = typeMap.getKey(); List fileList = typeMap.getValue(); if (fileList == null) { continue; } // 循环添加文件 for (File file : fileList) { if (file != null) { // 获取扩展名及没有扩展名的文件名 String extensionName = FileUtil.getExtensionName(file.getName()); String fileNameNoEx = FileUtil.getFileNameNoEx(file.getName()); builder.addFormDataPart(key, fileNameNoEx + "_" + typeMapKey + "." + extensionName, RequestBody.create(FILE_TYPE, file)); } } } } RequestBody build = builder.build(); return getBuilderAddTag(tag).post(build).url(url).build(); }

3、创建代理,统一处理 OKHttp 的回调

public class DisposeDataHandler { private DisposeDataListener mListener; private Class mClass; public DisposeDataHandler(DisposeDataListener mListener) { this.mListener = mListener; } public DisposeDataHandler(DisposeDataListener mListener, Class mClass) { this.mListener = mListener; this.mClass = mClass; } public void onSuccess(String responseObj) { mListener.onSuccess(responseObj); } public void onFailure(OkHttpException exception) { mListener.onFailure(exception); } public Class getClassType() { return mClass; } }

4、处理 OKHttp 的回调

public class CommonCallback implements Callback { private static final String TAG = "[CommonCallback]"; private static String VALIDATION_FAILED = "Chain validation failed"; protected DisposeDataHandler mDisposeHandler; // 切换回主线程 protected Handler mHandler = new Handler(Looper.getMainLooper()); public CommonCallback(DisposeDataHandler mDisposeHandler) { this.mDisposeHandler = mDisposeHandler; } /** * 网络请求失败, 开始监测网络 * * @param call * @param e */ @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { LogUtil.e(TAG, "onFailure connection error!", e.toString(), DeviceUtils.getRequestUuid()); // 请求失败,网络错误 mHandler.post(() -> { LogUtil.e(TAG, "onFailure connection error or time out! begin to callback"); String message = e.getMessage(); if (VALIDATION_FAILED.equals(message)) { mDisposeHandler.onFailure(new OkHttpException(PlatErrorCode.VALIDATION_ERROR, message)); } else { mDisposeHandler.onFailure(new OkHttpException(PlatErrorCode.CONNECTION_ERROR, PlatErrorCode.MSG_CONNECTION_ERROR)); } // 开始监测网络 INetTool iNetTool = NTSDK.getInstance().getNetToolManager(); if (iNetTool != null) { iNetTool.checkNetSdkAuto(ApkUtils.getApplication().getApplicationContext()); } }); } @Override public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { if (!response.isSuccessful()) { LogUtil.e(TAG, "onResponse NOT success."); mHandler.post(() -> { LogUtil.e(TAG, "onResponse NOT success begin to callback."); mDisposeHandler.onFailure(new OkHttpException(PlatErrorCode.CONNECTION_RESPONSE_ERROR, PlatErrorCode.MSG_CONNECTION_RESPONSE_ERROR)); }); } else { LogUtil.i(TAG, "onResponse success."); mHandler.post(() -> { // 上传网络报告,如果有 INetTool iNetTool = NTSDK.getInstance().getNetToolManager(); if (iNetTool != null) { iNetTool.uploadNextTime(); } }); try { LogUtil.i(TAG, "onResponse success start handle response."); handleResponse(response); } catch (IOException e) { LogUtil.e(TAG, "onResponse io exception.",e.getMessage()); mDisposeHandler.onFailure(new OkHttpException(PlatErrorCode.RESPONSE_IO_ERROR, PlatErrorCode.MSG_RESPONSE_IO_ERROR)); e.printStackTrace(); } catch (Exception e) { LogUtil.e(TAG, "onResponse exception.",e.toString()); mDisposeHandler.onFailure(new OkHttpException(PlatErrorCode.RESPONSE_IO_ERROR, PlatErrorCode.MSG_RESPONSE_IO_ERROR)); e.printStackTrace(); } } } protected void handleResponse(Response response) throws IOException { ResponseBody body = response.body(); int code = response.code(); if (body == null) { LogUtil.e(TAG, "handleResponse:" + code, " body is NULL!"); mDisposeHandler.onFailure(new OkHttpException(code, PlatErrorCode.MSG_NO_DATA + code)); return; } String result = body.string(); if (TextUtils.isEmpty(result)) { LogUtil.e(TAG, "handleResponse:", " body is Empty!", result); mDisposeHandler.onFailure(new OkHttpException(PlatErrorCode.NO_DATA, PlatErrorCode.MSG_NO_DATA)); return; } LogUtil.e(TAG, "handleResponse:", " onSuccess ", result); mDisposeHandler.onSuccess(result); } }

5、根据和服务端的约定,对返回数据进行统一处理

private static class MyDisposeDataListener implements DisposeDataListener { private boolean isChange = false; private BaseCallback mBaseCallback; private String mUrl; private String mUrlType; public MyDisposeDataListener(BaseCallback mBaseCallback, String url, String urlType) { this.mBaseCallback = mBaseCallback; this.mUrl = url; this.mUrlType = urlType; } @Override public void onSuccess(String response) { LogUtil.i(TAG, mUrl, "disposeDataHandler.onSuccess"); LogUtil.i(TAG, mUrl, "onSuccess request uuid: ", DeviceUtils.getRequestUuid()); try { // 统一解析 parseNotPlatResponse(response); } catch (Exception e) { if (mBaseCallback != null) { mBaseCallback.onFailure(PlatErrorCode.DEFAULT_ERROR, PlatErrorCode.MSG_DEFAULT_ERROR); } e.printStackTrace(); } } }

6、将数据回调到业务层

public abstract class BaseCallback { /** * 部分接口不需要提示处理,例如启动时请求的接口,默认需要处理,如不需要处理单独传参数 */ private boolean isShowToast = true; private Context mContext; public BaseCallback(Context context) { this.mContext = context; } public BaseCallback(Context context, boolean isShowToast) { this.mContext = context; this.isShowToast = isShowToast; } /** * 数据请求成功 * * @param data 请求到的数据 ---- 不必使用泛型 */ protected abstract void onSuccess(JSONObject data); /** * 使用网络API接口请求方式时,虽然已经请求成功但是由 * 于{@code msg}的原因无法正常返回数据。 * * @param code 服务端返回的错误码 * @param msg 错误消息 */ public void onFailure(int code, String msg) { LogUtil.e(" request onFailure(),code = " + code, " msg = " + msg); if (isShowToast && mContext != null) { // 统一提示 if (code == PlatErrorCode.DEFAULT_ERROR) { ToastUtil.showNormalToast((Activity) mContext, RUtil.getString((Activity) mContext, "string_unknown_error_toast") + code); } else if (code == PlatErrorCode.NETWORK_ERROR) { ToastUtil.showNormalToast((Activity) mContext, RUtil.getString(mContext, "string_network_error_toast")); } } } }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3