Glide4.1.1 支持代理Proxy+Authenticator(用戶名+密碼驗(yàn)證)

Glide圖片加載框架的強(qiáng)大录煤,使用簡(jiǎn)單相信是眾所周知的,之前在項(xiàng)目中一直都用的荞胡,但是最近在項(xiàng)目中需要使用代理妈踊,在加載圖片的時(shí)候也要用,本來(lái)以為很簡(jiǎn)單泪漂,網(wǎng)上查了下發(fā)現(xiàn)這一塊其實(shí)沒(méi)那么容易啊廊营,只能邊看源碼邊學(xué)習(xí)解決。

  1. Glide項(xiàng)目地址: Glide Github
  2. Glide WIKI
  3. Glide 4.0源碼解析

查看源碼萝勤,可以看到進(jìn)行網(wǎng)絡(luò)請(qǐng)求的時(shí)候露筒,使用的是HttpUrlConnection(這里我們沒(méi)有使用OKHttp或者Volley進(jìn)行優(yōu)化,有興趣的可以查看WIKI文檔進(jìn)行集成)
源碼分析基于Glide 4.1.1

Glide常用方式與源碼分析:

Glide.with(activity).load(url).into(imageview)

1. 初始化敌卓,配置 Loader

  • 查看Glide類,Registry類:
    Glide.with(activity)慎式,對(duì)Glide進(jìn)行初始化,主要方法如下:

Glide.getRetriever()
Glide.get(context)
Glide.checkAndInitializeGlide(context);
Glide.initializeGlide

Glide glide = builder.build(applicationContext);

Glide 用GlideBuilder對(duì)象生成Glide對(duì)象,Glide類的構(gòu)造函數(shù)中趟径,對(duì)Glide對(duì)象進(jìn)行初始化瘪吏,這里涉及到的初始化的重點(diǎn)是對(duì)Registry對(duì)象的初始化:

 registry.register(ByteBuffer.class, new ByteBufferEncoder())
        .register(InputStream.class, new StreamEncoder(arrayPool))
        /* Bitmaps */
        .append(ByteBuffer.class, Bitmap.class,
            new ByteBufferBitmapDecoder(downsampler))
        .append(InputStream.class, Bitmap.class,
            new StreamBitmapDecoder(downsampler, arrayPool))
        .append(ParcelFileDescriptor.class, Bitmap.class, new VideoBitmapDecoder(bitmapPool))
        .register(Bitmap.class, new BitmapEncoder())
        /* GlideBitmapDrawables */
        .append(ByteBuffer.class, BitmapDrawable.class,
            new BitmapDrawableDecoder<>(resources, bitmapPool,
                new ByteBufferBitmapDecoder(downsampler)))
        .append(InputStream.class, BitmapDrawable.class,
            new BitmapDrawableDecoder<>(resources, bitmapPool,
                new StreamBitmapDecoder(downsampler, arrayPool)))
        .append(ParcelFileDescriptor.class, BitmapDrawable.class,
            new BitmapDrawableDecoder<>(resources, bitmapPool, new VideoBitmapDecoder(bitmapPool)))
        .register(BitmapDrawable.class, new BitmapDrawableEncoder(bitmapPool, new BitmapEncoder()))
        /* GIFs */
        .prepend(InputStream.class, GifDrawable.class,
            new StreamGifDecoder(registry.getImageHeaderParsers(), byteBufferGifDecoder, arrayPool))
        .prepend(ByteBuffer.class, GifDrawable.class, byteBufferGifDecoder)
        .register(GifDrawable.class, new GifDrawableEncoder())
        /* GIF Frames */
        .append(GifDecoder.class, GifDecoder.class, new UnitModelLoader.Factory<GifDecoder>())
        .append(GifDecoder.class, Bitmap.class, new GifFrameResourceDecoder(bitmapPool))
        /* Files */
        .register(new ByteBufferRewinder.Factory())
        .append(File.class, ByteBuffer.class, new ByteBufferFileLoader.Factory())
        .append(File.class, InputStream.class, new FileLoader.StreamFactory())
        .append(File.class, File.class, new FileDecoder())
        .append(File.class, ParcelFileDescriptor.class, new FileLoader.FileDescriptorFactory())
        .append(File.class, File.class, new UnitModelLoader.Factory<File>())
        /* Models */
        .register(new InputStreamRewinder.Factory(arrayPool))
        .append(int.class, InputStream.class, new ResourceLoader.StreamFactory(resources))
        .append(
                int.class,
                ParcelFileDescriptor.class,
                new ResourceLoader.FileDescriptorFactory(resources))
        .append(Integer.class, InputStream.class, new ResourceLoader.StreamFactory(resources))
        .append(
                Integer.class,
                ParcelFileDescriptor.class,
                new ResourceLoader.FileDescriptorFactory(resources))
        .append(String.class, InputStream.class, new DataUrlLoader.StreamFactory())
        .append(String.class, InputStream.class, new StringLoader.StreamFactory())
        .append(String.class, ParcelFileDescriptor.class, new StringLoader.FileDescriptorFactory())
        .append(Uri.class, InputStream.class, new HttpUriLoader.Factory())
        .append(Uri.class, InputStream.class, new AssetUriLoader.StreamFactory(context.getAssets()))
        .append(
                Uri.class,
                ParcelFileDescriptor.class,
                new AssetUriLoader.FileDescriptorFactory(context.getAssets()))
        .append(Uri.class, InputStream.class, new MediaStoreImageThumbLoader.Factory(context))
        .append(Uri.class, InputStream.class, new MediaStoreVideoThumbLoader.Factory(context))
        .append(
            Uri.class,
             InputStream.class,
             new UriLoader.StreamFactory(context.getContentResolver()))
        .append(Uri.class, ParcelFileDescriptor.class,
             new UriLoader.FileDescriptorFactory(context.getContentResolver()))
        .append(Uri.class, InputStream.class, new UrlUriLoader.StreamFactory())
        .append(URL.class, InputStream.class, new UrlLoader.StreamFactory())
        .append(Uri.class, File.class, new MediaStoreFileLoader.Factory(context))
        .append(GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory())
        .append(byte[].class, ByteBuffer.class, new ByteArrayLoader.ByteBufferFactory())
        .append(byte[].class, InputStream.class, new ByteArrayLoader.StreamFactory())
        /* Transcoders */
        .register(Bitmap.class, BitmapDrawable.class,
            new BitmapDrawableTranscoder(resources, bitmapPool))
        .register(Bitmap.class, byte[].class, new BitmapBytesTranscoder())
        .register(GifDrawable.class, byte[].class, new GifDrawableBytesTranscoder());

  • Registry類是用來(lái):
/**
 * Manages component registration to extend or replace Glide's default loading, decoding, and
 * encoding logic.
 */

簡(jiǎn)單來(lái)說(shuō):就是根據(jù)傳入什么樣的modelClass來(lái)取使用哪個(gè)ModelLoaderFactory。類似于EventBus 這種蜗巧,根據(jù)傳入的類型調(diào)用具體的處理邏輯掌眠。這里我們的httpUrl字符串在Glide中會(huì)被封裝成GlideUrl對(duì)象,對(duì)應(yīng)的loader類是HttpGlideUrlLoader

GlideUrl.class, InputStream.class, new HttpGlideUrlLoader.Factory()

2. GlideApp

在Glide4中會(huì)在make之后自動(dòng)生成GlideApp:

  1. 在build.gradle中添加:
dependencies {

    compile 'com.github.bumptech.glide:glide:4.1.1'
    annotationProcessor  'com.github.bumptech.glide:compiler:4.1.1'
}

其中第二句annotationProcessor 是重點(diǎn)

  1. 使用:@GlideModule

@GlideModule
public class MyGlideAppModule extends AppGlideModule {

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
        super.applyOptions(context, builder);

        int memoryCacheSizeBytes = 1024 * 1024 * 20; // 20mb
        builder.setMemoryCache(new LruResourceCache(memoryCacheSizeBytes));


    }

    @Override
    public boolean isManifestParsingEnabled() {
        return false;
    }
}

  1. Clean and make

3. HttpGlideUrlLoader幕屹,HttpUrlFetcher

這兩個(gè)類主要負(fù)責(zé)進(jìn)行網(wǎng)絡(luò)請(qǐng)求蓝丙。

  1. HttpUrlFetcher 這個(gè)類中使用HttpURLConnection進(jìn)行網(wǎng)絡(luò)請(qǐng)求
@Override
  public void loadData(Priority priority, DataCallback<? super InputStream> callback) {
    long startTime = LogTime.getLogTime();
    final InputStream result;
    try {
      result = loadDataWithRedirects(glideUrl.toURL(), 0 /*redirects*/, null /*lastUrl*/,
          glideUrl.getHeaders());
    } catch (IOException e) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "Failed to load data for url", e);
      }
      callback.onLoadFailed(e);
      return;
    }

    if (Log.isLoggable(TAG, Log.VERBOSE)) {
      Log.v(TAG, "Finished http url fetcher fetch in " + LogTime.getElapsedMillis(startTime)
          + " ms and loaded " + result);
    }
    callback.onDataReady(result);
  }

  private InputStream loadDataWithRedirects(URL url, int redirects, URL lastUrl,
      Map<String, String> headers) throws IOException {
    if (redirects >= MAXIMUM_REDIRECTS) {
      throw new HttpException("Too many (> " + MAXIMUM_REDIRECTS + ") redirects!");
    } else {
      // Comparing the URLs using .equals performs additional network I/O and is generally broken.
      // See http://michaelscharf.blogspot.com/2006/11/javaneturlequals-and-hashcode-make.html.
      try {
        if (lastUrl != null && url.toURI().equals(lastUrl.toURI())) {
          throw new HttpException("In re-direct loop");

        }
      } catch (URISyntaxException e) {
        // Do nothing, this is best effort.
      }
    }

    urlConnection = connectionFactory.build(url);
    for (Map.Entry<String, String> headerEntry : headers.entrySet()) {
      urlConnection.addRequestProperty(headerEntry.getKey(), headerEntry.getValue());
    }
    urlConnection.setConnectTimeout(timeout);
    urlConnection.setReadTimeout(timeout);
    urlConnection.setUseCaches(false);
    urlConnection.setDoInput(true);

    // Stop the urlConnection instance of HttpUrlConnection from following redirects so that
    // redirects will be handled by recursive calls to this method, loadDataWithRedirects.
    urlConnection.setInstanceFollowRedirects(false);

    // Connect explicitly to avoid errors in decoders if connection fails.
    urlConnection.connect();
    if (isCancelled) {
      return null;
    }
    final int statusCode = urlConnection.getResponseCode();
    if (statusCode / 100 == 2) {
      return getStreamForSuccessfulRequest(urlConnection);
    } else if (statusCode / 100 == 3) {
      String redirectUrlString = urlConnection.getHeaderField("Location");
      if (TextUtils.isEmpty(redirectUrlString)) {
        throw new HttpException("Received empty or null redirect url");
      }
      URL redirectUrl = new URL(url, redirectUrlString);
      return loadDataWithRedirects(redirectUrl, redirects + 1, url, headers);
    } else if (statusCode == -1) {
      throw new HttpException(statusCode);
    } else {
      throw new HttpException(urlConnection.getResponseMessage(), statusCode);
    }
  }

而HttpUrlConnection 是使用HttpUrlFetcher中的connectionFactory字段進(jìn)行生成的HttpUrlConnection:

 @Override
    public HttpURLConnection build(URL url) throws IOException {
      return (HttpURLConnection) url.openConnection();
    }

  1. HttpGlideUrlLoader:核心代碼如下,創(chuàng)建HttpUrlFetcher進(jìn)行網(wǎng)絡(luò)請(qǐng)求
  @Override
  public LoadData<InputStream> buildLoadData(GlideUrl model, int width, int height,
      Options options) {
    // GlideUrls memoize parsed URLs so caching them saves a few object instantiations and time
    // spent parsing urls.
    GlideUrl url = model;
    if (modelCache != null) {
      url = modelCache.get(model, 0, 0);
      if (url == null) {
        modelCache.put(model, 0, 0, model);
        url = model;
      }
    }
    int timeout = options.get(TIMEOUT);
    return new LoadData<>(url, new HttpUrlFetcher(url, timeout));
  }

主要的代碼分析就到這里望拖。

不難看出要想支持Proxy只需要改動(dòng)一行代碼即可:

 @Override
    public HttpURLConnection build(URL url) throws IOException {
      return (HttpURLConnection) url.openConnection(proxy);
    }

當(dāng)然還要加一些是否存在proxy的判斷:

 @Override
    public HttpURLConnection build(URL url) throws IOException {
            HttpURLConnection conn = null;
            if (existProxy()) {
                conn = (HttpURLConnection) url.openConnection(getProxy());
            } else {
                conn = (HttpURLConnection) url.openConnection();
            }
    }

  • 如果需要添加用戶名和密碼認(rèn)證:

          if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(pwd)) {
                Authenticator authenticator = new Authenticator() {

                    public PasswordAuthentication getPasswordAuthentication() {
                        return (new PasswordAuthentication(username,
                                pwd.toCharArray()));
                    }
                };
                Authenticator.setDefault(authenticator);
            } else {
                Authenticator.setDefault(null);
            }

注意 關(guān)鍵是如何添加渺尘??靠娱?
這里能夠想到的是使用自定義的HttpGlideUrlLoader。
問(wèn)題1. HttpUrlFetcher中的connectionFactory是final的,HttpUrlConnectionFactory 是default的不能擴(kuò)展,HttpUrlFetchery構(gòu)造函數(shù)也是default的

  // Visible for testing.
  HttpUrlFetcher(GlideUrl glideUrl, int timeout, HttpUrlConnectionFactory connectionFactory) {
    this.glideUrl = glideUrl;
    this.timeout = timeout;
    this.connectionFactory = connectionFactory;
  }

解決方案

1. 方案一

修改Glide源碼掠兄,重新打包像云⌒咳福或者直接將源碼放到項(xiàng)目中修改

  1. HttpUrlFetcher第二個(gè)構(gòu)造方法置為public,將HttpUrlFetcher$HttpUrlConnectionFactory 接口置為 public
  2. HttpGlideUrlLoader中添加Proxy字段與設(shè)置方法迅诬,建議是 static腋逆,如果proxy為空就調(diào)用HttpUrlFetcher第一個(gè)構(gòu)造函數(shù),如果不為空就調(diào)用第二個(gè)侈贷,實(shí)現(xiàn)HttpUrlFetcher$HttpUrlConnectionFactory接口并將proxy傳入即可
 private static class ProxyHttpUrlConnectionFactory implements HttpUrlConnectionFactory {
  
    private Proxy mProxy

    private static boolean setProxy(proxy p,String username,String pwd){
          mProxy = p;

          if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(pwd)) {
                Authenticator authenticator = new Authenticator() {

                    public PasswordAuthentication getPasswordAuthentication() {
                        return (new PasswordAuthentication(username,
                                pwd.toCharArray()));
                    }
                };
                Authenticator.setDefault(authenticator);
            } else {
                Authenticator.setDefault(null);
            }
    }

   private static boolean exist(){
      return mProxy != null;
    }

    @Synthetic
    public ProxyHttpUrlConnectionFactory () { }

    @Override
    public HttpURLConnection build(URL url) throws IOException {
      //return (HttpURLConnection) url.openConnection(mProxy);
    }
  }

這個(gè)方案有點(diǎn):

  1. 改動(dòng)少
  2. 容易理解和實(shí)現(xiàn)

缺點(diǎn):

  1. 改動(dòng)了源碼撑蚌,對(duì)于后續(xù)Glide升級(jí)存在問(wèn)題

2. 方案二

自定義HttpGlideUrlLoader,前面說(shuō)到的Registry類的初始化中,當(dāng)modelClass是GlideUrl時(shí),使用的就是HttpGlideUrlLoader,那這里取代這個(gè)默認(rèn)的Loader继蜡,使用自定義即可:
假設(shè)自定義的是MyHttpGlideUriLoader, 擴(kuò)展 extends ModelLoader<GlideUrl, InputStream>, 則:

 registry.replace(GlideUrl.class, InputStream.class, new MyHttpGlideUrlLoader.Factory());

并把HttpGlideUrlLoader中的代碼復(fù)制到MyHttpGlideUriLoader鳄逾,區(qū)別只有buildLoadData方法中最后return時(shí)殴俱。

然后自定義MyHttpUrlFetcher,將HttpUrlFetcher復(fù)制一份,區(qū)別只有DefaultHttpUrlConnectionFactory :

  private static class DefaultHttpUrlConnectionFactory implements HttpUrlConnectionFactory {
  
    private static Proxy mProxy

    private static boolean setProxy(proxy p,String username,String pwd){
          mProxy = p;

          if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(pwd)) {
                Authenticator authenticator = new Authenticator() {

                    public PasswordAuthentication getPasswordAuthentication() {
                        return (new PasswordAuthentication(username,
                                pwd.toCharArray()));
                    }
                };
                Authenticator.setDefault(authenticator);
            } else {
                Authenticator.setDefault(null);
            }
    }

   private static boolean exist(){
      return mProxy != null;
    }

    @Synthetic
    DefaultHttpUrlConnectionFactory() { }

    @Override
    public HttpURLConnection build(URL url) throws IOException {
      //return (HttpURLConnection) url.openConnection();
    // 改成
         HttpURLConnection conn = null;
            if (existProxy()) {
                conn = (HttpURLConnection) url.openConnection(mProxy);
            } else {
                conn = (HttpURLConnection) url.openConnection();
            }

    }
  }

這個(gè)方案其實(shí)很好理解的。但是這個(gè)方案需要改的東西太多,有點(diǎn)挫,其實(shí)真正的改動(dòng)只是HttpUrlConnectionFactory.build 方法而已,

優(yōu)點(diǎn):

  1. 容易理解

缺點(diǎn):

  1. 改動(dòng)太多,添加太多冗余代碼

3. 方案三

使用反射+動(dòng)態(tài)代理

思路說(shuō)明:因?yàn)檎嬲枰薷牡闹皇荋ttpUrlFetcher$HttpUrlConnectionFactory 接口實(shí)現(xiàn)的build方法,如果能夠?qū)ttpUrlFetcher.connectionFactory替換掉就行了饼问,所以這里需要使用到反射讹开。先反射出HttpUrlConnectionFactory接口闹击,并實(shí)現(xiàn),然后反射HttpUrlFetcher.connectionFactory字段并賦值。這就是整體的思路

反射這里就不講了,主要是如何實(shí)現(xiàn)HttpUrlConnectionFactory接口呢埃撵?
動(dòng)態(tài)代理,如果看過(guò)retrofit源碼的話應(yīng)該不難理解洲赵。

  1. 首先跟方案二一樣,自定義MyGlideUrlLoader,使用register.replace替換默認(rèn)的loader。

  2. 在MyGlideUrlLoader中buildLoadData方法進(jìn)行復(fù)寫:

public class MyHttpGlideUrlLoader extends HttpGlideUrlLoader {
    private static final String TAG = MyHttpGlideUrlLoader.class.getSimpleName();

    public MyHttpGlideUrlLoader(ModelCache<GlideUrl, GlideUrl> modelCache) {
        super(modelCache);
    }

    @Override
    public LoadData<InputStream> buildLoadData(GlideUrl model, int width, int height, Options options) {
        LoadData old = super.buildLoadData(model, width, height, options);//if proy is not null,drop the result,just set the modelCache
        if (!MyHttpUrlConnectionFactory.existProxy()) {
            return old;
        }

        //反射出HttpUrlFetcher$HttpUrlConnectionFactory接口
        Class factoryInterface = null;
        try {
            factoryInterface = Class.forName("com.bumptech.glide.load.data.HttpUrlFetcher$HttpUrlConnectionFactory");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //動(dòng)態(tài)代理這個(gè)接口
        Object factoryProxy = null;
        if (factoryInterface != null) {
            factoryProxy = java.lang.reflect.Proxy.newProxyInstance(factoryInterface.getClassLoader(),
                    new Class[]{factoryInterface},
                    new MyInvocationHandler(new MyHttpUrlConnectionFactory()));
        }
        
        if (old != null && old.fetcher != null && factoryProxy != null) {
            try {
                //給connectionFactory字段賦新的值
                Field factoryField = HttpUrlFetcher.class.getDeclaredField("connectionFactory");
                factoryField.setAccessible(true);
                factoryField.set(old.fetcher, factoryProxy);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        return old;
    }

    private static class MyInvocationHandler implements InvocationHandler {
        private MyHttpUrlConnectionFactory factory;

        public MyInvocationHandler(MyHttpUrlConnectionFactory f) {
            factory = f;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            URL url = null;
            if(args != null && args.length > 0 && args[0] instanceof URL){
                url = (URL) args[0];
            }
            if(url != null){
                return factory.build(url);
            }

            return method.invoke(proxy,args);
        }
    }

//代理類中真正的執(zhí)行是這個(gè)類
    public static class MyHttpUrlConnectionFactory {
        private static Proxy sProxyConfig;

        public static void setProxy(Proxy proxy,final String username,final String pwd) {
            if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(pwd)) {
                Authenticator authenticator = new Authenticator() {

                    public PasswordAuthentication getPasswordAuthentication() {
                        return (new PasswordAuthentication(username,
                                pwd.toCharArray()));
                    }
                };
                Authenticator.setDefault(authenticator);
            } else {
                Authenticator.setDefault(null);
            }

            MyHttpUrlConnectionFactory.sProxyConfig = proxy;
        }

        public static void clearProxy() {
            setProxy(null,null,null);
        }

        public static boolean existProxy() {
            return sProxyConfig != null;
        }

        public MyHttpUrlConnectionFactory() {

        }

        public HttpURLConnection build(URL url) throws IOException {
            HttpURLConnection conn = null;
            if (existProxy()) {
                conn = (HttpURLConnection) url.openConnection(sProxyConfig);
            } else {
                conn = (HttpURLConnection) url.openConnection();
            }
            return conn;
        }
    }


    /**
     * The default factory for {@link MyHttpGlideUrlLoader}s.
     */
    public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
        private final ModelCache<GlideUrl, GlideUrl> modelCache = new ModelCache<>(500);

        @Override
        public ModelLoader<GlideUrl, InputStream> build(MultiModelLoaderFactory multiFactory) {
            return new MyHttpGlideUrlLoader(modelCache);
        }

        @Override
        public void teardown() {
            // Do nothing.
        }
    }
}

方案三優(yōu)點(diǎn):

  1. 代碼改動(dòng)量不大
  2. 使用反射和動(dòng)態(tài)代理拇厢,比方案二看上去高大上了點(diǎn)邪媳,動(dòng)態(tài)代理之前只是看沒(méi)有實(shí)踐過(guò)迅涮,呵呵。徽龟。叮姑。。

缺點(diǎn):

  1. 需要理解反射和動(dòng)態(tài)代理
  2. 在Glide后續(xù)版本升級(jí)中,如果com.bumptech.glide.load.data.HttpUrlFetcher$HttpUrlConnectionFactory名稱改了传透,或者com.bumptech.glide.load.data.HttpUrlFetcher.connectionFactory字段名稱改了耘沼,就歇菜了。朱盐。群嗤。

綜上

個(gè)人還是傾向于方案一,改動(dòng)量很小兵琳,但是在項(xiàng)目中要求不能改動(dòng)第三方代碼狂秘,所以使用了方案三

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市躯肌,隨后出現(xiàn)的幾起案子者春,更是在濱河造成了極大的恐慌,老刑警劉巖清女,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件钱烟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡嫡丙,警方通過(guò)查閱死者的電腦和手機(jī)拴袭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)曙博,“玉大人稻扬,你說(shuō)我怎么就攤上這事⊙虼瘢” “怎么了泰佳?”我有些...
    開(kāi)封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)尘吗。 經(jīng)常有香客問(wèn)我逝她,道長(zhǎng),這世上最難降的妖魔是什么睬捶? 我笑而不...
    開(kāi)封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任黔宛,我火速辦了婚禮,結(jié)果婚禮上擒贸,老公的妹妹穿的比我還像新娘臀晃。我一直安慰自己,他們只是感情好介劫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布徽惋。 她就那樣靜靜地躺著,像睡著了一般座韵。 火紅的嫁衣襯著肌膚如雪险绘。 梳的紋絲不亂的頭發(fā)上踢京,一...
    開(kāi)封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音宦棺,去河邊找鬼瓣距。 笑死,一個(gè)胖子當(dāng)著我的面吹牛代咸,可吹牛的內(nèi)容都是我干的蹈丸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼呐芥,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼白华!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起贩耐,我...
    開(kāi)封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎厦取,沒(méi)想到半個(gè)月后潮太,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡虾攻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年铡买,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霎箍。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奇钞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出漂坏,到底是詐尸還是另有隱情景埃,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布顶别,位于F島的核電站谷徙,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏驯绎。R本人自食惡果不足惜完慧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望剩失。 院中可真熱鬧屈尼,春花似錦、人聲如沸拴孤。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)演熟。三九已至涨椒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚕冬。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工免猾, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人囤热。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓猎提,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親旁蔼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锨苏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,163評(píng)論 25 707
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)棺聊,斷路器伞租,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 立秋:斗指西南維為立秋,陰意出地始?xì)⑷f(wàn)物,按秋訓(xùn)示,谷熟也"。 三候:涼風(fēng)至;白露降;寒蟬鳴 秋風(fēng) 南方靠海的城市...
    阿阿閑閱讀 407評(píng)論 4 8
  • 一限佩、精彩摘錄 P4完成任何任務(wù)都需要一定的時(shí)間葵诈。同時(shí),任何任務(wù)都最好或必須在某個(gè)特定的時(shí)間點(diǎn)之前完成祟同,即作喘,任務(wù)都有...
    發(fā)憤的海洱閱讀 246評(píng)論 0 1
  • .1. 有個(gè)小個(gè)子 愛(ài)上了小胖子 每次見(jiàn)面 醞釀無(wú)數(shù)次 .2. 小胖子低下頭 擦了擦手 眼神蕩漾著溫柔 ...
    綠子的信閱讀 217評(píng)論 3 4