最近,筆者對安卓開發(fā)的興趣愈發(fā)濃厚,而且不斷嘗試了許多傳聞很棒的開發(fā)庫 -- 大部分也真的很不錯。于是打算寫一個系列文章,介紹使用這些讓人驚嘆的庫建立安卓示例應用的實踐。這樣,讀者可以自行判斷,這些庫有多好用。本文是該系列的第一篇。
今天,筆者要研究對應用來說,使用 Retrofit 2 的 HTTP 客戶端有多復雜,以及能帶來什么好處。Retrofit是Square Inc.發(fā)布在開源社區(qū)的一款令人嘆為觀止的工具。它是一個類型安全的 HTTP 客戶端,適用于安卓和 Java 應用。
類型安全 HTTP 客戶端主要意味著,你只需關(guān)心發(fā)送出去的網(wǎng)絡請求的語義,而不必考慮 URL 構(gòu)建的細節(jié),以及參數(shù)設(shè)定是否正確之類的事。Retrofit應對這些易如反掌,你只需寫幾個接口即可。就是這么簡單!
我們通過一個例子來看看它是怎么實現(xiàn)的。筆者已經(jīng)把這里所有的代碼都放在了 Github 的 資源庫里。如你所知,最好的學習方法就是查看這些代碼,自己調(diào)試調(diào)試。
首先,筆者在 Android Studio 里建了一個空項目。老實說,每次看到歡迎界面上出現(xiàn)的那堆有關(guān)兼容性、以及應用所支持的 API 的選項,筆者都會感到非常迷茫。


好在我們只是弄個“玩具”項目而已,因此我們可以大膽地選擇最新的 SDK,然后快速掠過項目向?qū)?,進入我們偉大的「hello world」安卓應用。
如果你自己不想糾結(jié)這些選項,可以直接點擊這個 Github 的鏈接,導入筆者提交的現(xiàn)成項目:Floating button app skeleton.
通常,筆者在所有項目中都會啟用 JRebel for Android。這是一個 Android Studio 的插件?!皢⒂盟被旧弦馕吨?,只需點擊一下自定義按鈕就能運行應用,其余所有工作都交給它了。JRebel for Android 對安卓開發(fā)者來說是一款提升效率的工具,它可以在運行著的設(shè)備和模擬器上即時更新代碼。這本質(zhì)上意味著在開發(fā)應用時,你不必浪費寶貴的時間來等待應用重啟,并因此而中途放棄這些應用的狀態(tài)記錄。總之特別棒!
說明一下,我在 ZeoTurnaround 公司工作,也就是創(chuàng)造JRebel for Android的公司。但它真的很好用,你值得一試。
Android Studio 2.0附帶的新模擬器用著也很爽?,F(xiàn)在,MainActivity 類的模擬器屏幕被激活了。
點一下浮動按鈕,出現(xiàn) snackbar??雌饋聿诲e。讓我們隨意更改一段代碼,檢驗一下代碼重新加載的功能。筆者首先想到的是修改 snackbar 的字符串值。
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Snackbar.make(view, “Super fast hello world”, Snackbar.LENGTH_LONG)
.setAction(“Action”, null).show();
}
});
注意看,我們保存了一個文件,點擊了一下按鈕,接著模擬器上的代碼就重新加載了。現(xiàn)在一切就緒!也就是說,我們可以研究 Retrofit 2 到底有什么用處了。
Retrofit 2 是一個類型安全的 HTTP 客戶端,適用于安卓(及 Java)。但首先,它也是一個庫。所以我們要先聲明依賴。這個容易。不過,請注意,我們需要顯式依賴gson 轉(zhuǎn)換器,將 JSON 應答轉(zhuǎn)換為 model 類。這與Retrofit 1不同。所以需要注意一下。
Add these two lines to the build.gradle file:
將這兩行代碼加到 build.gradle 文件中去:
compile ‘com.squareup.retrofit2:retrofit:2.0.0-beta2’
compile ‘com.squareup.retrofit2:gson-converter:2.0.0-beta2’
Retrofit 的主要功能是可以在運行時生成代碼,發(fā)送 HTTP服務查詢請求。開發(fā)者只需寫一個“說明”接口即可。假設(shè)我們有如下的一個 model 類:
通過這個我們可以創(chuàng)建一個名為GithubService的接口,用于實現(xiàn) HTTP 通信。
public interface GitHubService {
@GET(“repos/{owner}/{repo}/contributors”)
Call<List<Contributor>> repoContributors(
@Path(“owner”) String owner,
@Path(“repo”) String repo);
}
This is the simplest example, we add the @GET annotation on an interface method and provide the path part of the URL that we want to expose it on. Conveniently, the method parameters can be referenced in the path string so you won’t need to jump through hoops to set those. Additionally, with other annotations you can specify query parameters, POST request body and so on:
- @Query(“key”)?—?for GET request query parameter
- @QueryMap?—?for the map of parameters
- @Body?—?use it with the @POST annotation to provide the query body content.
這是一個最簡單的例子。我們在接口方法中加入 @GET 注解,并且提供想要展示的 URL 的path部分。方便的是,由于可以在 path 字符串中引用方法參數(shù),因此我們無需跳出循環(huán)再重新設(shè)定。另外,通過這些注解,你可以設(shè)定查詢參數(shù),以及 POST 請求主體等等:
- @Query(“key”)? -- 用于GET請求查詢參數(shù)
- @QueryMap -- 用于參數(shù)映射
- @Body -- 與@POST注解一起使用,提供查詢主體內(nèi)容
下面,為了能在運行時使用這個接口,我們需要構(gòu)建一個 Retrofit 對象:
interface GitHubService {
@GET("repos/{owner}/{repo}/contributors")
Call<List<Contributor>> repoContributors(
@Path("owner") String owner,
@Path("repo") String repo);
public static final Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
}
筆者傾向于在包含網(wǎng)站請求的接口內(nèi)使用 Retrofit builder。這么做并不是想讓它變得復雜難懂。而是處于通用配置的考慮。例如,我們有默認的轉(zhuǎn)換器,將 JSON 響應對象轉(zhuǎn)為 Java 對象,但是,即使在每個 service 中復制粘貼一遍,也好過使用單一的抽象類,后者很容易造成泄漏。
With these pieces in place, we just need to perform the network call:
- the specification of our queries
- the Retrofit object builder
準備好這些代碼后,我們只需發(fā)出網(wǎng)絡請求: - 請求的說明
- Retrofit對象builder
為了實現(xiàn)GitHubService接口,需要初始化一個用于執(zhí)行 HTTP 查詢請求的 Call 對象。
GitHubService gitHubService = GitHubService.retrofit.create(GitHubService.class);
Call<List<Contributor>> call = gitHubService.repoContributors(“square”, “retrofit”);
List<Contributor> result = call.execute().body();
另外,有人可能也會選擇設(shè)定請求的時間,讓它成為異步請求,同時在執(zhí)行完畢后提供 callback。
call.enqueue(new Callback<List<Contributor>>() {
@Override
public void onResponse(Response<List<Contributor>> response, Retrofit retrofit) {
// handle success
}
@Override
public void onFailure(Throwable t) {
// handle failure
}
});
聽起來超簡單!我們來處理一下 UI,然后寫入代碼。在浮動按鈕的應用模板下,我們需要修改 content_main.xml 文件。筆者添加了一個發(fā)起請求查詢的按鈕,以及一個用以顯示請求結(jié)果的文本區(qū)域:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fetch"
android:id="@+id/button"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="151dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text=""
android:id="@+id/textView"
android:layout_above="@+id/button"
android:layout_alignParentEnd="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:textIsSelectable="false" />
第一次執(zhí)行時,你的網(wǎng)絡請求代碼看起來可能是這樣的:
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
GitHubService gitHubService = GitHubService.retrofit.create(GitHubService.class);
Call<List<Contributor>> call = gitHubService.repoContributors(“square”, “retrofit”);
String result = call.execute().body().toString();
TextView textView = (TextView) findViewById(R.id.textView);
textView.setText(result);
}
});
這段代碼自然無法運行。安卓框架不允許用戶在 UI 線程中執(zhí)行網(wǎng)絡請求。UI 線程只應該用來處理一些用戶輸入。在這個線程中執(zhí)行任何引起長時間阻塞的操作都會讓用戶體驗變得非常糟糕。
因此,我們需要重構(gòu)這段代碼,把網(wǎng)絡請求移入后臺線程。使用 JRebel for Android 可以不費任何時間就搞定這事。我們將代碼提取到 AsyncTask -- 這是一個在安卓上運行大型運算的默認框架。AsyncTask不太好看,它的具體運行方式也不算清潔,例如每次我們點擊按鈕時它都會去創(chuàng)建一個 Retrofit 對象。但是能用就行。
private class NetworkCall extends AsyncTask<Call, Void, String> {
@Override
protected String doInBackground(Call… params) {
try {
Call<List<Contributor>> call = params[0];
Response<List<Contributor>> response = call.execute();
return response.body().toString();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
protected void onPostExecute(String result) {
final TextView textView = (TextView) findViewById(R.id.textView);
textView.setText(result);
}
}
接著,從 EventListener 中調(diào)用它:
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
GitHubService gitHubService = GitHubService.retrofit.create(GitHubService.class);
final Call<List<Contributor>> call = gitHubService.repoContributors("square", "retrofit");
new NetworkCall().execute(call);
}
});
好啦,現(xiàn)在代碼可以運行了。文本視圖會根據(jù) HTTP 請求的結(jié)果實時刷新。

現(xiàn)在,這個框架應用程序已經(jīng)完成了,代碼順利構(gòu)建并運行完畢。你可以自己去試試 Retrofit和JRebel for Android 。隨意修改一些代碼,檢查一下新代碼構(gòu)成的應用程序運行效果如何,也不會浪費任何時間。嘗試為 Contributor 類增加幾個區(qū)域,替換 contributor 組件的 文本視圖,更改 HTTP 的端點,將請求發(fā)往另一個站點。一切盡在你掌握!
在本文中,我們了解了如何使用 Retrofit 2 創(chuàng)建一個發(fā)送網(wǎng)絡請求的簡易框架應用程序。程序的代碼可以在 Github 上找到。對本文的最佳使用方式是,將它看作一篇快速指導,根據(jù)你的側(cè)重點寫一段自己的代碼,接著復制這個應用程序的代碼,導入 Android studio,然后試試你自己的代碼吧!
今后,筆者會擴展這個框架應用程序,加入一些依賴注入框架,把UI代碼與其他代碼區(qū)分開來,以及使用RxAndroid來響應式處理一些網(wǎng)絡請求等等。另外,你在安卓項目中默認使用的庫是哪一個?研究一下這個問題也許也會很有意思呢!在Twitter上聯(lián)系我:@shelajev。期待與你們互動!
OneAPM Mobile Insight 以真實用戶體驗為度量標準進行 Crash 分析,監(jiān)控網(wǎng)絡請求及網(wǎng)絡錯誤,提升用戶留存。訪問 OneAPM 官方網(wǎng)站感受更多應用性能優(yōu)化體驗,想閱讀更多技術(shù)文章,請訪問 OneAPM 官方技術(shù)博客。
本文轉(zhuǎn)自 OneAPM 官方博客