来仿一仿retrofit

为什么要重复造轮子

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

重复造轮子

上面扯了这么多, 下面我们就开始来造轮子了(话说回来, 我已经造了很多轮子了^_^). 这篇博客我们来仿一个最近很火的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方法直接调用了mClientpost方法! 话说回来, 都到这里了, 我们还没看到真正的网络请求的实现, 是的, 为了提供灵活性, 我们将网络请求抽象出来, 大家可以任意去实现自己的网络请求, 我们先来看看这个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

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
背景 前面一篇总结了Serializable的序列化与反序列化,现在接着总结XML。主要内容:XML基本的序列化与反序列化方法、一些注意事项、以及自定义了一个XML注解框架(简洁代码,解放双手)。 XML的序列化与反序列化 先与Serializable进行简单的对比: Serializable存储的文件,打开后无法正常查看,安全性高。xml文件可通过文本编辑器查看与编辑,可读性高(浏览器会格式化xml文件,更方便查看),安全性低; Serializable文件通过了签名,只能在自己的程序中反序列化,或RM
每日更新关注 : http://weibo.com/hanjunqiang   新浪微博! iOS 开发者交流QQ群 : 446310206   有问题或技术交流可以咨询! 欢迎加入 ! 这篇直接搬了一份官方文档过来看的 由于之前没用markdown搞的乱七八糟的 所以重新做了一份 后面看到官网的中文文档更新不及时看着英文翻译了一点 搞的更乱了 :( 英文好的直接点右边- 官方OC文档 Realm 是一个移动端的数据库, Realm 是 SQLite 和 CoreData 的替代者。它可以节省你成千上万行

2.3.1 存储数据到data目录中 - 2016-07-25 14:07:09

当应用安装到Android后,系统会根据每个应用的包名创建一个/data/data/包名/的文件夹,访问自己包名下的目录是不需要权限的,并且Android已经提供了非常简便的API可以直接去访问该文件夹。 下面以一个案例来演示data目录的使用。 一、需求 如图1-5 所示,在用户将CheckBox选中的前提下点击登录,就将用户名和密码保存在data文件夹中,在用户不勾选CheckBox的前提下点击登录,就不保存用户名和密码,同时删除data文件中的历史数据。 如果数据已经保存到了data目录中,那么下次
本页面更新日期: 2016年07月23日 package 包 前面提到了 包 这个概念. 什么是包? 由于非常多的人参与 Java 的开发, 这难免会遇到一个问题 – 类名冲突 . 也就是说难免会遇到重名的情况. 所以 Java 引入了包这个机制. 提供了类的多层命名空间. 用于解决类的命名冲突 / 类文件管理等问题. Java允许将一组功能相关的类放在同一个 package 下. 从而组成逻辑上的 类库单元 . 如果希望把一个类放在指定的包结构下,应该在Java源程序的第一个非注释行放置如下格式的代码:
目录: APP项目如何与插件化无缝结合(一)  APP项目如何与插件化无缝结合(二)  APP项目如何与插件化无缝结合(三)  搬砖码字不易,转载请注明转自: http://blog.csdn.net/u011176685/article/details/52006474 上面一篇主要介绍了Small的原理,相信大家应该现在心里有个大概的了解。好,我们接下来继续开始! 一、Small的使用 关于Small的使用, Small的使用 这里讲的很详细,关于这里提下我当时遇到的问题和解决办法。 1.Small作
在Windows下试了试用Ionic开发Android应用,试通了。记录了过程。列在下面,供参考。 1. JDK 我用的jdk8,这里下载: http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html 。我老早下载的,8u66,i586(32位的),现在是8u102: http://download.oracle.com/otn-pub/java/jdk/8u102-b14/jdk-8u102-wi
最近写的项目中有用到数据库,写了不少蛋疼的sql语句,每次都是好几行代码,而且每次都是重复的没有一点技术含量的代码,虽然也有不少基于sqlite的封装,不过用起来还是感觉不够面向对象! 为了不再写重复的代码,花了几天时间,基于SQLite3简单封装了下,实现了一行代码解决增删改查等常用的功能!并没有太过高深的知识,主要用了runtime和KVC: 首先我们创建个大家都熟悉的Person类,并声明两个属性,下面将以类此展开分析 @interface Person : NSObject @property (

以太网帧格式 - 2016-07-24 18:07:25

浅谈以太网帧格式                                       一、Ethernet帧格式的发展 1980 DEC,Intel,Xerox制订了Ethernet I的标准 1982 DEC,Intel,Xerox又制订了Ehternet II的标准 1982 IEEE开始研究Ethernet的国际标准802.3  1983 迫不及待的Novell基于IEEE的802.3的原始版开发了专用的Ethernet帧格式 1985 IEEE推出IEEE 802.3规范,后来为解决Eth
  谷歌改良了ndk的开发流程,对于Windows环境下NDK的开发,如果使用的NDK是r7之前的版本,必须要安装Cygwin才能使用NDK。而在NDKr7开始,Google的Windows版的NDK提供了一个ndk-build.cmd的脚本,这样,就可以直接利用这个脚本编译,而不需要使用Cygwin了。只需要为Eclipse Android工程添加一个Builders,而为Eclipse配置的builder,其实就是在执行Cygwin,然后传递ndk-build作为参数,这样就能让Eclipse自动编译
文章系列 视频驱动V4L2子系统驱动架构 - 驱动框架 视频驱动V4L2子系统驱动架构 - ioctl 基于linux4.6.3,最后会附上一张ioctl调用总图,分析代码还是要用图来说明,这样更清晰一点,我就是这么分析的,不过平时分析的图很随便,而且很大,所以就不能在这里呈现,我在这里会贴出一个简略图 ioctl详解 进入ioctl都是从cdev-ops-ioctl进入的,一般的驱动cdev都是驱动自己初始化的,在v4l2架构中,cdev都已经初始化完成,不需要驱动开发者来初始化,下面是v4l2的cde