【Android - 框架】之Retrofit的使用

  Retrofit是Square公司發佈的一個能夠應用在Android和Java中的Http客戶端訪問框架,其底層應用的是OkHttp。html

  在這個帖子中,咱們如下面這個Http請求爲例:git

https://api.github.com/users/basil2style

  其請求結果(JSON)以下所示:程序員

{
  "login": "basil2style",
  "id": 1285344,
  "avatar_url": "https://avatars.githubusercontent.com/u/1285344?v=3",
  "gravatar_id": "",
  "url": "https://api.github.com/users/basil2style",
  "html_url": "https://github.com/basil2style",
  "followers_url": "https://api.github.com/users/basil2style/followers",
  "following_url": "https://api.github.com/users/basil2style/following{/other_user}",
  "gists_url": "https://api.github.com/users/basil2style/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/basil2style/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/basil2style/subscriptions",
  "organizations_url": "https://api.github.com/users/basil2style/orgs",
  "repos_url": "https://api.github.com/users/basil2style/repos",
  "events_url": "https://api.github.com/users/basil2style/events{/privacy}",
  "received_events_url": "https://api.github.com/users/basil2style/received_events",
  "type": "User",
  "site_admin": false,
  "name": "Basil",
  "company": "MakeInfo",
  "blog": "http://www.themakeinfo.com",
  "location": "Toronto,Canada",
  "email": "basiltalias92@gmail.com",
  "hireable": true,
  "bio": "Developer | Marketer | Reader | Cinephile | Entrepreneur",
  "public_repos": 35,
  "public_gists": 4,
  "followers": 64,
  "following": 155,
  "created_at": "2011-12-26T00:17:22Z",
  "updated_at": "2016-11-12T00:58:15Z"
}

  接下來咱們從Retrofit的用法到原理,來介紹一下這個框架。github

 

1、Retrofit的用法:api

0、配置Retrofit的開發環境:服務器

  咱們在使用Retrofit以前須要先導入Retrofit的包,本DEMO是在Android Studio中開發的,所以只須要在gradle文件中導入依賴便可。下面是Retrofit的依賴:網絡

compile 'com.squareup.retrofit2:retrofit:2.1.0'

  另外,咱們從網絡中獲取到的數據的格式是不一樣的,多是JSON/GSON格式,也多是Jackson、Wire等其餘格式,咱們須要在後面的編碼中用到一個格式轉化工廠ConverterFactory,所以咱們還須要導入一些有關格式的依賴。本DEMO中使用的是JSON/GSON格式,所以導入相關依賴以下:框架

compile 'com.squareup.retrofit2:converter-gson:2.1.0'

  其餘格式須要導入的依賴列表以下(後面的版本號本身添加):異步

    Gson: com.squareup.retrofit2:converter-gson
    Jackson: com.squareup.retrofit2:converter-jackson
    Moshi: com.squareup.retrofit2:converter-moshi
    Protobuf: com.squareup.retrofit2:converter-protobuf
    Wire: com.squareup.retrofit2:converter-wire
    Simple XML: com.squareup.retrofit2:converter-simplexml
    Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars 

  至此,Retrofit的開發環境搭建就完成了。ide

一、建立一個從網絡中獲取數據的Service接口:

  Retrofit使用動態代理的方式,將一個網絡請求轉換成一個接口,用戶只須要調用一個接口就能夠進行網絡訪問。這個接口的代碼以下:

public interface RetrofitService {
    @GET("/users/basil2style")
    Call<InfoData> getInfoData();
}

  從上面這段代碼中能夠看出來, @GET 標籤中的值只是這個請求的一部分。通常咱們在進行網絡請求的時候,大多數請求都是從一個服務器發送的數據,所以咱們能夠對這些請求的根URL進行統一的管理。在這個DEMO中,我把網絡請求的根URL放在SharedData類中:

public class SharedData {
    /**
     * Base Url
     */
    public static final String BASE_URL = "https://api.github.com";
}

  和其餘網絡請求相同,Retrofit能夠經過 @GET 和 @POST 兩種方法進行網絡請求,這些請求有時候須要攜帶參數,有時候不須要,上面的getInfoData()方法就是一個不攜帶任何參數的網絡請求方法。固然,這裏還須要建立一個Bean類InfoData,用來存儲從網絡中獲取到的數據:

public class InfoData {
    private String login;
    private int id;
    private String avatarUrl;
    private String gravatarId;
    private String url;
    private String htmlUrl;
    private String followersUrl;
    private String followingUrl;
    private String gistsUrl;
    private String starredUrl;
    private String subscriptionsUrl;
    private String organizationsUrl;
    private String reposUrl;
    private String eventsUrl;
    private String receivedEventsUrl;
    private String type;
    private boolean siteAdmin;
    private String name;
    private String company;
    private String blog;
    private String location;
    private String email;
    private boolean hireable;
    private String bio;
    private int publicRepos;
    private int publicGists;
    private int followers;
    private int following;
    private String createdAt;
    private String updatedAt;

    // Getter、Setter方法
}

  再來介紹一下Call。Call能夠用來發送一個請求,Call對象中有兩個方法:enqueue()和execute(),前者用來發送一個異步請求,後者又來發送一個同步請求,下面會有代碼介紹。

二、初始化Retrofit對象:

  建立Retrofit對象的代碼以下:

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(SharedData.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

  能夠看到,Retrofit對象是經過Retrofit.Builder內部類建立出來的,baseUrl是須要訪問的根URL;因爲請求的數據是JSON格式的,咱們可使用一個和JSON有關的轉換工廠來處理這些數據,這裏用到的是和JSON差很少的GSON轉化工廠,即GsonConverterFactory。

三、調用Retrofit對象進行網絡訪問:

  獲取到Retrofit對象後,咱們經過這個對象獲取到網絡請求接口RetrofitService,再調用其中的getInfoData()方法獲取到網絡數據便可。具體代碼以下:

        RetrofitService service = retrofit.create(RetrofitService.class);

        Call<InfoData> call = service.getInfoData();
        call.enqueue(new Callback<InfoData>() {
            @Override
            public void onResponse(Call<InfoData> call, Response<InfoData> response) {
                InfoData data = response.body();
                Message message = Message.obtain();
                message.what = 1;
                message.obj = data;
                handler.sendMessage(message);
            }

            @Override
            public void onFailure(Call<InfoData> call, Throwable t) {
                handler.sendEmptyMessage(0);
            }
        });

  這裏還須要說明一下,Retrofit不像Volley能夠直接把異步數據拉回到主線程,Retrofit中Callback類的onResponse()方法仍然是在異步線程中的,若是咱們要將數據拿到主線程,須要使用AsyncTask或Handler,本DEMO中使用的是Handler,如下是Handler中的代碼:

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    InfoData data = (InfoData) msg.obj;
                    Toast.makeText(MainActivity.this, data.getBlog(), Toast.LENGTH_SHORT).show();
                    break;
                case 0:
                    Toast.makeText(MainActivity.this, "獲取數據失敗", Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    };

  到此爲止Retrofit訪問網絡獲取數據的DEMO就完成了,運行結果以下圖所示:


2、RetrofitService接口方法種類:

  上面的RetrofitService接口中的getInfoData()方法只是一個沒有攜帶任何參數的GET請求,咱們在訪問網絡數據的時候有時須要使用POST請求,而且可能會攜帶一些參數等,下面來逐個介紹一下這些狀況下接口方法的編寫方式。

  特別說明:下面訪問的接口和上面DEMO中的接口沒有任何關係,兩兩之間也沒有任何關係!

一、沒有參數的GET請求:

    @GET("users/list")
    Call<List<User>> getUserList();

二、有參數的GET請求:

(1)第一種方式:在GET標籤中添加參數:

    @GET("users/list?sort=desc")
    Call<List<User>> getUserList();

(2)第二種方式:在方法參數中設置參數:

    @GET("users/list")
    Call<List<User>> getUserList(@Query("sort") String sort); 

三、在GET標籤中設置路徑參數:

    @GET("group/{id}/users")
    Call<List<User>> groupList(@Path("id") int groupId);

四、傳入參數列表:

    @GET("group/{id}/users")
    Call<List<User>> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);

五、POST方式請求:

    @POST("users/new")
    Call<User> createUser(@Body User user);

 

3、Retrofit原理:

  Retrofit用註解來描述一個網絡請求,將一個網絡請求抽象成一個接口,而後使用Java動態代理的方式動態的將這個接口「翻譯」成一個網絡請求,最後調用OkHttp去執行這個請求。

  Retrofit中的動態代理主要體如今下面這行代碼:

RetrofitService service = retrofit.create(RetrofitService.class);

  create()方法的源碼以下:

/** Create an implementation of the API defined by the {@code service} interface. */
public <T> T create(final Class<T> service) {
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
       eagerlyValidateMethods(service);
    }
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() {
        private final Platform platform = Platform.get();

        public Object invoke(Object proxy, Method method, Object... args) throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
                return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
                return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            ServiceMethod serviceMethod = loadServiceMethod(method);
            OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
            return serviceMethod.callAdapter.adapt(okHttpCall);
        }
    });
}

  可見,所謂的動態代理,就是給程序員一種可能:在執行某個操做(如打開某個Class)以前,插入一段你想要執行的代碼。好比你在執行某個操做以前判斷用戶是否已經登陸,等。

  當外界(Activity)經過create()方法打開這個接口並調用其中的方法時,底層就調用了上面這段代碼中的invode()方法。這個方法是經過Java反射機制獲取到接口中的getInfoData()方法,用這個方法做爲參數建立一個ServiceMethod對象,最後使用OkHttp的API調用進行網絡訪問。

  總結一下:Retrofit使用註解的方式將一個網絡請求封裝成一個接口,而後使用動態代理的方法,在插入的代碼中經過反射機制找到接口中的方法,封裝到OkHttp中進行網絡訪問,最後對網絡訪問獲得的Call對象進行enqueue()或execute()操做,在回調的方法中處理網絡獲取到的數據。

  以上就是Retrofit的工做原理。

相關文章
相關標籤/搜索