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


老生常談

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

A type-safe HTTP client for Android and Java

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

本文流程

福利

依賴注入

  • 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~ 大家直接用即可

  • 別急,前面也說了 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 頁面為例:

百度圖片 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 對象生成器,門路很多這里都告訴你們啦

生成接口對象

  • 老樣子,先看看代碼
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 哦~:

  • Demo 地址:FIWKeepApp - LoginFragment

總結

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

加油!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM