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閱讀 229,565評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,810評論 18 139
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,729評論 18 399
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,693評論 25 708
  • 也是2015年畫的啦,還有一張,戳這里?動漫----古裝女? 看圖
    皮卡章魚閱讀 296評論 5 4
  • 這一周拖到最后才把珠寶完成。 畫彩鉛是極大的鍛煉了觀察能力。 幾點感受: 1、鉛筆打底稿特別重要。形出了問題,最后...
    甜趣陶心閱讀 228評論 0 0