来仿一仿retrofit

来源:互联网 发布:倩女幽魂联赛对战算法 编辑:程序博客网 时间:2024/05/19 19:15

新媒体管家

点击上方“程序员大咖”,选择“置顶公众号”

关键时刻,第一时间送达!


为什么要重复造轮子


在开发领域有一句很流行的话就是不要重复造轮子,因为我们在开发中用到的很多东西早已有很多人去实现了,而且这些实现都是经过时间和开发者检验过的,一般不会遇到什么坑,而如果我们自己去实现的话,那不仅会增加工作量,最大的隐患还是我们并不能预见以后是否会遇到大坑。不过大家注意了吗? 上面不要重复造轮子的一个前提是开发中,是的, 这句名言在开发中是适用的,那在学习阶段的? 我可以大概的告诉你-忘记这句话!为什么不要重复造轮子不适合在学习阶段使用呢? 如果我们在学习的时候什么东西都依赖别人的实现, 是不是我们就没有了自己的核心价值? 而且重复造轮子还有个好处就是-可以拿我们的代码和别人的代码做对比, 这样我们可以很快的发现自己的不足。


重复造轮子


上面扯了这么多, 下面我们就开始来造轮子了(话说回来, 我已经造了很多轮子了^_^)。这篇博客我们来仿一个最近很火的Android网络框架的二次封装-retrofit(这个名字真难记)。新项目的名字我们起个简单的-glin。 而且项目我已经放github上了,感兴趣的同学可以参考https://github.com/qibin0506/Glin。


如何使用


因为我们是仿retrofit,所以用法上肯定和retrofit大致相同,首先是配置。


Glin glin = new Glin.Builder()

    .client(new OkClient())

    .baseUrl("http://192.168.201.39")

    .debug(true)

    .parserFactory(new FastJsonParserFactory())

    .timeout(10000)

    .build();


几个方法需要简单的解释一下, client指定使用的什么网络框架去访问网络,parserFactory指定了我们怎么去解析返回的数据。


配置完成了以后,我们怎么去使用呢? 和retrofit一样,我们需要使用接口来定义业务。


public interface UserApi {

     @POST("/users/list")

     Call<User> list(@Arg("name") String userName);

}


注解@POST指定了我们要Post到的api地址,list方法中@Arg注解制定了这个参数对应在网络请求中的参数key,方法的返回值是一个Call类型,这个Call代表了一个请求。


使用


UserApi api = glin.create(UserApi.class, getClass().getName());

Call<User> call = api.list("qibin");

call.enqueue(new Callback<User>() {

     @Override

     public void onResponse(Result<User> result) {

         if (result.isOK()) {

             Toast.makeText(MainActivity.this, result.getResult().getName(), Toast.LENGTH_SHORT).show();

         }else {

             Toast.makeText(MainActivity.this, result.getMessage(), Toast.LENGTH_SHORT).show();

         }

     }

});


熟悉retrofit的同学对这里应该很熟悉了, 这里我就不再多嘴了, 下面我们赶紧进入主题, 如果去实现glin!


实现


马上, 我们就要进去主题啦, 首先我们先来看看Glin这个类是干嘛的.


public class Glin {

    private IClient mClient;

    private String mBaseUrl;

    private CallFactory mCallFactory;


    private Glin(IClient client, String baseUrl) {

        mClient = client;

        mBaseUrl = baseUrl;

        mCallFactory = new CallFactory();

    }


    @SuppressWarnings("unchecked")

    public <T> T create(Class<T> klass, Object tag) {

        return (T) Proxy.newProxyInstance(klass.getClassLoader(),

                new Class<?>[] {klass}, new Handler(tag));

    }


    public void cancel(String tag) {

        mClient.cancel(tag);

    }


    public void regist(Class<? extends Annotation> key, Class<? extends Call> value) {

        mCallFactory.regist(key, value);

    }

}


Glin这个类还是很简单的,构造方法是private的,因为大家都清楚, 我们强制要用使用建造者模式去实现。


三个变量中CallFactory是我们不熟悉的, 这个CallFactory是干嘛的? 这里来解释一下,还记得我们在定义接口的时候接口中方法的返回值是一个Call吗? 其实这个Call是一个抽象类,它有很多实现, 这些实现和方法的注解是对应的,例如上面的POST注解对应的就是使用PostCall这个实现, 所以这里的CallFactory类似一个mapping,他提供了注解->call的键值对,这样Glin就可以根据注解来找到要使用哪个Call了。


create方法貌似是我们使用的一个入口,我们来看看create方法的实现,其他Glin是使用了动态代理,他的代理者,也是Glin的核心就是Proxy.newProxyInstance的第三个参数-Handler, 我们接着来看看这个Handler如果实现。


class Handler implements InvocationHandler {

    private Object mTag;


    public Handler(Object tag) {

        mTag = tag;

    }


    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Class<? extends Annotation> key = null;

        String path = null;


        HashMap<Class<? extends Annotation>, Class<? extends Call>> mapping = mCallFactory.get();

        Class<? extends Annotation> item;

        Annotation anno;

        for (Iterator<Class<? extends Annotation>> iterator = mapping.keySet().iterator();

             iterator.hasNext();) {

            item = iterator.next();

            if (method.isAnnotationPresent(item)) {

                key = item;

                anno = method.getAnnotation(item);

                path = (String) anno.getClass().getDeclaredMethod("value").invoke(anno);

                break;

            }

        }


        if (key == null) {

            throw new UnsupportedOperationException("cannot find annotations");

        }


        Class<? extends Call> callKlass = mCallFactory.get(key);

        if (callKlass == null) {

            throw new UnsupportedOperationException("cannot find calls");

        }


        Constructor<? extends Call> constructor = callKlass.getConstructor(IClient.class, String.class, Params.class, Object.class);

        Call<?> call = constructor.newInstance(mClient, justUrl(path), params(method, args), mTag);

        return call;

    }


    private String justUrl(String path) {

        String url = mBaseUrl == null ? "" : mBaseUrl;

        path = path == null ? "" : path;

        if (isFullUrl(path)) { url = path;}

        else { url += path;}

        return url;

    }


    private boolean isFullUrl(String url) {

        if (url == null || url.length() == 0) { return false;}

        if (url.toLowerCase().startsWith("http://")) { return true;}

        if (url.toLowerCase().startsWith("https://")) {return true;}

        return false;

    }


    private Params params(Method method, Object[] args) {

        Params params = new Params();

        if (args == null || args.length == 0) {

            return params;

        }


        // method.getParameterAnnotations.length always equals args.length

        Annotation[][] paramsAnno = method.getParameterAnnotations();

        if (method.isAnnotationPresent(JSON.class)) {

            params.add(Params.DEFAULT_JSON_KEY, args[0]);

            return params;

        }


        int length = paramsAnno.length;

        for (int i = 0; i < length; i++) {

            if (paramsAnno[i].length == 0) { params.add(Params.DEFAULT_JSON_KEY, args[i]);}

            else { params.add(((Arg)paramsAnno[i][0]).value(), args[i]);}

        }


        return params;

    }

}


这是Glin类的一个内部类, 虽然看起来很长, 但是基本都是一些辅助方法, 例如: justUrl是根据baseUrl和注解中指定的地址做一个拼接, isFullUrl方法是判断注解中的url是不是一个完成的url, 因为如果是一个完成的url, 我们就不需要在url中拼接上baseUrl了, 这个类中的一个实现的方法invoke和一个params是最主要的, 我们接下来就来详细的说一下这两个方法。


在invoke方法中, 首先我们获取所有的注解->call键值对, 然后去遍历这个map并且判断我们使用的那个方法是使用了哪个注解, 然后记录这个注解,并且记录他的value值, 也就是api提交的地址, 接下来,我们通过得到的注解来从mCallFactory中来获取这个注解对应的Call, 因为在CallFactory中我们存放的是Call的class, 所以接下来我们是通过反射来实例化这个Call, 并且返回这个call, 其实, 在预先知道目的的情况下,这里都是很好理解的, 这里我们的目的就是要得到具体Call的实例.那Call需要什么参数呢? 我们来看看Call的构造吧。


public abstract class Call<T> {

    protected String mUrl;

    protected Params mParams;

    protected IClient mClient;

    protected Object mTag;


    public Call(IClient client, String url, Params params, Object tag) {

        mClient = client;

        mUrl = url;

        mParams = params;

        mTag = tag;

    }

}


client我们知道在哪, url我们从注解中取到了, 那就剩下一个params了, 这个params怎么获取呢? 下面我们就来看看上面提到的那个params方法. 再贴一遍代码:


private Params params(Method method, Object[] args) {

    Params params = new Params();

    if (args == null || args.length == 0) {

        return params;

    }


    // method.getParameterAnnotations.length always equals args.length

    Annotation[][] paramsAnno = method.getParameterAnnotations();

    if (method.isAnnotationPresent(JSON.class)) {

        params.add(Params.DEFAULT_JSON_KEY, args[0]);

        return params;

    }


    int length = paramsAnno.length;

    for (int i = 0; i < length; i++) {

        if (paramsAnno[i].length == 0) { params.add(Params.DEFAULT_JSON_KEY, args[i]);}

        else { params.add(((Arg)paramsAnno[i][0]).value(), args[i]);}

    }


    return params;

}


首先我们先new了一个Params对象, 这样做,不至于我们在使用Params的时候它是一个null, 接下来, 我们通过method.getParameterAnnotations来获取参数中的注解, 这里返回的是一个二位数组, 为什么是一个二维的? 很简单, 因为每个参数可能会有多个注解, 接下来是一个对JSON数据的处理, 我们不用关心, 最后, 我们来遍历这些参数, 并且将参数的注解value和我们传递的参数值存放的 params中, 这样我们就做到了通过接口来获取提交参数的目的。


到现在为止, 一个具体的Call我们就实现好了,接下来就是去调用Call的enqueue方法了, 我们就拿Post请求来看看enqueue方法吧。


public class PostCall<T> extends Call<T> {


    public PostCall(IClient client, String url, Params params, Object tag) {

        super(client, url, params, tag);

    }


    @Override

    public void enqueue(final Callback<T> callback) {

        mClient.post(mUrl, mParams, mTag, callback);

    }

}


enqueue方法直接调用了mClient的post方法! 话说回来, 都到这里了, 我们还没看到真正的网络请求的实现, 是的, 为了提供灵活性, 我们将网络请求抽象出来, 大家可以任意去实现自己的网络请求, 我们先来看看这个IClient接口中都是定义了什么方法, 然后我们在来看看post是如何实现的。


public interface IClient {

    <T> void get(final String url, final Object tag, final Callback<T> callback);

    <T> void post(final String url, final Params params, final Object tag, final Callback<T> callback);

    <T> void post(final String url, final String json, final Object tag, final Callback<T> callback);

    <T> void put(final String url, final Params params, final Object tag, final Callback<T> callback);

    <T> void put(final String url, final String json, final Object tag, final Callback<T> callback);

    <T> void delete(final String url, final Object tag, final Callback<T> callback);


    void cancel(final Object tag);

    void parserFactory(ParserFactory factory);

    void timeout(long ms);

    void debugMode(boolean debug);


    LinkedHashMap<String, String> headers();

}


其实就是定义了一些基本的http请求方法, 下面我们就来看看一个具体的post请求是如何实现的。


@Override

public <T> void post(String url, Params params, Object tag, Callback<T> callback) {

    StringBuilder debugInfo = new StringBuilder();

    MultipartBody builder = createRequestBody(params, debugInfo);

    Request request = new Request.Builder()

            .url(url).post(builder).build();

    call(request, callback, tag, debugInfo);

}


这里使用了okhttp来作为网络请求的底层框架, 所以这里都是和okhttp相关的代码. 这里我们也就不再多说了。


现在我们可以搞定网络请求了, 还剩下什么? 数据解析. 数据解析怎么搞定了? 我们来看看具体的实现代码。


@Override

public void onResponse(final Call call, Response response) throws IOException {

    String resp = response.body().string();

    prntInfo("Response->" + resp);

    callback(callback, (Result<T>) getParser(callback.getClass()).parse(callback.getClass(), resp));

}


主要的还是getParser方法。


private <T> org.loader.glin.parser.Parser getParser(Class<T> klass) {

    Class<?> type = Helper.getType(klass);

    if (type.isAssignableFrom(List.class)) {

        return mParserFactory.getListParser();

    }

    return mParserFactory.getParser();

}


这里有一个淫技, 我们通过Callback的范型类型来判断要使用什么方式去解析, 为什么说是淫技, 因为在Java中我们只能获取到父类的范型类型, 所以这里的Callback并不是大家印象中的接口, 而是一个抽象类。


public abstract class Callback<T> {

    public abstract void onResponse(Result<T> result);

}


而且我们在使用Callback的时候, 肯定是要去实现他的, 所以这里正好就可以获取到它的范型了。


通过上面的getParser的代码, 我们还得到了什么信息? 那就是尼玛mParserFactory的实现绝壁简单, 就是获取json数组和json对象的解析实现类!


好了, 大体的流程到这里我们就完成了, 具体的一些实现, 大家可以去github上查看代码。


项目的地址是:https://github.com/qibin0506/Glin


  • 来自:CSDN-亓斌

  • http://blog.csdn.net/qibin0506/article/details/52019247

  • 程序员大咖整理发布,转载请联系作者获得授权

【点击成为Python大神】

原创粉丝点击