老生常談
- 什么是
Retrofit
驮配? -
Retrofit
早已不是什么新技術(shù)了,想必看到這篇博客的大家都早已熟知着茸,這里就不啰嗦了壮锻,簡(jiǎn)單介紹下:
- Retrofit 是一個(gè)針對(duì) Java 和 Android 的設(shè)計(jì)的
REST
客戶機(jī)。它通過(guò)基于REST
的 web 服務(wù)檢索和上傳 JSON (或其他結(jié)構(gòu)化數(shù)據(jù))變得相對(duì)容易涮阔。在使用中猜绣,您可以配置用于數(shù)據(jù)序列化的轉(zhuǎn)換器。對(duì)于 JSON 敬特,通常使用Gson
掰邢,但是可以添加自定義轉(zhuǎn)換器來(lái)處理XML
或其他協(xié)議。Retrofit 對(duì) HTTP 請(qǐng)求使用OkHttp
庫(kù)伟阔。
A type-safe HTTP client for Android and Java
- 好了介紹結(jié)束辣之,想必大家的大刀都饑渴難耐了,那么我們直接開(kāi)始吧
本文流程
依賴注入
- so Easy 不用說(shuō)了吧
- 在 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 服務(wù)器數(shù)據(jù)交互
api 'com.google.code.gson:gson:2.8.6'
依賴注入很簡(jiǎn)單皱炉, Retrofit 一直是結(jié)合
OkHttp
和 Gson(無(wú)所謂什么 JSON 解析器都行怀估,這里就用Gson
了)
我這里專門找了最新的版本庫(kù),so~ 大家直接用即可
- 別急合搅,前面也說(shuō)了
Retrofit
是結(jié)合OkHttp
做網(wǎng)絡(luò)請(qǐng)求用的多搀,所以悉心提醒記得開(kāi)下網(wǎng)絡(luò)權(quán)限:
<uses-permission android:name="android.permission.INTERNET" />
全面進(jìn)擊
- 網(wǎng)上關(guān)于
Retrofit
的教程可謂琳瑯滿目,但是總給人一種云里霧里的感覺(jué) - 所以本文的亮點(diǎn)就在于灾部,我會(huì)通過(guò)我自己實(shí)際項(xiàng)目的代碼來(lái)給大家介紹 Retrofit 到底牛在哪
Retrofit 開(kāi)始之前
- 這里我將以我的一個(gè)開(kāi)源項(xiàng)目 FIWKeepApp 的登錄模塊舉例
- 在
Retrofit
出現(xiàn)之前康铭,原始社會(huì)的我們一般是這樣進(jìn)行網(wǎng)絡(luò)請(qǐng)求的:
public void login2() {
OkHttpClient okHttpClient = new OkHttpClient();
//Form表單格式的參數(shù)傳遞
FormBody formBody = new FormBody
.Builder()
//設(shè)置參數(shù)名稱和參數(shù)值
.add("username",mAccountEdit.getText().toString())
.add("password",mPasswordEdit.getText().toString())
.build();
Request request = new Request
.Builder()
//Post請(qǐng)求的參數(shù)傳遞
.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();
}
});
}
- 有沒(méi)有一種云里霧里的感覺(jué)?
- 首先你得先將要發(fā)送的表單信息封裝為
Post
請(qǐng)求的Body
對(duì)象赌髓,那么有的同學(xué)會(huì)問(wèn)什么是POST
麻削,什么是Body
?這個(gè)問(wèn)題建議大家Google
下春弥,這里我建議大家學(xué)一些后端或者計(jì)網(wǎng)的知識(shí),很簡(jiǎn)單也很有必要 - 接著你需要再封裝一個(gè)
Request
對(duì)象叠荠,也就是我們的請(qǐng)求體匿沛,在這里設(shè)置信息要提交到哪去 - 最后調(diào)用
okHttpClient
的相應(yīng)方法,將前面實(shí)現(xiàn)的東西組合發(fā)送榛鼎,并在回調(diào)里接收 - 所以逃呼,這一步步鳖孤,又是封裝
FormBody
又是封裝Request
,搞了半天還要用okHttpClient
發(fā)送抡笼,一套下來(lái)頭暈眼花苏揣,那么如何解決呢? - 那么
Retrofit
救世主就出現(xiàn)了
Retrofit 實(shí)現(xiàn)
- 還是我項(xiàng)目中的登錄模塊推姻,我將其改為
Retrofit
的形式 - 同樣完成上面的功能平匈,如果用
Retrofit
實(shí)現(xiàn)只需要:
// baseUrl() 設(shè)置路由地址
Retrofit retrofit = new Retrofit
.Builder()
.baseUrl(ApiUtils.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
// 設(shè)置參數(shù)
Call<UserBean> call = retrofit.create(UserMgrService.class)
.login( mAccountEdit.getText().toString(),
mPasswordEdit.getText().toString());
// 回調(diào)
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) {
// 失敗時(shí)做處理
}
});
- 如上就實(shí)現(xiàn)了和純
okHttp
代碼一樣的功能 - 大家可能會(huì)覺(jué)得,這也沒(méi)簡(jiǎn)單多少啊 藏古?但細(xì)心觀察發(fā)現(xiàn)增炭,第一步
Retrofit
的實(shí)例化過(guò)程,只要服務(wù)器不換代碼幾乎是不變的拧晕,所以我們完全可以將它封裝
- 而且大家有沒(méi)有發(fā)現(xiàn)隙姿,如果單單使用
OkHttp
我們的返回值是一個(gè)Response
對(duì)象,我們還需要在其中提取相應(yīng)JSON
對(duì)象厂捞,進(jìn)行類型轉(zhuǎn)換输玷,而在Retrofit
中,由于使用了數(shù)據(jù)解析器靡馁,所以這一大塊代碼都省略了 - 還有很多優(yōu)點(diǎn)欲鹏,這里就不嘮叨了,我們直接開(kāi)始學(xué)習(xí)使用之路吧奈嘿!
實(shí)現(xiàn)流程
- 那么現(xiàn)在就給大家解釋下使用的每個(gè)步驟
創(chuàng)建接口
- 首先我們要?jiǎng)?chuàng)建 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()
注解就可以猜到貌虾,這將會(huì)是一個(gè)Get
請(qǐng)求 - 我們?cè)诳捶椒w,返回值會(huì)是一個(gè)封裝了
UserBean
的Call<>
對(duì)象 - 參數(shù)有兩個(gè)裙犹,分別是
String username
和String password
- 與平常方法不同的是尽狠,這兩個(gè)參數(shù)各自帶上了
@Query("...")
注解 - 通過(guò)
@Query("...")
里的參數(shù)我們發(fā)現(xiàn),這與okHttp
創(chuàng)建FormBody
時(shí)叶圃,add
的參數(shù)不謀而合
看到這里想必大家都明白了袄膏,如果大家還不明白什么是 Get 請(qǐng)求,以及 @Query("...") 里的 username 和 password 是怎么的話掺冠,我這里簡(jiǎn)單說(shuō)下
比如說(shuō)我們現(xiàn)在隨便打開(kāi)一個(gè)網(wǎng)頁(yè)沉馆,就拿百度圖片里搜索 Github 頁(yè)面為例:
- 后端寫(xiě)服務(wù)器的同學(xué)會(huì)通過(guò)這些參數(shù),像
HashMap get(“key”)
方法取值一樣拿出來(lái)
POST
- 這樣解釋德崭,想必大家就明白了
- 除了
GET
方法之外 還有一種POST
方法斥黑,相比于使用GET
,使用POST
有很多其他的優(yōu)點(diǎn)眉厨,這里就不多說(shuō)了 - 他使用和
GET
的思路一樣锌奴,如果用POST
那么我們的代碼將會(huì)是這樣的:
public interface UserMgrService {
/**
* POST 用 Field
*/
@POST("login")
@FormUrlEncoded
Call<UserBean> login(@Field("username") String username, @Field("password") String password);
}
- 就是把注解換了套名字,然后在
@POST("...")
下再加上一個(gè)@FormUrlEncoded
注解 - 這里就不多說(shuō)了憾股,我們直接進(jìn)入下一步
生成 Retrofit 對(duì)象
- 我們先看下怎么創(chuàng)建和設(shè)置的:
// baseUrl() 設(shè)置路由地址
Retrofit retrofit = new Retrofit
.Builder()
.baseUrl(ApiUtils.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
- 這里主要是兩步鹿蜀,設(shè)置
baseUrl
箕慧、設(shè)置數(shù)據(jù)解析器 - 老樣子什么是
baseUrl
?就拿我之前用OkHttp
設(shè)置的那個(gè) url 為例
http://hyh.hljdx.net:8080/SitUpWebServer/login
- 大家可以這么理解:上面的這個(gè)
url = baseurl + @GET("...")
注解里傳入的字符串 - 如果我們前面設(shè)置的是
@GET("login")
那這里baseurl
就是:http://hyh.hljdx.net:8080/SitUpWebServer/
是不是一下子就明白了茴恰,但是其他博客不照顧新人颠焦,從沒(méi)說(shuō)清楚 - 然后就是數(shù)據(jù)解析器,大家應(yīng)該還記得剛開(kāi)始的時(shí)候我們導(dǎo)入了一個(gè)三方庫(kù):
// Gson 服務(wù)器數(shù)據(jù)交互
api 'com.google.code.gson:gson:2.8.6'
- 我們和服務(wù)器的數(shù)據(jù)往枣,都是以
JSON
的形式交互的伐庭,比如Bing
每日壁紙接口
- 設(shè)置了這個(gè)數(shù)據(jù)解析器,就可以把返回的信息自動(dòng)封裝為相應(yīng)的對(duì)象婉商,明白了吧
具體這個(gè)對(duì)象怎么獲得似忧,大家可以聯(lián)系后端,或者百度搜下 JsonFormat 插件使用或者 JSON 對(duì)象生成器丈秩,門路很多這里都告訴你們啦
生成接口對(duì)象
- 老樣子盯捌,先看看代碼
UserMgrService service = retrofit.create(UserMgrService.class);
- 過(guò)于簡(jiǎn)單,調(diào)用前面
retrofit
對(duì)象的create()
方法傳入接口的class
文件即可
獲得 Call 對(duì)象
- 由剛開(kāi)始的代碼我們知道
- 我們向服務(wù)器發(fā)送請(qǐng)求需要調(diào)用
call
對(duì)象的enqueue()
方法 - 那么
Call
對(duì)象怎么獲得呢蘑秽?其實(shí)很簡(jiǎn)單:
Call<UserBean> call = service.login( mAccountEdit.getText().toString(), mPasswordEdit.getText().toString());
- 說(shuō)白了就是饺著,直接調(diào)用接口的相應(yīng)方法,他返回的直接就是一個(gè)
Call
對(duì)象
發(fā)送請(qǐng)求
- 請(qǐng)求分兩種 同步的和異步的
- 由于請(qǐng)求是耗時(shí)的肠牲,假設(shè)我們發(fā)送同步請(qǐng)求 幼衰,在請(qǐng)求就過(guò)返回之前,應(yīng)用界面會(huì)進(jìn)去阻塞狀態(tài)
- 說(shuō)白了就是會(huì)卡缀雳,甚至卡死渡嚣。。肥印。所以說(shuō)這種請(qǐng)求很少用到
- 雖然不用识椰,但負(fù)責(zé)的我還是也給大家代碼:
Response<UserBean> response = call.execute();
Log.d("123123", "msg--" + response.body().getUser_head_img());
- 具體就不說(shuō)了,就是調(diào)用
call
的execute()
會(huì)返回一個(gè)值 - 這個(gè)值就是請(qǐng)求結(jié)果深碱,大家直接用就是( 但是在這個(gè)只沒(méi)返回腹鹉,比如網(wǎng)速慢時(shí),手機(jī)會(huì)卡在那動(dòng)不了甚至
ANR
) - 這里我介紹下異步請(qǐng)求:
// 回調(diào)
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) {
// 失敗時(shí)做處理
}
});
- 這就是異步方法敷硅,直接調(diào)用
call
的enqueue
方法功咒,傳入一個(gè)Callback
接口即可 - 調(diào)用后系統(tǒng)自動(dòng)釋放資源,不會(huì)阻塞绞蹦,等到請(qǐng)求結(jié)果返回時(shí)
- 就會(huì)自動(dòng)調(diào)用
onResponse
方法力奋,方法 里的response
就是處理好的結(jié)果 - 本文代碼運(yùn)行后結(jié)果 Demo Example 是不是特別簡(jiǎn)單!
登錄功能實(shí)戰(zhàn)
- 到這里想必大家都已經(jīng)學(xué)會(huì)了
Retrofit
的使用 - 那么現(xiàn)在我就拿登錄功能舉例幽七,看看如何在項(xiàng)目中引用
Retrofit
- 實(shí)戰(zhàn)部分先置條件是
MVP
+ButterKnife
景殷,大家很容易在網(wǎng)上找到資料,這就不贅述了
搭建 Model 層
- 創(chuàng)建接口
ILoginModel
- 接口對(duì)外暴露 username password 和 一個(gè)監(jiān)聽(tīng)回調(diào)接口 (接口通過(guò)泛型傳入)
/**
* @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);
}
- 實(shí)現(xiàn)回調(diào)接口
- 觀察者模式,當(dāng)請(qǐng)求信息返回后動(dòng)態(tài)通知 P 層
/**
* @author fishinwater-1999
* @version 2019-12-23
*/
public interface IBaseRetCallback<T> {
void onSucceed(Response<T> response);
void onFailed(Throwable t);
}
- 創(chuàng)建
LoginModel
實(shí)現(xiàn)ILoginModel
接口 - 實(shí)現(xiàn)
login
方法滨彻,請(qǐng)求成功后回調(diào)IBaseRetCallback
監(jiān)聽(tīng)
/**
* @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() 設(shè)置路由地址
Retrofit retrofit = new Retrofit
.Builder()
.baseUrl(ApiUtils.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
// 設(shè)置參數(shù)
UserMgrService service = retrofit.create(UserMgrService.class);
retrofit2.Call<UserBean> call = service.login( userAccount, userPassword);
// 回調(diào)
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) {
// 失敗時(shí)做處理
retCallback.onFailed(t);
}
});
}
}
搭建 Presenter 層
- 首先實(shí)現(xiàn)
Presenter
層基類 - 同樣的,要搭建
Presenter
層基類挪蹭,首先要實(shí)現(xiàn)器接口
/**
* @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);
}
- 編寫(xiě)抽象類
BasePresenter
實(shí)現(xiàn)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
類的實(shí)現(xiàn) -
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);
}
});
}
}
- 上面的代碼中亭饵,構(gòu)造方法 LogPresenter 持有了 Model 層
- 同時(shí)暴露了 login(..., ..., Listener) 接口,可供調(diào)用者調(diào)用
View 層實(shí)現(xiàn)
-
View
層負(fù)責(zé)實(shí)例化Model
層梁厉,并與Presenter
層綁定 - 老樣子辜羊,創(chuàng)建
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);
// 自動(dòng)綁定
if (mBaseResister == null) {
mBaseResister = createProsenter();
}
}
/**
* 在這里確定要生成的 Presenter 對(duì)象類型
* @return
*/
public abstract P createProsenter();
/**
* 獲得 Presenter 對(duì)象
* @return
*/
public P getPresenter() {
if (mBaseResister == null) {
createProsenter();
}
return mBaseResister;
}
/**
* 碎片銷毀時(shí)解綁
*/
@Override
public void onStop() {
super.onStop();
mBaseResister = null;
}
}
- 實(shí)現(xiàn)
View
層邏輯 - View 層只負(fù)責(zé)用戶界面響應(yīng)
/**
* @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(), "用戶名錯(cuò)誤", Toast.LENGTH_LONG).show();
}else if (errCode == ErrCode.WRONG_USER_PWD){
Toast.makeText(getActivity(), "密碼錯(cuò)誤", Toast.LENGTH_LONG).show();
}else if (errCode == ErrCode.WRONG_NET_WORK) {
Toast.makeText(getActivity(), "未知,請(qǐng)檢查網(wǎng)絡(luò)", Toast.LENGTH_LONG).show();
}
}
}
- 這里我使用了 DataBinding 的形式词顾,對(duì)數(shù)據(jù)進(jìn)行綁定
- 當(dāng)然八秃,你也可以選用 ButterKnife 等優(yōu)秀的三方庫(kù)
- 那么為什么我選 DataBinding 呢?親兒子 懂吧肉盹? /壞笑
運(yùn)行
- 關(guān)于 測(cè)序的大致便是如此了
- 至于細(xì)枝末節(jié)的東西大家可以直接到這個(gè)庫(kù)里面看昔驱,地址在文末
更多模塊實(shí)戰(zhàn) FIWKeepApp
這里我將上述過(guò)程寫(xiě)在我的
Demo
里,地址在GitHub
大家可以直接查看改倉(cāng)庫(kù)源碼上忍,記得給我點(diǎn)個(gè)star
哦~:Demo
地址:FIWKeepApp - LoginFragment
總結(jié)
- 想必看到這兒的讀者對(duì) Retrofit 的使用都已近有了一定的了解骤肛,但 Retrofit 的好處并不只是這些,還有很多跟深入的只是需要了解窍蓝,但本文限于篇幅腋颠,無(wú)法向大家一一介紹
- 對(duì)于我前面的 FIWKeepApp 這個(gè)倉(cāng)庫(kù),我將一步步轉(zhuǎn)換到 Retrofit + OkHttp 的形式下吓笙,歡迎大家關(guān)注我的 這個(gè)倉(cāng)庫(kù)淑玫,進(jìn)行學(xué)習(xí),也歡迎各位老鐵給個(gè) star
- 后面我還會(huì)對(duì)
Android
的各種知識(shí)點(diǎn)面睛、Framework
層源碼絮蒿,三方庫(kù)等進(jìn)行解析,歡迎大家關(guān)注 _yuanhao 簡(jiǎn)書(shū) 及時(shí)接收更多優(yōu)質(zhì)博文侮穿!