老生常談
- 什么是
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請求 - 我們在看方法體,返回值會是一個封裝了
UserBean的Call<>對象 - 參數有兩個,分別是
String username和String password - 與平常方法不同的是,這兩個參數各自帶上了
@Query("...")注解 - 通過
@Query("...")里的參數我們發現,這與okHttp創建FormBody時,add的參數不謀而合
看到這里想必大家都明白了,如果大家還不明白什么是 Get 請求,以及 @Query("...") 里的 username 和 password 是怎么的話,我這里簡單說下
比如說我們現在隨便打開一個網頁,就拿百度圖片里搜索 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每日壁紙接口

- 設置了這個數據解析器,就可以把返回的信息自動封裝為相應的對象,明白了吧
具體這個對象怎么獲得,大家可以聯系后端,或者百度搜下 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());
- 具體就不說了,就是調用
call的execute()會返回一個值 - 這個值就是請求結果,大家直接用就是( 但是在這個只沒返回,比如網速慢時,手機會卡在那動不了甚至
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) {
// 失敗時做處理
}
});
- 這就是異步方法,直接調用
call的enqueue方法,傳入一個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 博客園 及時接收更多優質博文!

