「2020 新手必備 」極速入門 Retrofit + OkHttp 網絡框架使用,這一篇就夠了!

老生常談

  • 什麼是 Retrofit
  • Retrofit 早已不是什麼新技術了,想必看到這篇博客的你們都早已熟知,這裏就不囉嗦了,簡單介紹下:
  • Retrofit 是一個針對 Java 和 Android 的設計的 REST 客戶機。它經過基於 REST 的 web 服務檢索和上傳 JSON (或其餘結構化數據)變得相對容易。在使用中,您能夠配置用於數據序列化的轉換器。對於 JSON ,一般使用Gson ,可是能夠添加自定義轉換器來處理 XML 或其餘協議。Retrofit 對 HTTP 請求使用 OkHttp 庫。

A type-safe HTTP client for Android and Javajava

  • 好了介紹結束,想必你們的大刀都飢渴難耐了,那麼咱們直接開始吧

本文流程

福利

依賴注入

  • so Easy 不用說了吧
  • 在 app module 下的 build.gradle 中添加如下依賴:
// OkHttp3
api 'com.squareup.okhttp3:okhttp:3.10.0'
api 'com.squareup.okio:okio:1.8.0'
// Retrofit
api 'com.squareup.retrofit2:retrofit:2.7.0'
// Gson 服務器數據交互
api 'com.google.code.gson:gson:2.8.6'
複製代碼

依賴注入很簡單, Retrofit 一直是結合 OkHttp 和 Gson(無所謂什麼 JSON 解析器都行,這裏就用 Gson 了) 我這裏專門找了最新的版本庫,so~ 你們直接用便可android

  • 別急,前面也說了 Retrofit 是結合 OkHttp 作網絡請求用的,因此悉心提醒記得開下網絡權限:
<uses-permission android:name="android.permission.INTERNET" />
複製代碼

全面進擊

  • 網上關於 Retrofit 的教程可謂琳瑯滿目,可是總給人一種雲裏霧裏的感受
  • 因此本文的亮點就在於,我會經過我本身實際項目的代碼來給你們介紹 Retrofit 到底牛在哪

亮點

Retrofit 開始以前

  • 這裏我將以個人一個開源項目 FIWKeepApp 的登陸模塊舉例
  • Retrofit 出現以前,原始社會的咱們通常是這樣進行網絡請求的:
public void login2() {
        OkHttpClient okHttpClient = new OkHttpClient();
        //Form表單格式的參數傳遞
        FormBody formBody = new FormBody
            .Builder()
            //設置參數名稱和參數值
            .add("username",mAccountEdit.getText().toString())
            .add("password",mPasswordEdit.getText().toString())
            .build();
        Request request = new Request
            .Builder()
            //Post請求的參數傳遞
            .post(formBody)
            .url("http://hyh.hljdx.net:8080/SitUpWebServer/login")
            .build();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {
                Log.d("my_Test", e.getMessage());
            }

            @Override
            public void onResponse(okhttp3.Call call, Response response) throws IOException {
                String result = response.body().toString();
                UserBean userBean = JSON.parseObject(result, UserBean.class);
                Log.d("my_Test",userBean.getUser_head_img());
                response.body().close();
            }
        });
    }
複製代碼
  • 有沒有一種雲裏霧裏的感受?
  • 首先你得先將要發送的表單信息封裝爲 Post 請求的 Body 對象,那麼有的同窗會問什麼是 POST ,什麼是 Body?這個問題建議你們 Google 下,這裏我建議你們學一些後端或者計網的知識,很簡單也頗有必要
  • 接着你須要再封裝一個 Request 對象,也就是咱們的請求體,在這裏設置信息要提交到哪去
  • 最後調用 okHttpClient 的相應方法,將前面實現的東西組合發送,並在回調裏接收
  • 因此,這一步步,又是封裝 FormBody 又是封裝 Request ,搞了半天還要用 okHttpClient 發送,一套下來頭暈眼花,那麼如何解決呢?
  • 那麼 Retrofit 救世主就出現了

Retrofit 實現

  • 仍是我項目中的登陸模塊,我將其改成 Retrofit 的形式
  • 一樣完成上面的功能,若是用 Retrofit 實現只須要:
// baseUrl() 設置路由地址
    Retrofit retrofit = new Retrofit
        .Builder()
        .baseUrl(ApiUtils.BASE_URL)
        .addConverterFactory(GsonConverterFactory.create())
        .build();
        
    // 設置參數
    Call<UserBean> call = retrofit.create(UserMgrService.class)
        .login( mAccountEdit.getText().toString(),
            mPasswordEdit.getText().toString());
            
    // 回調
    call.enqueue(new Callback<UserBean>() {
        @Override
        public void onResponse(Call<UserBean> call, Response<UserBean> response) {
            Log.d("123123", "msg--" + response.body().getUser_head_img());
        }

        @Override
        public void onFailure(Call<UserBean> call, Throwable t) {
            // 失敗時作處理
        }
    });
複製代碼
  • 如上就實現了和純 okHttp 代碼同樣的功能
  • 你們可能會以爲,這也沒簡單多少啊 ?但細心觀察發現,第一步 Retrofit 的實例化過程,只要服務器不換代碼幾乎是不變的,因此咱們徹底能夠將它封裝

優勢

  • 並且你們有沒有發現,若是單單使用 OkHttp 咱們的返回值是一個 Response 對象,咱們還須要在其中提取相應 JSON 對象,進行類型轉換,而在 Retrofit 中,因爲使用了數據解析器,因此這一大塊代碼都省略了
  • 還有不少優勢,這裏就不嘮叨了,咱們直接開始學習使用之路吧!

實現流程

  • 那麼如今就給你們解釋下使用的每一個步驟

建立接口

  • 首先咱們要建立 UserMgrService 接口
/** * @author fishinwater-1999 * @version 2019-12-21 */
public interface UserMgrService {

    /** * GET 用 Query */
    @GET("login")
    Call<UserBean> login(@Query("username") String username, @Query("password") String password);

}
複製代碼
  • @GET() 註解就能夠猜到,這將會是一個 Get 請求
  • 咱們在看方法體,返回值會是一個封裝了 UserBeanCall<> 對象
  • 參數有兩個,分別是 String usernameString password
  • 與日常方法不一樣的是,這兩個參數各自帶上了 @Query("...") 註解
  • 經過 @Query("...") 裏的參數咱們發現,這與 okHttp 建立 FormBody 時,add 的參數不謀而合

看到這裏想必你們都明白了,若是你們還不明白什麼是 Get 請求,以及 @Query("...") 裏的 username 和 password 是怎麼的話,我這裏簡單說下 好比說咱們如今隨便打開一個網頁,就拿百度圖片裏搜索 Github 頁面爲例:git

百度圖片 GITHUB

  • 後端寫服務器的同窗會經過這些參數,像 HashMap get(「key」) 方法取值同樣拿出來

POST

  • 這樣解釋,想必你們就明白了
  • 除了 GET 方法以外 還有一種 POST 方法,相比於使用 GET ,使用 POST 有不少其餘的優勢,這裏就很少說了
  • 他使用和 GET 的思路同樣,若是用 POST 那麼咱們的代碼將會是這樣的:
public interface UserMgrService {

    /** * POST 用 Field */
    @POST("login")
    @FormUrlEncoded
    Call<UserBean> login(@Field("username") String username, @Field("password") String password);

}
複製代碼
  • 就是把註解換了套名字,而後在 @POST("...") 下再加上一個 @FormUrlEncoded 註解
  • 這裏就很少說了,咱們直接進入下一步

生成 Retrofit 對象

  • 咱們先看下怎麼建立和設置的:
// baseUrl() 設置路由地址
Retrofit retrofit = new Retrofit
    .Builder()
    .baseUrl(ApiUtils.BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .build();
複製代碼
  • 這裏主要是兩步,設置 baseUrl 、設置數據解析器
  • 老樣子什麼是 baseUrl ?就拿我以前用 OkHttp 設置的那個 url 爲例
http://hyh.hljdx.net:8080/SitUpWebServer/login
複製代碼
  • 你們能夠這麼理解:上面的這個 url = baseurl + @GET("...") 註解裏傳入的字符串
  • 若是咱們前面設置的是 @GET("login") 那這裏 baseurl 就是:http://hyh.hljdx.net:8080/SitUpWebServer/ 是否是一會兒就明白了,可是其餘博客不照顧新人,從沒說清楚
  • 而後就是數據解析器,你們應該還記得剛開始的時候咱們導入了一個三方庫:
// Gson 服務器數據交互
api 'com.google.code.gson:gson:2.8.6'
複製代碼
  • 咱們和服務器的數據,都是以 JSON 的形式交互的,好比 Bing 每日壁紙接口

JSON

  • 設置了這個數據解析器,就能夠把返回的信息自動封裝爲相應的對象,明白了吧

具體這個對象怎麼得到,你們能夠聯繫後端,或者百度搜下 JsonFormat 插件使用或者 JSON 對象生成器,門路不少這裏都告訴大家啦github

生成接口對象

  • 老樣子,先看看代碼
UserMgrService service = retrofit.create(UserMgrService.class);
複製代碼
  • 過於簡單,調用前面 retrofit 對象的 create() 方法傳入接口的 class 文件便可

得到 Call 對象

  • 由剛開始的代碼咱們知道
  • 咱們向服務器發送請求須要調用 call 對象的 enqueue() 方法
  • 那麼 Call 對象怎麼得到呢?其實很簡單:
Call<UserBean> call = service.login( mAccountEdit.getText().toString(), mPasswordEdit.getText().toString());
複製代碼
  • 說白了就是,直接調用接口的相應方法,他返回的直接就是一個 Call 對象

發送請求

  • 請求分兩種 同步的和異步的

比較

  • 因爲請求是耗時的,假設咱們發送同步請求 ,在請求就過返回以前,應用界面會進去阻塞狀態
  • 說白了就是會卡,甚至卡死。。。因此說這種請求不多用到
  • 雖然不用,但負責的我仍是也給你們代碼:
Response<UserBean> response = call.execute();
Log.d("123123", "msg--" + response.body().getUser_head_img());
複製代碼
  • 具體就不說了,就是調用 callexecute() 會返回一個值
  • 這個值就是請求結果,你們直接用就是( 可是在這個只沒返回,好比網速慢時,手機會卡在那動不了甚至 ANR
  • 這裏我介紹下異步請求:
// 回調
call.enqueue(new Callback<UserBean>() {
    @Override
    public void onResponse(Call<UserBean> call, Response<UserBean> response) {
        Log.d("123123", "msg--" + response.body().getUser_head_img());
    }

    @Override
    public void onFailure(Call<UserBean> call, Throwable t) {
        // 失敗時作處理
    }
});
複製代碼
  • 這就是異步方法,直接調用 callenqueue 方法,傳入一個 Callback 接口便可
  • 調用後系統自動釋放資源,不會阻塞,等到請求結果返回時
  • 就會自動調用 onResponse 方法,方法 裏的 response 就是處理好的結果
  • 本文代碼運行後結果 Demo Example 是否是特別簡單!

登陸功能實戰

  • 到這裏想必你們都已經學會了 Retrofit 的使用
  • 那麼如今我就拿登陸功能舉例,看看如何在項目中引用 Retrofit
  • 實戰部分先置條件是 MVP + ButterKnife,你們很容易在網上找到資料,這就不贅述了

搭建 Model 層

  • 建立接口 ILoginModel
  • 接口對外暴露 username password 和 一個監聽回調接口 (接口經過泛型傳入)
/** * @author fishinwater-1999 * @version 2019-11-12 */
public interface IBaseLog<L> {

    /** * 登陸 Api * @param userAccount * @param mPassword * @param loginCallback */
    void login(String userAccount, String mPassword, L loginCallback);

}
複製代碼
  • 實現回調接口
  • 觀察者模式,當請求信息返回後動態通知 P 層
/** * @author fishinwater-1999 * @version 2019-12-23 */
public interface IBaseRetCallback<T> {

    void onSucceed(Response<T> response);

    void onFailed(Throwable t);

}
複製代碼
  • 建立 LoginModel 實現 ILoginModel 接口
  • 實現 login 方法,請求成功後回調 IBaseRetCallback 監聽
/** * @author fishinwater-1999 * @version 2019-11-12 */
public class LogViewModel implements IBaseLog<IBaseRetCallback<UserBean>> {

    private final String TAG = "LogViewModel";

    @Override
    public void login(String userAccount, String userPassword, final IBaseRetCallback<UserBean> retCallback) {
        // baseUrl() 設置路由地址
        Retrofit retrofit = new Retrofit
                .Builder()
                .baseUrl(ApiUtils.BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        // 設置參數
        UserMgrService service = retrofit.create(UserMgrService.class);
        retrofit2.Call<UserBean> call = service.login( userAccount, userPassword);
        // 回調
        call.enqueue(new Callback<UserBean>() {
            @Override
            public void onResponse(retrofit2.Call<UserBean> call, Response<UserBean> response) {
                retCallback.onSucceed(response);
            }

            @Override
            public void onFailure(retrofit2.Call<UserBean> call, Throwable t) {
                // 失敗時作處理
                retCallback.onFailed(t);
            }
        });
    }

}
複製代碼

搭建 Presenter 層

  • 首先實現 Presenter 層基類
  • 一樣的,要搭建 Presenter 層基類,首先要實現器接口
/** * @author fishinwater-1999 * @version 2019-11-12 */
public interface IBasePresenter<V> {

    /** * 綁定 * @param mLogView */
    void attachView(V mLogView);

    /** * 解綁 */
    void detachView();

    /** * 登陸 * @param userName * @param userPassword * @param resultListener */
    void login(String userName, String userPassword, V resultListener);

}
複製代碼
  • 編寫抽象類 BasePresenter 實現 IBasePresenter 接口
/** * @author fishinwater-1999 * @version 2019-11-12 */
public abstract class BasePresenter<V> implements IBasePresenter<V> {

    private V view;

    @Override
    public void attachView(V mLogView) {
        this.view = mLogView;
    }

    @Override
    public void detachView() {
        this.view = null;
    }

    @Override
    public V getLoginVew() {
        return this.view;
    }

}
複製代碼
  • 而後就到了咱們具體的 LogPresenter 類的實現
  • LogPresenter 類須要持有 View 層和 Model 層接口
/** * @author fishinwater-1999 * @version 2019-11-12 */
public class LogPresenter extends BasePresenter<ILoginView> {

    private IBaseLog logViewModel;

    public LogPresenter(IBaseLog logViewModel) {
        this.logViewModel = logViewModel;
    }

    @Override
    public void login(String userName, String userPassword, final ILoginView iLoginView) {
        logViewModel.login(userName, userPassword, new IBaseRetCallback<UserBean>() {
            @Override
            public void onSucceed(Response<UserBean> response) {
                UserBean userBean = response.body();
                if (userBean != null) {
                    String user_id = userBean.getUser_id();
                    iLoginView.showLoginSuccess(user_id);
                }
            }

            @Override
            public void onFailed(Throwable t) {
                iLoginView.showLoginFailed(ILoginView.ErrCode.WRONG_NET_WORK);
            }
        });

    }
}
複製代碼
  • 上面的代碼中,構造方法 LogPresenter 持有了 Model 層
  • 同時暴露了 login(..., ..., Listener) 接口,可供調用者調用

View 層實現

  • View 層負責實例化 Model 層,並與 Presenter 層綁定
  • 老樣子,建立 BaseFragment<V , P extends IBasePresenter<V>> 基類
/** * @author fishinwater-1999 * @version 2019-11-12 */
public abstract class BaseFragment<V , P extends IBasePresenter<V>> extends Fragment {

    /** * Presenter 層 */
    private P mBaseResister;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 自動綁定
        if (mBaseResister == null) {
            mBaseResister = createProsenter();
        }
    }

    /** * 在這裏肯定要生成的 Presenter 對象類型 * @return */
    public abstract P createProsenter();

    /** * 得到 Presenter 對象 * @return */
    public P getPresenter() {
        if (mBaseResister == null) {
            createProsenter();
        }
        return mBaseResister;
    }

    /** * 碎片銷燬時解綁 */
    @Override
    public void onStop() {
        super.onStop();
        mBaseResister = null;
    }
}
複製代碼
  • 實現 View 層邏輯
  • View 層只負責用戶界面響應
/** * @author fishinwater-1999 */
public class LoginFragment extends BaseFragment<ILoginView, LogPresenter> implements ILoginView {

    private static final String TAG = "LoginFragment";

    private LogViewModel mLogViewModel;

    private LoginFragmentBinding binding;

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        binding = DataBindingUtil.inflate(inflater, R.layout.login_fragment, container, false);
        View view = binding.getRoot();
        binding.setLogCallback(getLogActivity());
        binding.setFragment(this);
        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (mLogViewModel == null) {
            mLogViewModel = new LogViewModel();
        }
    }

    public void login(View v) {
        getPresenter().login(
                getUserName(),
                getUserPwd(),
                this);
    }

    @Override
    public LogPresenter createPresenter() {
        if (mLogViewModel == null) {
            mLogViewModel = new LogViewModel();
        }
        return new LogPresenter(mLogViewModel);
    }

    @Override
    public String getUserName() {
        return binding.userAccount.getText().toString();
    }

    @Override
    public String getUserPwd() {
        return binding.userPassword.getText().toString();
    }

    @Override
    public void showLoginSuccess(String response) {
        Toast.makeText(getActivity(), "登陸成功", Toast.LENGTH_LONG).show();
        SharedPreferencesUtil.putString(getActivity(), SharedPreferencesUtil.PRE_NAME_SITUP, SharedPreferencesUtil.USER_ID, response);
        ARouter.getInstance().build(RouteUtils.MainActivity).navigation();
        getActivity().finish();
    }

    @Override
    public void showLoginFailed(ErrCode errCode) {
        if (errCode == ErrCode.WRONG_USER_NAME) {
            Toast.makeText(getActivity(), "用戶名錯誤", Toast.LENGTH_LONG).show();
        }else if (errCode == ErrCode.WRONG_USER_PWD){
            Toast.makeText(getActivity(), "密碼錯誤", Toast.LENGTH_LONG).show();
        }else if (errCode == ErrCode.WRONG_NET_WORK) {
            Toast.makeText(getActivity(), "未知,請檢查網絡", Toast.LENGTH_LONG).show();
        }
    }
}
複製代碼
  • 這裏我使用了 DataBinding 的形式,對數據進行綁定
  • 固然,你也能夠選用 ButterKnife 等優秀的三方庫
  • 那麼爲何我選 DataBinding 呢?親兒子 懂吧? /壞笑

運行

  • 關於 測序的大體即是如此了
  • 至於細枝末節的東西你們能夠直接到這個庫裏面看,地址在文末

更多模塊實戰 FIWKeepApp

  • 這裏我將上述過程寫在個人 Demo 裏,地址在 GitHub 你們能夠直接查看改倉庫源碼,記得給我點個 star 哦~:web

  • Demo 地址:FIWKeepApp - LoginFragment後端

總結

  • 想必看到這兒的讀者對 Retrofit 的使用都已近有了必定的瞭解,但 Retrofit 的好處並不僅是這些,還有不少跟深刻的只是須要瞭解,但本文限於篇幅,沒法向你們一一介紹
  • 對於我前面的 FIWKeepApp 這個倉庫,我將一步步轉換到 Retrofit + OkHttp 的形式下,歡迎你們關注個人 這個倉庫,進行學習,也歡迎各位老鐵給個 star
  • 後面我還會對 Android 的各類知識點、Framework 層源碼,三方庫等進行解析,歡迎你們關注 _yuanhao的掘金 及時接收更多優質博文!

加油!
相關文章
相關標籤/搜索