Android實(shí)現(xiàn)Rxjava2+Retrofit完美封裝

去年的時(shí)候?qū)W習(xí)了Rxjava和Retrofit的基本用法,但一直沒(méi)有在實(shí)際項(xiàng)目中運(yùn)用。今年開(kāi)做新項(xiàng)目,果斷在新項(xiàng)目中引入了RxJava和Retrofit。本篇文章將介紹筆者在項(xiàng)目中對(duì)Retrofit的封裝。
先來(lái)看一下封裝過(guò)后的Retrofit如何使用。

RetrofitHelper.getApiService()
                .getMezi()
                .compose(this.<List<MeiZi>>bindToLifecycle())
                .compose(ProgressUtils.<List<MeiZi>>applyProgressBar(this))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DefaultObserver<List<MeiZi>>() {
                    @Override
                    public void onSuccess(List<MeiZi> response) {
                        showToast("請(qǐng)求成功,妹子個(gè)數(shù)為" + response.size());
                    }
                });

沒(méi)錯(cuò),就是這么簡(jiǎn)潔的一個(gè)鏈?zhǔn)秸{(diào)用,可以顯示加載動(dòng)畫(huà),還加入了Retrofit生命周期的管理。
開(kāi)始之前需要先在module項(xiàng)目里的Gradle文件中添加用到的依賴(lài)庫(kù)

    compile "io.reactivex.rxjava2:rxjava:$rootProject.ext.rxjava2Version"
    compile "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofit2Version"
    compile "com.squareup.retrofit2:converter-scalars:$rootProject.ext.retrofit2Version"
    compile "com.squareup.retrofit2:converter-gson:$rootProject.ext.retrofit2Version"
    compile "com.squareup.retrofit2:adapter-rxjava2:$rootProject.ext.retrofit2Version"
    compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
    compile "com.trello.rxlifecycle2:rxlifecycle:$rootProject.ext.rxlifecycle"
    //compile "com.trello.rxlifecycle2:rxlifecycle-android:$rootProject.ext.rxlifecycle"
    compile "com.trello.rxlifecycle2:rxlifecycle-components:$rootProject.ext.rxlifecycle"

為了方便依賴(lài)庫(kù)版本的修改我們采用”io.reactivex.rxjava2:rxjava:$rootProject.ext.rxjava2Version”這中方式添加依賴(lài),因此需要在project的build.gradle文件的加上以下內(nèi)容:

ext {
    supportLibVersion = '25.1.0'
    butterknifeVersion = '8.5.1'
    rxjava2Version = '2.0.8'
    retrofit2Version = '2.2.0'
    rxlifecycle='2.1.0'
    gsonVersion = '2.8.0'
}

下面將通過(guò)幾個(gè)小節(jié)對(duì)本次封裝作詳細(xì)的解析:

  • 服務(wù)器響應(yīng)數(shù)據(jù)的基類(lèi)BasicResponse
  • 構(gòu)建初始化Retrofit的工具類(lèi)IdeaApi
  • 通過(guò)GsonConverterFactory獲取真實(shí)響應(yīng)數(shù)據(jù)
  • 封裝DefaultObserver處理服務(wù)器響應(yīng)
  • 處理加載Loading
  • 管理Retrofit生命周期
  • 如何使用封裝
  • 小結(jié)

一.服務(wù)器響應(yīng)數(shù)據(jù)的基類(lèi)BasicResponse。

假定服務(wù)器返回的Json數(shù)據(jù)格式如下:

{
 "code": 200,
 "message": "成功",
 "content": {
    ...
    }
}

根據(jù)Json數(shù)據(jù)格式構(gòu)建我們的BasicResponse(BasicResponse中的字段內(nèi)容需要根據(jù)自己服務(wù)器返回的數(shù)據(jù)確定)。代碼如下:

public class BasicResponse<T> {

    private int code;
    private String message;
    private T content;
    ...此處省去get、set方法。

二.構(gòu)建初始化Retrofit的工具類(lèi)IdeaApi。

該類(lèi)通過(guò)RetrofitUtils來(lái)獲取ApiService的實(shí)例。代碼如下:

public class IdeaApi {
    public static <T> T getApiService(Class<T> cls,String baseUrl) {
        Retrofit retrofit = RetrofitUtils .getRetrofitBuilder(baseUrl).build();
        return retrofit.create(cls);
    }
}

RetrofitUtils用來(lái)構(gòu)建Retrofit.Builder,并對(duì)OkHttp做以下幾個(gè)方面的配置:
1. 設(shè)置日志攔截器,攔截服務(wù)器返回的json數(shù)據(jù)。Retrofit將請(qǐng)求到j(luò)son數(shù)據(jù)直接轉(zhuǎn)換成了實(shí)體類(lèi),但有時(shí)候我們需要查看json數(shù)據(jù),Retrofit并沒(méi)有提供直接獲取json數(shù)據(jù)的功能。因此我們需要自定義一個(gè)日志攔截器攔截json數(shù)據(jù),并輸入到控制臺(tái)。
2. 設(shè)置Http請(qǐng)求頭。給OkHttp 添加請(qǐng)求頭攔截器,配置請(qǐng)求頭信息。還可以為接口統(tǒng)一添加請(qǐng)求頭數(shù)據(jù)。例如,把用戶(hù)名、密碼(或者token)統(tǒng)一添加到請(qǐng)求頭。后續(xù)每個(gè)接口的請(qǐng)求頭中都會(huì)攜帶用戶(hù)名、密碼(或者token)數(shù)據(jù),避免了為每個(gè)接口單獨(dú)添加。
3. 為OkHttp配置緩存。同樣可以同過(guò)攔截器實(shí)現(xiàn)緩存處理。包括控制緩存的最大生命值,控制緩存的過(guò)期時(shí)間。
4. 如果采用https,我們還可以在此處理證書(shū)校驗(yàn)以及服務(wù)器校驗(yàn)。
5. 為Retrofit添加GsonConverterFactory。此處是一個(gè)比較重要的環(huán)節(jié),將在后邊詳細(xì)講解。
RetrofitUtils 代碼如下:

public class RetrofitUtils {
    public static OkHttpClient.Builder getOkHttpClientBuilder() {

        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                try {
                    LogUtils.e("OKHttp-----", URLDecoder.decode(message, "utf-8"));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    LogUtils.e("OKHttp-----", message);
                }
            }
        });
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        File cacheFile = new File(Utils.getContext().getCacheDir(), "cache");
        Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb

        return new OkHttpClient.Builder()
                .readTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
                .connectTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
                .addInterceptor(loggingInterceptor)
                .addInterceptor(new HttpHeaderInterceptor())
                .addNetworkInterceptor(new HttpCacheInterceptor())
               // .sslSocketFactory(SslContextFactory.getSSLSocketFactoryForTwoWay())  // https認(rèn)證 如果要使用https且為自定義證書(shū) 可以去掉這兩行注釋?zhuān)⒆孕信渲谱C書(shū)。
               // .hostnameVerifier(new SafeHostnameVerifier())
                .cache(cache);
    }

    public static Retrofit.Builder getRetrofitBuilder(String baseUrl) {
        Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create();
        OkHttpClient okHttpClient = RetrofitUtils.getOkHttpClientBuilder().build();
        return new Retrofit.Builder()
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(baseUrl);
    }
}

三.通過(guò)GsonConverterFactory獲取真實(shí)響應(yīng)數(shù)據(jù)

在第一節(jié)中我們構(gòu)建了服務(wù)器響應(yīng)數(shù)據(jù)BasicResponse,BasicResponse由code、message、和content三個(gè)字段。其中code為服務(wù)器返回的錯(cuò)誤碼。我們會(huì)事先和服務(wù)器約定成功時(shí)的code值,比如200表示請(qǐng)求成功。但通常在請(qǐng)求服務(wù)器數(shù)據(jù)過(guò)程中免不了會(huì)出現(xiàn)各種錯(cuò)誤。例如用戶(hù)登錄時(shí)密碼錯(cuò)誤、請(qǐng)求參數(shù)錯(cuò)誤的情況。此時(shí)服務(wù)器會(huì)根據(jù)錯(cuò)誤情況返回對(duì)應(yīng)的錯(cuò)誤碼。一般來(lái)說(shuō),我們只關(guān)心成功時(shí)即code為200時(shí)的content數(shù)據(jù)。而對(duì)于code不為200時(shí)我們只需要給出對(duì)應(yīng)的Toast提示即可。事實(shí)上我們對(duì)我們有用的僅僅時(shí)code為200時(shí)的content數(shù)據(jù)。因此我們可以考慮過(guò)濾掉code和message,在請(qǐng)求成功的回調(diào)中只返回content的內(nèi)容。
在此種情況下就需要我們通過(guò)自定義GsonConverterFactory來(lái)實(shí)現(xiàn)了。我們可以直接從Retrofit的源碼中copy出GsonConverterFactory的三個(gè)相關(guān)類(lèi)來(lái)做修改。
其中最終要的一部分是修改GsonResponseBodyConverter中的convert方法。在該方法中拿到服務(wù)器響應(yīng)數(shù)據(jù)并判斷code是否為200。如果是,則獲取到content并返回,如果不是,則在此處可以?huà)伋鰧?duì)應(yīng)的自定義的異常。然后再Observer中統(tǒng)一處理異常情況。GsonResponseBodyConverter代碼如下:

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, Object> {

    private final TypeAdapter<T> adapter;

    GsonResponseBodyConverter(TypeAdapter<T> adapter) {
        this.adapter = adapter;
    }

    @Override
    public Object convert(ResponseBody value) throws IOException {
        try {
            BasicResponse response = (BasicResponse) adapter.fromJson(value.charStream());
            if (response.getCode()==200) {
            return response.getResults();
            } else {
                // 特定 API 的錯(cuò)誤,在相應(yīng)的 DefaultObserver 的 onError 的方法中進(jìn)行處理
                throw new ServerResponseException(response.getCode(), response.getMessage());
            }
        } finally {
            value.close();
        }
        return null;
    }
}

四.構(gòu)建DefaultObserver處理服務(wù)器響應(yīng)。

上一節(jié)中我們講到了在請(qǐng)求服務(wù)器時(shí)可能出現(xiàn)的一些例如密碼錯(cuò)誤、參數(shù)錯(cuò)誤的情況,服務(wù)器給我們返回了對(duì)應(yīng)的錯(cuò)誤碼,我們根據(jù)錯(cuò)誤碼拋出了對(duì)應(yīng)自定義異常。除此之外在我們發(fā)起網(wǎng)絡(luò)請(qǐng)求時(shí)還可能發(fā)生一些異常情況。例如沒(méi)有網(wǎng)絡(luò)、請(qǐng)求超時(shí)或者服務(wù)器返回了數(shù)據(jù)但在解析時(shí)出現(xiàn)了數(shù)據(jù)解析異常等。對(duì)于這樣的情況我們也要進(jìn)行統(tǒng)一處理的。那么我們就需要自定義一個(gè)DefaultObserver類(lèi)繼承Observer,并重寫(xiě)相應(yīng)的方法。
該類(lèi)中最重要的兩個(gè)方法時(shí)onNext和onError。
1.在服務(wù)器返回?cái)?shù)據(jù)成功的情況下會(huì)回調(diào)到onNext方法。因此我們可以在DefaultObserver中定義一個(gè)抽象方法onSuccess(T response),在調(diào)用網(wǎng)絡(luò)時(shí)重寫(xiě)onSuccess方法即可。
2.如果在請(qǐng)求服務(wù)器過(guò)程中出現(xiàn)任何異常,都會(huì)回調(diào)到onError方法中。包括上節(jié)中我們自己拋出的異常都會(huì)回調(diào)到onError。因此我們的重頭戲就是處理onError。在onError中我們根據(jù)異常信息給出對(duì)應(yīng)的Toast提示即可。
DefaultObserver類(lèi)的代碼如下:

public abstract class DefaultObserver<T> implements Observer<T> {
    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(T response) {
        onSuccess(response);
        onFinish();
    }

    @Override
    public void onError(Throwable e) {
        LogUtils.e("Retrofit", e.getMessage());
        if (e instanceof HttpException) {     //   HTTP錯(cuò)誤
            onException(ExceptionReason.BAD_NETWORK);
        } else if (e instanceof ConnectException
                || e instanceof UnknownHostException) {   //   連接錯(cuò)誤
            onException(ExceptionReason.CONNECT_ERROR);
        } else if (e instanceof InterruptedIOException) {   //  連接超時(shí)
            onException(ExceptionReason.CONNECT_TIMEOUT);
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException) {   //  解析錯(cuò)誤
            onException(ExceptionReason.PARSE_ERROR);
        }else if(e instanceof ServerResponseException){
            onFail(e.getMessage());
        } else {
            onException(ExceptionReason.UNKNOWN_ERROR);
        }
        onFinish();
    }

    @Override
    public void onComplete() {
    }

    /**
     * 請(qǐng)求成功
     *
     * @param response 服務(wù)器返回的數(shù)據(jù)
     */
    abstract public void onSuccess(T response);

    /**
     * 服務(wù)器返回?cái)?shù)據(jù),但響應(yīng)碼不為200
     *
     */
    public void onFail(String message) {
        ToastUtils.show(message);
    }

    public void onFinish(){}

    /**
     * 請(qǐng)求異常
     *
     * @param reason
     */
    public void onException(ExceptionReason reason) {
        switch (reason) {
            case CONNECT_ERROR:
                ToastUtils.show(R.string.connect_error, Toast.LENGTH_SHORT);
                break;

            case CONNECT_TIMEOUT:
                ToastUtils.show(R.string.connect_timeout, Toast.LENGTH_SHORT);
                break;

            case BAD_NETWORK:
                ToastUtils.show(R.string.bad_network, Toast.LENGTH_SHORT);
                break;

            case PARSE_ERROR:
                ToastUtils.show(R.string.parse_error, Toast.LENGTH_SHORT);
                break;

            case UNKNOWN_ERROR:
            default:
                ToastUtils.show(R.string.unknown_error, Toast.LENGTH_SHORT);
                break;
        }
    }

    /**
     * 請(qǐng)求網(wǎng)絡(luò)失敗原因
     */
    public enum ExceptionReason {
        /**
         * 解析數(shù)據(jù)失敗
         */
        PARSE_ERROR,
        /**
         * 網(wǎng)絡(luò)問(wèn)題
         */
        BAD_NETWORK,
        /**
         * 連接錯(cuò)誤
         */
        CONNECT_ERROR,
        /**
         * 連接超時(shí)
         */
        CONNECT_TIMEOUT,
        /**
         * 未知錯(cuò)誤
         */
        UNKNOWN_ERROR,
    }
}

五.處理加載Loading

關(guān)于Loading我們可以通過(guò)RxJava的compose操作符來(lái)做一個(gè)非常優(yōu)雅的處理。首先定義一個(gè)ProgressUtils工具類(lèi),然后通過(guò)RxJava的ObservableTransformer做一個(gè)變換來(lái)處理Loading。想要顯示Loading,只需要加上.compose(ProgressUtils.< T >applyProgressBar(this))即可。
ProgressUtils代碼如下:

public class ProgressUtils {
    public static <T> ObservableTransformer<T, T> applyProgressBar(
            @NonNull final Activity activity, String msg) {
        final WeakReference<Activity> activityWeakReference = new WeakReference<>(activity);
        final DialogUtils dialogUtils = new DialogUtils();
        dialogUtils.showProgress(activityWeakReference.get());
        return new ObservableTransformer<T, T>() {
            @Override
            public ObservableSource<T> apply(Observable<T> upstream) {
                return upstream.doOnSubscribe(new Consumer<Disposable>() {
                    @Override
                    public void accept(Disposable disposable) throws Exception {

                    }
                }).doOnTerminate(new Action() {
                    @Override
                    public void run() throws Exception {
                        Activity context;
                        if ((context = activityWeakReference.get()) != null
                                && !context.isFinishing()) {
                            dialogUtils.dismissProgress();
                        }
                    }
                }).doOnSubscribe(new Consumer<Disposable>() {
                    @Override
                    public void accept(Disposable disposable) throws Exception {
                        /*Activity context;
                        if ((context = activityWeakReference.get()) != null
                                && !context.isFinishing()) {
                            dialogUtils.dismissProgress();
                        }*/
                    }
                });
            }
        };
    }

    public static <T> ObservableTransformer<T, T> applyProgressBar(
            @NonNull final Activity activity) {
        return applyProgressBar(activity, "");
    }
}

至此關(guān)于RxJava和Retrofit的二次封裝已經(jīng)基本完成。但是我們不能忽略了很重要的一點(diǎn),就是網(wǎng)絡(luò)請(qǐng)求的生命周期。我們將在下一節(jié)中詳細(xì)講解。

六、管理Retrofit生命周期

當(dāng)activity被銷(xiāo)毀時(shí),網(wǎng)絡(luò)請(qǐng)求也應(yīng)該隨之終止的。要不然就可能造成內(nèi)存泄漏。會(huì)嚴(yán)重影到響App的性能!因此Retrofit生命周期的管理也是比較重要的一點(diǎn)內(nèi)容。在這里我們使用 RxLifecycle來(lái)對(duì)Retrofit進(jìn)行生命周期管理。其使用流程如下:

1.在gradel中添加依賴(lài)如下:

compile 'com.trello.rxlifecycle2:rxlifecycle:2.1.0'
compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'

2.讓我們的BaseActivity繼承RxAppCompatActivity。
具體代碼如下:

public abstract class BaseActivity extends RxAppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutId());
        init(savedInstanceState);
    }
    protected void showToast(String msg) {
        ToastUtils.show(msg);
    }

    protected abstract @LayoutRes int getLayoutId();

    protected abstract void init(Bundle savedInstanceState);
}

同樣我們項(xiàng)目的BaseFragment繼承RxFragment(注意使用繼承V4包下的RxFragment),如下:

public abstract class BaseFragment extends RxFragment {

    public View rootView;
    public LayoutInflater inflater;

    @Nullable
    @Override
    public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        this.inflater = inflater;
        if (rootView == null) {
            rootView = inflater.inflate(this.getLayoutId(), container, false);
            init(savedInstanceState);
        }
        ViewGroup parent = (ViewGroup) rootView.getParent();
        if (parent != null) {
            parent.removeView(rootView);
        }
        return rootView;
    }

    protected abstract int getLayoutId();

    protected abstract void init(Bundle savedInstanceState);

    protected void showToast(String msg) {
        ToastUtils.show(msg);
    }

    @Override
    public void onResume() {
        super.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
    }
}

3.使用compose操作符管理Retrofit生命周期了:

myObservable
            .compose(bindToLifecycle())
            .subscribe();

或者

myObservable
    .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
    .subscribe();

關(guān)于RxLifecycle的詳細(xì)使用方法可以參考 RxLifecycle官網(wǎng)

七.如何使用封裝

前面幾節(jié)內(nèi)容講解了如何RxJava進(jìn)行二次封裝,封裝部分的代碼可以放在我們項(xiàng)目的Library模塊中。那么封裝好之后我們應(yīng)該如何在app模塊中使用呢?
1.定義一個(gè)接口來(lái)存放我們項(xiàng)目的API

public interface IdeaApiService {
    /**
     * 此接口服務(wù)器響應(yīng)數(shù)據(jù)BasicResponse的泛型T應(yīng)該是List<MeiZi>
     * 即BasicResponse<List<MeiZi>>
     * @return BasicResponse<List<MeiZi>>
     */
    @Headers("Cache-Control: public, max-age=10")//設(shè)置緩存 緩存時(shí)間為100s
    @GET("福利/10/1")
    Observable<List<MeiZi>> getMezi();

    /**
     * 登錄 接口為假接口 并不能返回?cái)?shù)據(jù)
     * @return
     */
    @POST("login.do")
    Observable<LoginResponse> login(@Body LoginRequest request);

    /**
     * 刷新token 接口為假接口 并不能返回?cái)?shù)據(jù)
     * @return
     */
    @POST("refresh_token.do")
    Observable<RefreshTokenResponseBean> refreshToken(@Body RefreshTokenRequest request);

    @Multipart
    @POST("upload/uploadFile.do")
    Observable<BasicResponse> uploadFiles(@Part List<MultipartBody.Part> partList);
}

2.定義一個(gè)RetrofitHelper 類(lèi),通過(guò)IdeaApi來(lái)獲取IdeaApiService的實(shí)例。

public class RetrofitHelper {
    private static IdeaApiService mIdeaApiService;

    public static IdeaApiService getApiService(){
        return mIdeaApiService;
    }
    static {
       mIdeaApiService= IdeaApi.getApiService(IdeaApiService.class, Constants.API_SERVER_URL);
    }
}

3.在A(yíng)ctivity或者Fragment中發(fā)起網(wǎng)絡(luò)請(qǐng)求

    /**
     * Get請(qǐng)求
     * @param view
     */
    public void getData(View view) {
        RetrofitHelper.getApiService()
                .getMezi()
                .compose(this.<List<MeiZi>>bindToLifecycle())
                .compose(ProgressUtils.<List<MeiZi>>applyProgressBar(this))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DefaultObserver<List<MeiZi>>() {
                    @Override
                    public void onSuccess(List<MeiZi> response) {
                        showToast("請(qǐng)求成功,妹子個(gè)數(shù)為" + response.size());
                    }
                });
    }

八.小結(jié)

本篇文章主要講解了Rxjava和Retrofit的二次封裝。以上內(nèi)容也是筆者參考多方面的資料經(jīng)過(guò)長(zhǎng)時(shí)間的改動(dòng)優(yōu)化而來(lái)。但鑒于本人能力有限,其中也避免不了出現(xiàn)不當(dāng)之處。還請(qǐng)大家多多包涵。另外,在投稿郭神公眾號(hào)時(shí)文章可能還存在很多處理不優(yōu)雅的地方,比如對(duì)響應(yīng)數(shù)據(jù)的處理以及對(duì)Loading的處理。在投稿被推送后收到了很多小伙伴的建議,因此筆者也參考了大家的意見(jiàn)并做了優(yōu)化,在此感謝大家。最后如果有疑問(wèn)歡迎在文章留言評(píng)論。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容