Android 优雅的让RxJava2.0+Retrofit2.0结合使用
来源:互联网 发布:服装软件管理系统 编辑:程序博客网 时间:2024/06/05 14:45
前言
本文参考:
RxJava 与 Retrofit 结合的最佳实践http://gank.io/post/56e80c2c677659311bed9841
与上文不同的是:
- 本文采用最新的RxJava2.0与Retrifit2.0来实现,并针对于1.x不同的地方进行处理。
- 针对请求过程进行的封装,额外增加缓存策略和请求头部处理。
- RxJava2.0使用笔记:http://blog.csdn.net/demonliuhui/article/details/77848691
- Retrifit2.0使用笔记:http://blog.csdn.net/demonliuhui/article/details/77854530
使用配置
添加依赖
dependencies { ... compile 'io.reactivex.rxjava2:rxjava:2.1.3' compile 'com.squareup.retrofit2:retrofit:2.3.0' compile 'com.squareup.retrofit2:converter-gson:2.3.0' compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' compile 'io.reactivex.rxjava2:rxandroid:2.0.1' compile 'com.squareup.okhttp3:okhttp:3.9.0' compile 'com.squareup.okhttp3:logging-interceptor:3.9.0' compile 'com.squareup.retrofit2:converter-scalars:2.3.0' ...}
注意:
- 添加RxAndroid是为了解决线程调度问题。
okhttp3:logging-interceptor
是为了配置缓存策略,必须跟okhttp同一版本号,否则会报错:Failed resolution of: Lokhttp3/internal/Platform
converter-scalars
用于将请求结果转换为基本类型,一般为Stringconverter-gson
用于将请求结果转换为实体类型
添加权限
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
测试Api
豆瓣电影的Top250做测试连接,目标地址为:
https://api.douban.com/v2/movie/top250?start=0&count=10
返回的数据格式,大家自己访问下链接就看到了。
创建数据实体类
知道数据格式后我们创建对应数据实体类,方便处理获取后的数据。
这里只是简单获取几个数据,大家可以根据自己需求去编写自己想要的数据。
public class Movie { private String title; private List<Subjects> subjects; public String getTitle() { return title; } public List<Subjects> getSubjects() { return subjects; }}public class Subjects { private String title, year, id; public Subjects(String title, String year, String id) { this.title = title; this.year = year; this.id = id; } public String getTitle() { return title; } public String getYear() { return year; } public String getId() { return id; }}
最基本的RxJava+Retrofit
1.创建Service类
因为要结合使用RxJava,所以返回值就不在是一个Call了,而是一个Observable。
public interface ApiService { @GET("top250") Observable<Movie> getTopMovie(@Query("start") int start, @Query("count") int count);}
2.创建Retrofit请求过程
String baseUrl = "https://api.douban.com/v2/movie/"; Retrofit retrofit = new Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(ScalarsConverterFactory.create())//请求结果转换为基本类型 .addConverterFactory(GsonConverterFactory.create())//请求的结果转为实体类 .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //适配RxJava2.0, RxJava1.x则为RxJavaCallAdapterFactory.create() .build(); apiService = retrofit.create(ApiService.class);
3.Activity发出请求(RxJava订阅)及处理数据
注意:RxJava2.0开始subscribe的对象不能是Subscriber,只能是Observer和Consumer,为了处理完整的过程我们这里选择Observer。 onSubscribe等同于onStart。
apiService.getTopMovie(0, 10) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<Movie>() { @Override public void onSubscribe(Disposable d) { Log.d(TAG, "onSubscribe: "); } @Override public void onNext(Movie movie) { Log.d(TAG, "onNext: " + movie.getTitle()); List<Subjects> list = movie.getSubjects(); for (Subjects sub : list) { Log.d(TAG, "onNext: " + sub.getId() + "," + sub.getYear() + "," + sub.getTitle()); } } @Override public void onError(Throwable e) { Log.e(TAG, "onError: " + e.getMessage()); } @Override public void onComplete() { Log.d(TAG, "onComplete: Over!"); } });
4.运行结果
09-06 14:25:09.151 30008-30008/eee.rxjavaretrofit D/MainActivity: onSubscribe: 09-06 14:25:11.308 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 豆瓣电影Top25009-06 14:25:11.308 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1292052,1994,肖申克的救赎09-06 14:25:11.308 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1291546,1993,霸王别姬09-06 14:25:11.308 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1295644,1994,这个杀手不太冷09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1292720,1994,阿甘正传09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1292063,1997,美丽人生09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1291561,2001,千与千寻09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1295124,1993,辛德勒的名单09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 1292722,1997,泰坦尼克号09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 3541415,2010,盗梦空间09-06 14:25:11.309 30008-30008/eee.rxjavaretrofit D/MainActivity: onNext: 2131459,2008,机器人总动员09-06 14:25:11.311 30008-30008/eee.rxjavaretrofit D/MainActivity: onComplete: Over!
这样就完成了最基本的RxJava+Retrofit,结果也与预期一致。
但是这样显然不能让我们去优雅的使用。毕竟优雅的复用已写过的代码才是我们的追求。
因此我们需要进一步对刚才的代码进行封装。
基本的封装RxJava+Retrofit
1.封装Retrofit请求过程
在之前的代码的基础上,创建Api.java用于封装Retrofit,代码如下:
public class Api { public static String baseUrl = "https://api.douban.com/v2/movie/"; public static ApiService apiService; //单例 public static ApiService getApiService() { if (apiService == null) { synchronized (Api.class) { if (apiService == null) { new Api(); } } } return apiService; } private Api() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create())//请求的结果转为实体类 //适配RxJava2.0,RxJava1.x则为RxJavaCallAdapterFactory.create() .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); apiService = retrofit.create(ApiService.class); }}
2.封装线程管理和订阅
在使用RxJava的过程中,每次都重复写线程管理和订阅代码也是一件繁琐的事情。
创建ApiMethods用于封装线程管理和订阅的过程及调用请求。如下:
public class ApiMethods { /** * 封装线程管理和订阅的过程 */ public static void ApiSubscribe(Observable observable, Observer observer) { observable.subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(observer); } /** * 用于获取豆瓣电影Top250的数据 * * @param observer 由调用者传过来的观察者对象 * @param start 起始位置 * @param count 获取长度 */ public static void getTopMovie(Observer<Movie> observer, int start, int count) { ApiSubscribe(Api.getApiService().getTopMovie(start, count), observer); }}
3.Activity中调用及处理数据
Observer<Movie> observer = new Observer<Movie>() { @Override public void onSubscribe(Disposable d) { Log.d(TAG, "onSubscribe: "); } @Override public void onNext(Movie movie) { Log.d(TAG, "onNext: " + movie.getTitle()); List<Subjects> list = movie.getSubjects(); for (Subjects sub : list) { Log.d(TAG, "onNext: " + sub.getId() + "," + sub.getYear() + "," + sub.getTitle()); } } @Override public void onError(Throwable e) { Log.e(TAG, "onError: " + e.getMessage()); } @Override public void onComplete() { Log.d(TAG, "onComplete: Over!"); } }; ApiMethods.getTopMovie(observer, 0, 10);
4.测试结果
09-06 15:14:01.413 16819-16819/eee.rxjavaretrofit D/MainActivity: onSubscribe: 09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 豆瓣电影Top25009-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1292052,1994,肖申克的救赎09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1291546,1993,霸王别姬09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1295644,1994,这个杀手不太冷09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1292720,1994,阿甘正传09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1292063,1997,美丽人生09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1291561,2001,千与千寻09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1295124,1993,辛德勒的名单09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 1292722,1997,泰坦尼克号09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 3541415,2010,盗梦空间09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onNext: 2131459,2008,机器人总动员09-06 15:14:02.465 16819-16819/eee.rxjavaretrofit D/MainActivity: onComplete: Over!
经过两次封装,如果有新的Api接口我们只需要在ApiService类中增加对应的接口方法,然后在ApiMethods类中写对应的请求方法即可。与基本的RxJava+Retrofit相比,在额外代码量和易用性上都明显占优。
进一步封装RxJava+Retrofit
看完上一步,你还是肯定还是会觉得,在Activity每次处理订阅返回的数据的时候都要实例化一个Observer对象,然后重写onSubscribe,onNext,onError,onComplete四个方法。虽然Android Studio一键就能补全代码,我们只用简单处理一下就ok了。但是那么多代码堆在那里还是让人不爽的。
因此我们进一步针对Observer进行封装。
针对重写方法的封装最好的方法就是使用接口+implements/继承的方式实现。
仔细分析会发现,其实除了onNext涉及数据处理,其余三个方法都可以进行模板化处理,比如显示一个Toast,输出错误信息等;当然如果你的业务需要,也可以按照onNext特别处理。
在之前代码的基础上。
1.新建监听接口
使用通配泛型,适用于所有类型的数据。
public interface ObserverOnNextListener<T> { void onNext(T t);}
2.重写Observer
public class MyObserver<T> implements Observer<T> { private static final String TAG = "MyObserver"; private ObserverOnNextListener listener; private Context context; public MyObserver(Context context, ObserverOnNextListener listener) { this.listener = listener; this.context = context; } @Override public void onSubscribe(Disposable d) { Log.d(TAG, "onSubscribe: "); //添加业务处理 } @Override public void onNext(T t) { listener.onNext(t); } @Override public void onError(Throwable e) { Log.e(TAG, "onError: ", e); //添加业务处理 } @Override public void onComplete() { Log.d(TAG, "onComplete: "); //添加业务处理 }}
3.Activity中使用
ObserverOnNextListener<Movie> listener = new ObserverOnNextListener<Movie>() { @Override public void onNext(Movie movie) { Log.d(TAG, "onNext: " + movie.getTitle()); List<Subjects> list = movie.getSubjects(); for (Subjects sub : list) { Log.d(TAG, "onNext: " + sub.getId() + "," + sub.getYear() + "," + sub.getTitle()); } } }; ApiMethods.getTopMovie(new MyObserver<Movie>(this, listener), 0, 10);
4.测试结果
09-06 16:01:59.918 3487-3487/eee.rxjavaretrofit D/MyObserver: onSubscribe: 09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 豆瓣电影Top25009-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1292052,1994,肖申克的救赎09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1291546,1993,霸王别姬09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1295644,1994,这个杀手不太冷09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1292720,1994,阿甘正传09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1292063,1997,美丽人生09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1291561,2001,千与千寻09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1295124,1993,辛德勒的名单09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 1292722,1997,泰坦尼克号09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 3541415,2010,盗梦空间09-06 16:02:00.717 3487-3487/eee.rxjavaretrofit D/MainActivity: onNext: 2131459,2008,机器人总动员09-06 16:02:00.718 3487-3487/eee.rxjavaretrofit D/MyObserver: onComplete:
优雅的封装RxJava+Retrofit
上面的代码可谓是封装的彻底了,但是还不够优雅。
如何优雅?
网络请求一般都是耗时的,我们需要在进行网络请求的时候有一个进度框,增加用户体验。
根据上一步,我们只需要将进度框show在onSubscribe中,然后在onError/onComplete里cancel掉就行了。如果用户取消关闭了进度框,那么也随之取消了当前的Http请求。
1.编写ProgressDialog类代码
Handler接收两个消息来控制显示Dialog还是关闭Dialog。
public class ProgressDialogHandler extends Handler { public static final int SHOW_PROGRESS_DIALOG = 1; public static final int DISMISS_PROGRESS_DIALOG = 2; private ProgressDialog pd; private Context context; private boolean cancelable; private ProgressCancelListener mProgressCancelListener; public ProgressDialogHandler(Context context, ProgressCancelListener mProgressCancelListener, boolean cancelable) { super(); this.context = context; this.mProgressCancelListener = mProgressCancelListener; this.cancelable = cancelable; } private void initProgressDialog() { if (pd == null) { pd = new ProgressDialog(context); pd.setCancelable(cancelable); if (cancelable) { pd.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialogInterface) { mProgressCancelListener.onCancelProgress(); } }); } if (!pd.isShowing()) { pd.show(); } } } private void dismissProgressDialog() { if (pd != null) { pd.dismiss(); pd = null; } } @Override public void handleMessage(Message msg) { switch (msg.what) { case SHOW_PROGRESS_DIALOG: initProgressDialog(); break; case DISMISS_PROGRESS_DIALOG: dismissProgressDialog(); break; } }}
此处安全按照参考文章来写的,当然你也可以直接ProgressDialog dialog = new ProgressDialog(context);
2.编写监听取消的接口类
public interface ProgressCancelListener { void onCancelProgress();}
3.在MyObserver中添加ProgressDialog
将上面ProgressDialog在Observer中初始化,并在相应的位置进行显示和隐藏。
public class ProgressObserver<T> implements Observer<T>, ProgressCancelListener { private static final String TAG = "ProgressObserver"; private ObserverOnNextListener listener; private ProgressDialogHandler mProgressDialogHandler; private Context context; private Disposable d; public ProgressObserver(Context context, ObserverOnNextListener listener) { this.listener = listener; this.context = context; mProgressDialogHandler = new ProgressDialogHandler(context, this, true); } private void showProgressDialog() { if (mProgressDialogHandler != null) { mProgressDialogHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget(); } } private void dismissProgressDialog() { if (mProgressDialogHandler != null) { mProgressDialogHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG) .sendToTarget(); mProgressDialogHandler = null; } } @Override public void onSubscribe(Disposable d) { this.d = d; showProgressDialog(); } @Override public void onNext(T t) { listener.onNext(t); } @Override public void onError(Throwable e) { dismissProgressDialog(); Log.e(TAG, "onError: ", e); } @Override public void onComplete() { dismissProgressDialog(); Log.d(TAG, "onComplete: "); } @Override public void onCancelProgress() { //如果处于订阅状态,则取消订阅 if (!d.isDisposed()) { d.dispose(); } }}
5.Activity中使用
ObserverOnNextListener<Movie> listener = new ObserverOnNextListener<Movie>() { @Override public void onNext(Movie movie) { Log.d(TAG, "onNext: " + movie.getTitle()); List<Subjects> list = movie.getSubjects(); for (Subjects sub : list) { Log.d(TAG, "onNext: " + sub.getId() + "," + sub.getYear() + "," + sub.getTitle()); } } }; ApiMethods.getTopMovie(new ProgressObserver<Movie>(this, listener), 0, 10);
6.测试结果
09-06 16:32:05.135 6823-6823/eee.rxjavaretrofit D/ProgressObserver: onSubscribe: 09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 豆瓣电影Top25009-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1292052,1994,肖申克的救赎09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1291546,1993,霸王别姬09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1295644,1994,这个杀手不太冷09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1292720,1994,阿甘正传09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1292063,1997,美丽人生09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1291561,2001,千与千寻09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1295124,1993,辛德勒的名单09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 1292722,1997,泰坦尼克号09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 3541415,2010,盗梦空间09-06 16:32:05.990 6823-6823/eee.rxjavaretrofit D/MainActivity: onNext: 2131459,2008,机器人总动员09-06 16:32:05.992 6823-6823/eee.rxjavaretrofit D/ProgressObserver: onComplete:
优化Retrofit,设置缓存,超时策略
使用OkHttpClient对Retrofit设置设置缓存,超时策略。
public class ApiStrategy { public static String baseUrl = "https://api.douban.com/v2/movie/"; //读超时长,单位:毫秒 public static final int READ_TIME_OUT = 7676; //连接时长,单位:毫秒 public static final int CONNECT_TIME_OUT = 7676; public static ApiService apiService; public static ApiService getApiService() { if (apiService == null) { synchronized (Api.class) { if (apiService == null) { new ApiStrategy(); } } } return apiService; } private ApiStrategy() { HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor(); logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); //缓存 File cacheFile = new File(MyApplication.getContext().getCacheDir(), "cache"); Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb //增加头部信息 Interceptor headerInterceptor = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request build = chain.request().newBuilder() //.addHeader("Content-Type", "application/json")//设置允许请求json数据 .build(); return chain.proceed(build); } }; //创建一个OkHttpClient并设置超时时间 OkHttpClient client = new OkHttpClient.Builder() .readTimeout(READ_TIME_OUT, TimeUnit.MILLISECONDS) .connectTimeout(CONNECT_TIME_OUT, TimeUnit.MILLISECONDS) .addInterceptor(mRewriteCacheControlInterceptor) .addNetworkInterceptor(mRewriteCacheControlInterceptor) .addInterceptor(headerInterceptor) .addInterceptor(logInterceptor) .cache(cache) .build(); Retrofit retrofit = new Retrofit.Builder() .client(client) .baseUrl(baseUrl) .addConverterFactory(GsonConverterFactory.create())//请求的结果转为实体类 //适配RxJava2.0,RxJava1.x则为RxJavaCallAdapterFactory.create() .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build(); apiService = retrofit.create(ApiService.class); } /** * 设缓存有效期为两天 */ private static final long CACHE_STALE_SEC = 60 * 60 * 24 * 2; /** * 云端响应头拦截器,用来配置缓存策略 * Dangerous interceptor that rewrites the server's cache-control header. */ private final Interceptor mRewriteCacheControlInterceptor = new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); String cacheControl = request.cacheControl().toString(); if (!NetWorkUtils.isNetConnected(MyApplication.getContext())) { request = request.newBuilder() .cacheControl(TextUtils.isEmpty(cacheControl) ? CacheControl .FORCE_NETWORK : CacheControl.FORCE_CACHE) .build(); } Response originalResponse = chain.proceed(request); if (NetWorkUtils.isNetConnected(MyApplication.getContext())) { return originalResponse.newBuilder() .header("Cache-Control", cacheControl) .removeHeader("Pragma") .build(); } else { return originalResponse.newBuilder() .header("Cache-Control", "public, only-if-cached, max-stale=" + CACHE_STALE_SEC) .removeHeader("Pragma") .build(); } } };}
总结
目前为止,就封装完毕了。
现在我们再写一个新的网络请求,步骤是这样的:
- 在Service中定义一个新的方法。
- 在ApiMethods封装对应的请求(代码基本可以copy)
- 创建一个ObserverOnNextListener处理请求数据。
- 在onNext处理业务逻辑。
Module代码下载
PS:因为是Module的代码,所以请谨慎导入。
CSDN代码下载:http://download.csdn.net/download/demonliuhui/9966975
只看代码不下载可以GitHub:https://github.com/DeMonLiu623/DeMonTest/tree/master/RxJavaRetrofit
- Android 优雅的让RxJava2.0+Retrofit2.0结合使用
- Android 优雅的让RxJava2.0+Retrofit2.0结合使用
- 【Android高级】RxJava2.0和Retrofit2.0的使用探究
- Rxjava2.0和Retrofit2.0结合使用(2)post请求
- RxJava2和Retrofit2.0的简单使用
- Retrofit2.0与RxJava2.0结合出现的问题解决
- Retrofit2.0与RXJava2.0最新结合:
- android Retrofit2.0 RxJava2.0
- Android基于Retrofit2.0+RxJava的结合使用,让你的网络请求更简单
- Android使用Retrofit2.0和RxJava2.0处理网络请求
- Retrofit2+Rxjava2之优雅的封装
- Retrofit2.0+Rxjava2 相关的文章
- RxJava和Retrofit2.0的结合使用
- 优雅地封装使用rxjava2+retrofit2发送http请求
- RxJava2 + Retrofit2 优雅简洁封装
- Retrofit2.0+Rxjava2.0+MVP使用小记(一)
- 继续趁热!!!Retrofit2使用(基于RxJava2.0,修正一次)
- RxJava2.0 和 Retrofit 结合使用时的配置问题
- ImageView
- 脚手架vue-cli从不会到入坑
- MySQL中的if和case语句使用总结
- java中String与StringBuilder与StringBuffer的区别
- windows与linux socket程序的不同
- Android 优雅的让RxJava2.0+Retrofit2.0结合使用
- JAVA基础-IO流中的SequenceInputStream的用法
- Struts2-OGNL与值栈
- 反转二叉树(Invert Binary Tree)
- 二分查找
- 安卓ui坐标和底层内存图坐标的转换关系
- vsftp如何配置虚拟用户实现不同用户拥有不同权限和不同目录
- 给初学者的RxJava2.0教程(三)
- Java用ScriptEngine解析脚本