rxjava2+retrofit2+okhttp+rxCache+rxlifecycle2构建通用网络请求

用到的jar包版本
    compile "com.squareup.okhttp3:okhttp:3.9.0"
    compile "com.squareup.okhttp3:logging-interceptor:3.9.0"
    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:rxjava:2.0.6"
    compile "io.reactivex.rxjava2:rxandroid:2.0.6"
    compile "com.trello.rxlifecycle2:rxlifecycle:2.2.1"
    compile "com.trello.rxlifecycle2:rxlifecycle-components:2.2.1"
    compile "com.github.VictorAlbertos.RxCache:runtime:1.8.1-2.x"
    compile 'com.github.VictorAlbertos.Jolyglot:gson:0.0.3'

话不多说直接上代码

  1. 管理类
public class ApiManager {
   private static ApiManager apiManager;
   public GankApi gankApi;
   public GankApiCacheProvider gankApiCacheProvider;

   public static ApiManager getInstance() {
       if (apiManager == null) {
           synchronized (ApiManager.class) {
               if (apiManager == null) {
                   apiManager = new ApiManager();
               }
           }
       }
       return apiManager;
   }

   private ApiManager() {
       OkHttpClient okHttpClient = newOkHttpClient();
       gankApi = new Retrofit.Builder()
               .client(okHttpClient)
               .addConverterFactory(GsonConverterFactory.create())
               .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
               .baseUrl(ApiConfig.BASE_GANK_API_URL)
               .build()
               .create(GankApi.class);
       File file = FsUtils.createOnNotFound(new File(App.getInstance().getCacheDir(), "RxCache"));
       RxCache rxCache = new RxCache.Builder()
               .setMaxMBPersistenceCache(50)
               .persistence(file, new GsonTSpeaker());//解析缓存的一个类
       gankApiCacheProvider = rxCache.using(GankApiCacheProvider.class);
   }

   private OkHttpClient newOkHttpClient() {
       OkHttpClient.Builder builder = new OkHttpClient.Builder()
               .cookieJar(new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(App.getContext())))//cookice设置
               .connectTimeout(10, TimeUnit.SECONDS)
               .readTimeout(20, TimeUnit.SECONDS)
               .writeTimeout(20, TimeUnit.SECONDS)
               .retryOnConnectionFailure(true)
               .addInterceptor(new GzipRequestInterceptor())//使用gizp压缩数据,可以减少流量,也可以不加
               .addInterceptor(new HeaderInterceptor());//头部信息拦截器,也可以不加
       if (BuildConfig.DEBUG) {//请求日志
           HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
           loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
           builder.addInterceptor(loggingInterceptor);
           StethoUtils.addNetworkInterceptor(builder);
       }
       return builder.build();
   }
}

import com.google.gson.Gson;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

import io.victoralbertos.jolyglot.JolyglotGenerics;
import io.victoralbertos.jolyglot.Types;

/**
 * Created by Xiong Ke on 2017/8/23.
 */

public class GsonTSpeaker implements JolyglotGenerics {

    private final Gson gson;

    public GsonTSpeaker(Gson gson) {
        this.gson = gson;
    }

    public GsonTSpeaker() {
        this.gson = new Gson();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toJson(Object src) {
        return gson.toJson(src);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toJson(Object src, Type typeOfSrc) {
        return gson.toJson(src, typeOfSrc);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T fromJson(String json, Class<T> classOfT) throws RuntimeException {
        Type genType = classOfT.getGenericSuperclass();
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
        return gson.fromJson(json, params[0]);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T fromJson(String json, Type typeOfT) throws RuntimeException {
        Type[] params = ((ParameterizedType) typeOfT).getActualTypeArguments();
        return gson.fromJson(json, params[0]);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T fromJson(File file, Class<T> classOfT) throws RuntimeException {
        BufferedReader reader = null;

        try {
            reader = new BufferedReader(new FileReader(file.getAbsoluteFile()));

            Type genType = classOfT.getGenericSuperclass();
            Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

            T object = gson.fromJson(reader, params[0]);
            reader.close();
            return object;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException i) {
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> T fromJson(File file, Type typeOfT) throws RuntimeException {
        BufferedReader reader = null;

        try {
            reader = new BufferedReader(new FileReader(file.getAbsoluteFile()));
            Type[] params = ((ParameterizedType) typeOfT).getActualTypeArguments();
            T object = gson.fromJson(reader, params[0]);
            reader.close();
            return object;
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException i) {
                }
            }
        }
    }

    @Override
    public GenericArrayType arrayOf(Type componentType) {
        return Types.arrayOf(componentType);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ParameterizedType newParameterizedType(Type rawType, Type... typeArguments) {
        return Types.newParameterizedType(rawType, typeArguments);
    }
}
package com.xk.gvido.app.model.net.interceptor;

import android.support.annotation.NonNull;

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okio.Buffer;
import okio.BufferedSink;
import okio.GzipSink;
import okio.Okio;

/**
 * Created by Xiong Ke on 2017/8/23.
 */

public class GzipRequestInterceptor implements Interceptor {
    @Override
    public Response intercept(@NonNull Chain chain) throws IOException {
        Request originalRequest = chain.request();
        if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
            return chain.proceed(originalRequest);
        }
        Request compressedRequest = originalRequest.newBuilder()
                .header("Content-Encoding", "gzip")
                .method(originalRequest.method(), forceContentLength(gzip(originalRequest.body())))
                .build();
        return chain.proceed(compressedRequest);
    }

    private RequestBody forceContentLength(final RequestBody requestBody) throws IOException {
        final Buffer buffer = new Buffer();
        requestBody.writeTo(buffer);
        return new RequestBody() {
            @Override
            public MediaType contentType() {
                return requestBody.contentType();
            }

            @Override
            public long contentLength() {
                return buffer.size();
            }

            @Override
            public void writeTo(@NonNull BufferedSink sink) throws IOException {
                sink.write(buffer.snapshot());
            }
        };
    }


    private RequestBody gzip(final RequestBody body) {
        return new RequestBody() {
            @Override
            public MediaType contentType() {
                return body.contentType();
            }

            @Override
            public long contentLength() {
                return -1; // We don't know the compressed length in advance!
            }

            @Override
            public void writeTo(@NonNull BufferedSink sink) throws IOException {
                BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
                body.writeTo(gzipSink);
                gzipSink.close();
            }
        };
    }
}

提几个我遇到过的错误的心得体会:

a. 项目cookice丢失
    OkHttpClient 多次创建导致cookice丢失,一个项目只能实例化一个OkHttpClient
b. 提取缓存是报了一个转换异常
    日志我就不贴了,加入GsonTSpeaker转换一下就行,(只针对json,别的数据格式不支持)
c. 缓存创建目录设置在app内部缓存里面,防止无法创建目录引起异常
d. rxcache主要就是防止多次刷新请求服务器,浪费资源


  1. 错误处理类
public class ApiError {
    //HTTP错误
    private static final int UNAUTHORIZED = 401;
    private static final int FORBIDDEN = 403;
    private static final int NOT_FOUND = 404;
    private static final int REQUEST_TIMEOUT = 408;
    private static final int INTERNAL_SERVER_ERROR = 500;
    private static final int BAD_GATEWAY = 502;
    private static final int SERVICE_UNAVAILABLE = 503;
    private static final int GATEWAY_TIMEOUT = 504;

    //解析错误
    private static final int PARSE_ERROR = 1000;
    //网络错误
    private static final int NETWORK_ERROR = 1001;
    //协议出错
    private static final int HTTP_ERROR = 1002;
    //服务器出错
    private static final int RESULT_ERROR = 1003;
    //未知错误
    public static final int UNKNOWN = 1004;
    //緩存错误
    private static final int CACHE_ERROR = 1005;

    public static ApiException handleThrowable(Throwable e) {
        ApiException ex;
        if (e instanceof HttpException) {             //HTTP错误
            HttpException httpException = (HttpException) e;
            ex = new ApiException(e, HTTP_ERROR);
            switch (httpException.code()) {
                case UNAUTHORIZED:
                case FORBIDDEN:
                case NOT_FOUND:
                case REQUEST_TIMEOUT:
                    ex.setCode(REQUEST_TIMEOUT);
                    ex.setDisplayMessage(StringUtils.getString(R.string.error_network_request_timeout));
                case GATEWAY_TIMEOUT:
                case INTERNAL_SERVER_ERROR:
                case BAD_GATEWAY:
                case SERVICE_UNAVAILABLE:
                default:
                    ex.setDisplayMessage(StringUtils.getString(R.string.error_network_network_error));
                    break;
            }
            return ex;
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException) {
            ex = new ApiException(e, PARSE_ERROR);
            ex.setDisplayMessage(StringUtils.getString(R.string.error_parsing_error));
        } else if (e instanceof ConnectException) {
            ex = new ApiException(e, HTTP_ERROR);
            ex.setDisplayMessage(StringUtils.getString(R.string.error_network_connection_failure));
        } else if (e instanceof ExecutionException
                || e instanceof InterruptedException) {
            ex = new ApiException(e, UNKNOWN);
            ex.setDisplayMessage(StringUtils.getString(R.string.error_network_get_image_failure));
        } else if (e instanceof RxCacheException) {
            ex = new ApiException(e, CACHE_ERROR);
            ex.setDisplayMessage(StringUtils.getString(R.string.error_network_cache_failure));
        } else if (e instanceof CompositeException) {
            CompositeException compositeE = (CompositeException) e;
            ex = new ApiException(e);
            for (Throwable throwable : compositeE.getExceptions()) {
                if (throwable instanceof SocketTimeoutException) {
                    ex.setCode(NETWORK_ERROR);
                    ex.setDisplayMessage(StringUtils.getString(R.string.error_network_network_error));
                } else if (throwable instanceof ConnectException) {
                    ex.setCode(NETWORK_ERROR);
                    ex.setDisplayMessage(StringUtils.getString(R.string.error_network_network_error));
                } else if (throwable instanceof UnknownHostException) {
                    ex.setCode(NETWORK_ERROR);
                    ex.setDisplayMessage(StringUtils.getString(R.string.error_network_network_error));
                } else if (throwable instanceof RxCacheException) {
                    ex.setCode(CACHE_ERROR);
                    ex.setDisplayMessage(StringUtils.getString(R.string.error_network_network_error));
                } else if (throwable instanceof MalformedJsonException) {
                    ex.setCode(PARSE_ERROR);
                    ex.setDisplayMessage(StringUtils.getString(R.string.error_parsing_error));
                }
            }
        } else {
            ex = new ApiException(e, UNKNOWN);
            ex.setDisplayMessage(e.getMessage());
        }
        return ex;
    }
public class ApiException extends RuntimeException {
    private int code;
    private String displayMessage;

    public ApiException(Throwable throwable) {
        super(throwable);
    }

    public ApiException(Throwable throwable, int code) {
        super(throwable);
        this.code = code;
    }

    public void setDisplayMessage(String displayMessage) {
        this.displayMessage = displayMessage;
    }

    public String getDisplayMessage() {
        return displayMessage;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }
}

错误你可以自己加,缺少的字符串我就不贴了


  1. rx调度线程的工具类
public class RxSchedulersHelper {

    public static <T> ObservableTransformer<T, T> io_main() {
        return upstream -> upstream.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .unsubscribeOn(Schedulers.io());
    }

    public static <T> ObservableTransformer<T, T> io_io() {
        return upstream -> upstream.subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io());
    }

    public static <T> ObservableTransformer<T, T> main_main() {
        return upstream -> upstream.subscribeOn(AndroidSchedulers.mainThread())
                .observeOn(AndroidSchedulers.mainThread())
                .unsubscribeOn(Schedulers.io());
    }

    public static <T, C> ObservableTransformer<T, T> addDisposable(final C context) {
        if (context instanceof BaseFragment) {
            BaseFragment baseFragment = (BaseFragment) context;
            return baseFragment.bindToLifecycle();
        } else if (context instanceof BasePreferenceFragment) {
            BasePreferenceFragment basePreferenceFragment = (BasePreferenceFragment) context;
            return basePreferenceFragment.bindToLifecycle();
        } else if (context instanceof BaseActivity) {
            BaseActivity baseActivity = (BaseActivity) context;
            return baseActivity.bindToLifecycle();
        } else {
            return upstream -> upstream;
        }
    }

    public static <T> ObservableTransformer<T, T> errorResult() {
        return upstream -> upstream.onErrorResumeNext((Function<Throwable, ObservableSource<? extends T>>)
                throwable -> {
                    return Observable.error(ApiError.handleThrowable(throwable));
                });
    }

    public static <T> Observable<T> createData(final T t) {
        return Observable.create(observableEmitter -> {
            if (!observableEmitter.isDisposed()) {
                try {
                    observableEmitter.onNext(t);
                    observableEmitter.onComplete();
                } catch (Exception e) {
                    observableEmitter.onError(e);
                }
            }
        });
    }
}
说明

a. io_main,io_io,main_main线程的调度方法
b. addDisposable这个方法很重要,为了防止请求完成 界面销毁了,造成的内存泄漏需要说明的是BaseFragment要继承RxFragment,BasePreferenceFragment,BaseActivity也都要继承RxPreferenceFragment,RxAppCompatActivity
c. errorResult发生错误将会发送一个消息到error方法
d. createData创建一个Observable对象


  1. 数据结果处理类
public class ApiResultHandler {

    public static <T> ObservableTransformer<ServerGanHuoResponse<T>, T> handleGanHuoResult() {
        return upstream -> upstream.flatMap((Function<ServerGanHuoResponse<T>, ObservableSource<T>>) ganHuoResponse -> {
            if (!ganHuoResponse.isError()) {
                return RxSchedulersHelper.createData(ganHuoResponse.getResults());
            } else {
                return Observable.error(new Throwable("服务器返回error"));
            }
        });
    }
}

  1. 接口API
public interface GankApi {
    /**
     * 福利列表
     */
    @GET("data/福利/{count}/{pageIndex}")
    Observable<ServerGanHuoResponse<List<WelfareBean>>> getGanHuoWelfareApiCall(@Path("count") @IntRange(from = 1) int count,
                                                                                @Path("pageIndex") @IntRange(from = 1) int pageIndex);

    /**
     * 休息视频
     */
    @GET("data/休息视频/{count}/{pageIndex}")
    Observable<ServerGanHuoResponse<List<VideoBean>>> getGanHuoVideoApiCall(@Path("count") @IntRange(from = 1) int count,
                                                                            @Path("pageIndex") @IntRange(from = 1) int pageIndex);
}


6.缓存API

public interface GankApiCacheProvider {
    /**
     * 福利列表
     *
     * @param observable
     * @param page 緩存key
     * @param evictProvider 清楚缓存
     * @return
     */
    @Expirable(value = false)
    @LifeCache(duration = 6, timeUnit = TimeUnit.HOURS)
    Observable<ServerGanHuoResponse<List<WelfareBean>>> getGanHuoWelfareApiCall(Observable<ServerGanHuoResponse<List<WelfareBean>>> observable, DynamicKey page, EvictProvider evictProvider);

    /**
     * 休息视频
     *
     * @param observable
     * @param page 緩存key
     * @param evictProvider 清楚缓存
     * @return
     */
    @Expirable(value = false)
    @LifeCache(duration = 6, timeUnit = TimeUnit.HOURS)
    Observable<ServerGanHuoResponse<List<VideoBean>>> getGanHuoVideoApiCall(Observable<ServerGanHuoResponse<List<VideoBean>>> observable, DynamicKey page, EvictProvider evictProvider);
}

7.处理类

public class GankApiHelper {
    public static <T> Observable<List<WelfareBean>> getGanHuoWelfareApiCall(final T context, @IntRange(from = 1) int count,
                                                                            @IntRange(from = 1) int pageIndex, boolean isClearCache) {
        return ApiManager.getInstance().gankApiCacheProvider
                .getGanHuoWelfareApiCall(ApiManager.getInstance().gankApi.getGanHuoWelfareApiCall(count, pageIndex),
                        new DynamicKey(pageIndex), new EvictDynamicKey(isClearCache))
                .compose(ApiResultHandler.handleGanHuoResult())
                .compose(RxSchedulersHelper.addDisposable(context))
                .compose(RxSchedulersHelper.errorResult())
                .compose(RxSchedulersHelper.io_main());
    }

    public static <T> Observable<List<VideoBean>> getGanHuoVideoApiCall(final T context, @IntRange(from = 1) int count,
                                                                        @IntRange(from = 1) int pageIndex, boolean isClearCache) {
        return ApiManager.getInstance().gankApiCacheProvider
                .getGanHuoVideoApiCall(ApiManager.getInstance().gankApi.getGanHuoVideoApiCall(count, pageIndex),
                        new DynamicKey(pageIndex), new EvictDynamicKey(isClearCache))
                .compose(ApiResultHandler.handleGanHuoResult())
                .compose(RxSchedulersHelper.addDisposable(context))
                .compose(RxSchedulersHelper.errorResult())
                .compose(RxSchedulersHelper.io_main());
    }
}
说明

由于我使用了rxcache所以为了方便使用写了一个helper类做一下处理工作,如果你的项目里没用rxcache就可以删掉GankApiCacheProvider


8.调用

 Observable.zip(GankApiHelper.getGanHuoVideoApiCall(this, REQ_COUNT, currentPage, isClearCache),
                GankApiHelper.getGanHuoWelfareApiCall(this, REQ_COUNT, currentPage, isClearCache),
                (videoBeen, welfareDatas) -> {
                    List<GanHuoBean> ganHuoBeanList = new ArrayList<>();
                    if (videoBeen.size() == REQ_COUNT && welfareDatas.size() == REQ_COUNT) {
                        for (int i = 0; i < REQ_COUNT; i++) {
                            ganHuoBeanList.add(GanHuoBean.newInstance(videoBeen.get(i),
                                    welfareDatas.get(i)));
                        }
                    }
                    return ganHuoBeanList;
                }).subscribe(new RxSubscriber<List<GanHuoBean>>() {

            @Override
            public void onSubscribe(Disposable d) {
                super.onSubscribe(d);
                if (isRefresh) {
                    swipeRefreshLayout.setRefreshing(true);
                }
            }

            @Override
            public void rxOnNext(List<GanHuoBean> ganHuoBeans) {
                gankPictureAdapter.loadMoreComplete();
                if (ganHuoBeans.size() < REQ_COUNT) {
                    gankPictureAdapter.loadMoreEnd();
                }
                if (isRefresh) {
                    gankPictureAdapter.setNewData(ganHuoBeans);
                    swipeRefreshLayout.setRefreshing(false);
                } else {
                    gankPictureAdapter.addData(ganHuoBeans);
                }
                if (gankPictureAdapter.getData().isEmpty()) {
                    loadingLayout.showEmpty();
                } else {
                    loadingLayout.showContent();
                }
                saveMeiZiInDb(ganHuoBeans);
            }

            @Override
            public void rxOnError(ApiException apiException) {
                if (gankPictureAdapter.getData().isEmpty()) {
                    loadingLayout.setErrorText(apiException.getDisplayMessage());
                    loadingLayout.showError();
                } else {
                    gankPictureAdapter.loadMoreFail();
                }
                swipeRefreshLayout.setRefreshing(false);
            }
        });
说明

我是将两个请求的结果转换成一个新的数据结构,所以是这样写的


这样写的话,方便解耦易于扩展。写完了大致就这样,如果写的有问题欢迎在评论区指正.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,898评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,401评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,058评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,539评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,382评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,319评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,706评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,370评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,664评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,715评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,476评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,326评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,730评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,003评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,275评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,683评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,877评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,510评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,513评论 18 399
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,128评论 25 707
  • 也是2015年画的啦,还有一张,戳这里❤动漫----古装女❤ 看图
    皮卡章鱼阅读 285评论 5 4
  • 这一周拖到最后才把珠宝完成。 画彩铅是极大的锻炼了观察能力。 几点感受: 1、铅笔打底稿特别重要。形出了问题,最后...
    甜趣陶心阅读 214评论 0 0