Fresco知识点笔记03-ImagePipelineConfig-AnimatedImageFactory

原创-android技术研究-转载请注明出处

Fresco初始化之前会有一些自定义的相关配置需要开发人员自己 去设置,设置配置的对象是ImagePipelineConfig,现在开始分析ImagePipelineConfig中第一个配置参数AnimatedImageFactory的相关知识点,以下代码是使用Fresco时,设置配置属性的需要的代码demo。
<pre>
Set<RequestListener> listeners = new HashSet<>();
// 添加请求日志监听包含请求及请求过程
listeners.add(new RequestLoggingListener());
// 设置配置属性
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
.setRequestListeners(listeners)
.build();
</pre>

<pre>
//true
@Nullable private final AnimatedImageFactory mAnimatedImageFactory;
//error
@NonNull private final AnimatedImageFactory mAnimatedImageFactory = null;
</pre>

上述代码是在ImagePipelineConfig设置的AnimatedImageFactory属性

知识点@Nullable与@NonNull

@Nullable标记当前对象可以为null值,如果使用@NonNull则表示当前属性不能为null值,如果给它赋值null,则会编译器报错。

AnimatedImageFactory是一个接口,它的主要作用是对带动画的图像进行解码,它的实现是AnimatedImageFactoryImpl,具体代码如下(可以直接跳过,代码后是相关的知识点):
<pre>
public interface AnimatedImageFactory {
public CloseableImage decodeGif(
final EncodedImage encodedImage,
final ImageDecodeOptions options,
final Bitmap.Config bitmapConfig);

public CloseableImage decodeWebP(
final EncodedImage encodedImage,
final ImageDecodeOptions options,
final Bitmap.Config bitmapConfig);

}
</pre>

<pre>
public class AnimatedImageFactoryImpl implements AnimatedImageFactory {

private final AnimatedDrawableBackendProvider mAnimatedDrawableBackendProvider;
private final PlatformBitmapFactory mBitmapFactory;

static AnimatedImageDecoder sGifAnimatedImageDecoder = null;
static AnimatedImageDecoder sWebpAnimatedImageDecoder = null;

private static AnimatedImageDecoder loadIfPresent(final String className) {
try {
Class<?> clazz = Class.forName(className);
return (AnimatedImageDecoder) clazz.newInstance();
} catch (Throwable e) {
return null;
}
}

static {
sGifAnimatedImageDecoder = loadIfPresent("com.facebook.animated.gif.GifImage");
sWebpAnimatedImageDecoder = loadIfPresent("com.facebook.animated.webp.WebPImage");
}

public AnimatedImageFactoryImpl(
AnimatedDrawableBackendProvider animatedDrawableBackendProvider,
PlatformBitmapFactory bitmapFactory) {
mAnimatedDrawableBackendProvider = animatedDrawableBackendProvider;
mBitmapFactory = bitmapFactory;
}

/**

  • Decodes a GIF into a CloseableImage.

  • @param encodedImage encoded image (native byte array holding the encoded bytes and meta data)

  • @param options the options for the decode

  • @param bitmapConfig the Bitmap.Config used to generate the output bitmaps

  • @return a {@link CloseableImage} for the GIF image
    */
    public CloseableImage decodeGif(
    final EncodedImage encodedImage,
    final ImageDecodeOptions options,
    final Bitmap.Config bitmapConfig) {
    if (sGifAnimatedImageDecoder == null) {
    throw new UnsupportedOperationException("To encode animated gif please add the dependency " +
    "to the animated-gif module");
    }
    final CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef();
    Preconditions.checkNotNull(bytesRef);
    try {
    Preconditions.checkState(!options.forceOldAnimationCode);
    final PooledByteBuffer input = bytesRef.get();
    AnimatedImage gifImage = sGifAnimatedImageDecoder.decode(input.getNativePtr(), input.size());

    return getCloseableImage(options, gifImage, bitmapConfig);
    } finally {
    CloseableReference.closeSafely(bytesRef);
    }
    }

/**

  • Decode a WebP into a CloseableImage.
  • @param encodedImage encoded image (native byte array holding the encoded bytes and meta data)
  • @param options the options for the decode
  • @param bitmapConfig the Bitmap.Config used to generate the output bitmaps
  • @return a {@link CloseableImage} for the WebP image
    */
    public CloseableImage decodeWebP(
    final EncodedImage encodedImage,
    final ImageDecodeOptions options,
    final Bitmap.Config bitmapConfig) {
    if (sWebpAnimatedImageDecoder == null) {
    throw new UnsupportedOperationException("To encode animated webp please add the dependency " +
    "to the animated-webp module");
    }
    final CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef();
    Preconditions.checkNotNull(bytesRef);
    try {
    Preconditions.checkArgument(!options.forceOldAnimationCode);
    final PooledByteBuffer input = bytesRef.get();
    AnimatedImage webPImage = sWebpAnimatedImageDecoder.decode(
    input.getNativePtr(),
    input.size());
    return getCloseableImage(options, webPImage, bitmapConfig);
    } finally {
    CloseableReference.closeSafely(bytesRef);
    }
    }

private CloseableAnimatedImage getCloseableImage(
ImageDecodeOptions options,
AnimatedImage image,
Bitmap.Config bitmapConfig) {
List<CloseableReference<Bitmap>> decodedFrames = null;
CloseableReference<Bitmap> previewBitmap = null;
try {
int frameForPreview = options.useLastFrameForPreview ? image.getFrameCount() - 1 : 0;
if (options.decodeAllFrames) {
decodedFrames = decodeAllFrames(image, bitmapConfig);
previewBitmap = CloseableReference.cloneOrNull(decodedFrames.get(frameForPreview));
}

  if (options.decodePreviewFrame && previewBitmap == null) {
    previewBitmap = createPreviewBitmap(image, bitmapConfig, frameForPreview);
  }
  AnimatedImageResult animatedImageResult = AnimatedImageResult.newBuilder(image)
      .setPreviewBitmap(previewBitmap)
      .setFrameForPreview(frameForPreview)
      .setDecodedFrames(decodedFrames)
      .build();
  return new CloseableAnimatedImage(animatedImageResult);
} finally {
  CloseableReference.closeSafely(previewBitmap);
  CloseableReference.closeSafely(decodedFrames);
}

}

private CloseableReference<Bitmap> createPreviewBitmap(
AnimatedImage image,
Bitmap.Config bitmapConfig,
int frameForPreview) {
CloseableReference<Bitmap> bitmap = createBitmap(
image.getWidth(),
image.getHeight(),
bitmapConfig);
AnimatedImageResult tempResult = AnimatedImageResult.forAnimatedImage(image);
AnimatedDrawableBackend drawableBackend =
mAnimatedDrawableBackendProvider.get(tempResult, null);
AnimatedImageCompositor animatedImageCompositor = new AnimatedImageCompositor(
drawableBackend,
new AnimatedImageCompositor.Callback() {
@Override
public void onIntermediateResult(int frameNumber, Bitmap bitmap) {
// Don't care.
}

      @Override
      public CloseableReference<Bitmap> getCachedBitmap(int frameNumber) {
        return null;
      }
    });
animatedImageCompositor.renderFrame(frameForPreview, bitmap.get());
return bitmap;

}

private List<CloseableReference<Bitmap>> decodeAllFrames(
AnimatedImage image,
Bitmap.Config bitmapConfig) {
final List<CloseableReference<Bitmap>> bitmaps = new ArrayList<>();
AnimatedImageResult tempResult = AnimatedImageResult.forAnimatedImage(image);
AnimatedDrawableBackend drawableBackend =
mAnimatedDrawableBackendProvider.get(tempResult, null);
AnimatedImageCompositor animatedImageCompositor = new AnimatedImageCompositor(
drawableBackend,
new AnimatedImageCompositor.Callback() {
@Override
public void onIntermediateResult(int frameNumber, Bitmap bitmap) {
// Don't care.
}

      @Override
      public CloseableReference<Bitmap> getCachedBitmap(int frameNumber) {
        return CloseableReference.cloneOrNull(bitmaps.get(frameNumber));
      }
    });
for (int i = 0; i < drawableBackend.getFrameCount(); i++) {
  CloseableReference<Bitmap> bitmap = createBitmap(
      drawableBackend.getWidth(),
      drawableBackend.getHeight(),
      bitmapConfig);
  animatedImageCompositor.renderFrame(i, bitmap.get());
  bitmaps.add(bitmap);
}
return bitmaps;

}

@SuppressLint("NewApi")
private CloseableReference<Bitmap> createBitmap(
int width,
int height,
Bitmap.Config bitmapConfig) {
CloseableReference<Bitmap> bitmap = mBitmapFactory.createBitmap(width, height, bitmapConfig);
bitmap.get().eraseColor(Color.TRANSPARENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
bitmap.get().setHasAlpha(true);
}
return bitmap;
}
}
</pre>

AnimatedImageFactoryImpl代码相关知识点

知识点final变量

final相关资料
【final关键字的使用方法】:
【修饰变量】:
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
【修饰方法】:
final方法不能被子类方法覆盖,但可以被继承。
【修饰类】:
final类不能被继承,没有子类,final类中所有方法都是final的。
<pre>
public class MyFinal {
private static final int finalValue = 10;
public static void main(String[] args) {
//error final修饰的变量赋值后不可以被改变
finalValue = 11;
}
}
</pre>

知识点反射获取类对象

AnimatedImageFactoryImpl静态块利用反射的方式初始化了两个AnimatedImageDecoder(接口),它们实际指向的是GifImage和WebPImage对象。
<pre>
static AnimatedImageDecoder sGifAnimatedImageDecoder = null;
static AnimatedImageDecoder sWebpAnimatedImageDecoder = null;
private static AnimatedImageDecoder loadIfPresent(final String className) {
try {
//通过反射创建类实例
Class<?> clazz = Class.forName(className);
return (AnimatedImageDecoder) clazz.newInstance();
} catch (Throwable e) {
return null;
}
}
static {
sGifAnimatedImageDecoder = loadIfPresent("com.facebook.animated.gif.GifImage");
sWebpAnimatedImageDecoder = loadIfPresent("com.facebook.animated.webp.WebPImage");
}
</pre>

知识点@SuppressLint

SuppressLint作用是屏蔽android lint错误在Android代码中,我们有时会使用比我们在AndroidManifest中设置的android:minSdkVersion版本更高的方法,此时编译器会提示警告.解决方法是在方法上加上@SuppressLint("NewApi")作用仅仅是屏蔽android lint错误,所以在方法中还要判断版本做不同的操作.
<pre>
@SuppressLint("NewApi")
private CloseableReference<Bitmap> createBitmap(
int width,
int height,
Bitmap.Config bitmapConfig) {
CloseableReference<Bitmap> bitmap = mBitmapFactory.createBitmap(width, height, bitmapConfig);
bitmap.get().eraseColor(Color.TRANSPARENT);
// @SuppressLint("NewApi")检测到大于最小版本的api了
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
bitmap.get().setHasAlpha(true);
}
return bitmap;
}
</pre>

以上包含了AnimatedImageFactoryImpl的相关知识点,那么接着分析AnimatedImageFactoryImpl中相关属性中包含的知识点,这个类中包含4个属性,其中两个是AnimatedImageDecoder,它们用来解码webp和gif的,剩下两个是AnimatedDrawableBackendProvider、PlatformBitmapFactory,具体的解释在后续的知识点分析中再做解释。

AnimatedImageFactoryImpl中decodeGif方法分析

<pre>
public CloseableImage decodeGif(
final EncodedImage encodedImage,//图片编码对象
final ImageDecodeOptions options,//图像解码选项
final Bitmap.Config bitmapConfig) //Bitmap配置信息{

final CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef();

try {
  //省略部分验证代码
  final PooledByteBuffer input = bytesRef.get();
  AnimatedImage gifImage = sGifAnimatedImageDecoder.decode(input.getNativePtr(), input.size());

  return getCloseableImage(options, gifImage, bitmapConfig);
} finally {
  CloseableReference.closeSafely(bytesRef);
}

}
</pre>

首先我们先分析获取NativePooledByteBuffer过程中使用的知识点
<pre>
//这里从EncodeImage对象中获取了一个CloseableReference
//泛型类型实际指向的是NativePooledByteBuffer,它实现了PooledByteBuffer
final CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef();
//获取NativePooledByteBuffer对象
final PooledByteBuffer input = bytesRef.get();
</pre>
在上述代码中返回了一个CloseableReference,CloseableReference实现了Cloneable和Closeable接口,然后调用了CloseableReference的get方法获取NativePooledByteBuffer对象。
<pre>
public final class CloseableReference<T> implements Cloneable, Closeable {
//省略部分代码

public synchronized T get() {
//从SharedReference获取NativePooledByteBuffer对象
return mSharedReference.get();
}
}
</pre>

知识点Cloneable

一.Cloneable 的用途
Cloneable和Serializable一样都是标记型接口,它们内部都没有方法和属性,implements Cloneable表示该对象能被克隆,能使用Object.clone()方法。如果没有implements Cloneable的类调用Object.clone()方法就会抛出CloneNotSupportedException。
二.克隆的分类
(1)浅克隆(shallow clone),浅拷贝是指拷贝对象时仅仅拷贝对象本身和对象中的基本变量,而不拷贝对象包含的引用指向的对象。
(2)深克隆(deep clone),深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。
下面是一个浅克隆的demo
<pre>
public class MyClone implements Cloneable {
String name = "wyd";
public static void main(String[] args) {
MyClone myClone = new MyClone();
try {
//克隆一个当前对象
MyClone copyMyClone = (MyClone) myClone.clone();
//修改当前克隆对象中的参数值
copyMyClone.name = "wydcopy";
System.out.println("copyMyClone.name is " + copyMyClone.name + ", myClone.name is " + myClone.name);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
</pre>
输出结果 copyMyClone.name is wydcopy, myClone.name is wyd,从上面的结果可以看出克隆的对象如果发生变化,那么不会影响到原始对象。

知识点Closeable

Closeable接口也定义了close()方法。实现了Closeable接口的类的对象可以被关闭。从JDK7开始,Closeable扩展了AutoCloseable。因此,在JDK7中,所有实现了Closeable接口的类也都实现了AutoCloseable接口。

AutoCloseable接口对JDK7新添加的带资源的try语句提供了支持,这种try语句可以自动执行资源关闭过程。只有实现了AutoCloseable接口的类的对象才可以由带资源的try语句进行管理。AutoCloseable接口只定义了close()方法:
void close() throws Exception
这个方法关闭调用对象,释放可能占用的所有资源。在带资源的try语句的末尾,会自动调用该方法,因此消除了显式调用close()方法的需要。
<pre>
//JDK1.7以前的版本,释放资源的写法
//这段代码需要自己手动调用close方法去释放
static String readFirstLineFromFile(String path) throws IOException {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(path));
return br.readLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null)
br.close();
}
return null;
}
//JDK1.7中的写法,利用AutoCloseable接口
//这段代码会自动调用close,不再需要手动调用了
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
</pre>
以上代码可以看出JDK1.7后因为所有的可被关闭资源对象都实现了AutoCloseable接口,因此,只要在try的表达式中加入了资源对象,那么就不在需要close当前对象了,close的方法会被系统自动调用。

接下来分析CloseableReference上述中调用SharedReference的知识点
<pre>
public class SharedReference<T> {
@GuardedBy("itself")
private static final Map<Object, Integer> sLiveObjects = new IdentityHashMap<>();

private static void addLiveReference(Object value) {
synchronized (sLiveObjects) {
Integer count = sLiveObjects.get(value);
if (count == null) {
sLiveObjects.put(value, 1);
} else {
sLiveObjects.put(value, count + 1);
}
}
}
private static void removeLiveReference(Object value) {
synchronized (sLiveObjects) {
Integer count = sLiveObjects.get(value);
if (count == null) {
// Uh oh.
FLog.wtf(
"SharedReference",
"No entry in sLiveObjects for value of type %s",
value.getClass());
} else if (count == 1) {
sLiveObjects.remove(value);
} else {
sLiveObjects.put(value, count - 1);
}
}
}

}
</pre>

知识点IdentityHashMap

IdentityHashMap是无序的Map集合,可以添加重复的key的Map,它的key比较的是内存地址,如果两个key的内存地址是相同的,则不能重复;如果两个key的内存地址不一样,如new出来的对象,这个时候两个值完全一样的key也是可以添加进去的。
<pre>
public class MyIdentityHashMap {
public static void main(String[] args) {
Map<Object, Integer> map = new IdentityHashMap<>();
//内存地址相等则后面的会覆盖前面的key
map.put("aaa", 10);
map.put("aaa", 11);
//内存地址相等则后面的会覆盖前面的key
map.put(1, 14);
map.put(1, 15);
//内存地址不想等则不会被覆盖
map.put(new String("aaa"), 12);
map.put(new String("aaa"), 13);
Set set = map.keySet();
for (Object str :
set) {
System.out.println(map.get(str));
}
System.out.println("aaa" == "aaa");
//输出结果
// 11
// 13
// 12
// 15
// true
}
}
</pre>

知识点@GuardedBy("itself")

@GuardedBy("itself")表示当前注释的变量的同步由变量本身去控制
<pre>
@GuardedBy("itself")//标注变量的同步由变量本身去控制
private static final Map<Object, Integer> sLiveObjects = new IdentityHashMap<>();
private static void addLiveReference(Object value) {
// 同步自身
synchronized (sLiveObjects) {}
}
</pre>

接下来分析解码方法中使用的知识点,相关代码如下
<pre>
//sGifAnimatedImageDecoder代表GifImage对象,实际调用的是GifImage中的decode方法
AnimatedImage gifImage = sGifAnimatedImageDecoder.decode(input.getNativePtr(), input.size());
</pre>
GifImage对象中decode方法代码如下
<pre>
@ThreadSafe
@DoNotStrip
public class GifImage implements AnimatedImage, AnimatedImageDecoder {

private volatile static boolean sInitialized;

// Accessed by native methods
@SuppressWarnings("unused")
@DoNotStrip
private long mNativeContext;
//判断是否初始化了gifimage本地库
private static synchronized void ensure() {
if (!sInitialized) {
sInitialized = true;
//下面代码实际调用的是System.loadLibrary(libraryName)
SoLoaderShim.loadLibrary("gifimage");
}
}
//解码方法
@Override
public AnimatedImage decode(long nativePtr, int sizeInBytes) {
return GifImage.create(nativePtr, sizeInBytes);
}
//创建GifImage对象
public static GifImage create(long nativePtr, int sizeInBytes) {
ensure();
Preconditions.checkArgument(nativePtr != 0);
return nativeCreateFromNativeMemory(nativePtr, sizeInBytes);
}
//通过本地内存创建GifImage
private static native GifImage nativeCreateFromNativeMemory(long nativePtr, int sizeInBytes);
</pre>

知识点@SuppressWarnings("unused")

如果使用 SuppressWarnings("unused") 注释了某个变量,那么表示当前这个变量是预留变量,在当前类中没有使用。

知识点System.loadLibrary(libraryName)

加载本地库,只有加载了本地库才可以调用jni中的方法,否则会报错,这里调用的是nativeCreateFromNativeMemory本地方法

知识点volatile关键字

可见性:
  可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉。通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。
  可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就这这个操作同样存在线程安全问题。
原子性:
  原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

volatile原理:
当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
  在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。


  当对非volatile变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以考虑到不同的CPU cache中。
  而声明变量是volatile的,JVM保证了每次读变量都从内存中读,跳过CPU cache这一步。

从以上的一些知识点中其实可以分析出来,Fresco解码gif是在native块做的,webp的流程和gif的流程类似,那么ImagePipelineConfig中AnimatedImageFactory属性的职责就是解码webp、gif图片。

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

推荐阅读更多精彩内容

  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,633评论 0 11
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,014评论 11 349
  • 生活中总有一些人,他们有着自己对生活的向往与要求,他们不会将就,他们坚守着自己引以为傲的原则。而这些人,往往是固执...
    DrJoseph阅读 466评论 0 0
  • 《桃花赋》 文:木子亲斤 春风吻开了漫山遍野的桃花 桃花羞涩了姑娘绯红的脸颊 春雨润醒了小草嫩嫩的黄芽 那一潭春水...
    寂寞去旅行阅读 688评论 2 2