Android组件化开发(二)--网络请求组件封装

简介: 前面一篇文章我们讲解了`maven私服`的搭建,maven私服在`组件化框架`中有一个很重要的地位就是可以将我们的`lib`库放到局域网中,供公司其他开发者使用,实现类库的分享。下面是这个系列准备实现的一个`组件化实战项目框架`:
🔥 Hi,我是小余。

本文已收录到 GitHub · Androider-Planet 中。这里有 Android 进阶成长知识体系,关注公众号 [小余的自习室] ,在成功的路上不迷路!

前言

前面一篇文章我们讲解了maven私服的搭建,maven私服在组件化框架中有一个很重要的地位就是可以将我们的lib库放到局域网中,供公司其他开发者使用,实现类库的分享。

下面是这个系列准备实现的一个组件化实战项目框架

组件化开发.png

  • 笔者打算从下往上依次来实现我们项目中的组件,毕竟地基稳固了,房子才可以搭的很结实

注意:这里不会对封装代码进行长篇大论,主要还是以思路点拨的方式进行,如果需要看完整代码的可以移步到github。

GitHub - ByteYuhb/anna_music_app

  • 这篇文章实现的是一个lib_network库:

实现一个组件的封装前,我们有几个步骤,这几个步骤不可或缺,如果上来就直接码代码,最后会让你从激情到放弃

  • 1.先分析需求

    • 每个类的封装都是有了新的需求一步一步实现扩大的,不可能一蹴而就,包括笔者今天讲解的lib_network类库,后期也会根据用户需求一步一步壮大。

      既然是要实现一个网络请求的封装库:主要包括get,postform表单请求,文件上传和下载等一些基础网络功能的实现

  • 2.根据需求进行技术选型

    • 技术选型在类库封装中也是一个重要步骤,这里我们只是实现一些网络基础功能,笔者打算在HttpUrlConnectionVollyOkHttp中选择我们我们的封装基础库

1.技术选型.png

几个待选型的技术对比:

  • HttpUrlConnection

这个类是一个比较底层的类库了,如果使用这个类库来封装,我们需要实现很多轮子工作,而在另外两个开源框架VollyOkHttp已经实现了这些工作,没必要重复造轮子,在这里不考虑。

如果您对库有更深层次的要求且对自己技术比较自信,可以考虑使用这个类库去封装实现,毕竟开源库也是在这些基础库上实现的。

  • Volly

这个类库是在HttpUrlConnection 基础上做的一层封装,为了减少用户使用HttpUrlConnection的复杂度。

一开始出来是google主推的网络请求框架,google希望统一Android网路请求库推出的一个框架。那为什么后面用的人越来越少了呢。那就是因为下面我们要说的OkHttp框架。

  • OkHttp

看过源码的都知道,OkHttp也是在HttpUrlConnecttion上做的封装,其继承了Volly的优势,且在Volly上构建了自己的有优点,包括:连接池复用重试重定向机制拦截器模式等、

具体关于OkHttp的介绍可以参考我的另外一篇文章:

Android体系课 之 OkHttp你想知道的都在这里了--

笔者最后选择使用OkHttp来做我们基础库的封装工作

  • 3.封装思路

    前面通过对具体需求分析,并且也做了技术选型

接下来就是怎么去实现这个封装?需要封装哪些类?

我们来回忆下使用OkHttp的方式:

fun testOkHttp(){
    val client = OkHttpClient()
    val r1:RequestBody = formBodyBuilder.build()
    val request = Request.Builder().get().url("http://host:port/api").build()
    val response = client.newCall(request).execute()
    print(response.body()?.bytes())
}

笔者思路:首先封装的是和用户直接打交道的类

RequestResponseOkHttpClient以及异步情况下的Callback是直接和用户打交道的地方,那我们就从这几个类下手依次对其进行封装:

我们先列出来我们技术方案:

2.封装思路.png

有了上面几个分析步骤:接下来我们就来实现具体的封装流程:

  • 1.Request请求的封装 Request:用户请求类,在OkHttp中封装了我们的业务请求信息

这里我们创建一个CommonRequest类来再次封装我们的Request

笔者只列出了部分类的框架代码:完整代码在github上

public class CommonRequest {
    /**创建一个Post的请求,不包括headers
     * @return
     */
    public static Request createPostRequest(String url, RequestParams params){
        return createPostRequest(url,params,null);
    }
    /**创建一个Post的请求,包括headers
     * @param url
     * @param params body的参数集合
     * @param headers header的参数集合
     * @return
     */
    public static Request createPostRequest(String url, RequestParams params, RequestParams headers){
        FormBody.Builder mFormBodyBuilder = new FormBody.Builder();
        if(params!=null){
            for(Map.Entry<String,String> entry:params.urlParams.entrySet()){
                mFormBodyBuilder.add(entry.getKey(),entry.getValue());
            }
        }
        Headers.Builder mHeadersBuilder = new Headers.Builder();
        if(headers!=null){
            for(Map.Entry<String,String> entry:headers.urlParams.entrySet()){
                mHeadersBuilder.add(entry.getKey(),entry.getValue());
            }
        }
        return new Request.Builder()
                .url(url)
                .headers(mHeadersBuilder.build())
                .post(mFormBodyBuilder.build())
                .build();
    }

    /**创建一个不包含header的get请求
     * @return
     */
    public static Request createGetRequest(String url,RequestParams params){
        return createGetRequest(url,params,null);
    }
    /**创建一个Get的请求,包括headers
     * @return
     */
    public static Request createGetRequest(String url,RequestParams params,RequestParams headers){
        StringBuilder stringBuilder = new StringBuilder(url).append("?");
        if(params != null){
            for(Map.Entry<String,String> entry:params.urlParams.entrySet()){
                stringBuilder.append(entry.getKey()).append("=").append(entry.getValue());
            }
        }
        Headers.Builder mHeadersBuilder = new Headers.Builder();
        if(headers!=null){
            for(Map.Entry<String,String> entry:headers.urlParams.entrySet()){
                mHeadersBuilder.add(entry.getKey(),entry.getValue());
            }
        }
        return new Request.Builder()
                .url(stringBuilder.toString())
                .headers(mHeadersBuilder.build())
                .get()
                .build();
    }
    private static final MediaType FILE_TYPE = MediaType.parse("application/octet-stream");

    /**文件上传请求
     * @return
     */
    public static Request createMultiPostRequest(String url,RequestParams params){
        MultipartBody.Builder requestBuilder = new MultipartBody.Builder();
        requestBuilder.setType(MultipartBody.FORM);
        if(params != null){
            for (Map.Entry<String, Object> entry : params.fileParams.entrySet()) {
                if (entry.getValue() instanceof File) {
                    requestBuilder.addPart(Headers.of("Content-Disposition","form-data; name="" + entry.getKey() + """),
                            RequestBody.create(FILE_TYPE, (File) entry.getValue()));
                }else if (entry.getValue() instanceof String) {
                    requestBuilder.addPart(Headers.of("Content-Disposition", "form-data; name="" + entry.getKey() + """),
                            RequestBody.create(null, (String) entry.getValue()));
                }
            }
        }
        return new Request.Builder().url(url).post(requestBuilder.build()).build();
    }

    /**文件下载请求
     * @param url
     * @return
     */
    public static Request createFileDownLoadRequest(String url,RequestParams params){
        return createGetRequest(url,params);
    }
    /**文件下载请求
     * @param url
     * @return
     */
    public static Request createFileDownLoadRequest(String url){
        return createGetRequest(url,null);
    }
}

可以看到我们在这个类里面创建了几个方法:

  • 1.Get请求
  • 2.Post请求
  • 3.文件上传
  • 4.文件下载

这几个请求,已经可以基本满足我们实战项目的要求了

    1. response响应的封装

由于response请求是在CallBack中返回的,思路就是,自定义业务层需要的CallBack,尽量让代码轻量化

这里创建了两个CallBack类:

  • 1.CommonFileResponse

这个类主要是由来对文件类型的请求回调进行封装:

CommonFileResponse封装思路:

  • 1.对失败相应直接通过业务层传递下来的Listener回调给业务层失败结果
  • 2.对成功的相应,我们先将输入流中的数据写入到文件中,并在主线程中回调文件下载进度给业务层。业务层可以在获取文件结果的同时,也可以获取文件下载进度。
/**
 * 专门处理文件的回调
 */
public class CommonFileCallBack implements Callback {
    /**
     * the java layer exception, do not same to the logic error
     */
    protected final int NETWORK_ERROR = -1; // the network relative error
    protected final int IO_ERROR = -2; // the JSON relative error
    protected final String EMPTY_MSG = "";
    /**
     * 将其它线程的数据转发到UI线程
     */
    private static final int PROGRESS_MESSAGE = 0x01;
    private Handler mDeliveryHandler;
    private DisposeDownloadListener mListener;
    private String mFilePath;
    private int mProgress;
    public CommonFileCallBack(DisposeDataHandle handle){
        this.mListener = (DisposeDownloadListener) handle.mListener;
        this.mFilePath = handle.mSource;
        this.mDeliveryHandler = new Handler(Looper.getMainLooper()){
            @Override
            public void handleMessage(@NonNull Message msg) {
                switch (msg.what) {
                    case PROGRESS_MESSAGE:
                        mListener.onProgress((int) msg.obj);
                        break;
                }
            }
        };
    }
    @Override
    public void onFailure(Call call, IOException e) {
        mDeliveryHandler.post(new Runnable() {
            @Override
            public void run() {
                mListener.onFailure(new OkHttpException(NETWORK_ERROR, e));
            }
        });
    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        final File file = handleResponse(response);
        mDeliveryHandler.post(new Runnable() {
            @Override
            public void run() {
                if (file != null) {
                    mListener.onSuccess(file);
                } else {
                    mListener.onFailure(new OkHttpException(IO_ERROR, EMPTY_MSG));
                }
            }
        });
    }

    private File handleResponse(Response response) {
        ...
            while ((length = inputStream.read(buffer)) != -1) {
                fos.write(buffer, 0, length);
                currentLength += length;
                mProgress = (int) (currentLength / sumLength * 100);
                mDeliveryHandler.obtainMessage(PROGRESS_MESSAGE, mProgress).sendToTarget();
            }
            fos.flush();
        } catch (Exception e) {
            file = null;
        } finally {
            ...
        }
        return file;
    }

}
  • 2.CommonJsonResponse

这个类用处和我们的Retrofit类似,将请求转换为我们需要的类,通过Gson或者fastJson等框架处理:

/**
 * @author anna
 * @function 专门处理JSON的回调
 */
public class CommonJsonCallback implements Callback {

    /**
     * the logic layer exception, may alter in different app
     */
    protected final String RESULT_CODE = "ecode"; // 有返回则对于http请求来说是成功的,但还有可能是业务逻辑上的错误
    protected final int RESULT_CODE_VALUE = 0;
    protected final String ERROR_MSG = "emsg";
    protected final String EMPTY_MSG = "";

    /**
     * the java layer exception, do not same to the logic error
     */
    protected final int NETWORK_ERROR = -1; // the network relative error
    protected final int JSON_ERROR = -2; // the JSON relative error
    protected final int OTHER_ERROR = -3; // the unknow error

    /**
     * 将其它线程的数据转发到UI线程
     */
    private Handler mDeliveryHandler;
    private DisposeDataListener mListener;
    private Class<?> mClass;

    public CommonJsonCallback(DisposeDataHandle handle) {
        this.mListener = handle.mListener;
        this.mClass = handle.mClass;
        this.mDeliveryHandler = new Handler(Looper.getMainLooper());
    }

    @Override
    public void onFailure(final Call call, final IOException ioexception) {
        /**
         * 此时还在非UI线程,因此要转发
         */
        mDeliveryHandler.post(new Runnable() {
            @Override
            public void run() {
                mListener.onFailure(new OkHttpException(NETWORK_ERROR, ioexception));
            }
        });
    }

    @Override
    public void onResponse(final Call call, final Response response) throws IOException {
        final String result = response.body().string();
        mDeliveryHandler.post(new Runnable() {
            @Override
            public void run() {
                handleResponse(result);
            }
        });
    }

    private void handleResponse(Object responseObj) {
        if (responseObj == null || responseObj.toString().trim().equals("")) {
            mListener.onFailure(new OkHttpException(NETWORK_ERROR, EMPTY_MSG));
            return;
        }

        try {
            /**
             * 协议确定后看这里如何修改
             */
            JSONObject result = new JSONObject(responseObj.toString());
            if (mClass == null) {
                mListener.onSuccess(result);
            } else {
                Object obj = new Gson().fromJson(responseObj.toString(), mClass);
                if (obj != null) {
                    mListener.onSuccess(obj);
                } else {
                    mListener.onFailure(new OkHttpException(JSON_ERROR, EMPTY_MSG));
                }
            }
        } catch (Exception e) {
            mListener.onFailure(new OkHttpException(OTHER_ERROR, e.getMessage()));
            e.printStackTrace();
        }
    }
}
    1. OkHttpClient请求的封装

OkHttpClient是我们OkHttp的核心枢纽,业务层以及核心框架层都需要使用到这个类,我们来思考下怎么去封装这个类:

1.我们使用装饰器模式:在CommonOkHttpClient中封装一个OkHttpClient对象,通过CommonOkHttpClient去代理这个OkHttpClient对象

来看下我们封装的代码:

public class CommonOkHttpClient {
    private static final int TIME_OUT = 30;
    private static OkHttpClient mOkHttpClient;
    static {
        OkHttpClient.Builder mOkHttpClientBuilder = new OkHttpClient.Builder();
        mOkHttpClientBuilder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });
        //添加自定义的拦截器
        mOkHttpClientBuilder.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request()
                        .newBuilder()
                        .addHeader("User-Agent","anna-movie")
                        .build();
                return chain.proceed(request);
            }
        });
        mOkHttpClientBuilder.cookieJar(new SimpleCookieJar());
        mOkHttpClientBuilder.connectTimeout(TIME_OUT, TimeUnit.SECONDS);
        mOkHttpClientBuilder.readTimeout(TIME_OUT, TimeUnit.SECONDS);
        mOkHttpClientBuilder.writeTimeout(TIME_OUT, TimeUnit.SECONDS);
        //设置是否支持重定向
        mOkHttpClientBuilder.followRedirects(true);
        //设置代理
       // mOkHttpClientBuilder.proxy()
        mOkHttpClientBuilder.sslSocketFactory(HttpsUtils.initSSLSocketFactory(),
                HttpsUtils.initTrustManager());
        mOkHttpClient = mOkHttpClientBuilder.build();
    }
    public static OkHttpClient getOkHttpClient() {
        return mOkHttpClient;
    }

    /**
     * 通过构造好的Request,Callback去发送请求
     */
    public static Call get(Request request, DisposeDataHandle handle) {
        Call call = mOkHttpClient.newCall(request);
        call.enqueue(new CommonJsonCallback(handle));
        return call;
    }
    public static Call post(Request request, DisposeDataHandle handle) {
        Call call = mOkHttpClient.newCall(request);
        call.enqueue(new CommonJsonCallback(handle));
        return call;
    }

    public static Call downloadFile(Request request, DisposeDataHandle handle) {
        Call call = mOkHttpClient.newCall(request);
        call.enqueue(new CommonFileCallBack(handle));
        return call;
    }
}

这里面封装了一个getpost,以及文件下载的请求:

对于业务层只需要调用:

CommonOkHttpClient.get(...)
CommonOkHttpClient.post(...)
CommonOkHttpClient.downloadFile(..)

总结:

本篇文章主要以封装思路的方式进行讲解,具体代码可以移步到github

GitHub - ByteYuhb/anna_music_app

对于大部分类库的封装都可以使用我们上面的思路,再结合maven私服的使用。可以很好的将我们代码作为一个组件共享给开发同事使用

组件化道路长远,这里我们只是封装了一个网络请求库,后面会不定期对其他类库进行封装,最后整合成一个完整的组件化框架

组件化开发.png

相关文章
|
25天前
|
Java Android开发
Android 开发获取通知栏权限时会出现两个应用图标
Android 开发获取通知栏权限时会出现两个应用图标
12 0
|
2天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
20 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
7天前
|
Android开发 开发者
Android网络和数据交互: 请解释Android中的AsyncTask的作用。
Android&#39;s AsyncTask simplifies asynchronous tasks for brief background work, bridging UI and worker threads. It involves execute() for starting tasks, doInBackground() for background execution, publishProgress() for progress updates, and onPostExecute() for returning results to the main thread.
9 0
|
7天前
|
网络协议 安全 API
Android网络和数据交互: 什么是HTTP和HTTPS?在Android中如何进行网络请求?
HTTP和HTTPS是网络数据传输协议,HTTP基于TCP/IP,简单快速,HTTPS则是加密的HTTP,确保数据安全。在Android中,过去常用HttpURLConnection和HttpClient,但HttpClient自Android 6.0起被移除。现在推荐使用支持TLS、流式上传下载、超时配置等特性的HttpsURLConnection进行网络请求。
8 0
|
11天前
|
存储 数据库 Android开发
构建高效安卓应用:采用Jetpack架构组件优化用户体验
【4月更文挑战第12天】 在当今快速发展的数字时代,Android 应用程序的流畅性与响应速度对用户满意度至关重要。为提高应用性能并降低维护成本,开发者需寻求先进的技术解决方案。本文将探讨如何利用 Android Jetpack 中的架构组件 — 如 LiveData、ViewModel 和 Room — 来构建高质量的安卓应用。通过具体实施案例分析,我们将展示这些组件如何协同工作以实现数据持久化、界面与逻辑分离,以及确保数据的即时更新,从而优化用户体验并提升应用的可维护性和可测试性。
|
16天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。
|
18天前
|
监控 算法 Android开发
安卓应用开发:打造高效启动流程
【4月更文挑战第5天】 在移动应用的世界中,用户的第一印象至关重要。特别是对于安卓应用而言,启动时间是用户体验的关键指标之一。本文将深入探讨如何优化安卓应用的启动流程,从而减少启动时间,提升用户满意度。我们将从分析应用启动流程的各个阶段入手,提出一系列实用的技术策略,包括代码层面的优化、资源加载的管理以及异步初始化等,帮助开发者构建快速响应的安卓应用。
|
18天前
|
Java Android开发
Android开发之使用OpenGL实现翻书动画
本文讲述了如何使用OpenGL实现更平滑、逼真的电子书翻页动画,以解决传统贝塞尔曲线方法存在的卡顿和阴影问题。作者分享了一个改造后的外国代码示例,提供了从前往后和从后往前的翻页效果动图。文章附带了`GlTurnActivity`的Java代码片段,展示如何加载和显示书籍图片。完整工程代码可在作者的GitHub找到:https://github.com/aqi00/note/tree/master/ExmOpenGL。
19 1
Android开发之使用OpenGL实现翻书动画
|
18天前
|
Android开发 开发者
Android开发之OpenGL的画笔工具GL10
这篇文章简述了OpenGL通过GL10进行三维图形绘制,强调颜色取值范围为0.0到1.0,背景和画笔颜色设置方法;介绍了三维坐标系及与之相关的旋转、平移和缩放操作;最后探讨了坐标矩阵变换,包括设置绘图区域、调整镜头参数和改变观测方位。示例代码展示了如何使用这些方法创建简单的三维立方体。
15 1
Android开发之OpenGL的画笔工具GL10
|
20天前
|
SQL 前端开发 Java
五邑大学餐厅网络点餐系统设计与实现(包含完整源码详细开发过程)
五邑大学餐厅网络点餐系统设计与实现(包含完整源码详细开发过程)