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

相关文章
|
21天前
|
搜索推荐 前端开发 API
探索安卓开发中的自定义视图:打造个性化用户界面
在安卓应用开发的广阔天地中,自定义视图是一块神奇的画布,让开发者能够突破标准控件的限制,绘制出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战技巧,逐步揭示如何在安卓平台上创建和运用自定义视图来提升用户体验。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你的应用在众多同质化产品中脱颖而出。
42 19
|
21天前
|
JSON Java API
探索安卓开发:打造你的首个天气应用
在这篇技术指南中,我们将一起潜入安卓开发的海洋,学习如何从零开始构建一个简单的天气应用。通过这个实践项目,你将掌握安卓开发的核心概念、界面设计、网络编程以及数据解析等技能。无论你是初学者还是有一定基础的开发者,这篇文章都将为你提供一个清晰的路线图和实用的代码示例,帮助你在安卓开发的道路上迈出坚实的一步。让我们一起开始这段旅程,打造属于你自己的第一个安卓应用吧!
46 14
|
20天前
|
开发框架 Android开发 iOS开发
安卓与iOS开发中的跨平台策略:一次编码,多平台部署
在移动应用开发的广阔天地中,安卓和iOS两大阵营各占一方。随着技术的发展,跨平台开发框架应运而生,它们承诺着“一次编码,到处运行”的便捷。本文将深入探讨跨平台开发的现状、挑战以及未来趋势,同时通过代码示例揭示跨平台工具的实际运用。
|
22天前
|
XML 搜索推荐 前端开发
安卓开发中的自定义视图:打造个性化UI组件
在安卓应用开发中,自定义视图是一种强大的工具,它允许开发者创造独一无二的用户界面元素,从而提升应用的外观和用户体验。本文将通过一个简单的自定义视图示例,引导你了解如何在安卓项目中实现自定义组件,并探讨其背后的技术原理。我们将从基础的View类讲起,逐步深入到绘图、事件处理以及性能优化等方面。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧。
|
21天前
|
搜索推荐 前端开发 测试技术
打造个性化安卓应用:从设计到开发的全面指南
在这个数字时代,拥有一个定制的移动应用不仅是一种趋势,更是个人或企业品牌的重要延伸。本文将引导你通过一系列简单易懂的步骤,从构思你的应用理念开始,直至实现一个功能齐全的安卓应用。无论你是编程新手还是希望拓展技能的开发者,这篇文章都将为你提供必要的工具和知识,帮助你将创意转化为现实。
|
21天前
|
Java Android开发 开发者
探索安卓开发:构建你的第一个“Hello World”应用
在安卓开发的浩瀚海洋中,每个新手都渴望扬帆起航。本文将作为你的指南针,引领你通过创建一个简单的“Hello World”应用,迈出安卓开发的第一步。我们将一起搭建开发环境、了解基本概念,并编写第一行代码。就像印度圣雄甘地所说:“你必须成为你希望在世界上看到的改变。”让我们一起开始这段旅程,成为我们想要见到的开发者吧!
29 0
|
10天前
|
SQL 安全 网络安全
网络安全与信息安全:知识分享####
【10月更文挑战第21天】 随着数字化时代的快速发展,网络安全和信息安全已成为个人和企业不可忽视的关键问题。本文将探讨网络安全漏洞、加密技术以及安全意识的重要性,并提供一些实用的建议,帮助读者提高自身的网络安全防护能力。 ####
48 17
|
21天前
|
存储 SQL 安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
随着互联网的普及,网络安全问题日益突出。本文将介绍网络安全的重要性,分析常见的网络安全漏洞及其危害,探讨加密技术在保障网络安全中的作用,并强调提高安全意识的必要性。通过本文的学习,读者将了解网络安全的基本概念和应对策略,提升个人和组织的网络安全防护能力。
|
22天前
|
SQL 安全 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
随着互联网的普及,网络安全问题日益突出。本文将从网络安全漏洞、加密技术和安全意识三个方面进行探讨,旨在提高读者对网络安全的认识和防范能力。通过分析常见的网络安全漏洞,介绍加密技术的基本原理和应用,以及强调安全意识的重要性,帮助读者更好地保护自己的网络信息安全。
43 10
|
23天前
|
SQL 安全 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
在数字化时代,网络安全和信息安全已成为我们生活中不可或缺的一部分。本文将介绍网络安全漏洞、加密技术和安全意识等方面的内容,并提供一些实用的代码示例。通过阅读本文,您将了解到如何保护自己的网络安全,以及如何提高自己的信息安全意识。
47 10