iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0

介紹到了最後一篇了,最後就來介紹Retrofit和RxJava,那我們就開始吧 (¦3[▓▓]

Retrofit跟RxJava是什麼?

當 App 需要與後端伺服器進行資料交換時,我們需要一個穩定的網路請求方案。Retrofit和RxJava 的組合,讓處理複雜的網路操作和執行緒切換變得輕鬆簡單

  • Retrofit:它能將REST API轉換成簡單的Java Interface,讓你像呼叫本地方法一樣輕鬆發起網路請求
  • RxJava: 實現Reactive Programming的函式庫。它將任何資料(包括網路回應),視為一個可觀察的資料流,你可以訂閱這個資料流,並在其產生資料、完成或發生錯誤時做出反應

它們如何協同工作?

Retrofit透過一個Adapter,可以直接將API的回應轉換成RxJava 的ObservableSingle物件。這讓我們可以利用RxJava的鏈式操作符來處理、轉換和組合網路回應,並用其內建的排程器在背景執行緒執行網路請求,然後在主執行緒更新 UI

核心組成元件

Retrofit Instance:透過Retrofit.Builder 建立的單例物件,設定了 API 的基底 URL、資料轉換器和轉接器

API Interface:Java介面,使用 Retrofit 的註解 (@GET, @POST, @Path 等) 來定義所有的 API 端點

資料模型:用來對應 API 回應 JSON 結構的 Java 物件

Gson Converter:Retrofit的轉換器,負責自動將JSON 字串與 Java 物件進行序列化和反序列化

RxJava Adapter:Retrofit的轉接器,讓 API 介面中的方法可以回傳 RxJava 的 ObservableSingle 型別

RxJava Schedulers:RxJava 的執行緒排程器,Schedulers.io() 用於執行 I/O 密集型任務(如網路請求),AndroidSchedulers.mainThread() 用於在 Android 的主執行緒執行任務

Disposable / CompositeDisposable:RxJava 中的訂閱控制器。每次訂閱都會產生一個 Disposable 物件,我們需要用 CompositeDisposable 容器來管理它們,並在Activity/Fragment銷毀時取消所有訂閱,防止記憶體洩漏

範例

我們用免費的線上測試API JSONPlaceholder,連結點進去後往下滑,我們用黃色框的連結來作範例

https://ithelp.ithome.com.tw/upload/images/20251013/20176154ZMmWSJsdb9.png

先在build.gradle檔案中加入依賴

dependencies {
    // Retrofit
    implementation 'com.squareup.retrofit2:retrofit:3.0.0'
    // Gson Converter
    implementation 'com.squareup.retrofit2:converter-gson:3.0.0'
    // RxJava 3 Adapter
    implementation 'com.squareup.retrofit2:adapter-rxjava3:3.0.0'

    // RxJava 3
    implementation 'io.reactivex.rxjava3:rxjava:3.1.12'
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
}

在AndroidManifest.xml 中宣告網路權限

<uses-permission android:name="android.permission.INTERNET" />

建立資料模型

根據 API 回應的 JSON 格式建立 Post.java,下面是連結點進去後的格式,然後就照上面格式寫

https://ithelp.ithome.com.tw/upload/images/20251013/20176154GPC5KjbucI.png
Post.java

import com.google.gson.annotations.SerializedName;

public class Post {
    @SerializedName("id")
    private int id;
    @SerializedName("userId")
    private int userId;
    @SerializedName("title")
    private String title;
    @SerializedName("body")
    private String body;

    // Getters and Setters...
    public int getId() { return id; }
    public String getTitle() { return title; }
    public String getBody() { return body; }
}

建立 API Interface

ApiService.java:

import io.reactivex.rxjava3.core.Single;
import retrofit2.http.GET;
import retrofit2.http.Path;

public interface ApiService {
    //Single<T>適用於那種只會發射一次資料或一個錯誤的場景
    @GET("posts/{id}")//這邊就是@GET https://jsonplaceholder.typicode.com/posts/1後面的posts/1,前面的BASE_URL會另外寫
    Single<Post> getPost(@Path("id") int postId);
}

建立Retrofit Client

建立一個單例的 Retrofit Client 來提供ApiService的實例。

RetrofitClient.java:

import retrofit2.Retrofit;
import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitClient {
    private static final String BASE_URL = "https://jsonplaceholder.typicode.com/";
    private static Retrofit instance;

    public static Retrofit getInstance() {
        if (instance == null) {
            instance = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create()) //加入Gson轉換器
                    .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) //加入RxJava轉接器
                    .build();
        }
        return instance;
    }
}

在 Activity 中發起請求並處理回應

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/result_main_tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

MainActivity.java:

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.observers.DisposableSingleObserver;
import io.reactivex.rxjava3.schedulers.Schedulers;
import retrofit2.Retrofit;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";//這行方便偵錯用
    private TextView resultTextView;
    private final CompositeDisposable compositeDisposable = new CompositeDisposable();//建立一個CompositeDisposable來管理所有的訂閱
    private ApiService apiService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        resultTextView= findViewById(R.id.result_main_tv);

        //取得ApiService 實例
        apiService = RetrofitClient.getInstance().create(ApiService.class);

        fetchPost();
    }

    private void fetchPost() {
        resultTextView.setText("載入中...");
        //發起請求並串接RxJava 操作符
        compositeDisposable.add(apiService.getPost(1)
                .subscribeOn(Schedulers.io()) //指定網路請求在I/O 執行緒中執行
                .observeOn(AndroidSchedulers.mainThread()) //指定觀察者在主執行緒中執行
                .subscribeWith(new DisposableSingleObserver<Post>() { //建立一個觀察者
                    @Override
                    public void onSuccess(Post post) {
                        //當請求成功時呼叫
                        String content = "";
                        content += "ID: " + post.getId() + "\n";
                        content += "Title: " + post.getTitle() + "\n";
                        content += "Body: " + post.getBody() + "\n";
                        resultTextView.setText(content);
                    }

                    @Override
                    public void onError(Throwable e) {
                        //當請求發生錯誤時呼叫
                        resultTextView.setText("錯誤: " + e.getMessage());
                        Log.e(TAG, "onError: ", e);
                    }
                })
        );
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //在Activity銷毀時,取消所有訂閱
        compositeDisposable.clear();
    }
}

重要注意事項

  • 執行緒管理:記得使用subscribeOn(Schedulers.io())將網路請求切換到背景執行緒,並用 observeOn(AndroidSchedulers.mainThread())將結果切換回主執行緒來更新 UI
  • 記憶體洩漏問題:盡量使用 CompositeDisposable 來管理所有訂閱,並在 onDestroy() 中呼叫 clear()dispose()
  • 錯誤處理onError回呼是處理所有類型錯誤(網路中斷、伺服器錯誤、JSON 解析失敗等)的集中地,要做好完整的錯誤處理

恭喜我們剩下最後一天,明天會來為這個30天鐵人賽做個結尾,最後一天見了各位( ՞ټ՞)


上一篇
Day28 Glide介紹
下一篇
Day30 結語
系列文
Android 菜鳥30天從0到1的學習紀錄30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言