自定義 Lint 檢查實(shí)踐指南

本文在官方文檔的基礎(chǔ)上,詳細(xì)講解了自定義 Lint 檢查代碼的步驟,并給出了調(diào)試代碼的方法和發(fā)布流程柏锄,方便團(tuán)隊(duì)進(jìn)行代碼的管理。
本文由 “谷歌開(kāi)發(fā)者” 官方微信公眾號(hào)轉(zhuǎn)載复亏,地址:https://mp.weixin.qq.com/s/B9p4EUIaFhL-JcNAjopOKw

目錄

1. 背景

之前開(kāi)發(fā)過(guò)程中遇到過(guò)一些坑趾娃,并產(chǎn)生了大量的線(xiàn)上崩潰,遇到過(guò)的一些問(wèn)題如下:

  1. 有些顏色值是通過(guò)后端下發(fā)的缔御,但是在使用 Color.parseColor() 方法時(shí)抬闷,如果后端返回的不是標(biāo)準(zhǔn)的顏色格式,就會(huì) crash耕突。
  2. AndroidManifest.xml 文件中對(duì)一個(gè) Activity 同時(shí)設(shè)置方向和透明主題時(shí)笤成,在 Android 8.0 手機(jī)上會(huì) crash。

但是這些類(lèi)似的錯(cuò)誤并不是每位開(kāi)發(fā)者都會(huì)知道眷茁,所以即使一個(gè)人遇到過(guò)炕泳,以后可能還會(huì)有人犯同類(lèi)的錯(cuò)誤。

因此上祈,為了避免后人踩相同的坑培遵,我們可以利用 Lint 檢查工具,對(duì)大家寫(xiě)的代碼進(jìn)行檢查登刺,針對(duì)可能會(huì)產(chǎn)生問(wèn)題的代碼進(jìn)行友好的提示籽腕,并在打包中的 Lint 檢查過(guò)程中禁止編譯通過(guò)。

IDE 自帶的 Lint 檢查的使用可參見(jiàn) https://developer.android.com/studio/write/lint纸俭,但是這是不能滿(mǎn)足我們需求的皇耗,因此需要我們自己實(shí)現(xiàn) Lint 檢查的代碼。

下面來(lái)看一下是如何自定義 Lint 檢查的揍很。

2. 創(chuàng)建 Lint 檢查項(xiàng)目

2.1 新建工程

使用 Android Studio 新建一個(gè)空工程郎楼,在選擇工程模板的界面,選擇第一個(gè) No Activity窒悔,然后其余的和常規(guī)項(xiàng)目沒(méi)有區(qū)別呜袁。

在項(xiàng)目根目錄的 build.gradle 文件添加依賴(lài):

buildscript {
    // ...
    dependencies {
        classpath "com.android.tools.lint:lint:26.3.2"
    }
}

2.2 新建 lint module

新建一個(gè) module,在選擇 module 類(lèi)型的界面蛉迹,選擇 Java or Kotlin Library傅寡,然后給新建的 module 命名放妈,例如 lint北救。

在新建的 module 下的 build.gradle 文件添加依賴(lài):

dependencies {
    // Lint
    compileOnly "com.android.tools.lint:lint-api:26.3.2"
    compileOnly "com.android.tools.lint:lint-checks:26.3.2"
    // Lint Testing
    testImplementation "com.android.tools.lint:lint:26.3.2"
    testImplementation "com.android.tools.lint:lint-tests:26.3.2"
}

2.3 在 app module 添加 lintChecks

為了方便在寫(xiě)完 Lint 檢查的代碼后進(jìn)行測(cè)試荐操,在 app module 下的 build.gradle 文件添加依賴(lài):

dependencies {
    lintChecks project(':lint')
}

3. 注冊(cè)檢查列表

在 lint module 新建一個(gè)類(lèi)繼承自 IssueRegistry,其中 getIssues() 方法先返回一個(gè)空列表珍策,并重寫(xiě)一下 getApi() 方法:

class MyIssueRegistry extends IssueRegistry {
    @NotNull
    @Override
    public List<Issue> getIssues() {
        List<Issue> issues = new ArrayList<>();
        return issues;
    }

    @Override
    public int getApi() {
        return ApiKt.CURRENT_API;
    }
}

然后在 lint module 下的 build.gradle 文件添加如下配置:

jar {
    manifest {
        attributes("Lint-Registry": "com.jimmysun.android.lint.MyIssueRegistry")
    }
}

4. 自定義 Lint 檢查

下面來(lái)看看如何實(shí)現(xiàn)自定義 Lint 檢查的代碼托启。

4.1 Issues vs Detectors

首先來(lái)區(qū)分一下這兩個(gè)概念。Issue 代表你想要發(fā)現(xiàn)并提示給開(kāi)發(fā)者的一種問(wèn)題攘宙,包含描述屯耸、更全面的解釋、類(lèi)型和優(yōu)先級(jí)等等蹭劈。官方提供了一個(gè) Issue 類(lèi)疗绣,你只需要實(shí)例化一個(gè) Issue,并注冊(cè)到 IssueRegistry 里铺韧。

另外你還需要實(shí)現(xiàn)一個(gè) Detector多矮。Detector 負(fù)責(zé)掃描代碼并找到有問(wèn)題的地方,然后把它們報(bào)告出來(lái)哈打。一個(gè) Detector 可以報(bào)告多種類(lèi)型的 Issue塔逃,你可以針對(duì)不同類(lèi)型的問(wèn)題使用不同的嚴(yán)重程度,這樣用戶(hù)可以更精確地控制他們想要看到的內(nèi)容料仗。

下面我們就以檢測(cè) AndroidManifest.xml 和資源文件來(lái)舉例湾盗。創(chuàng)建一個(gè) Detector

public class FixOrientationTransDetector extends Detector {
    private static final Implementation IMPLEMENTATION =
            new Implementation(FixOrientationTransDetector.class, EnumSet.of(Scope.MANIFEST,
                    Scope.ALL_RESOURCE_FILES));

    public static final Issue ISSUE = Issue.create(
            "FixOrientationTransError",
            "不要在 AndroidManifest.xml 文件里同時(shí)設(shè)置方向和透明主題",
            "Activity 同時(shí)設(shè)置方向和透明主題在 Android 8.0 手機(jī)會(huì) Crash",
            Category.CORRECTNESS,
            8,
            Severity.ERROR,
            IMPLEMENTATION);
}

Implementation 我們后面再解釋。先看 Issue.create() 方法立轧,其參數(shù)定義如下:

  1. id:唯一的 id格粪,簡(jiǎn)要表達(dá)當(dāng)前問(wèn)題。
  2. briefDescription:簡(jiǎn)單描述當(dāng)前問(wèn)題氛改。
  3. explanation:詳細(xì)解釋當(dāng)前問(wèn)題和修復(fù)建議匀借。
  4. category:?jiǎn)栴}類(lèi)別,在 Android 中主要有如下六大類(lèi):
    • SECURITY:安全性平窘。例如在 AndroidManifest.xml 中沒(méi)有配置相關(guān)權(quán)限等吓肋。
    • USABILITY:易用性。例如重復(fù)圖標(biāo)瑰艘,一些黃色警告等是鬼。
    • PERFORMANCE:性能。例如內(nèi)存泄漏紫新,xml 結(jié)構(gòu)冗余等均蜜。
    • CORRECTNESS:正確性。例如超版本調(diào)用 API芒率,設(shè)置不正確的屬性值等囤耳。
    • A11Y:無(wú)障礙(Accessibility)。例如單詞拼寫(xiě)錯(cuò)誤等。
    • I18N:國(guó)際化(Internationalization)充择。例如字符串缺少翻譯等德玫。
  5. priority:優(yōu)先級(jí),從 1 到 10椎麦,10 最重要宰僧。
  6. severity:嚴(yán)重程度,包括 FATAL观挎、ERROR琴儿、WARNINGINFORMATIONALIGNORE嘁捷。
  7. implementation:Issue 和哪個(gè) Detector 綁定造成,以及聲明檢查的范圍。

之后將 FixOrientationTransDetector 注冊(cè)到上面的 MyIssueRegistry 里:

public List<Issue> getIssues() {
    List<Issue> issues = new ArrayList<>();
    issues.add(FixOrientationTransDetector.ISSUE);
    return issues;
}

4.2 Scopes

再來(lái)說(shuō)說(shuō)上面創(chuàng)建的 Implementation 對(duì)象雄嚣,它的構(gòu)造方法的第二個(gè)參數(shù)傳入一個(gè) Scope 枚舉類(lèi)的集合谜疤,包括:

  • 資源文件
  • Java 源文件
  • Class 文件
  • Proguard 配置文件
  • Manifest 文件
  • 等等

Issue 需要指定分析代碼所需的范圍,例如上面代碼我們要檢查的是 Manifest 文件和資源文件现诀。

4.3 Scanner

自定義 Detector 還需要實(shí)現(xiàn)一個(gè)或多個(gè)以下接口:

  • UastScanner:掃描 Java 文件和 Kotlin 文件
  • ClassScanner:掃描 Class 文件
  • XmlScanner:掃描 XML 文件
  • ResourceFolderScanner:掃描資源文件夾
  • BinaryResourceScanner:掃描二進(jìn)制資源文件
  • OtherFileScanner:掃描其他文件
  • GradleScanner:掃描 Gradle 腳本

因?yàn)槲覀冃枰獟呙璧?AndroidManifest.xmlstyles.xml 都是 XML 文件夷磕,那么需要實(shí)現(xiàn) XMLScanner 接口:

public class FixOrientationTransDetector extends Detector implements XmlScanner

4.4 掃描 XML 文件

要分析一個(gè) XML 文件,你可以重寫(xiě) visitDocument() 方法仔沿。這個(gè)方法每個(gè) XML 文件都會(huì)調(diào)用一次坐桩,然后傳入 XML DOM 模型,之后你就可以自己遍歷并做分析封锉。

但是呢绵跷,我們通常只關(guān)注一些特定的標(biāo)簽和一些特定的屬性,為了讓掃描更快成福,Detector 可以指定我們關(guān)注的元素和屬性碾局。

要篩選我們關(guān)注的元素或?qū)傩裕恍鑼?shí)現(xiàn) getApplicableElements()getApplicableAttributes() 方法奴艾,并返回一個(gè)標(biāo)簽或?qū)傩悦Q(chēng)的字符串列表净当。然后再實(shí)現(xiàn) visitElement()visitAttribute() 方法,這兩個(gè)方法針對(duì)每個(gè)指定的元素和屬性都會(huì)調(diào)用一次蕴潦。

接上例像啼,我們需要分析的是 activitystyle 標(biāo)簽,那么需要實(shí)現(xiàn) getApplicableElements() 方法:

@Override
public Collection<String> getApplicableElements() {
    return Arrays.asList(SdkConstants.TAG_ACTIVITY, SdkConstants.TAG_STYLE);
}

你也可以從 getApplicableElements()getApplicableAttributes() 方法返回一個(gè) ALL 常量潭苞,這樣對(duì)于所有的元素或?qū)傩远紩?huì)調(diào)用一次忽冻。

另外 SdkConstants.java 類(lèi)內(nèi)置了很多常量可以直接使用,包括 TAG_MANIFEST此疹、TAG_RESOURCES 等等僧诚,如果沒(méi)有也可以自己手寫(xiě)遮婶。

之后我們要實(shí)現(xiàn) visitElement() 方法來(lái)進(jìn)行分析。我們需要判斷 activity 標(biāo)簽中配置的 android:screenOrientation 的某些屬性與透明主題是否同時(shí)設(shè)置的湖笨,如果出現(xiàn)這種情況則報(bào)告出來(lái)旗扑,代碼如下:

private final Map<ElementEntity, String> mThemeMap = new HashMap<>();

@Override
public void visitElement(@NotNull XmlContext context, @NotNull Element element) {
    switch (element.getTagName()) {
        case SdkConstants.TAG_ACTIVITY:
            if (isFixedOrientation(element)) {
                String theme = element.getAttributeNS(SdkConstants.ANDROID_URI,
                        SdkConstants.ATTR_THEME);
                if ("@style/Theme.AppTheme.Transparent".equals(theme)) {
                    reportError(context, element);
                } else {
                    // 將主題設(shè)置暫存起來(lái)
                    mThemeMap.put(new ElementEntity(context, element),
                            theme.substring(theme.indexOf('/') + 1));
                }
            }
            break;
        case SdkConstants.TAG_STYLE:
            String styleName = element.getAttribute(SdkConstants.ATTR_NAME);
            mThemeMap.forEach((elementEntity, theme) -> {
                if (theme.equals(styleName)) {
                    if (isTranslucentOrFloating(element)) {
                        reportError(elementEntity.getContext(), elementEntity.getElement());
                    } else if (element.hasAttribute(SdkConstants.ATTR_PARENT)) {
                        // 替換成父主題
                        mThemeMap.put(elementEntity,
                                element.getAttribute(SdkConstants.ATTR_PARENT));
                    }
                }
            });
            break;
        default:
    }
}

private boolean isFixedOrientation(Element element) {
    switch (element.getAttributeNS(SdkConstants.ANDROID_URI, "screenOrientation")) {
        case "landscape":
        case "sensorLandscape":
        case "reverseLandscape":
        case "userLandscape":
        case "portrait":
        case "sensorPortrait":
        case "reversePortrait":
        case "userPortrait":
        case "locked":
            return true;
        default:
            return false;
    }
}

private boolean isTranslucentOrFloating(Element element) {
    for (Node child = element.getFirstChild(); child != null; child = child.getNextSibling()) {
        if (child instanceof Element
                && SdkConstants.TAG_ITEM.equals(((Element) child).getTagName())
                && child.getFirstChild() != null
                && SdkConstants.VALUE_TRUE.equals(child.getFirstChild().getNodeValue())) {
            switch (((Element) child).getAttribute(SdkConstants.ATTR_NAME)) {
                case "android:windowIsTranslucent":
                case "android:windowSwipeToDismiss":
                case "android:windowIsFloating":
                    return true;
                default:
            }
        }
    }
    return "Theme.AppTheme.Transparent".equals(element.getAttribute(SdkConstants.ATTR_PARENT));
}

private void reportError(XmlContext context, Element element) {
    context.report(
            ISSUE,
            element,
            context.getLocation(element),
            "請(qǐng)不要在 AndroidManifest.xml 文件里同時(shí)設(shè)置方向和透明主題"
    );
}

private static class ElementEntity {
    private final XmlContext mContext;
    private final Element mElement;

    public ElementEntity(XmlContext context, Element element) {
        mContext = context;
        mElement = element;
    }

    public XmlContext getContext() {
        return mContext;
    }

    public Element getElement() {
        return mElement;
    }
}

這里先提一下 Lint 處理文件的順序:

  1. Manifest 文件
  2. 資源文件,按字母順序處理(先是按資源文件夾的字母順序赶么,然后在每個(gè)文件夾里的字母順序)
  3. Java 源文件
  4. Java class 文件肩豁,按字母順序處理(如果有內(nèi)部類(lèi)脊串,外部類(lèi)在內(nèi)部類(lèi)之前)
  5. Proguard 文件

那么上面代碼大體邏輯是這樣的:因?yàn)?Lint 分析會(huì)先檢查 AndroidManifest 文件辫呻,后檢查資源文件,那么在檢查 AndroidManifest 文件時(shí)如果遇到 Activity 同時(shí)設(shè)置了方向和主題琼锋,將相應(yīng)節(jié)點(diǎn)和主題名先暫存下來(lái)放闺。在檢查資源文件時(shí),判斷暫存的主題里是否存在透明設(shè)置缕坎,如果存在則上報(bào)出來(lái)怖侦,否則將暫存的主題名改成父主題(如果有的話(huà))。這里會(huì)有個(gè)缺陷谜叹,就是如果主題的繼承關(guān)系比較復(fù)雜匾寝,可能會(huì)有漏報(bào)的情況。

另外荷腊,上面代碼中有個(gè)上報(bào)錯(cuò)誤的方法 reportError()艳悔,這個(gè)后面再詳細(xì)說(shuō)明。

4.5 分析 Java/Kotlin 源文件

此外我們?cè)賮?lái)講講如何分析 Java 和 Kotlin 文件女仰,我們以分析 Color.parseColor() 方法為例進(jìn)行說(shuō)明猜年。舊版本的 Detector 需要實(shí)現(xiàn) JavaScanner 接口,新的已經(jīng)被 UastScanner 替代疾忍。示例代碼:

public class ParseColorDetector extends Detector implements Detector.UastScanner {
    private static final Implementation IMPLEMENTATION =
            new Implementation(ParseColorDetector.class, Scope.JAVA_FILE_SCOPE);
    public static final Issue ISSUE = Issue.create(
            "ParseColorError",
            "Color.parseColor 解析可能 crash",
            "后端下發(fā)的色值可能無(wú)法解析乔外,導(dǎo)致 crash",
            Category.CORRECTNESS,
            8,
            Severity.ERROR, IMPLEMENTATION)
            .setAndroidSpecific(true);
 
    @Override
    public List<String> getApplicableMethodNames() {
        return Collections.singletonList("parseColor");
    }
 
    @Override
    public void visitMethodCall(@NotNull JavaContext context, @NotNull UCallExpression node,
                                @NotNull PsiMethod method) {
        // 不是 android.graphics.Color 類(lèi)的方法,直接返回
        if (!context.getEvaluator().isMemberInClass(method, "android.graphics.Color")) {
            return;
        }
        // 參數(shù)寫(xiě)死的比如 "#FFFFFF" 這種一罩,簡(jiǎn)單判斷如果是 # 號(hào)開(kāi)頭杨幼,直接返回
        if (isConstColor(node)) {
            return;
        }
        // 已經(jīng)做了 try catch 處理,直接返回
        if (isWrappedByTryCatch(node, context)) {
            return;
        }
        reportError(context, node);
    }
 
    private boolean isConstColor(UCallExpression node) {
        return node.getValueArguments().get(0).evaluate().toString().startsWith("#");
    }
 
    private boolean isWrappedByTryCatch(UCallExpression node, JavaContext context) {
        if (context.getUastFile() instanceof KotlinUFile) {
            return UastUtils.getParentOfType(node.getUastParent(), UTryExpression.class) != null;
        }
        for (PsiElement parent = node.getSourcePsi().getParent(); parent != null && !(parent instanceof MethodElement); parent = parent.getParent()) {
            if (parent instanceof PsiTryStatement) {
                return true;
            }
        }
        return false;
    }
 
    private void reportError(JavaContext context, UCallExpression node) {
        context.report(ISSUE, node, context.getCallLocation(node, false, false)
                , "Color.parseColor 解析后端下發(fā)的值可能導(dǎo)致 crash聂渊,請(qǐng) try catch");
    }
}

同分析 XML 文件一樣推汽,你需要實(shí)現(xiàn) getApplicableXXX()visitXXX() 方法,例如我們需要分析 parseColor() 方法歧沪,那么就要重寫(xiě) getApplicableMethodNames()visitMethodCall() 方法歹撒。

4.6 報(bào)告錯(cuò)誤

如果你的 Detector 定位到一個(gè)問(wèn)題,需要使用 Context 對(duì)象(Detector 的每個(gè)方法都會(huì)傳入進(jìn)來(lái))調(diào)用 report() 方法來(lái)報(bào)告錯(cuò)誤诊胞,例如 4.4 節(jié)中的代碼如下:

private void reportError(XmlContext context, Element element) {
    context.report(
            ISSUE,
            element,
            context.getLocation(element),
            "請(qǐng)不要在 AndroidManifest.xml 文件里同時(shí)設(shè)置方向和透明主題"
    );
}

除了列出要報(bào)告的問(wèn)題外暖夭,還需要提供位置锹杈、作用域節(jié)點(diǎn)和錯(cuò)誤提示消息:

  • 作用域節(jié)點(diǎn):對(duì)于 XML 和 Java 源文件,是指發(fā)生的錯(cuò)誤周?chē)罱?XML DOM 或 Parse AST 樹(shù)節(jié)點(diǎn)迈着,例如上面?zhèn)魅氲?element 對(duì)象竭望。
  • 位置:是指錯(cuò)誤發(fā)生的位置。一般只需將 AST/XML 節(jié)點(diǎn)傳遞給 context.getLocation() 方法裕菠,該方法將創(chuàng)建一個(gè)具有正確文件名和與給定節(jié)點(diǎn)相對(duì)應(yīng)的偏移量的 Location咬清。如果你的錯(cuò)誤與某個(gè)屬性有關(guān),則傳遞該屬性奴潘,以使該問(wèn)題更好地指出錯(cuò)誤發(fā)生的位置旧烧。

好了,這樣一個(gè)完整的自定義 Lint 檢查的代碼就算完成了画髓。

更多關(guān)于狀態(tài)保存掘剪、多階段操作、分析 class 文件和增量 Lint 等高級(jí)用法可以參見(jiàn):http://tools.android.com/tips/lint/writing-a-lint-check

5. 執(zhí)行 Lint 檢查

在編寫(xiě)完 Lint 檢查的代碼之后就可以使用 ./gradlew :app:lintDebug 命令執(zhí)行 Lint 檢查了奈虾,我在 app module 下故意寫(xiě)了兩個(gè)出問(wèn)題的代碼夺谁,對(duì)應(yīng)輸出結(jié)果如下:

lintDebug 輸出

上面兩個(gè)鏈接是分析報(bào)告,下面是錯(cuò)誤的提示肉微。

5.1 分析報(bào)告

一般 HTML 版的報(bào)告更清晰一些匾鸥,我們復(fù)制鏈接到瀏覽器里查看一下,可以看到與我們代碼對(duì)應(yīng)的關(guān)系:

HTML 報(bào)告

點(diǎn)擊 FixOrientationTransError 可以看到 report() 方法輸出的信息和定義的問(wèn)題類(lèi)別碉纳、嚴(yán)重程度和優(yōu)先級(jí)等勿负,如下:

FixOrientationTransError 詳情

截圖中間那部分是我后加的,讀者不用在意村象。

5.2 錯(cuò)誤提示

剛才終端輸出的錯(cuò)誤提示也是 report() 方法輸出的信息笆环,因?yàn)槲覀儌鬟f了 Location,所以輸出了問(wèn)題出現(xiàn)在哪個(gè)文件的哪一行并可以直接點(diǎn)擊跳轉(zhuǎn)源碼對(duì)應(yīng)的位置厚者。

6. 調(diào)試代碼

有的時(shí)候我們寫(xiě)完代碼可能并不會(huì)完美地按照我們的想法去分析躁劣,那么我們還可以通過(guò)調(diào)試代碼來(lái)查找問(wèn)題,方法如下库菲。(該方法也適用于自定義 gradle plugin 的調(diào)試账忘。)

6.1 新建 Remote 配置

找到「Edit Configurations...」,如圖:

Edit Configurations...

然后點(diǎn)擊左上角的加號(hào)選擇 Remote熙宇,如圖:

新建 Remote

然后在右側(cè)輸入一個(gè)名字鳖擒,例如 LintCheckDebug,其它的使用默認(rèn)值就好烫止,最后點(diǎn)擊 OK蒋荚,如圖:

Remote 配置

6.2 開(kāi)啟調(diào)試

在命令行啟動(dòng)遠(yuǎn)程調(diào)試器來(lái)調(diào)試對(duì)應(yīng)的任務(wù),例如我們要調(diào)試的任務(wù)是 lintDebug馆蠕,那么就輸入如下命令:

./gradlew --no-daemon -Dorg.gradle.debug=true :app:lintDebug

最后期升,我們?cè)诖a中打好相應(yīng)的斷點(diǎn)惊奇,選中我們上一步創(chuàng)建的 Remote 配置,點(diǎn)擊 Debug 按鈕即可開(kāi)始調(diào)試我們的自定義 Lint 檢查的代碼了播赁。

7. 發(fā)布

我們可以發(fā)布 aar 到遠(yuǎn)程倉(cāng)庫(kù)颂郎,步驟可以參見(jiàn) https://juejin.cn/post/6844904135314128903#heading-28

但是我這里走的公司內(nèi)部發(fā)布流程,上面方法并沒(méi)有驗(yàn)證過(guò)容为。

最后各個(gè)組件可以在 build.gradle 文件添加 lint 檢查:

dependencies {
    lintChecks "com.xxx.lint:lint-checks:x.x.x"
}

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末坎背,一起剝皮案震驚了整個(gè)濱河市替劈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沼瘫,老刑警劉巖抬纸,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咙俩,死亡現(xiàn)場(chǎng)離奇詭異耿戚,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)阿趁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)膜蛔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人脖阵,你說(shuō)我怎么就攤上這事皂股。” “怎么了命黔?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵呜呐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我悍募,道長(zhǎng)蘑辑,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任坠宴,我火速辦了婚禮洋魂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喜鼓。我一直安慰自己副砍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布庄岖。 她就那樣靜靜地躺著豁翎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪隅忿。 梳的紋絲不亂的頭發(fā)上心剥,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天启搂,我揣著相機(jī)與錄音,去河邊找鬼刘陶。 笑死胳赌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的匙隔。 我是一名探鬼主播疑苫,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼纷责!你這毒婦竟也來(lái)了捍掺?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤再膳,失蹤者是張志新(化名)和其女友劉穎挺勿,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體喂柒,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡不瓶,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灾杰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚊丐。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖艳吠,靈堂內(nèi)的尸體忽然破棺而出麦备,到底是詐尸還是另有隱情,我是刑警寧澤昭娩,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布凛篙,位于F島的核電站,受9級(jí)特大地震影響栏渺,放射性物質(zhì)發(fā)生泄漏呛梆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一迈嘹、第九天 我趴在偏房一處隱蔽的房頂上張望削彬。 院中可真熱鬧,春花似錦秀仲、人聲如沸融痛。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)雁刷。三九已至,卻和暖如春保礼,著一層夾襖步出監(jiān)牢的瞬間沛励,已是汗流浹背责语。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留目派,地道東北人坤候。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像企蹭,于是被迫代替她去往敵國(guó)和親白筹。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359