細細品讀Retrofit的設計之美二

本篇文章已授權為微信公眾號 code小生
發(fā)布轉載請注明出處:http://www.reibang.com/p/dab7f5720aa5

1.細細品讀Retrofit的設計之美一
2. 細細品讀Retrofit的設計之美二


引言

在上一篇 品讀Retrofit設計之美后汪疮,我們了解了Builder構建者模式和(動態(tài))代理模式在Retrofit中的做用,以及它們的使用套路楼眷。今天繼續(xù)品讀Retrofit框架值得我們好好思考的設計:抽象工廠模式

抽象工廠模式

在看Retrofit的抽象工廠模式的應用前,先來了解下,抽象工廠模式的套路膝擂,不扯虛的直接舉一個實用的例子:

我們都知道作為app開發(fā)者,通常的app應用都會有用戶系統(tǒng)拂募,一個用戶系統(tǒng)往往都包含了以下模塊:1. 登錄模塊庭猩。 2. 注冊模塊。 3. 找回密碼模塊陈症。 4. 用戶個人信息模塊蔼水。
這幾個模塊代表工廠需要生產(chǎn)的不同類型的產(chǎn)品,用戶系統(tǒng)帳號录肯,我們可能是app自身的帳號趴腋、密碼、或者手機短信驗證碼的登錄方式嘁信,也可能是第三方平臺帳號登錄:微信于样、QQ、新浪微博等等潘靖。對于不同的平臺的用戶帳號我們可以看做不同的品牌工廠穿剖,比如:app自身的用戶帳號工廠、微信的用戶帳號工廠卦溢、QQ的用戶帳號工廠糊余、新浪微博的用戶帳號工廠。

這樣來設計一個用戶系統(tǒng)是不是更清晰點单寂,而且不同的品牌的工廠便于替換贬芥,也就是替換登錄的平臺,不同的產(chǎn)品模塊類的功能職責也變的比較單一符合設計模式的單一原則宣决。

案例實現(xiàn)

  1. 首先抽象出各個產(chǎn)品接口出來蘸劈,每種模塊產(chǎn)品都有各自的功能
// ******************IBaseUser.java,抽象用戶實體
/**
 * 抽象用戶實體接口尊沸,便于泛型化設計
 */
public interface IBaseUser {
}

// 1. ******************ILoginer.java威沫,登錄模塊
/**
 * 登錄抽象接口
 * @param <U>   用戶信息
 */
public interface ILoginer<U extends IBaseUser> {

    // 登錄
    void login(U user);

    // 注銷贤惯、退出帳號
    void logout(U user);
}

// 2. ******************IRegister.java,注冊模塊
/**
 * 注冊帳號接口
 * @param <U> 用戶信息
 */
public interface IRegister<U extends IBaseUser> {
    // 注冊帳號
    void registerAccount(U user);
}

//  3. ******************IFindPwder.java棒掠,找回密碼模塊
/**
 * 找回密碼接口
 * @param <U>   用戶信息
 */
public interface IFindPwder<U extends IBaseUser>  {
    // 找回密碼
    void findPwd(U user);
}

//  4. ******************IUserInfoer.java孵构,用戶信息模塊
/**
 * 用戶信息相關接口
 * @param <U>   用戶信息
 */
public interface IUserInfoer<U extends IBaseUser> {

    // 獲取用戶信息
    U getUserInfo();

    // 保存用戶信息
    void saveUserInfo(U userInfo);
}

這些產(chǎn)品模塊的接口規(guī)范功能抽象,對于app的用戶系統(tǒng)來說基本夠用了烟很。當然上面的這些接口颈墅,也可以統(tǒng)一用一個接口文件來寫,這些模塊就作為子接口嵌套在里面雾袱,這是為了方便管理恤筛。

  1. 然后是工廠的抽象接口,用于生產(chǎn)不同品牌的不同產(chǎn)品
//  ******************IUserSystemFactory .java谜酒,抽象的工廠接口
/**
 * 用戶系統(tǒng)抽象工廠:登錄叹俏、注冊、找回密碼僻族、用戶信息等模塊
 */
public interface IUserSystemFactory {

    // 獲取登錄模塊粘驰,登錄器
    ILoginer getLoginer();

    // 獲取注冊模塊,注冊器
    IRegister getRegister();

    // 找回密碼模塊
    IFindPwder getFindPwder();

    // 用戶信息模塊
    IUserInfoer getUserInfoer();
}

主要就是獲取不同模塊的產(chǎn)品抽象接口對象述么,便于客戶端使用工廠的模塊對象的時候多態(tài)性蝌数。

  1. 實現(xiàn)不同登錄方式的工廠和具體的用戶系統(tǒng)模塊

因為用戶系統(tǒng)大部分情況下都需要和UI交互,所以封裝了一層基類把Context上下文統(tǒng)一起來度秘,減少子類的不必要的重復顶伞。

// *************BaseLoginer.java
/**
 * 登錄模塊的基類
 * @param <U>   用戶信息
 */
public abstract class BaseLoginer<U extends IBaseUser> implements ILoginer<U> {

    private Context mContext;

    public BaseLoginer(Context context) {
        this.mContext = context;
    }
}

// *************BaseUserSystemFactory.java
/**
 * 用戶系統(tǒng)工廠基類
 */
public abstract class BaseUserSystemFactory implements IUserSystemFactory {

    private Context mContext;

    public BaseUserSystemFactory(Context context) {
        this.mContext = context;
    }

    // 工廠對象可以獲取上下文
    public Context getContext(){
        return mContext;
    }
}

比如,當我們使用app自己的用戶帳號登錄的時候的實現(xiàn)

// ******************SystemAccountLoginer.java
/**
 * 使用應用帳號登錄
 */
public class SystemAccountLoginer extends BaseLoginer<User> {

    public SystemAccountLoginer(Context context) {
        super(context);
    }

    @Override
    public void login(User user) {
        // 登錄app
    }

    @Override
    public void logout(User user) {
        // 注銷退出帳號
    }
}

// ******************SystemAccountFactory.java
/**
 * 系統(tǒng)帳號登錄時的用戶系統(tǒng)工廠
 */
public class SystemAccountFactory extends BaseUserSystemFactory {
    private SystemAccountFactory(Context context) {
        super(context);
    }

    public static IUserSystemFactory create(Context context){
        return new SystemAccountFactory(context);
    }

    @Override
    public ILoginer getLoginer() {
        // 返回對應的登錄產(chǎn)品(app自己的帳號平臺登錄對象)
        return new SystemAccountLoginer(getContext());
    }

    @Override
    public IRegister getRegister() {
        // 返回對應的注冊產(chǎn)品(app自己的帳號平臺注冊對象)
        return null;
    }

    @Override
    public IFindPwder getFindPwder() {
        // 返回對應的找回密碼產(chǎn)品(app自己的帳號平臺找回密碼對象)
        return null;
    }

    @Override
    public IUserInfoer getUserInfoer() {
        // 返回對應的用戶信息產(chǎn)品(app自己的帳號平臺用戶信息對象)
        return null;
    }
}

再比如剑梳,用微信來登錄應用

// ******************WeixinLoginer.java
/**
 * 使用微信登錄
 */
public class WeixinLoginer extends BaseLoginer<User> {
    public WeixinLoginer(Context context) {
        super(context);
    }

    @Override
    public void login(User user) {
        // 使用微信登錄
    }

    @Override
    public void logout(User user) {
        // 退出登錄
    }
}

// ******************WeixinFactory.java
/**
 * 系統(tǒng)帳號登錄時的用戶系統(tǒng)工廠
 */
public class WeixinFactory extends BaseUserSystemFactory {
    private WeixinFactory(Context context) {
        super(context);
    }

    public static IUserSystemFactory create(Context context){
        return new WeixinFactory(context);
    }

    @Override
    public ILoginer getLoginer() {
        return new WeixinLoginer(getContext());
    }

    @Override
    public IRegister getRegister() {
        return null;
    }

    @Override
    public IFindPwder getFindPwder() {
        return null;
    }

    @Override
    public IUserInfoer getUserInfoer() {
        return null;
    }
}

這里我實現(xiàn)了登錄產(chǎn)品模塊的唆貌,其它的模塊也是一樣的。對于調(diào)用者的使用也很簡單:

// 客戶端調(diào)用
// 使用自己的帳號平臺
IUserSystemFactory factory = SystemAccountFactory.create(this);
// 使用微信平臺帳號
//        IUserSystemFactory weixinFactory = WeixinFactory.create(this);
User user = new User();
user.setUserId("1256339899879");
user.setPhone("13888888888");
// 使用自己的帳號登錄app
factory.getLoginer().login(user);
// 使用自己的帳號注冊
factory.getRegister().registerAccount(user);
// 使用找回自己帳號的密碼
factory.getFindPwder().findPwd(user);
// 獲取用戶信息
factory.getUserInfoer().getUserInfo();

對于調(diào)用者來說很簡單垢乙,只要關心當前用的是什么平臺的帳號系統(tǒng)锨咙,而不需要關心具體的實現(xiàn)方式。也把不同平臺的登錄追逮、注冊酪刀、獲取用戶信息等分離開來。當然往往不同的平臺可能退出當前帳號的方式是一樣钮孵,這個時候骂倘,其實可以把BaseLoginer當做代理對象,目標接口就是ILoginer巴席,目標對象另外新建一個類實現(xiàn)目標接口历涝,利用代理模式。

Retrofit抽象工廠的應用

我們都知道網(wǎng)絡請求通訊,當服務端返回數(shù)據(jù)后荧库,都需要進行解析轉換為可以直接使用的實體對象诱担,便于設置顯示到UI界面上,我們在構建Retrofit對象的時候往往會給構建器注入一個解析轉換器工廠對象电爹。

new Retrofit.Builder()
                .baseUrl(AppConst.BASE_URL)
                .client(buildHttpClient())
                .addConverterFactory(FastJsonConverterFactory.create())
                .build();

其中FastJsonConverterFactory.create()創(chuàng)建的就是一個Factory抽象工廠對象。

// 數(shù)據(jù)轉換器抽象產(chǎn)品類
// F是入?yún)⒘暇Γ琓是出參(轉換后的數(shù)據(jù)類型)
public interface Converter<F, T> {
  // 產(chǎn)品的轉換操作
  T convert(F value) throws IOException;

  // 抽象工廠類
  abstract class Factory {
    // 工廠生產(chǎn)的請求響應的轉換器產(chǎn)品
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }

    // 工廠生產(chǎn)的請求發(fā)起的轉換器產(chǎn)品
    public Converter<?, RequestBody> requestBodyConverter(Type type,
        Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
      return null;
    }

   // 工廠生產(chǎn)的用于轉換字符串數(shù)據(jù)類型的轉換器產(chǎn)品
    public Converter<?, String> stringConverter(Type type, Annotation[] annotations,
        Retrofit retrofit) {
      return null;
    }
  }
}

接下來看看使用FastJson作為轉換器的工廠實現(xiàn)類:

public class FastJsonConverterFactory extends Converter.Factory {
  // 創(chuàng)建工廠對象
  public static FastJsonConverterFactory create() {
    return new FastJsonConverterFactory();
  }

  private FastJsonConverterFactory() {
  }

  @Override
  public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations,
                                                          Retrofit retrofit) {
    return new FastJsonResponseBodyConverter<>(type, mParserConfig, featureValues, features);
  }

  @Override
  public Converter<?, RequestBody> requestBodyConverter(Type type,
         Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
    return new FastJsonRequestBodyConverter<>(serializeConfig, serializerFeatures);
  }
}

通過封裝一個create方法丐箩,來創(chuàng)建工廠對象,外部調(diào)用者就不需要關系工廠對象是如何創(chuàng)建的恤煞。這點和我上面舉的例子是一樣的屎勘。再一個通過responseBodyConverter、requestBodyConverter方法分別創(chuàng)建了請求響應和請求發(fā)起這兩種產(chǎn)品的對象居扒。

再來看看FastJsonRequestBodyConverter請求發(fā)起轉換產(chǎn)品的實現(xiàn):

// 實現(xiàn)了轉換器這抽象產(chǎn)品類概漱,入?yún)⑹荝equestBody,返回的結果是泛型T
final class FastJsonRequestBodyConverter<T> implements Converter<T, RequestBody> {
  private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
  private SerializeConfig serializeConfig;
  private SerializerFeature[] serializerFeatures;

  FastJsonRequestBodyConverter(SerializeConfig config, SerializerFeature... features) {
    serializeConfig = config;
    serializerFeatures = features;
  }

  @Override
  public RequestBody convert(T value) throws IOException {
    byte[] content;
    if (serializeConfig != null) {
      if (serializerFeatures != null) {
        content = JSON.toJSONBytes(value, serializeConfig, serializerFeatures);
      } else {
        content = JSON.toJSONBytes(value, serializeConfig);
      }
    } else {
      if (serializerFeatures != null) {
        content = JSON.toJSONBytes(value, serializerFeatures);
      } else {
        content = JSON.toJSONBytes(value);
      }
    }
    return RequestBody.create(MEDIA_TYPE, content);
  }
}

實現(xiàn)了轉換器這抽象產(chǎn)品接口類喜喂,入?yún)⑹荝equestBody瓤摧,返回的結果是泛型T(因為請求的參數(shù)是針對具體業(yè)務的作為框架無法確定,于是用泛型來代替)玉吁,這個FastJsonRequestBodyConverter產(chǎn)品的功能就是convert轉換功能照弥,這里使用了阿里巴巴的json解析庫fastJson來轉換,具體的實現(xiàn)就是通過JSON.toJSONBytes方法轉換出json的字節(jié)數(shù)組进副,然后交由給OkHttp的RequestBody.create來構建一個請求體这揣,并且請求的多媒體類型是json格式的。OkHttp中的實現(xiàn):

public static RequestBody create(final MediaType contentType, final byte[] content,
      final int offset, final int byteCount) {
    if (content == null) throw new NullPointerException("content == null");
    Util.checkOffsetAndCount(content.length, offset, byteCount);
    return new RequestBody() {
      @Override public MediaType contentType() {
        return contentType;
      }

      @Override public long contentLength() {
        return byteCount;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        // content請求的參數(shù)內(nèi)容都通過Okio的BufferedSink來寫入了
        sink.write(content, offset, byteCount);
      }
    };
  }

你會發(fā)現(xiàn)RequestBody是個抽象類影斑,writeTo是個抽象方法给赞,那么必定就有調(diào)用此方法的地方。也不能盲目的看源碼找矫户,一個請求的構建最好的地方就是發(fā)起請求的時候片迅,call.enqueue(callback),通過enqueue發(fā)起一個異步的請求吏垮,但Call是接口障涯,也不曉得實現(xiàn)類。還有個辦法就是倒退的方式膳汪,將光標放置上門的writeTo方法上唯蝶,按組合鍵(有使用到writeTo的地方):ctrl + alt + F7:

有使用到writeTo的地方

很明顯是最后一個ReqeustBuilder,請求構建類遗嗽,跟進去是ContentTypeOverridingRequestBody粘我,它是個代理類,目標對象是其內(nèi)部的RequestBody對象這個對象我們猜測就是上文FastJsonRequestBodyConverter的converter轉換創(chuàng)建的RequestBody。再來看看ContentTypeOverridingRequestBody在RequestBuild的build()構建方法中有使用:

// 很明顯因為請求對象初始化比較復雜征字,就通過構建者模式構建了一個OkHttp的Request對象
class RequestBuild{
  Request build() {
    // 很明顯我們在構建Retrofit的時候有傳入FastJson的請求發(fā)起產(chǎn)品的生成工廠對象都弹,因此姑且任務body是有值的不等于null
    RequestBody body = this.body;
    if (body == null) {
      // Try to pull from one of the builders.
      if (formBuilder != null) {
        body = formBuilder.build();
      } else if (multipartBuilder != null) {
        body = multipartBuilder.build();
      } else if (hasBody) {
        // Body is absent, make an empty body.
        body = RequestBody.create(null, new byte[0]);
      }
    }
    
    // 這里給body做了一層代理,實際的目標接口還是之前FastJsonRequestBodyConverter創(chuàng)建的body目標對象自己來調(diào)用的
    // 而后把代理對象body給了Request進行構建請求發(fā)起對象匙姜。
    MediaType contentType = this.contentType;
    if (contentType != null) {
      if (body != null) {
        body = new ContentTypeOverridingRequestBody(body, contentType);
      } else {
        requestBuilder.addHeader("Content-Type", contentType.toString());
      }
    }

    // 這里又通過OkHttp的Request類自身的構建者最終創(chuàng)建了Request對象
    return requestBuilder
        .url(url)
        .method(method, body)
        .build();
  }
}

繼續(xù)看RequestBuild的build()的調(diào)用者是ServiceMethod的toRequest()方法:

Request toRequest(Object... args) throws IOException {
    RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers,
        contentType, hasBody, isFormEncoded, isMultipart);
    // ....省略代碼
    for (int p = 0; p < argumentCount; p++) {
      handlers[p].apply(requestBuilder, args[p]);
    }
    return requestBuilder.build();
  }

先看看apply方法畅厢,它是ParameterHandler的抽象方法,里面有很多參數(shù)的創(chuàng)建的實現(xiàn):

image.png
@Override void apply(RequestBuilder builder, T value) {
      if (value == null) {
        throw new IllegalArgumentException("Body parameter value must not be null.");
      }
      RequestBody body;
      try {
        // 調(diào)用的這個convert這個方法就是上面fastjson工廠轉換創(chuàng)建請求發(fā)起RequestBody對象的調(diào)用處
        body = converter.convert(value);
      } catch (IOException e) {
        throw new RuntimeException("Unable to convert " + value + " to RequestBody", e);
      }
      // 這里把創(chuàng)建的RequestBody對象設置給了RequestBuild構建者氮昧。這就是構建者的好處(初始化一個Request對象不容易框杜,屬性的初始化時機和位置有各種情況)
      builder.setBody(body);
    }

ServiceMethod的toRequest()方法調(diào)用者是OkHttpCall的createRawCall()

private okhttp3.Call createRawCall() throws IOException {
    Request request = serviceMethod.toRequest(args);
    okhttp3.Call call = serviceMethod.callFactory.newCall(request);
    if (call == null) {
      throw new NullPointerException("Call.Factory returned null.");
    }
    return call;
  }

上面的代碼意思是,通過一些參數(shù)創(chuàng)建了一個請求發(fā)起對象袖肥,然后再通過一個工廠對象創(chuàng)建了一個用于發(fā)起請求的okhttp3的call對象咪辱,再來看看createRawCall()方法的調(diào)用,它有三個地方調(diào)用了:

// 一個同步的請求方法
public synchronized Request request() {}

// 異步的請求方法椎组,但是沒有請求回調(diào)
public Response<T> execute() throws IOException {}

// 異步的請求方法油狂,有請求回調(diào)接口對象處理
public void enqueue(final Callback<T> callback) {}

很明顯我們在發(fā)起一個網(wǎng)絡業(yè)務請求的時候,使用的就是enqueue(callback)方法寸癌,大概來看看具體的實現(xiàn):

@Override public void enqueue(final Callback<T> callback) {
    // 這里請求回調(diào)如果是null专筷,直接就報空指針異常,這點在開發(fā)的時候需要做好非空判斷處理
    if (callback == null) throw new NullPointerException("callback == null");

    okhttp3.Call call;
    Throwable failure;

    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      call = rawCall;
      failure = creationFailure;    // 創(chuàng)建的時候的有可能有錯
      if (call == null && failure == null) {
        try {
          // 初次構建使用的時候蒸苇,會去創(chuàng)建一個call
          call = rawCall = createRawCall();
        } catch (Throwable t) {
          failure = creationFailure = t;
        }
      }
    }

    if (failure != null) {
      // 出錯則回調(diào)請求失敗
      callback.onFailure(this, failure);
      return;
    }

    if (canceled) {
     // 請求如有取消仁堪,則取消
      call.cancel();
    }

    // 此處才是,真正發(fā)起請求的地方填渠,把請求交由給底層OkHttp來做弦聂。
    call.enqueue(new okhttp3.Callback() {
      @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse)
          throws IOException {
        Response<T> response;
        try {
          // 請求成功返回后,解析響應
          response = parseResponse(rawResponse);
        } catch (Throwable e) {
          callFailure(e);
          return;
        }
        // 告知回調(diào)請求成功
        callSuccess(response);
      }
      
      // 請求失敗
      @Override public void onFailure(okhttp3.Call call, IOException e) {
        try {
          callback.onFailure(OkHttpCall.this, e);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }

      private void callFailure(Throwable e) {
        try {
          callback.onFailure(OkHttpCall.this, e);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }

      private void callSuccess(Response<T> response) {
        try {
          // 回調(diào)給業(yè)務請求調(diào)用處氛什,告知請求成功
          callback.onResponse(OkHttpCall.this, response);
        } catch (Throwable t) {
          t.printStackTrace();
        }
      }
    });
  }

這樣倒過來分析莺葫,不知有沒有更清晰點,梳理下:

  1. Retrofit構建的時候枪眉,為其設置了FastJson的工廠對象捺檬。

  2. 上面可知call.enqueue(callback),call就是OkHttpCall對象贸铜。

  3. enqueue創(chuàng)建的時候會先調(diào)createRawCall

  4. createRawCall會先調(diào)用serviceMethod的toRequest方法

  5. 在toRequest方法中堡纬,創(chuàng)建RequestBuild對象,并且把設置的業(yè)務請求的api里的參數(shù)對象請求體Body使用FastJson工廠創(chuàng)建的FastJsonRequestConverter來convert出一個RequestBody設置給RequestBuild對象蒿秦,并最終通過構建者模式創(chuàng)建Request對象烤镐。

  6. 再通過callFactory工廠創(chuàng)建一個用于請求的call,最終交由okhttp的enqueue方法來發(fā)起真正的網(wǎng)絡請求棍鳖。


總結

今天的篇幅也比較長炮叶,主要說明了抽象工廠設計模式的使用碗旅,具體舉了個在開發(fā)中比較實用的多平臺登錄的用戶系統(tǒng)模塊的問題,當然這只是個例子實際項目中需要完善的還很多镜悉。通用的例子還有很多比如:多種支付方式的切換祟辟、多種地圖SDK的切換、多種網(wǎng)絡框架的切換侣肄、多種持久化數(shù)據(jù)存儲方式的切換旧困、多種數(shù)據(jù)處理方式的切換、多種圖片加載器的切換等等稼锅。

后面主要介紹了Retrofit中抽象工廠的應用叮喳,以及簡單分析了,Retrofit是如何構建請求和發(fā)起請求的缰贝。


我是JerryloveEmily,感謝您的閱讀畔濒,

喜歡就點個贊唄剩晴,“?喜歡”,

鼓勵又不花錢侵状,您在看赞弥,我就繼續(xù)寫~

非簡書用戶,可以點右上角的三個“...”趣兄,然后"在Safari中打開”绽左,就可以點贊咯~

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市艇潭,隨后出現(xiàn)的幾起案子拼窥,更是在濱河造成了極大的恐慌,老刑警劉巖蹋凝,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鲁纠,死亡現(xiàn)場離奇詭異,居然都是意外死亡鳍寂,警方通過查閱死者的電腦和手機改含,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迄汛,“玉大人捍壤,你說我怎么就攤上這事“鞍” “怎么了鹃觉?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長睹逃。 經(jīng)常有香客問我帜慢,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任粱玲,我火速辦了婚禮躬柬,結果婚禮上,老公的妹妹穿的比我還像新娘抽减。我一直安慰自己允青,他們只是感情好,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布卵沉。 她就那樣靜靜地躺著颠锉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪史汗。 梳的紋絲不亂的頭發(fā)上琼掠,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機與錄音停撞,去河邊找鬼瓷蛙。 笑死,一個胖子當著我的面吹牛戈毒,可吹牛的內(nèi)容都是我干的艰猬。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼埋市,長吁一口氣:“原來是場噩夢啊……” “哼冠桃!你這毒婦竟也來了?” 一聲冷哼從身側響起道宅,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤食听,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后污茵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碳蛋,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年省咨,在試婚紗的時候發(fā)現(xiàn)自己被綠了肃弟。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡零蓉,死狀恐怖笤受,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情敌蜂,我是刑警寧澤箩兽,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站章喉,受9級特大地震影響汗贫,放射性物質發(fā)生泄漏身坐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一落包、第九天 我趴在偏房一處隱蔽的房頂上張望部蛇。 院中可真熱鬧,春花似錦咐蝇、人聲如沸涯鲁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抹腿。三九已至,卻和暖如春旭寿,著一層夾襖步出監(jiān)牢的瞬間警绩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工盅称, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留肩祥,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓微渠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親咧擂。 傳聞我的和親對象是個殘疾皇子逞盆,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,754評論 25 707
  • 我們在日常編寫代碼中免不了會用到各種各樣第三方庫,網(wǎng)絡請求松申、圖片加載云芦、數(shù)據(jù)庫等等。有些lib接入可能方便到幾行代碼...
    RockerLee閱讀 2,896評論 4 14
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理贸桶,服務發(fā)現(xiàn)舅逸,斷路器,智...
    卡卡羅2017閱讀 134,629評論 18 139
  • 安卓開發(fā)領域中皇筛,很多重要的問題都有很好的開源解決方案琉历,例如Square公司提供網(wǎng)絡請求 OkHttp , Retr...
    aaron688閱讀 1,905評論 1 20
  • 今天從《不持有的生活》這本書里,了解了更多對不持有生活的態(tài)度水醋。 不持有的生活旗笔,并不是:1. 低標準的生活,2. 節(jié)...
    老杜還在閱讀 226評論 0 0