栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 移动开发 > Android

深入浅出RxJava+Retrofit+OkHttp网络请求

Android 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

深入浅出RxJava+Retrofit+OkHttp网络请求

浅谈RxJava+Retrofit+OkHttp 封装使用 之前发出后收到很多朋友的关注,原本只是自己学习后的一些经验总结,但是有同学运用到实战当中,这让我很惶恐,所有后续一直更新了很多次版本,有些地方难免有所变动导致之前的博客有所出入,正好最近受到掘金邀请内测博客,所以决定重新写一版,按照最后迭代完成的封装详细的讲述一遍,欢迎大家关注!

注意:由于本章的特殊性,后续文章比较长而且复杂,涉及内容也很多,所以大家准备好茶水,前方高能预警。

简介:

Retrofit: Retrofit是Square 公司开发的一款正对Android 网络请求的框架。底层基于OkHttp 实现,OkHttp 已经得到了google 官方的认可。

OkHttp: 也是Square 开源的网络请求库

RxJava:RxJava 在 GitHub 主页上的自我介绍是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。这就是 RxJava ,概括得非常精准。总之就是让异步操作变得非常简单。

各自的职责:Retrofit 负责请求的数据和请求的结果,使用接口的方式呈现,OkHttp 负责请求的过程,RxJava 负责异步,各种线程之间的切换。

RxJava + Retrofit + okHttp 已成为当前Android 网络请求最流行的方式。

封装成果

封装完以后,具有如下功能:

    1.Retrofit+Rxjava+okhttp基本使用方法
    2.统一处理请求数据格式
    3.统一的ProgressDialog和回调Subscriber处理
    4.取消http请求
    5.预处理http请求
    6.返回数据的统一判断
    7.失败后的retry封装处理
    8.RxLifecycle管理生命周期,防止泄露

实现效果:

具体使用

封装后http请求代码如下

//  完美封装简化版
  private void simpleDo() {
    SubjectPost postEntity = new SubjectPost(simpleOnNextListener,this);
    postEntity.setAll(true);
    HttpManager manager = HttpManager.getInstance();
    manager.doHttpDeal(postEntity);
  }

  //  回调一一对应
  HttponNextListener simpleonNextListener = new HttpOnNextListener>() {
    @Override
    public void onNext(List subjects) {
      tvMsg.setText("已封装:n" + subjects.toString());
    }

    
    @Override
    public void onError(Throwable e) {
      super.onError(e);
      tvMsg.setText("失败:n" + e.toString());
    }
  };

是不是很简单?你可能说这还简单,好咱们对比一下正常使用Retrofit的方法

 
  private void onButton9Click() { 
    //手动创建一个OkHttpClient并设置超时时间 
    okhttp3.OkHttpClient.Builder builder = new OkHttpClient.Builder(); 
    builder.connectTimeout(5, TimeUnit.SECONDS); 

    Retrofit retrofit = new Retrofit.Builder() 
 .client(builder.build()) 
 .addConverterFactory(GsonConverterFactory.create()) 
 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 
 .baseUrl(HttpManager.base_URL) 
 .build(); 

/    加载框 
    final ProgressDialog pd = new ProgressDialog(this); 

    HttpService apiService = retrofit.create(HttpService.class); 
    Observable observable = apiService.getAllVedioBy(true); 
    observable.subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) 
 .subscribe( 
     new Subscriber() { 
@Override 
public void onCompleted() { 
  if (pd != null && pd.isShowing()) { 
    pd.dismiss(); 
  } 
} 

@Override 
public void onError(Throwable e) { 
  if (pd != null && pd.isShowing()) { 
    pd.dismiss(); 
  } 
} 

@Override 
public void onNext(RetrofitEntity retrofitEntity) { 
  tvMsg.setText("无封装:n" + retrofitEntity.getData().toString()); 
} 

@Override 
public void onStart() { 
  super.onStart(); 
  pd.show(); 
} 
     } 

 ); 
  }

可能你发现确是代码有点多,但是更加可怕的是,如果你一个activity或者fragment中多次需要http请求,你需要多次重复的写回调处理(一个回到就有4个方法呀!!!!反正我是忍受不了),而且以上处理还没有做过多的判断和错误校验就如此复杂!~好了介绍完了,开始咱们的优化之路吧!

项目结构:

Retrofit

咱家今天的主角来了,咱们也深入浅出一下了解下Retrofit使用,前方高能,如果你是深度Retrofit选手请直接跳过本节!!!

1.首先确保在AndroidManifest.xml中请求了网络权限

2.在app/build.gradle添加引用

 
  compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
  compile 'com.trello:rxlifecycle:1.0'
  compile 'com.trello:rxlifecycle-components:1.0'
  
  compile 'com.squareup.retrofit2:retrofit:2.1.0'
  compile 'com.squareup.retrofit2:converter-gson:2.0.0'
  compile 'com.google.code.gson:gson:2.8.0'

3.常用注解

这里介绍一些常用的注解的使用

  1. @Query、@QueryMap:用于Http Get请求传递参数
  2. @Field:用于Post方式传递参数,需要在请求接口方法上添加@FormUrlEncoded,即以表单的方式传递参数
  3. @Body:用于Post,根据转换方式将实例对象转化为对应字符串传递参数.比如Retrofit添加GsonConverterFactory则是将body转化为gson字符串进行传递
  4. @Path:用于URL上占位符
  5. @Part:配合@Multipart使用,一般用于文件上传
  6. @Header:添加http header
  7. @Headers:跟@Header作用一样,只是使用方式不一样,@Header是作为请求方法的参数传入,@Headers是以固定方式直接添加到请求方法上

ReTrofit基本使用:

首先给定一个测试接口文档,后面的博客中我们都是用这个接口调试

 
public interface MyApiEndpointInterface { 
  @POST("AppFiftyToneGraph/videolink") 
  Call getAllVedio(@Body boolean once_no)
}

3.得到call然后同步处理处理回调:

MyApiEndpointInterface apiService = retrofit.create(MyApiEndpointInterface.class); 
Call call = apiService.getAllVedio(true); 
call.enqueue(new Callback() { 
  @Override 
  public void onResponse(Response response, Retrofit retrofit) { 
    RetrofitEntity entity = response.body(); 
    Log.i("tag", "onResponse----->" + entity.getMsg()); 
  } 

  @Override 
  public void onFailure(Throwable t) { 
    Log.i("tag", "onFailure----->" + t.toString()); 

  } 
});

这就是简单的Retrofit使用步骤,接下来我们结合RxJava讲述

ReTrofit+Rxjava基本使用

对比之前的Retrofit使用

1.在于我们需要修改service接口返回信息我们需要返回一个Observable对象

@POST("AppFiftyToneGraph/videolink") 
Observable getAllVedioBy(@Body boolean once_no);

2.然后初始化Retrofit需要添加对Rxjava的适配,注意一定要retrofit2才有这个功能哦

Retrofit retrofit = new Retrofit.Builder() 
 .client(builder.build()) 
 .addConverterFactory(GsonConverterFactory.create()) 
 .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 
 .baseUrl(HttpManager.base_URL) 
 .build();

3.回调通过RxJava处理

HttpService apiService = retrofit.create(HttpService.class); 
    Observable observable = apiService.getAllVedioBy(true); 
    observable.subscribeOn(Schedulers.io()).unsubscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) 
 .subscribe( 
     new Subscriber() { 
@Override 
public void onCompleted() { 
} 

@Override 
public void onError(Throwable e) {  
} 

@Override 
public void onNext(RetrofitEntity retrofitEntity) { 
  tvMsg.setText("无封装:n" + retrofitEntity.getData().toString()); 
}
     } 

 );

简单的RxJava集合Retrofit的使用就介绍完了,同样的可以发现使用起来很多重复性的代码,而且使用也不是那么简单,所以才有了下面的封装

ReTrofit+Rxjava进阶封装之路

先来一张流程图压压惊

请求数据封装

1.参数

首先需要封装的使我们的数据类,在数据类中需要封装请求中用到的相关数据的设置,比如请求参数、方法、加载框显示设置等等

public abstract class baseApi implements Func1, T> {
  //rx生命周期管理
  private SoftReference rxAppCompatActivity;
  
  private SoftReference listener;
  
  private boolean cancel;
  
  private boolean showProgress;
  
  private boolean cache;
  
  private String baseUrl="http://www.izaodao.com/Api/";
  
  private String mothed;
  
  private int connectionTime = 6;
  
  private int cookieNetWorkTime=60;
  
  private int cookieNonetWorkTime=24*60*60*30;
}

注释很详细,这里不具体描述了,由于这里是最后封装完成以后的代码,所以有些内容本章还会部分不会涉及,因为功能太多,还是按照一开始的博客章节讲解。

2.抽象api接口

  
  public abstract Observable getObservable(Retrofit retrofit);

通过子类也即是我们的具体api接口,通过getObservable实现service中定义的接口方法,例如:

public class SubjectPostApi extends baseApi {
   xxxxxxx
   xxxxxxx

 @Override
  public Observable getObservable(Retrofit retrofit) {
    HttpPostService service = retrofit.create(HttpPostService.class);
    return service.getAllVedioBys(isAll());
  }
}

通过传入的Retrofit对象,可以随意切换挑选Service对象,得到定义的注解方法,初始完成以后返回Observable对象。

3.结果判断

这里结合RxJava的map方法在服务器返回数据中,统一处理数据处理,所以baseApi implements

Func1, T>,后边结合结果处理链接起来使用
  @Override
  public T call(baseResultEntity httpResult) {
    if (httpResult.getRet() == 0) {
      throw new HttpTimeException(httpResult.getMsg());
    }
    return httpResult.getData();
  }

由于测试接口,也是当前我们公司接口都是有统一规则的,想必大家都有这样的接口规则,所以才有这里的统一判断,规则如下:

 * ret:1成功,2失败 
 * msg:信息 
 * data:{ 
 *    name:视频名称 
 *    title:标题 
 * }

其实上面的接口文档中就介绍了,统一先通过ret判断,失败显示msg信息,data是成功后的数据也就是用户关心的数据,所以可封装一个结果对象baseResultEntity.

4.结果数据


public class baseResultEntity {
  // 判断标示
  private int ret;
  //  提示信息
  private String msg;
  //显示数据(用户需要关心的数据)
  private T data;


  xxxxx get-set xxxxx
}

这里结合baseApi的Func1判断,失败直接抛出一个异常,交个RxJava的onError处理,成功则将用户关心的数据传给Gson解析返回

5.泛型传递

baseResultEntity中的泛型T也就是我们所关心的回调数据,同样也是Gson最后解析返回的数据,传递的过程根节点是通过定义service方法是给定的,例如:

public interface HttpPostService {
  @POST("AppFiftyToneGraph/videolink")
  Call getAllVedio(@Body boolean once_no);
}

其中的RetrofitEntity就是用户关心的数据类,通过泛型传递给最后的接口。

6.强调

很多兄弟通过QQ群反馈给我说,使用一个接口需要写一个对应的api类继承baseApi是不是很麻烦,我这里强调一下,这样封装是为了将一个Api接口作为一个对象去封装,个人觉得有必要封装成一个类,在日后工程日益增加接口随着增加的同时,对象的做法更加有利于查找接口和修改接口有利于迭代。

操作类封装

1初始对象

首先初始化一个单利方便HttpManager请求;这里用了volatile的对象

  private volatile static HttpManager INSTANCE;

  //构造方法私有
  private HttpManager() {
  }

  //获取单例
  public static HttpManager getInstance() {
    if (INSTANCE == null) {
      synchronized (HttpManager.class) {
 if (INSTANCE == null) {
   INSTANCE = new HttpManager();
 }
      }
    }
    return INSTANCE;
  }

2接口处理和回调处理:

 
  public void doHttpDeal(baseApi basePar) {
    //手动创建一个OkHttpClient并设置超时时间缓存等设置
    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.connectTimeout(basePar.getConnectionTime(), TimeUnit.SECONDS);
    builder.addInterceptor(new cookieInterceptor(basePar.isCache()));

    
    Retrofit retrofit = new Retrofit.Builder()
 .client(builder.build())
 .addConverterFactory(GsonConverterFactory.create())
 .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
 .baseUrl(basePar.getbaseUrl())
 .build();


    
    ProgressSubscriber subscriber = new ProgressSubscriber(basePar);
    Observable observable = basePar.getObservable(retrofit)
 
 .retryWhen(new RetryWhenNetworkException())
 
 .compose(basePar.getRxAppCompatActivity().bindToLifecycle())
 
 .subscribeOn(Schedulers.io())
 .unsubscribeOn(Schedulers.io())
 
 .observeOn(AndroidSchedulers.mainThread())
 
 .map(basePar);

    
    observable.subscribe(subscriber);
  }

首先通过api接口类baseApi的实现类中数据初始化OkHttpClient和Retrofit对象,其中包含了url,超时等,接着通过baseApi的抽象方法getObservable得到Observable对象,得到Observable对象以后,我们就能随意的切换现成来处理,整个请求通过compose设定的rxlifecycle来管理生命周期,所以不会溢出和泄露无需任何担心,最后再服务器数据返回时,通过map判断结果,剔除错误信息,成功以后返回到自定义的ProgressSubscriber对象中,所以接下来封装ProgressSubscriber对象。

ProgressSubscriber封装

ProgressSubscriber其实是继承于Subscriber,封装的方法无非是对Subscriber的回调方法的封装

  1. onStart():开始
  2. onCompleted():结束
  3. onError(Throwable e):错误
  4. onNext(T t):成功

1.请求加载框

http请求都伴随着加载框的使用,所以这里需要在onStart()使用前初始一个加载框,这里简单的用ProgressDialog代替


public class ProgressSubscriber extends Subscriber {
  
  private boolean showPorgress = true;
  
  private SoftReference mSubscriberOnNextListener;
  
  private SoftReference mActivity;
  
  private ProgressDialog pd;
  
  private baseApi api;


  
  public ProgressSubscriber(baseApi api) {
    this.api = api;
    this.mSubscriberonNextListener = api.getListener();
    this.mActivity = new SoftReference<>(api.getRxAppCompatActivity());
    setShowPorgress(api.isShowProgress());
    if (api.isShowProgress()) {
      initProgressDialog(api.isCancel());
    }
  }


  
  private void initProgressDialog(boolean cancel) {
    Context context = mActivity.get();
    if (pd == null && context != null) {
      pd = new ProgressDialog(context);
      pd.setCancelable(cancel);
      if (cancel) {
 pd.setonCancelListener(new DialogInterface.onCancelListener() {
   @Override
   public void onCancel(DialogInterface dialogInterface) {
     onCancelProgress();
   }
 });
      }
    }
  }


  
  private void showProgressDialog() {
    if (!isShowPorgress()) return;
    Context context = mActivity.get();
    if (pd == null || context == null) return;
    if (!pd.isShowing()) {
      pd.show();
    }
  }


  
  private void dismissProgressDialog() {
    if (!isShowPorgress()) return;
    if (pd != null && pd.isShowing()) {
      pd.dismiss();
    }
  }
}

由于progress的特殊性,需要指定content而且不能是Application所以这里传递一个RxAppCompatActivity,而同时上面的HttpManager同样需要,所以这里统一还是按照baseApi传递过来,使用软引用的方式避免泄露。剩下的无非是初始化,显示和关闭方法,可以详细看代码。

2.onStart()实现

在onStart()中需要调用加载框,然后这里还有网络缓存的逻辑,后面会单独讲解,现在先忽略它的存在。

 
  @Override
  public void onStart() {
    showProgressDialog();
    
    if (api.isCache() && AppUtil.isNetworkAvailable(RxRetrofitApp.getApplication())) {

      cookieResulte cookieResulte = cookieDbUtil.getInstance().querycookieBy(api.getUrl());
      if (cookieResulte != null) {
 long time = (System.currentTimeMillis() - cookieResulte.getTime()) / 1000;
 if (time < api.getcookieNetWorkTime()) {
   if (mSubscriberOnNextListener.get() != null) {
     mSubscriberOnNextListener.get().onCacheNext(cookieResulte.getResulte());
   }
   onCompleted();
   unsubscribe();
 }
      }
    }
  }

3.onCompleted()实现

 
  @Override
  public void onCompleted() {
    dismissProgressDialog();
  }

4.onError(Throwable e)实现

在onError(Throwable e)是对错误信息的处理和缓存读取的处理,后续会讲解,先忽略。


  @Override
  public void onError(Throwable e) {
    dismissProgressDialog();
    
    if (api.isCache()) {
      Observable.just(api.getUrl()).subscribe(new Subscriber() {
 @Override
 public void onCompleted() {

 }

 @Override
 public void onError(Throwable e) {
   errorDo(e);
 }

 @Override
 public void onNext(String s) {
   
   cookieResulte cookieResulte = cookieDbUtil.getInstance().querycookieBy(s);
   if (cookieResulte == null) {
     throw new HttpTimeException("网络错误");
   }
   long time = (System.currentTimeMillis() - cookieResulte.getTime()) / 1000;
   if (time < api.getcookieNonetWorkTime()) {
     if (mSubscriberOnNextListener.get() != null) {
mSubscriberOnNextListener.get().onCacheNext(cookieResulte.getResulte());
     }
   } else {
     cookieDbUtil.getInstance().deletecookie(cookieResulte);
     throw new HttpTimeException("网络错误");
   }
 }
      });
    } else {
      errorDo(e);
    }
  }

  
  private void errorDo(Throwable e) {
    Context context = mActivity.get();
    if (context == null) return;
    if (e instanceof SocketTimeoutException) {
      Toast.makeText(context, "网络中断,请检查您的网络状态", Toast.LENGTH_SHORT).show();
    } else if (e instanceof ConnectException) {
      Toast.makeText(context, "网络中断,请检查您的网络状态", Toast.LENGTH_SHORT).show();
    } else {
      Toast.makeText(context, "错误" + e.getMessage(), Toast.LENGTH_SHORT).show();
    }
    if (mSubscriberOnNextListener.get() != null) {
      mSubscriberOnNextListener.get().onError(e);
    }
  }

5.onNext(T t)实现

  
  @Override
  public void onNext(T t) {
    if (mSubscriberOnNextListener.get() != null) {
      mSubscriberOnNextListener.get().onNext(t);
    }
  }

主要是是将得到的结果,通过自定义的接口返回给view界面,其中的软引用对象mSubscriberOnNextListener是自定义的接口回调类HttpOnNextListener.

6.HttpOnNextListener封装

现在只需关心onNext(T t)和onError(Throwable e)接口即可,回调的触发点都是在上面的ProgressSubscriber中调用


public abstract class HttpOnNextListener {
  
  public abstract void onNext(T t);

  
  public void onCacheNext(String string){

  }

  
  public void onError(Throwable e){

  }

  
  public void onCancel(){

  }
}

失败后的retry处理

这里你可能会问,Retrofit有自带的retry处理呀,的确Retrofit有自带的retry处理,但是有很多的局限,先看下使用

OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.retryonConnectionFailure(true);

使用起来还是很方便,只需要调用一个方法即可,但是它是不可控的,也就是没有办法设置retry时间次数,所以不太灵活,既然如此还不如自己封装一下,因为用RxJava实现这个简直小菜,无形中好像已经给RxJava打了广告,中毒太深。

很简单直接上代码:

 
public class RetryWhenNetworkException implements Func1, Observable> {
//  retry次数
  private int count = 3;
//  延迟
  private long delay = 3000;
//  叠加延迟
  private long increaseDelay = 3000;

  public RetryWhenNetworkException() {

  }

  public RetryWhenNetworkException(int count, long delay) {
    this.count = count;
    this.delay = delay;
  }

  public RetryWhenNetworkException(int count, long delay, long increaseDelay) {
    this.count = count;
    this.delay = delay;
    this.increaseDelay = increaseDelay;
  }

  @Override
  public Observable call(Observable observable) {
    return observable
 .zipWith(Observable.range(1, count + 1), new Func2() {
   @Override
   public Wrapper call(Throwable throwable, Integer integer) {
     return new Wrapper(throwable, integer);
   }
 }).flatMap(new Func1>() {
   @Override
   public Observable call(Wrapper wrapper) {
     if ((wrapper.throwable instanceof ConnectException
  || wrapper.throwable instanceof SocketTimeoutException
  || wrapper.throwable instanceof TimeoutException)
  && wrapper.index < count + 1) { //如果超出重试次数也抛出错误,否则默认是会进入onCompleted
return Observable.timer(delay + (wrapper.index - 1) * increaseDelay, TimeUnit.MILLISECONDS);

     }
     return Observable.error(wrapper.throwable);
   }
 });
  }

  private class Wrapper {
    private int index;
    private Throwable throwable;

    public Wrapper(Throwable throwable, int index) {
      this.index = index;
      this.throwable = throwable;
    }
  }
}

使用

到这里,我们第一步封装已经完成了,下面讲解下如何使用,已经看明白的各位看官,估计早就看明白了使用方式,无非是创建一个api对象继承baseApi初始接口信息,然后调用HttpManager对象的doHttpDeal(baseApi basePar)方法,最后静静的等待回调类HttpOnNextListener类返回的onNext(T t)成功数据或者onError(Throwable e)数据。

其实代码就是这样:

api接口对象


public class SubjectPostApi extends baseApi {
//  接口需要传入的参数 可自定义不同类型
  private boolean all;
  
//  String xxxxx;

  
  public SubjectPostApi(HttponNextListener listener, RxAppCompatActivity rxAppCompatActivity) {
    super(listener,rxAppCompatActivity);
    setShowProgress(true);
    setCancel(true);
    setCache(true);
    setMothed("AppFiftyToneGraph/videolink");
    setcookieNetWorkTime(60);
    setcookieNonetWorkTime(24*60*60);
  }

  public boolean isAll() {
    return all;
  }

  public void setAll(boolean all) {
    this.all = all;
  }

  @Override
  public Observable getObservable(Retrofit retrofit) {
    HttpPostService service = retrofit.create(HttpPostService.class);
    return service.getAllVedioBys(isAll());
  }
}

请求回调

 //  完美封装简化版
  private void simpleDo() {
    SubjectPostApi postEntity = new SubjectPostApi(simpleOnNextListener,this);
    postEntity.setAll(true);
    HttpManager manager = HttpManager.getInstance();
    manager.doHttpDeal(postEntity);
  }

  //  回调一一对应
  HttponNextListener simpleonNextListener = new HttpOnNextListener>() {
    @Override
    public void onNext(List subjects) {
      tvMsg.setText("网络返回:n" + subjects.toString());
    }

    @Override
    public void onCacheNext(String cache) {
      
      Gson gson=new Gson();
      java.lang.reflect.Type type = new TypeToken>>() {}.getType();
      baseResultEntity resultEntity= gson.fromJson(cache, type);
      tvMsg.setText("缓存返回:n"+resultEntity.getData().toString() );
    }

    
    @Override
    public void onError(Throwable e) {
      super.onError(e);
      tvMsg.setText("失败:n" + e.toString());
    }

    
    @Override
    public void onCancel() {
      super.onCancel();
      tvMsg.setText("取消請求");
    }
  };

后续

到这里,封装功能中很多功能还没涉及和讲解,后续会陆续更新!

先给大家看看为师的完全体功能:

    1.Retrofit+Rxjava+okhttp基本使用方法
    2.统一处理请求数据格式
    3.统一的ProgressDialog和回调Subscriber处理
    4.取消http请求
    5.预处理http请求
    6.返回数据的统一判断
    7.失败后的retry处理
    8.RxLifecycle管理生命周期,防止泄露
    9.文件上传下载(支持多文件,断点续传)
    10.Cache数据持久化和数据库(greenDao)两种缓存机制
    11.异常统一处理

来个图压压惊:

源码:

RxRetrofit-终极封装-深入浅出&网络请求-GitHub

其实我还有一个兄弟版本-传送门

我不会告诉你其实我还有个更加简单的版本

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/157252.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号