Android版&Kotlin版RxJava2+Retrofit2+OkHttp3的基础、封装和项目中的使用

@POST("top250")

Call getTop250 (@Field("start") int start , @Field("count") int count);

}

使用 POST 方式时需要注意两点:

  • 必须加上 @FormUrlEncoded标签,否则会抛异常。

  • 必须要有参数,否则会抛异常, 源码抛异常的地方如下:

if (isFormEncoded && !gotField) {

throw methodError("Form-encoded method must contain at least one @Field.");

}

4.用 Retrofit 创建 接口实例 MoiveService 并且调用接口中的方法进行网络请求:

异步方式请求:

//获取接口实例

MovieService movieService = retrofit.create(MovieService.class);

//调用方法得到一个Call

Call call = movieService.getTop250(0,20);

//进行网络请求

call.enqueue(new Callback() {

@Override

public void onResponse(Call call, Response response) {

mMovieAdapter.setMovies(response.body().subjects);

mMovieAdapter.notifyDataSetChanged();

}

@Override

public void onFailure(Call call, Throwable t) {

t.printStackTrace();

}

});

同步方式请求: 返回一个Response

Response response = call.execute();

<>二,配合RxJava 使用:
  1. 更改定义的接口,返回值不再是一个 Call ,而是返回的一个 Observble:

public interface MovieService {

//获取豆瓣Top250 榜单

@GET("top250")

Observable getTop250(@Query("start") int start, @Query("count")int count);

}

2.创建 Retrofit 的时候添加如下代码:

addCallAdapterFactory(RxJava2CallAdapterFactory.create())

3.添加转换器Converter(将 json 转为 JavaBean):

addConverterFactory(GsonConverterFactory.create())

举实际项目中使用的例子:

retrofit = new Retrofit.Builder()

.client(okHttpBuilder.build())

.addConverterFactory(GsonConverterFactory.create())

.addCallAdapterFactory(RxJava2CallAdapterFactory.create())

.baseUrl(BASE_URL)

.build();

4.Activity 或者 Fragment 中传入 DisposableObserver 建立订阅关系:

Subscription subscription = movieService.getTop250(0,20)

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe(new DisposableObserver() {

@Override

public void onComplete() {

}

@Override

public void onError(Throwable e) {

}

@Override

public void onNext(MovieSubject movieSubject) {

mMovieAdapter.setMovies(movieSubject.subjects);

mMovieAdapter.notifyDataSetChanged();

}

});

5.加入RxJava的好处:

  • 加入 RxJava 后的网络请求,返回不再是一个 Call ,而是一个 Observable。

  • 在Activity / Fragment 中传入一个Subscriber 建立订阅关系,就可以在 onNext 中处理结果了。

  • RxJava 的好处是帮我处理 线程之间的切换,我们可以在指定 订阅的在哪个线程,观察在哪个线程。

  • 可以 通过操作符 进行数据变换。

  • 整个过程都是链式的,简化逻辑。其中FlatMap 操作符 还可以解除多层嵌套的问题。

RxJava 很强大,能帮我处理很多复杂的场景,如果熟练使用的话,那么能提升我们的开发效率。

<>三,加入 OkHttp 配置:

通过OkHttpClient 可以配置很多东西,比如 链接超时时间,缓存,拦截器 等等。代码如下:

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

builder.connectTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS);//连接 超时时间

builder.writeTimeout(DEFAULT_TIME_OUT,TimeUnit.SECONDS);//写操作 超时时间

builder.readTimeout(DEFAULT_TIME_OUT,TimeUnit.SECONDS);//读操作 超时时间

builder.retryOnConnectionFailure(true);//错误重连

// 添加公共参数拦截器

BasicParamsInterceptor basicParamsInterceptor = new BasicParamsInterceptor.Builder()

.addHeaderParam("userName","")//添加公共参数

.addHeaderParam("device","")

.build();

builder.addInterceptor(basicParamsInterceptor);

// 创建Retrofit

mRetrofit = new Retrofit.Builder()

.client(builder.build())

.addCallAdapterFactory(RxJava2CallAdapterFactory.create())

.addConverterFactory(GsonConverterFactory.create())

.baseUrl(ApiConfig.BASE_URL)

.build();

列举项目中用到的如下:

//项目中设置头信息

Interceptor headerInterceptor = new Interceptor() {

@Override

public Response intercept(Chain chain) throws IOException {

Request originalRequest = chain.request();

Request.Builder requestBuilder = originalRequest.newBuilder()

.addHeader("Accept-Encoding", "gzip")

.addHeader("Accept", "application/json")

.addHeader("Content-Type", "application/json; charset=utf-8")

.method(originalRequest.method(), originalRequest.body());

requestBuilder.addHeader("Authorization", "Bearer " + BaseConstant.TOKEN);//添加请求头信息,服务器进行token有效性验证

Request request = requestBuilder.build();

return chain.proceed(request);

}

};

okHttpBuilder.addInterceptor(headerInterceptor);

//项目中创建Retrofit

retrofit = new Retrofit.Builder()

.client(okHttpBuilder.build())

.addConverterFactory(GsonConverterFactory.create())

.addCallAdapterFactory(RxJava2CallAdapterFactory.create())

.baseUrl(BASE_URL)

.build();

httpService = retrofit.create(HttpService.class);

<>封装篇
<>一,创建一个 统一生成接口实例的管理类 RetrofitServiceManager

创建了一个 RetrofitServiceManager 类,该类采用 单例模式,在 私有的 构造方法中,生成了 Retrofit 实例,并配置了OkHttpClient 和一些 公共配置。

提供了一个create()方法,生成 接口实例,接收 Class泛型。

代码如下:

public class RetrofitServiceManager {

private static final int DEFAULT_TIME_OUT = 5;//超时时间 5s

private static final int DEFAULT_READ_TIME_OUT = 10;

private Retrofit mRetrofit;

private RetrofitServiceManager(){

// 创建 OKHttpClient

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

builder.connectTimeout(DEFAULT_TIME_OUT, TimeUnit.SECONDS);//连接超时时间

builder.writeTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS);//写操作 超时时间

builder.readTimeout(DEFAULT_READ_TIME_OUT,TimeUnit.SECONDS);//读操作超时时间

// 添加公共参数拦截器

HttpCommonInterceptor commonInterceptor = new HttpCommonInterceptor.Builder()

.addHeaderParams("paltform","android")

.addHeaderParams("userToken","1234343434dfdfd3434")

.addHeaderParams("userId","123445")

.build();

builder.addInterceptor(commonInterceptor);

// 创建Retrofit

mRetrofit = new Retrofit.Builder()

.client(builder.build())

.addCallAdapterFactory(RxJava2CallAdapterFactory.create())

.addConverterFactory(GsonConverterFactory.create())

.baseUrl(ApiConfig.BASE_URL)

.build();

}

private static class SingletonHolder{

private static final RetrofitServiceManager INSTANCE = new RetrofitServiceManager();

}

/**

  • 获取RetrofitServiceManager

  • @return

*/

public static RetrofitServiceManager getInstance(){

return SingletonHolder.INSTANCE;

}

/**

  • 获取对应的Service

  • @param service Service 的 class

  • @param

  • @return

*/

public T create(Class service){

return mRetrofit.create(service);

}

}

接口实例Service都可以用这个来生成,代码如下:

mMovieService = RetrofitServiceManager.getInstance().create(MovieService.class);

<>二,创建接口,通过第一步获取实例

有了可以获取接口实例的方法,然后创建一个接口,代码如下:

public interface MovieService{

//获取豆瓣Top250 榜单

@GET("top250")

Observable getTop250(@Query("start") int start, @Query("count") int count);

@FormUrlEncoded

@POST("/x3/weather")

Call getWeather(@Field("cityId") String cityId, @Field("key") String key);

}

<>三,创建一个业务Loader ,如XXXLoder,获取Observable并处理相关业务

创建 Loader 的原因:每一个Api 都写一个接口很麻烦,因此就把 请求逻辑 封装在 一个业务Loader 里面,一个 Loader 里面可以处理多个Api 接口。代码如下:

public class MovieLoader extends ObjectLoader {

private MovieService mMovieService;

public MovieLoader(){

mMovieService = RetrofitServiceManager.getInstance().create(MovieService.class);

}

/**

  • 获取电影列表

  • @param start

  • @param count

  • @return

*/

public Observable<List> getMovie(int start, int count){

return observe(mMovieService.getTop250(start , count)).map(new Func1<MovieSubject, List>() {

@Override

public List call(MovieSubject movieSubject) {

return movieSubject.subjects;

}

});

}

public Observable getWeatherList(String cityId,String key){

return observe(mMovieService.getWeather(cityId , key)).map(new Func1<String , String>() {

@Override

public String call(String s) {

//可以处理对应的逻辑后在返回

return s;

}

});

}

public interface MovieService{

//获取豆瓣Top250 榜单

@GET("top250")

Observable getTop250(@Query("start") int start, @Query("count")int count);

@FormUrlEncoded

@POST("/x3/weather")

Call getWeather(@Field("cityId") String cityId, @Field("key") String key);

}

}

创建一个MovieLoader,构造方法中生成了mHttpService,而 Service 中可以定义和业务相关的多个api,比如:例子中的HttpService中,

可以定义和电影相关的多个api,获取电影列表、获取电影详情、搜索电影等api,就不用定义多个接口了。

MovieLoader 是从 ObjectLoader 中继承下来的,ObjectLoader 提取了一些公共的操作。代码如下:

/**

  • 将一些重复的操作提出来,放到父类以免Loader 里每个接口都有重复代码

*/

public class ObjectLoader {

/**

  • @param observable

  • @param

  • @return

*/

protected Observable observe(Observable observable){

return observable

.subscribeOn(Schedulers.io())

.unsubscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread());

}

}

<>四,Activity/Fragment 中的调用

创建Loader实例:

mMovieLoader = new MovieLoader();

通过Loader 调用方法获取结果,代码如下:

/**

  • 获取电影列表

*/

private void getMovieList(){

mMovieLoader.getMovie(0,10).subscribe(new Action1<List>() {

@Override

public void call(List movies) {

mMovieAdapter.setMovies(movies);

mMovieAdapter.notifyDataSetChanged();

}

}, new Action1() {

@Override

public void call(Throwable throwable) {

Log.e("TAG","error message:"+throwable.getMessage());

}

});

}

<>五,统一处理结果和错误

1.统一处理请求结果:

现实项目中,所有接口的返回结果都是同一格式,如:

{

"status": 200,

"message": "成功",

"data": {}

}

在请求api 接口的时候,只关心想要的数据,也就上面的 data{ },其他的东西不太关心,请求失败 的时候可以根据 status 判断进行 错误处理。

包装返回结果:首先需要根据服务端定义的 JSON 结构创建一个 BaseResponse 类,代码如下:

/**

  • 网络请求结果 基类

*/

public class BaseResponse {

public int status;

public String message;

public T data;

public boolean isSuccess(){

return status == 200;

}

}

有了统一的格式数据后,我们需要 剥离出data{ }返回给 上层调用者,创建一个 PayLoad 类,代码如下:

/**

  • 剥离 最终数据

*/

public class PayLoad implements Func1<BaseResponse{

@Override

public T call(BaseResponse tBaseResponse) {//获取数据失败时,包装一个Fault 抛给上层处理错误

if(!tBaseResponse.isSuccess()){

throw new Fault(tBaseResponse.status,tBaseResponse.message);

}

return tBaseResponse.data;

}

}

PayLoad 继承自 Func1,接收一个BaseResponse , 就是接口返回的 JSON 数据结构,返回的是 T,就是data{ },判断是否请求成功,请求成功 返回Data,请求失败 包装成一个 Fault 返回给上层统一处理错误。

在Loader类里面获取结果后,通过map 操作符剥离数据。代码如下:

public Observable<List> getMovie(int start, int count){

return observe(mMovieService.getTop250(start,count))

.map(new PayLoad<BaseResponse<List>());

}

2.统一处理错误:

在PayLoad 类里面,请求失败时,抛出了一个Fault 异常给上层,我在Activity/Fragment 中拿到这个异常,然后判断错误码,进行异常处理。在onError () 中添加。

对应 错误码 处理 相应的错误,代码如下:

public void call(Throwable throwable) {

Log.e("TAG","error message:"+throwable.getMessage());

if(throwable instanceof Fault){

Fault fault = (Fault) throwable;

if(fault.getErrorCode() == 404){

//错误处理

}else if(fault.getErrorCode() == 500){

//错误处理

}else if(fault.getErrorCode() == 501){

//错误处理

}

}

}

<>六,添加公共参数

实际项目中,每个接口都有一些基本的相同的参数,我们称之为公共参数,比如:userId、userToken、userName、deviceId等等,我们不必每个接口都去写,可以写一个拦截器,在拦截器里面拦截请求,为每个请求都添加相同的公共参数。

拦截器代码如下:

/*

  • 拦截器

  • 向请求头里添加公共参数

*/

public class HttpCommonInterceptor implements Interceptor {

private Map<String,String> mHeaderParamsMap = new HashMap<>();

public HttpCommonInterceptor() {

}

@Override

public Response intercept(Chain chain) throws IOException {

Log.d("HttpCommonInterceptor","add common params");

Request oldRequest = chain.request();

// 添加新的参数,添加到url 中

/*HttpUrl.Builder authorizedUrlBuilder = oldRequest.url().newBuilder()

.scheme(oldRequest.url().scheme())

.host(oldRequest.url().host());*/

// 新的请求

Request.Builder requestBuilder = oldRequest.newBuilder();

requestBuilder.method(oldRequest.method(), oldRequest.body());

//添加公共参数,添加到header中

if(mHeaderParamsMap.size() > 0){

for(Map.Entry<String,String> params:mHeaderParamsMap.entrySet()){

requestBuilder.header(params.getKey(),params.getValue());

}

}

Request newRequest = requestBuilder.build();

return chain.proceed(newRequest);

}

public static class Builder{

HttpCommonInterceptor mHttpCommonInterceptor;

public Builder(){

mHttpCommonInterceptor = new HttpCommonInterceptor();

}

public Builder addHeaderParams(String key, String value){

mHttpCommonInterceptor.mHeaderParamsMap.put(key,value);

return this;

}

public Builder addHeaderParams(String key, int value){

return addHeaderParams(key, String.valueOf(value));

}

public Builder addHeaderParams(String key, float value){

return addHeaderParams(key, String.valueOf(value));

}

public Builder addHeaderParams(String key, long value){

return addHeaderParams(key, String.valueOf(value));

}

public Builder addHeaderParams(String key, double value){

return addHeaderParams(key, String.valueOf(value));

}

public HttpCommonInterceptor build(){

return mHttpCommonInterceptor;

}

}

}

以上就是添加公共参数的拦截器,在 RetrofitServiceManager 类里面加入OkHttpClient 配置就好了。

代码如下:

// 添加公共参数拦截器

HttpCommonInterceptor commonInterceptor = new HttpCommonInterceptor.Builder()

.addHeaderParams("paltform","android")

.addHeaderParams("userToken","1234343434dfdfd3434")

.addHeaderParams("userId","123445")

.build();

builder.addInterceptor(commonInterceptor);

<>项目使用篇 ----->插入广告!本项目来源于金融研习社App,金融理财类的在线教育

项目是基于RxJava1

1.引入依赖:

compile 'com.google.code.gson:gson:2.6.2'//导入Gson 库

//导入RxJava 和 RxAndroid

compile 'io.reactivex.rxjava2:rxandroid:2.0.2'

compile 'io.reactivex.rxjava2:rxjava:2.x.y'

compile 'com.squareup.retrofit2:retrofit:2.3.0'//导入retrofit

compile 'com.squareup.retrofit2:converter-gson:2.3.0'//转换器,请求结果转换成Model

compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'//配合Rxjava 使用

compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'//添加HttpLoggingInterceptor进行调试

2.创建一个HttpService接口:

public interface HttpService {

/**

  • 获取用户详细资料

*/

@POST("api/XXX/GetUserAllDetails")

Observable getUserAllDetails(@Body GetUserAllDetailsRequestBean bean);

/**

  • @param apkUrl 下载地址

*/

@GET()

@Streaming

Call downloadNewApk(@Url String apkUrl);

/**

  • 获取推广大使分享图片

*/

@GET("api/XXX/InvitedImage")

Observable getInvitedImage(@QueryMap Map<String, Object> map);

}

3.创建http请求类,并在里面初始化并配置Retrofit和OkHttp:

public class HttpMethods {

public String TAG = "HttpMethods";

public static final String CACHE_NAME = "xxx";

public static String BASE_URL = URLConstant.BASE_URL;

private static final int DEFAULT_CONNECT_TIMEOUT = 30;

private static final int DEFAULT_WRITE_TIMEOUT = 30;

private static final int DEFAULT_READ_TIMEOUT = 30;

private Retrofit retrofit;

private HttpService httpService;

/**

  • 请求失败重连次数

*/

private int RETRY_COUNT = 0;

private OkHttpClient.Builder okHttpBuilder;

//构造方法私有

private HttpMethods() {

//手动创建一个OkHttpClient并设置超时时间

okHttpBuilder = new OkHttpClient.Builder();

/**

  • 设置缓存

*/

File cacheFile = new File(ApplicationContext.context.getExternalCacheDir(), CACHE_NAME);

Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);

Interceptor cacheInterceptor = new Interceptor() {

@Override

public Response intercept(Chain chain) throws IOException {

Request request = chain.request();

if (!NetUtil.isNetworkConnected()) {

request = request.newBuilder()

.cacheControl(CacheControl.FORCE_CACHE)

.build();

}

Response response = chain.proceed(request);

if (!NetUtil.isNetworkConnected()) {

int maxAge = 0;

// 有网络时 设置缓存超时时间0个小时

response.newBuilder()

.header("Cache-Control", "public, max-age=" + maxAge)

.removeHeader(CACHE_NAME)// 清除头信息,因为服务器如果不支持,会返回一些干扰信息,不清除下面无法生效

.build();

} else {

// 无网络时,设置超时为4周

int maxStale = 60 * 60 * 24 * 28;

response.newBuilder()

.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)

.removeHeader(CACHE_NAME)

.build();

}

return response;

}

};

okHttpBuilder.cache(cache).addInterceptor(cacheInterceptor);

/**

  • 设置头信息

*/

Interceptor headerInterceptor = new Interceptor() {

@Override

public Response intercept(Chain chain) throws IOException {

Request originalRequest = chain.request();

Request.Builder requestBuilder = originalRequest.newBuilder()

.addHeader("Accept-Encoding", "gzip")

.addHeader("Accept", "application/json")

.addHeader("Content-Type", "application/json; charset=utf-8")

.method(originalRequest.method(), originalRequest.body());

requestBuilder.addHeader("Authorization", "Bearer " + BaseConstant.TOKEN);//添加请求头信息,服务器进行token有效性验证

Request request = requestBuilder.build();

return chain.proceed(request);

}

};

okHttpBuilder.addInterceptor(headerInterceptor);

// if (BuildConfig.DEBUG) {

HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {

@Override

public void log(String message) {

Logger.d(message);

}

});

loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

//设置 Debug Log 模式

okHttpBuilder.addInterceptor(loggingInterceptor);

// }

/**

  • 设置超时和重新连接

*/

okHttpBuilder.connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS);

okHttpBuilder.readTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.SECONDS);

okHttpBuilder.writeTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS);

//错误重连

okHttpBuilder.retryOnConnectionFailure(true);

retrofit = new Retrofit.Builder()

.client(okHttpBuilder.build())

.addConverterFactory(GsonConverterFactory.create())//json转换成JavaBean

.addCallAdapterFactory(RxJava2CallAdapterFactory.create())

.baseUrl(BASE_URL)

.build();

httpService = retrofit.create(HttpService.class);

}

//在访问HttpMethods时创建单例

private static class SingletonHolder {

private static final HttpMethods INSTANCE = new HttpMethods();

}

//获取单例

public static HttpMethods getInstance() {

return SingletonHolder.INSTANCE;

}

/**

  • 获取retrofit

*/

public Retrofit getRetrofit() {

return retrofit;

}

public void changeBaseUrl(String baseUrl) {

retrofit = new Retrofit.Builder()

.client(okHttpBuilder.build())

.addConverterFactory(GsonConverterFactory.create())

.addCallAdapterFactory(RxJava2CallAdapterFactory.create())

.baseUrl(baseUrl)

.build();

httpService = retrofit.create(HttpService.class);

}

/**

  • 获取httpService

*/

public HttpService getHttpService() {

return httpService;

}

/**

  • 设置订阅 和 所在的线程环境

*/

public void toSubscribe(Observable o, DisposableObserver s) {

o.subscribeOn(Schedulers.io())

.unsubscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.retry(RETRY_COUNT)//请求失败重连次数

.subscribe(s);

}

}

4.设置回调:

调用者自己对请求数据进行处理 成功时 通过result是否等于1分别回调onSuccees和onFault,默认处理了401错误转登录。

public class OnSuccessAndFaultSub extends DisposableObserver implements ProgressCancelListener {

/**

  • 是否需要显示默认Loading

*/

private boolean showProgress = true;

private OnSuccessAndFaultListener mOnSuccessAndFaultListener;

private Context context;

private WaitProgressDialog progressDialog;

/**

  • @param mOnSuccessAndFaultListener 成功回调监听

*/

public OnSuccessAndFaultSub(OnSuccessAndFaultListener mOnSuccessAndFaultListener) {

this.mOnSuccessAndFaultListener = mOnSuccessAndFaultListener;

}

/**

  • @param mOnSuccessAndFaultListener 成功回调监听

  • @param context 上下文

*/

public OnSuccessAndFaultSub(OnSuccessAndFaultListener mOnSuccessAndFaultListener, Context context) {

this.mOnSuccessAndFaultListener = mOnSuccessAndFaultListener;

this.context = context;

progressDialog = new WaitProgressDialog(context, this);

}

/**

  • @param mOnSuccessAndFaultListener 成功回调监听

  • @param context 上下文

  • @param showProgress 是否需要显示默认Loading

*/

public OnSuccessAndFaultSub(OnSuccessAndFaultListener mOnSuccessAndFaultListener, Context context, boolean showProgress) {

this.mOnSuccessAndFaultListener = mOnSuccessAndFaultListener;

this.context = context;

progressDialog = new WaitProgressDialog(context, this);

this.showProgress = showProgress;

}

private void showProgressDialog() {

if (showProgress && null != progressDialog) {

progressDialog.show();

}

}

private void dismissProgressDialog() {

if (showProgress && null != progressDialog) {

progressDialog.dismiss();

}

}

/**

  • 订阅开始时调用

  • 显示ProgressDialog

*/

@Override

public void onStart() {

showProgressDialog();

}

/**

  • 完成,隐藏ProgressDialog

*/

@Override

public void onComplete() {

dismissProgressDialog();

progressDialog = null;

}

/**

  • 对错误进行统一处理

  • 隐藏ProgressDialog

*/

@Override

public void onError(Throwable e) {

try {

相关推荐
乾坤一气杀32 分钟前
Kotlin 协程线程切换原理 —— 以 Dispatchers.IO 为例
android
小书房1 小时前
Android各版本主要新特性
android
兄弟加油,别颓废了。2 小时前
ctf.show_web3
android
火柴就是我2 小时前
代码记录android怎么实现状态栏导航栏隐藏
android·flutter
梦里花开知多少2 小时前
浅谈ThreadPool
android·面试
帅次2 小时前
单例初始化中的耗时操作如何拖死主线程
android·webview·android runtime
用户0874881999172 小时前
Android 资源类型全解析及四大常用布局资源深度指南
android
海盐芝士不加糖2 小时前
我又又又辞职了,然后做了一款“离线版微信”
kotlin·app·android jetpack
火锅鸡的味道3 小时前
解决AOSP工程Android Studio打开卡顿
android·python·android studio