Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 來仿一仿retrofit

來仿一仿retrofit

編輯:關於Android編程

為什麼要重復造輪子

在開發領域有一句很流行的話就是不要重復造輪子, 因為我們在開發中用到的很多東西早已有很多人去實現了, 而且這些實現都是經過時間和開發者檢驗過的, 一般不會遇到什麼坑, 而如果我們自己去實現的話, 那不僅會增加工作量, 最大的隱患還是我們並不能預見以後是否會遇到大坑. 不過大家注意了嗎. 上面不要重復造輪子的一個前提是開發中, 是的, 這句名言在開發中是適用的, 那在學習階段的? 我可以大概的告訴你-忘記這句話!, 為什麼不要重復造輪子不適合在學習階段使用呢? 如果我們在學習的時候什麼東西都依賴別人的實現, 是不是我們就沒有了自己的核心價值? 而且重復造輪子還有個好處就是-可以拿我們的代碼和別人的代碼做對比, 這樣我們可以很快的發現自己的不足.

重復造輪子

上面扯了這麼多, 下面我們就開始來造輪子了(話說回來, 我已經造了很多輪子了^_^). 這篇博客我們來仿一個最近很火的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 list(@Arg("name") String userName);
}

注解@POST指定了我們要Post到的api地址, list方法中@Arg注解制定了這個參數對應在網絡請求中的參數key, 方法的返回值是一個Call類型, 這個Call代表了一個請求.

使用.

UserApi api = glin.create(UserApi.class, getClass().getName());
Call call = api.list("qibin");
call.enqueue(new Callback() {
     @Override
     public void onResponse(Result 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 create(Class 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 key, Class 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 key = null;
        String path = null;

        HashMap, Class> mapping = mCallFactory.get();
        Class item;
        Annotation anno;
        for (Iterator> 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 callKlass = mCallFactory.get(key);
        if (callKlass == null) {
            throw new UnsupportedOperationException("cannot find calls");
        }

        Constructor 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 {
    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 extends Call {

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

    @Override
    public void enqueue(final Callback callback) {
        mClient.post(mUrl, mParams, mTag, callback);
    }
}

enqueue方法直接調用了mClient的post方法! 話說回來, 都到這裡了, 我們還沒看到真正的網絡請求的實現, 是的, 為了提供靈活性, 我們將網絡請求抽象出來, 大家可以任意去實現自己的網絡請求, 我們先來看看這個IClient接口中都是定義了什麼方法, 然後我們在來看看post是如何實現的.

public interface IClient {
     void get(final String url, final Object tag, final Callback callback);
     void post(final String url, final Params params, final Object tag, final Callback callback);
     void post(final String url, final String json, final Object tag, final Callback callback);
     void put(final String url, final Params params, final Object tag, final Callback callback);
     void put(final String url, final String json, final Object tag, final Callback callback);
     void delete(final String url, final Object tag, final Callback callback);

    void cancel(final Object tag);
    void parserFactory(ParserFactory factory);
    void timeout(long ms);
    void debugMode(boolean debug);

    LinkedHashMap headers();
}

其實就是定義了一些基本的http請求方法, 下面我們就來看看一個具體的post請求是如何實現的,

@Override
public  void post(String url, Params params, Object tag, Callback 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) getParser(callback.getClass()).parse(callback.getClass(), resp));
}

主要的還是getParser方法.

private  org.loader.glin.parser.Parser getParser(Class klass) {
    Class type = Helper.getType(klass);
    if (type.isAssignableFrom(List.class)) {
        return mParserFactory.getListParser();
    }
    return mParserFactory.getParser();
}

這裡有一個淫技, 我們通過Callback的范型類型來判斷要使用什麼方式去解析, 為什麼說是淫技, 因為在java中我們只能獲取到父類的范型類型, 所以這裡的Callback並不是大家印象中的接口, 而是一個抽象類

public abstract class Callback {
    public abstract void onResponse(Result result);
}

而且我們在使用Callback的時候, 肯定是要去實現他的, 所以這裡正好就可以獲取到它的范型了.
通過上面的getParser的代碼, 我們還得到了什麼信息? 那就是尼瑪mParserFactory的實現絕壁簡單, 就是獲取json數組和json對象的解析實現類!

好了, 大體的流程到這裡我們就完成了, 具體的一些實現, 大家可以去github上查看代碼.
項目的地址是:https://github.com/qibin0506/Glin

  1. 上一頁:
  2. 下一頁:
Copyright © Android教程網 All Rights Reserved