DexClassLoader和PathClassLoader的區(qū)別

先說(shuō)結(jié)論

1仲翎、DexClassLoader可以加載jar/apk/dex,可以從SD卡中加載未安裝的apk
2拉岁、PathClassLoader只能加載系統(tǒng)中已經(jīng)安裝過(guò)的apk

PathClassLoader 源碼

以下源碼全部來(lái)自Android6.0.1

package dalvik.system;

public class PathClassLoader extends BaseDexClassLoader {

    /** 有興趣的可以看看注釋坷剧,故意沒(méi)刪
    * Creates a {@code PathClassLoader} that operates on a given list of files
    * and directories. This method is equivalent to calling
    * {@link #PathClassLoader(String, String, ClassLoader)} with a
    * {@code null} value for the second argument (see description there).
    *
    * @param dexPath the list of jar/apk files containing classes and
    * resources, delimited by {@code File.pathSeparator}, which
    * defaults to {@code ":"} on Android
    * @param parent the parent class loader
    */
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    /**
    * Creates a {@code PathClassLoader} that operates on two given
    * lists of files and directories. The entries of the first list
    * should be one of the following:
JAR/ZIP/APK files, possibly containing a "classes.dex" file as
    * well as arbitrary resources.
    *
Raw ".dex" files (not inside a zip file).
    *
    *
    * The entries of the second list should be directories containing
    * native library files.
    *
    * @param dexPath the list of jar/apk files containing classes and
    * resources, delimited by {@code File.pathSeparator}, which
    * defaults to {@code ":"} on Android
    * @param libraryPath the list of directories containing native
    * libraries, delimited by {@code File.pathSeparator}; may be
    * {@code null}
    * @param parent the parent class loader
    */
    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }
}

DexClassLoader 源碼

package dalvik.system;

import java.io.File;

/**
 * A class loader that loads classes from {@code .jar} and {@code .apk} files
 * containing a {@code classes.dex} entry. This can be used to execute code not
 * installed as part of an application.
 *  * <p>This class loader requires an application-private, writable directory to
 * cache optimized classes. Use {@code Context.getCodeCacheDir()} to create
 * such a directory: <pre>   {@code
 *   File dexOutputDir = context.getCodeCacheDir();
 * }</pre>
 *  * <p><strong>Do not cache optimized classes on external storage.</strong>
 * External storage does not provide access controls necessary to protect your
 * application from code injection attacks.
 */
public class DexClassLoader extends BaseDexClassLoader {
    /**
     * Creates a {@code DexClassLoader} that finds interpreted and native
     * code.  Interpreted classes are found in a set of DEX files contained
     * in Jar or APK files.
     *
     * <p>The path lists are separated using the character specified by the
     * {@code path.separator} system property, which defaults to {@code :}.
     *
     * @param dexPath the list of jar/apk files containing classes and
     *     resources, delimited by {@code File.pathSeparator}, which
     *     defaults to {@code ":"} on Android
     * @param optimizedDirectory directory where optimized dex files
     *     should be written; must not be {@code null}
     * @param libraryPath the list of directories containing native
     *     libraries, delimited by {@code File.pathSeparator}; may be
     *     {@code null}
     * @param parent the parent class loader
     */
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

父類BaseDexClassLoader源碼

/**
 * Base class for common functionality between various dex-based
 * {@link ClassLoader} implementations.
 */
public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;

    /**
     * Constructs an instance.
     *
     * @param dexPath the list of jar/apk files containing classes and
     * resources, delimited by {@code File.pathSeparator}, which
     * defaults to {@code ":"} on Android
     * @param optimizedDirectory directory where optimized dex files
     * should be written; may be {@code null}
     * @param libraryPath the list of directories containing native
     * libraries, delimited by {@code File.pathSeparator}; may be
     * {@code null}
     * @param parent the parent class loader
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }

    @Override
    protected URL findResource(String name) {
        return pathList.findResource(name);
    }

    @Override
    protected Enumeration<URL> findResources(String name) {
        return pathList.findResources(name);
    }

    @Override
    public String findLibrary(String name) {
        return pathList.findLibrary(name);
    }

    /**
     * Returns package information for the given package.
     * Unfortunately, instances of this class don't really have this
     * information, and as a non-secure {@code ClassLoader}, it isn't
     * even required to, according to the spec. Yet, we want to
     * provide it, in order to make all those hopeful callers of
     * {@code myClass.getPackage().getName()} happy. Thus we construct
     * a {@code Package} object the first time it is being requested
     * and fill most of the fields with dummy values. The {@code
     * Package} object is then put into the {@code ClassLoader}'s
     * package cache, so we see the same one next time. We don't
     * create {@code Package} objects for {@code null} arguments or
     * for the default package.
     *
     * <p>There is a limited chance that we end up with multiple
     * {@code Package} objects representing the same package: It can
     * happen when when a package is scattered across different JAR
     * files which were loaded by different {@code ClassLoader}
     * instances. This is rather unlikely, and given that this whole
     * thing is more or less a workaround, probably not worth the
     * effort to address.
     *
     * @param name the name of the class
     * @return the package information for the class, or {@code null}
     * if there is no package information available for it
     */
    @Override
    protected synchronized Package getPackage(String name) {
        if (name != null && !name.isEmpty()) {
            Package pack = super.getPackage(name);

            if (pack == null) {
                pack = definePackage(name, "Unknown", "0.0", "Unknown",
                        "Unknown", "0.0", "Unknown", null);
            }

            return pack;
        }

        return null;
    }

    /**
     * @hide
     */
    public String getLdLibraryPath() {
        StringBuilder result = new StringBuilder();
        for (File directory : pathList.getNativeLibraryDirectories()) {
            if (result.length() > 0) {
                result.append(':');
            }
            result.append(directory);
        }

        return result.toString();
    }

    @Override public String toString() {
        return getClass().getName() + "[" + pathList + "]";
    }
}

原因

DexClassLoader構(gòu)造函數(shù)

    //dexPath :dex路徑
    //optimizedDirectory :制定輸出dex優(yōu)化后的odex文件惰爬,可以為null
    //libraryPath:動(dòng)態(tài)庫(kù)路徑(將被添加到app動(dòng)態(tài)庫(kù)搜索路徑列表中)
    //parent:制定父類加載器喊暖,以保證雙親委派機(jī)制從而實(shí)現(xiàn)每個(gè)類只加載一次。
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }

PathClassLoader構(gòu)造函數(shù)

    //dexPath :dex路徑
    //optimizedDirectory :制定輸出dex優(yōu)化后的odex文件撕瞧,可以為null
    //libraryPath:動(dòng)態(tài)庫(kù)路徑(將被添加到app動(dòng)態(tài)庫(kù)搜索路徑列表中)
    //parent:制定父類加載器陵叽,以保證雙親委派機(jī)制從而實(shí)現(xiàn)每個(gè)類只加載一次。
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }

DexClassLoader 與 PathClassLoader 構(gòu)造函數(shù)區(qū)別就是多了個(gè)optimizedDirectory參數(shù)丛版。

繼續(xù)查看父類構(gòu)造函數(shù):

    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath,optimizedDirectory);
    }

和關(guān)鍵方法findClass

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }

Class c = pathList.findClass(name, suppressedExceptions);中的pathList是在構(gòu)造函數(shù)中new出來(lái)的巩掺,繼續(xù)看DexPathList的findClass方法:

    public Class findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;

            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

DexPathList會(huì)執(zhí)行findClass時(shí),會(huì)先將所有的dex包進(jìn)行遍歷页畦,從每個(gè)包查找相對(duì)應(yīng)name的class胖替,只要找到,馬上返回豫缨。

這里有關(guān)于熱修復(fù)實(shí)現(xiàn)的知識(shí)點(diǎn)独令,就是將補(bǔ)丁 dex 文件放到 dexElements 數(shù)組靠前位置,這樣在加載 class 時(shí)好芭,優(yōu)先找到補(bǔ)丁包中的 dex 文件燃箭,加載到 class 之后就不再尋找,從而原來(lái)的 apk 文件中同名的類就不會(huì)再使用舍败,從而達(dá)到修復(fù)的目的

至于這個(gè)dexElements怎么創(chuàng)造出來(lái)的呢招狸,在DexPathList構(gòu)造方法里面創(chuàng)造的:

public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        //.....
        //省略其他不重要代碼
        
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);

        //.....
        //省略部分代碼
    }

繼續(xù)看 makePathElements 方法:

    private static Element[] makePathElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions) {
        List<Element> elements = new ArrayList<>();
        for (File file : files) {
            File zip = null;
            File dir = new File("");
            DexFile dex = null;
            String path = file.getPath();
            String name = file.getName();
            if (path.contains(zipSeparator)) {
                String split[] = path.split(zipSeparator, 2);
                zip = new File(split[0]);
                dir = new File(split[1]);
            } else if (file.isDirectory()) {
                // We support directories for looking up resources and native libraries.
                // Looking up resources in directories is useful for running libcore tests.
                elements.add(new Element(file, true, null, null));
            } else if (file.isFile()) {
                if (name.endsWith(DEX_SUFFIX)) {
                    // Raw dex file (not inside a zip/jar).
                    try {
                        dex = loadDexFile(file, optimizedDirectory);
                    } catch (IOException ex) {
                        System.logE("Unable to load dex file: " + file, ex);
                    }
                } else {
                    zip = file;

                    try {
                        dex = loadDexFile(file, optimizedDirectory);
                    } catch (IOException suppressed) {
                        suppressedExceptions.add(suppressed);
                    }
                }
            } else {
                System.logW("ClassLoader referenced unknown path: " + file);
            }

            if ((zip != null) || (dex != null)) {
                elements.add(new Element(dir, false, zip, dex));
            }
        }

        return elements.toArray(new Element[elements.size()]);
    }

通過(guò)遍歷files(就是傳入的dexPath)敬拓,執(zhí)行l(wèi)oadDexFile轉(zhuǎn)變?yōu)閐ex,然后添加到elements中裙戏。
到這里乘凸,依然沒(méi)有看出有和無(wú)optimizedDirectory這個(gè)參數(shù)的區(qū)別,那就繼續(xù)看loadDexFile這個(gè)方法:

    private static DexFile loadDexFile(File file, File optimizedDirectory)
            throws IOException {
        if (optimizedDirectory == null) {
            return new DexFile(file);
        } else {
            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            return DexFile.loadDex(file.getPath(), optimizedPath, 0);
        }
    }

尼瑪累榜,終于找到區(qū)別的了...

 1. optimizedDirectory如果為null翰意,loadDexFile返回的是new DexFile(file)
 2. optimizedDirectory 不為null,返回的是DexFile.loadDex(file.getPath(), optimizedPath, 0)

繼續(xù)查DexFile信柿,看這兩個(gè)的區(qū)別,先是new DexFile(File):

    public DexFile(File file) throws IOException {
        this(file.getPath());
    }
    public DexFile(String fileName) throws IOException {
        mCookie = openDexFile(fileName, null, 0);
        mFileName = fileName;
        guard.open("close");
        //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
    }

    private DexFile(String sourceName, String outputName, int flags) throws IOException {
        if (outputName != null) {
            try {
                String parent = new File(outputName).getParent();
                if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
                    throw new IllegalArgumentException("Optimized data directory " + parent
                            + " is not owned by the current user. Shared storage cannot protect"
                            + " your application from code injection attacks.");
                }
            } catch (ErrnoException ignored) {
                // assume we'll fail with a more contextual error later
            }
        }

        mCookie = openDexFile(sourceName, outputName, flags);
        mFileName = sourceName;
        guard.open("close");
        //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
    }

最終指向了DexFile(String sourceName, String outputName, int flags)這個(gè)方法冀偶。

繼續(xù)看上面的DexFile.loadDex(file.getPath(), optimizedPath, 0)方法:

static public DexFile loadDex(String sourcePathName, String outputPathName,
        int flags) throws IOException {
        return new DexFile(sourcePathName, outputPathName, flags);
    }

同樣指向的是new DexFile(sourcePathName, outputPathName, flags);
上面兩者區(qū)別是,outputPathName參數(shù)為不為空渔嚷。

好了进鸠,現(xiàn)在PathClassLoader 和 DexClassLoader的區(qū)別就變成了看這個(gè)outputPathName參數(shù)為不為空的區(qū)別了。
而outputPathName為不為空形病,結(jié)果會(huì)有什么影響呢客年?繼續(xù)看DexFile中構(gòu)造函數(shù)openDexFile的源碼:

    private static Object openDexFile(String sourceName, String outputName, int flags) throws IOException {
        return openDexFileNative(new File(sourceName).getAbsolutePath(), (outputName == null) ? null : new File(outputName).getAbsolutePath(), flags);
    }

openDexFileNative是個(gè)Native方法,在源碼art/runtime/native/dalvik_system_DexFile.cc中找到(不要問(wèn)我怎么找到的漠吻,我也是看別人才找到的):

static jobject DexFile_openDexFileNative(
    JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
  ScopedUtfChars sourceName(env, javaSourceName);
  ScopedUtfChars sourceName(env, javaSourceName);
  if (sourceName.c_str() == nullptr) {
    return 0;
  }
  NullableScopedUtfChars outputName(env, javaOutputName);
  if (env->ExceptionCheck()) {
    return 0;
  }
  ClassLinker* linker = Runtime::Current()->GetClassLinker();
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  std::vector<std::string> error_msgs;
  dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs);
    //省略部分代碼
    //......
  }
}

代碼太多我只列出了追蹤javaOutputName這個(gè)參數(shù)的關(guān)鍵代碼量瓜,javaOutputName被轉(zhuǎn)換成了NullableScopedUtfChars類型的 outputName

NullableScopedUtfChars outputName(env, javaOutputName);

然后這個(gè)outputName被linker的對(duì)象執(zhí)行了OpenDexFilesFromOat方法使用:

ClassLinker* linker = Runtime::Current()->GetClassLinker();
//省略部分代碼
//.....
dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs);

有興趣的可以在art\runtime\class_linker.cc文件中查找OpenDexFilesFromOat方法:

std::vector<std::unique_ptr<const DexFile>> ClassLinker::OpenDexFilesFromOat(
    const char* dex_location, const char* oat_location,
    std::vector<std::string>* error_msgs) {
 //代碼太多,只列出部分....
//注意這里傳入了oat_location途乃,生成了OatFileAssistant 對(duì)象

  OatFileAssistant oat_file_assistant(dex_location, oat_location, kRuntimeISA,
     !Runtime::Current()->IsAotCompiler());
     
//省略部分代碼...

  // If we didn't have an up-to-date oat file open, try to load one from disk.
  //source_oat_file就是從已經(jīng)加載過(guò)的ota文件中查找到的目標(biāo)dex文件
  if (source_oat_file == nullptr) {
    // Update the oat file on disk if we can. This may fail, but that's okay.
    // Best effort is all that matters here.
    //source_oat_file為空的話绍傲,會(huì)通過(guò)oat_file_assistant.MakeUpToDate(&error_msg)方法在其他路徑嘗試加載
    if (!oat_file_assistant.MakeUpToDate(&error_msg)) {
      LOG(WARNING) << error_msg;
    }

    // Get the oat file on disk.
    std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
    if (oat_file.get() != nullptr) {
      // Take the file only if it has no collisions, or we must take it because of preopting.
      bool accept_oat_file = !HasCollisions(oat_file.get(), &error_msg);

//省略部分代碼

  // Fall back to running out of the original dex file if we couldn't load any
  // dex_files from the oat file.
  if (dex_files.empty()) {
    if (oat_file_assistant.HasOriginalDexFiles()) {
      if (Runtime::Current()->IsDexFileFallbackEnabled()) {
        if (!DexFile::Open(dex_location, dex_location, &error_msg, &dex_files)) {
          LOG(WARNING) << error_msg;
          error_msgs->push_back("Failed to open dex files from " + std::string(dex_location));
        }
      } else {
        error_msgs->push_back("Fallback mode disabled, skipping dex files.");
      }
    } else {
      error_msgs->push_back("No original dex files found for dex location "
          + std::string(dex_location));
    }
  }
  return dex_files;
}

主要看核心代碼oat_file_assistant.MakeUpToDate,在oat_file_assistant.cc這個(gè)文件里:

bool OatFileAssistant::MakeUpToDate(std::string* error_msg) {
  switch (GetDexOptNeeded()) {
    case kNoDexOptNeeded: return true;
    case kDex2OatNeeded: return GenerateOatFile(error_msg);
    case kPatchOatNeeded: return RelocateOatFile(OdexFileName(), error_msg);
    case kSelfPatchOatNeeded: return RelocateOatFile(OatFileName(), error_msg);
  }
  UNREACHABLE();
}

網(wǎng)上很多分析都不是基于Android6.0耍共,外加因?yàn)閏不太熟烫饼,這個(gè)方法分析理解如下(說(shuō)的不對(duì)請(qǐng)指出啊):
case kNoDexOptNeeded: return true;是指已經(jīng)在中加載過(guò)的dex文件试读,只要apk安裝過(guò)杠纵,運(yùn)行過(guò),都可以直接可以找到钩骇,不需要再加載了比藻;
case kDex2OatNeeded: return GenerateOatFile(error_msg);內(nèi)存中沒(méi)有的,就進(jìn)行第一次加載dex倘屹,轉(zhuǎn)換成oat文件银亲。這個(gè)時(shí)候,如果之前傳入的outputName為空的話唐瀑,就不能加載了群凶。
下面兩個(gè)case就看不太懂了。不過(guò)文章標(biāo)題的問(wèn)題似乎已經(jīng)找到答案了哄辣。

再次總結(jié)

1请梢、DexClassLoader可以加載jar/apk/dex赠尾,可以從SD卡中加載未安裝的apk 
2、PathClassLoader只能加載系統(tǒng)中已經(jīng)安裝過(guò)的apk
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末毅弧,一起剝皮案震驚了整個(gè)濱河市气嫁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌够坐,老刑警劉巖寸宵,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異元咙,居然都是意外死亡梯影,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)庶香,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)甲棍,“玉大人,你說(shuō)我怎么就攤上這事赶掖「忻停” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵奢赂,是天一觀的道長(zhǎng)陪白。 經(jīng)常有香客問(wèn)我,道長(zhǎng)膳灶,這世上最難降的妖魔是什么咱士? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮袖瞻,結(jié)果婚禮上司致,老公的妹妹穿的比我還像新娘拆吆。我一直安慰自己聋迎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布枣耀。 她就那樣靜靜地躺著霉晕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捞奕。 梳的紋絲不亂的頭發(fā)上牺堰,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天,我揣著相機(jī)與錄音颅围,去河邊找鬼伟葫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛院促,可吹牛的內(nèi)容都是我干的筏养。 我是一名探鬼主播斧抱,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼渐溶!你這毒婦竟也來(lái)了辉浦?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤茎辐,失蹤者是張志新(化名)和其女友劉穎宪郊,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體拖陆,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡弛槐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了依啰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丐黄。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖孔飒,靈堂內(nèi)的尸體忽然破棺而出灌闺,到底是詐尸還是另有隱情,我是刑警寧澤坏瞄,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布桂对,位于F島的核電站,受9級(jí)特大地震影響鸠匀,放射性物質(zhì)發(fā)生泄漏蕉斜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一缀棍、第九天 我趴在偏房一處隱蔽的房頂上張望宅此。 院中可真熱鬧,春花似錦爬范、人聲如沸父腕。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)璧亮。三九已至,卻和暖如春斥难,著一層夾襖步出監(jiān)牢的瞬間枝嘶,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工哑诊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留群扶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像竞阐,于是被迫代替她去往敵國(guó)和親提茁。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

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