Android 如何封装网络框架(以封装OkHttp为例)

版权声明:本文为博主原创文章,未经博主允许,也可以随意转载。但麻烦,加个友情链接,增加点人气。 https://blog.csdn.net/l_o_s/article/details/79390118

前言:
现在,在网络方面,安卓主流是采用Retrofit + RxJava2的组合。但是,天天用别人封好的东西,总不是办法。就好像天天去大宝健,总不如找个漂亮的女朋友好(有人说,有漂亮的女友,谁还去大宝健。。那就太年轻了,妻不如妾,妾不如妓,妓不如偷,偷不如偷不着)。虽然自己水平有限,但做人如果没有理想,那与咸鱼有什么区别。所以,冒着被喷成狗的风险,我毅然决然的写下这篇博客。毕竟骂不死我的,只会使我更强大。尝试不断的尝试,实践不断的实践,才会进步。。。 如果你觉得网上的其它封装思路过于复杂,可以看看这篇,简单易入手。封装的思路是低藕合,易扩展,易阅读,易维护,易调用。在这思路的基础上,实现了三个解藕:

1、解藕具体的第三方网络库。
2、解藕具体的第三方json解析库。
3、解藕具体的线程切换实现。

好了,开始主题,先从思考一个网络框架能做什么入手。

一、一个网络框架能做什么?(需求是发明之母)

一个网络框架能做什么?即是这个网络框架有哪些行为。为了规范定义网络框架的行为,需要定义一个IHttpManager接口。IHttpManager接口,用于定义网络框架的基本协议。比如,最基本的Get、Post请求,如果项目有需要,可以再添加诸如Put、Delete等其它请求方法。这样,不管用什么第三方库,对于封装了第三方库的类,都必须扩展自这个协议。比如,OkHttp和Retrofit,这两个第三方库来说。需要分别创建两个类OkHttpManager和RetrofitHttpManager,两者都实现IHttpManager接口。有了协议,那接下来,就是如何初始化基本的网络配置。

下面是IHttpManager的定义:

**
 * @Author Lyf
 * @CreateTime 2018/2/8
 * @Description An IHttpManager interface defines What the IHttpManager can do.
 * When you change your Http Framework to another, The new one must be implemented IHttpManager.
 * If do so, you will change nothing for the rest of project.
 **/
public interface IHttpManager {

    /**
     * @param url  A uniform resource locator (URL) with a scheme of either {@code http} or {@code https}.
     * @param params A set of params which will be sent to remote server when a request is sent.
     * @param responseCallback<T> a response returned by remote server.
     */
    <T> void doGet(@NonNull String url, @Nullable ArrayMap<String, Object> params, @Nullable Callback<T> responseCallback);

    /**
     * @param url  A uniform resource locator (URL) with a scheme of either {@code http} or {@code https}.
     * @param params A set of params which will be sent to remote server when a request is sent.
     * @param responseCallback<T> a response returned by remote server.
     */
    <T> void doPost(@NonNull String url, @Nullable ArrayMap<String, Object> params, @Nullable Callback<T> responseCallback);

}

二、如何初始化基本的网络配置?(如何开始)

基本的网络配置,比如超时、缓存、Https的证书配置。这样,就需要一个类,来统一初始化,于是创建了HttpManager。HttpManager采用Builder模式。有一个静态的方法,需要传入一个HttpBuilder类来初始化网络。同时,HttpManager,有一个getHttpManager的静态方法,但返回的不是HttpManager本身,而是一个IHttpManager对象(这个对象是静态的,采用单例实现,确保全局唯一)。因为不同的网络库,都会实现这个接口。所以,替换不同的网络库的时候,只要修改这个IHttpManager所代理的对象即可。这样就解藕了第三方网络库与实际请求。之前,考虑过用工厂模式来解藕。但因为,更换库的可能性比较小,同时,这里也达到解藕的目的。所以,采用工厂模式来获取不同网络库,需要再想。

下面是HttpManager的定义

/**
 * @Author Lyf
 * @CreateTime 2018/2/8
 * @Description A HttpManager class is in charge of Managing Http Framework.
 * Such as, sets some settings of http or decides to use which http library.
 **/
public final class HttpManager {

    // A tag for logging.
    private final static String TAG = HttpManager.class.getSimpleName();

    // mHttpManager is an IHttpManager instance.
    private static IHttpManager mHttpManager;

    // Init HttpManager.
    public static void initHttpManager(HttpBuilder httpBuilder) {

        mHttpManager = new OkHttpManager(httpBuilder);

        /*
         * Here is the code Shows you how to use Retrofit instead of OkHttp.
         * As you can see, What you only have to do is creating another Manager which implements the IHttpManager.
         */
        // mHttpManager = new RetrofitManager(httpBuilder);
    }

    /**
     * Returns An IHttpManager instance.
     * If the project never initialized the HttpManager, A RuntimeException will be thrown.
     */
    public static IHttpManager getHttpManager() {

        if (mHttpManager == null) {
            throw new RuntimeException("You have to invoke initHttpManager() to init HttpManager before you use it.");
        }

        return mHttpManager;
    }

    private HttpManager() {
        throw new UnsupportedOperationException("You can't instantiate this class but invoke getHttpManager() after called initHttpManager().");
    }

}

三、中间层。简化网络请求,并再次解藕具体项目与第三方网络框架。(如何简化与解藕)

为了简化网络请求和做一层请求拦截,并进步一步解藕xxHttpManager(比如OkHttpManager)与实际项目的耦合。这里抽象出一个BaseHttpUtil类,这是一个抽象类,有一个抽象方法,用于对参数进行签名。实际项目,继承该类,重写签名返回,需要签名,则签名。不需要,只要返回原参即可。这样做的好处是,在你更换第三方库的时候,签名的代码,并不会改动到。并且,任何中间层的拦截处理的代码,都不会改动到。同时,该类提供一些重载get、post的请求方法,因为有些网络请求不一定需要带参数或回调。

下面是BaseHttpUtil的定义

/**
 * @Author Lyf
 * @CreateTime 2018/1/23
 * @Description you can use BaseHttpUtil to do requests directly.
 * Note that: You may should rewrite the getSignParams() method to sign your params before doing request.
 * Return the original params if you don't need to sign your params.
 **/
public abstract class BaseHttpUtil {

    private IHttpManager mHttpManager = HttpManager.getHttpManager();

    /**
     * Add algorithms of sign.
     *
     * @param params the original requesting params.
     * @return The params with sign or the original params if you don't need to sign the params.
     */
    protected abstract ArrayMap<String, Object> getSignParams(ArrayMap<String, Object> params);

    public <T> void doGet(@NonNull String url, @Nullable ArrayMap<String, Object> params, @Nullable Callback<T> responseCallback) {
        mHttpManager.doGet(url, params, responseCallback);
    }

    public <T> void doPost(@NonNull String url, @Nullable ArrayMap<String, Object> params, @Nullable Callback<T> responseCallback) {
        mHttpManager.doPost(url, getSignParams(params), responseCallback);
    }

    // These methods below do request without params and listen.

    public void doGet(@NonNull String url) {
        doGet(url, null, null);
    }

    public void doPost(Object tag, String url) {
        doPost(url, null, null);
    }

    public void doPut(Object tag, String url) {

    }

    public void doDelete(Object tag, String url) {

    }

    // These methods below do request without params but with a listen.
    public <T> void doGet(@NonNull String url, @Nullable Callback<T> responseCallback) {
        doGet(url, null, responseCallback);
    }

    public <T> void doPost(@NonNull String url, @Nullable Callback<T> responseCallback) {
    }

    public void cancelRequestWithTag(Object tag) {

    }
}

四、创建具体的xxHttpManager类,来封装第三方名为xx的网络框架。(说了那么多,该怎么做?)

这里以OkHttpManager为例。创建一个OkHttpManager的同时,需要再创建一个IOkHttpManager接口(这个又继承自IHttpManager)。之所以,要创建这个IOkHttpManager接口,是为了将OkHttpManager的行为定义在接口中,这样以后维护的时候,就不是直接看该类的代码,而是优先看他的协议(面向协议)。同时,以后如果只是要重构这个OkHttpManager,也只需要照着IOkHttpManager的协议去重构即可。虽然,方法定义在接口里,会导致一些不必要对外暴露的方法的访问权限变成public,破坏了封装性。但是,外面实际是不能拿到IOkHttpManager或OkHttpManager的实例,而只能拿到IHttpManager这个最基础的代理。其封装性的破坏,相较于上面所述的好处,也就不重要了。

下面是OkHttpManager的定义

/**
 * @Author Lyf
 * @CreateTime 2018/2/8
 * @Description
 **/
public final class OkHttpManager implements IOkHttpManager {

    private final static String TAG = OkHttpManager.class.getSimpleName();

    // Cache the Headers to avoid creating Headers for each request.
    private static Headers mOkHttpHeaders;

    // It's better to use a single instance of OkHttpClient in a certain project.
    private final OkHttpClient mOkHttpClient = new OkHttpClient();

    // Manager Headers.
    private IHeaderManager mHeaderManager = HeaderManager.getHeaderManager();

    // Sets some settings of http to OkHttpClient with a HttpBuilder.
    public OkHttpManager(HttpBuilder httpBuilder) {

        OkHttpClient.Builder okHttpBuilder = mOkHttpClient.newBuilder();
        okHttpBuilder.readTimeout(httpBuilder.getReadTimeOut(), TimeUnit.MILLISECONDS)
                .writeTimeout(httpBuilder.getWriteTimeOut(), TimeUnit.MILLISECONDS)
                .connectTimeout(httpBuilder.getConnectTimeOut(), TimeUnit.MILLISECONDS);
        okHttpBuilder.build();

    }

    @Override
    public <T> void doGet(@NonNull String url, @Nullable ArrayMap<String, Object> params, final @Nullable Callback<T> responseCallback) {

        // Creates a basic request
        final Request request = new Request
                .Builder()
                .tag(url)
                .headers(getOkHttpHeaders(mHeaderManager.getHeaders()))
                .url(Util.composeParams(url, params))
                .build();

        handleNetTask(request, responseCallback);
    }

    @Override
    public <T> void doPost(@NonNull String url, @Nullable ArrayMap<String, Object> params, @Nullable Callback<T> responseCallback) {

        RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"),
                ParseUtil.toJson(params));

        Request request = new Request.Builder()
                .url(url)
                .post(body)
                .build();

        handleNetTask(request, responseCallback);
    }

    @Override
    public void cancelRequestWithTag(Object tag) {
            // cancle a request via a tag.
    }

    @Override
    public Headers getOkHttpHeaders(ArrayMap<String, String> originalHeaders) {
        return mOkHttpHeaders;
    }

五、Json解析库。(要用哪个解析库,怎么更换)

不管是用什么网络库,都要对接口返回的json进行解析。安卓这边有多种第三方库,用于解析json。为了解藕,解析库与xxHttpManager的关系。这里,也采用了类似代理的方式,定义IParseUtil协议。不同的工具库,都实现该协议,来达到解藕的目的。方便更换第三方库(虽然可能性不高,就我个人的经验来看,还没遇到过要换该库的需求,但理论上还是得过去,兼容以后的未知数)。

下面是ParseUtil的定义

public class ParseUtil {

    private static IParseUtil INSTANCE;

    private static IParseUtil getParseUtil() {

        if (INSTANCE == null) {
            synchronized (ParseUtil.class) {
                if (INSTANCE == null) {
                    INSTANCE = new GsonUtil();
                }
            }
        }

        return INSTANCE;
    }

    public static <T> T parseJson(String json, Class tClass) {
        return getParseUtil().parseJson(json, tClass);
    }

    public static String toJson(Object object) {
        return getParseUtil().toJson(object);
    }

}

六、线程切换。(线程切换方式这么多,如何实现无伤切换?)

线程切换。安卓中有UI线程和非UI线程之分。前者,用于处理UI绘制和响应。后者,用于处理一些费时操作,比如网络请求。同样为了解藕,线程切换与xxHttpManager的关系,也做了一些处理。现在用的是RxJava2(第三方库)去实现线程切换。

下面是负责线程切换的ThreadManager的定义

/**
 * @Author Lyf
 * @CreateTime 2018/2/6
 * @Description
 **/
public class ThreadManager {

    /**
     * This method is used to do something on two separately different threads(threads are called one after the other) in an async way.
     * For instance, you can do some heavy cpu operations on subThread and then updates your views on UI thread.
     *
     * @param subscribeListener do something in subThread(non-ui thread)
     * @param observerListener  do something in mainThread(ui thread)
     * @param <T>               after do something in subThread,
     *                          you may want to pass a T(bean) object as a result to ui thread to update views.
     */
    public static <T> void execute(SubscribeListener<T> subscribeListener,
                                   ObserverListener<T> observerListener) {

        Observable.create((ObservableOnSubscribe<T>) emitter -> {
            emitter.onNext(subscribeListener.runOnSubThread());
            emitter.onComplete();
        }).subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(observerListener::runOnUiThread);

    }

    /**
     * Run on Ui Thread.
     */
    public static void runOnUiThread(Runnable runnable) {

        Observable.just(runnable)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(Runnable::run);
    }

}

七、其它配置。

  • HeaderManager用于管理Header的配置和获取。
  • HttpCode用于定义最基本的业务逻辑的成功与失败的错误码。
  • Response和CallBack,是回调用的。具体怎么封,就看自己的定义。

八、最后总结:

1、面向协议。
2、第三方库,看需要,通过协议来解藕。

九、框架整体结构图。

这里写图片描述

十、Github的demo地址。

路径在framework的net包下,请求示例在app这个module的login包下。
封装示例入口

猜你喜欢

转载自blog.csdn.net/l_o_s/article/details/79390118
今日推荐