自己編寫(xiě)Android Studio插件

前言

為何會(huì)自己寫(xiě)插件呢,原因有兩個(gè)糠排,一個(gè)是之前看到鴻神寫(xiě)了一篇學(xué)會(huì)編寫(xiě)Android Studio插件 別停留在用的程度了的博客伶选,另一個(gè)是有些插件是不能滿(mǎn)足自己的需求的词渤,所有就需要自己來(lái)寫(xiě)螺垢;之前因?yàn)橼s項(xiàng)目沒(méi)時(shí)間喧务,今天抽空就學(xué)習(xí)了下。
這是第二篇博客
Android Studio插件GenerateFindViewById

這篇博客是根據(jù)輸入或者選中布局文件(如R.layout.activity_main枉圃,只需要選中activity_main或者輸入activity_main)功茴,來(lái)自動(dòng)生成字段,和獲取值(findViewById())孽亲。

適用Activity和Fragment

演示

編寫(xiě)插件

環(huán)境

Android Studio本身是不支持開(kāi)發(fā)插件的坎穿,所以需要下載IntelliJ IDEA來(lái)編寫(xiě),但是Android Studio是基于IntelliJ IDEA的墨林,用IntelliJ IDEA不會(huì)感到陌生赁酝,官網(wǎng)下載https://www.jetbrains.com/idea/

創(chuàng)建項(xiàng)目

創(chuàng)建項(xiàng)目

目錄結(jié)構(gòu)

目錄結(jié)構(gòu)
plugin.xml

plugin.xml是類(lèi)似Android項(xiàng)目里面的AndroidMenifest文件,用來(lái)配置信息的注冊(cè)和聲明旭等。

plugin.xml

  • id:(com.example.plugin.Name)插件的ID,保證插件上傳倉(cāng)庫(kù)后的唯一性衡载。
  • name:插件名稱(chēng)搔耕。
  • version:版本號(hào)。
  • description:插件的簡(jiǎn)介。
  • change-notes:版本更新信息弃榨。
  • extensions:擴(kuò)展組件注冊(cè) 菩收。

開(kāi)始編寫(xiě)

創(chuàng)建一個(gè)Action,是繼承AnAction類(lèi)的

右鍵src目錄->New->Action

New->Action

填寫(xiě)內(nèi)容

填寫(xiě)內(nèi)容

填寫(xiě)ActionID鲸睛,ClassName娜饵,Name,Description官辈;選擇放在哪個(gè)菜單箱舞,Anchor選擇First或者Last;設(shè)置快捷鍵KeyBoard Shortcuts拳亿;

ActionID:代表該Action的唯一的ID晴股,一般的格式為:pluginName.ID
ClassName:類(lèi)名
Name:就是最終插件在菜單上的名稱(chēng)
Description:對(duì)這個(gè)Action的描述信息
Groups:定義這個(gè)菜單選項(xiàng)出現(xiàn)的位置,這里選中CodeMenu(Code)肺魁,在Code菜單里面电湘。

可以在plugin.xml里面修改對(duì)應(yīng)的Action屬性

Action

編寫(xiě)Action

action

點(diǎn)擊ok之后會(huì)生成相應(yīng)的Action,在Action里面的actionPerformed方法會(huì)在點(diǎn)擊菜單或者快捷鍵的是否觸發(fā)鹅经。

思路

在獲取布局文件內(nèi)容后自動(dòng)解析布局文件并生成字段和findViewById代碼寂呛。

1.獲取布局文件
2.解析布局文件,獲取屬性
3.將代碼寫(xiě)入action

獲取布局文件

查找文件需要用到PsiFile類(lèi)瘾晃,通過(guò)FilenameIndex.getFilesByName(project, name, scope)來(lái)查找布局文件贷痪。
先獲取用戶(hù)選中內(nèi)容,如果沒(méi)選中酗捌,則彈出dialog讓用戶(hù)輸入內(nèi)容呢诬;


        // 獲取project
        Project project = e.getProject();
        // 獲取選中內(nèi)容
        final Editor mEditor = e.getData(PlatformDataKeys.EDITOR);
        if (null == mEditor) {
            return;
        }
        SelectionModel model = mEditor.getSelectionModel();
        String selectedText = model.getSelectedText();
        if (TextUtils.isEmpty(selectedText)) {
            // 未選中布局內(nèi)容,顯示dialog
            selectedText = Messages.showInputDialog(project, "layout(不需要輸入R.layout.):" , "未選中布局內(nèi)容胖缤,請(qǐng)輸入layout文件名", Messages.getInformationIcon());
            if (TextUtils.isEmpty(selectedText)) {
                Utils.showPopupBalloon(mEditor, "未輸入layout文件名");
                return;
            }
        }

然后根據(jù)輸入的內(nèi)容查找xml文件尚镰;

        // 獲取布局文件,通過(guò)FilenameIndex.getFilesByName獲取
        // GlobalSearchScope.allScope(project)搜索整個(gè)項(xiàng)目
        PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, selectedText + ".xml", GlobalSearchScope.allScope(project));
        if (psiFiles.length <= 0) {
            Utils.showPopupBalloon(mEditor, "未找到選中的布局文件");
            return;
        }
        XmlFile xmlFile = (XmlFile) psiFiles[0];

解析布局文件哪廓,獲取屬性

通過(guò)psiFile.accept(new PsiRecursiveElementWalkingVisitor()…);去遍歷一個(gè)文件的所有元素

    /**
     * 獲取所有id
     *
     * @param file
     * @param elements
     * @return
     */
    public static java.util.List<Element> getIDsFromLayout(final PsiFile file, final java.util.List<Element> elements) {
        // To iterate over the elements in a file
        // 遍歷一個(gè)文件的所有元素
        file.accept(new XmlRecursiveElementVisitor() {

            @Override
            public void visitElement(PsiElement element) {
                super.visitElement(element);
                // 解析Xml標(biāo)簽
                if (element instanceof XmlTag) {
                    XmlTag tag = (XmlTag) element;
                    // 獲取Tag的名字(TextView)或者自定義
                    String name = tag.getName();
                    // 如果有include
                    if (name.equalsIgnoreCase("include")) {
                        // 獲取布局
                        XmlAttribute layout = tag.getAttribute("layout", null);
                        // 獲取project
                        Project project = file.getProject();
                        // 布局文件
                        XmlFile include = null;
                        PsiFile[] psiFiles = FilenameIndex.getFilesByName(project, getLayoutName(layout.getValue()) + ".xml", GlobalSearchScope.allScope(project));
                        if (psiFiles.length > 0) {
                            include = (XmlFile) psiFiles[0];
                        }
                        if (include != null) {
                            // 遞歸
                            getIDsFromLayout(include, elements);
                            return;
                        }
                    }
                    // 獲取id字段屬性
                    XmlAttribute id = tag.getAttribute("android:id", null);
                    if (id == null) {
                        return;
                    }
                    // 獲取id的值
                    String idValue = id.getValue();
                    if (idValue == null) {
                        return;
                    }
                    XmlAttribute aClass = tag.getAttribute("class", null);
                    if (aClass != null) {
                        name = aClass.getValue();
                    }
                    // 添加到list
                    try {
                        Element e = new Element(name, idValue, tag);
                        elements.add(e);
                    } catch (IllegalArgumentException e) {

                    }
                }
            }
        });


        return elements;
    }

    /**
     * layout.getValue()返回的值為@layout/layout_view
     *
     * @param layout
     * @return
     */
    public static String getLayoutName(String layout) {
        if (layout == null || !layout.startsWith("@") || !layout.contains("/")) {
            return null;
        }

        // @layout layout_view
        String[] parts = layout.split("/");
        if (parts.length != 2) {
            return null;
        }
        // layout_view
        return parts[1];
    }

對(duì)應(yīng)的實(shí)體類(lèi)Element狗唉,里面包含獲取id的值,獲取類(lèi)型如(TextView或者com.example.CustomView)涡真,根據(jù)id設(shè)置變量名分俯。

    // 判斷id正則
    private static final Pattern sIdPattern = Pattern.compile("@\\+?(android:)?id/([^$]+)$", Pattern.CASE_INSENSITIVE);
    // id
    public String id;
    // 名字如TextView
    public String name;
    // 命名1 aa_bb_cc; 2 aaBbCc 3 mAaBbCc
    public int fieldNameType = 3;
    public XmlTag xml;

    /**
     * 構(gòu)造函數(shù)
     *
     * @param name View的名字
     * @param id   android:id屬性
     * @throws IllegalArgumentException When the arguments are invalid
     */
    public Element(String name, String id, XmlTag xml) {
        // id
        final Matcher matcher = sIdPattern.matcher(id);
        if (matcher.find() && matcher.groupCount() > 1) {
            this.id = matcher.group(2);
        }

        if (this.id == null) {
            throw new IllegalArgumentException("Invalid format of view id");
        }

        String[] packages = name.split("\\.");
        if (packages.length > 1) {
            // com.example.CustomView
            this.name = packages[packages.length - 1];
        } else {
            this.name = name;
        }

        this.xml = xml;
    }

    /**
     * 獲取id,R.id.id
     *
     * @return
     */
    public String getFullID() {
        StringBuilder fullID = new StringBuilder();
        String rPrefix = "R.id.";
        fullID.append(rPrefix);
        fullID.append(id);
        return fullID.toString();
    }

    /**
     * 獲取變量名
     *
     * @return
     */
    public String getFieldName() {
        String fieldName = id;
        String[] names = id.split("_");
        if (fieldNameType == 2) {
            // aaBbCc
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < names.length; i++) {
                if (i == 0) {
                    sb.append(names[i]);
                } else {
                    sb.append(firstToUpperCase(names[i]));
                }
            }
            fieldName = sb.toString();
        } else if (fieldNameType == 3) {
            // mAaBbCc
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < names.length; i++) {
                if (i == 0) {
                    sb.append("m");
                }
                sb.append(firstToUpperCase(names[i]));
            }
            fieldName = sb.toString();
        }
        return fieldName;
    }

    // 第一個(gè)字母大寫(xiě)
    public static String firstToUpperCase(String key) {
        return key.substring(0, 1).toUpperCase(Locale.CHINA) + key.substring(1);
    }

將代碼寫(xiě)入action

Intellij Platform不允許在主線程中進(jìn)行實(shí)時(shí)的文件寫(xiě)入哆料,需通過(guò)異步任務(wù)來(lái)進(jìn)行缸剪,可以通過(guò)繼承WriteCommandAction.Simple,然后在run方法里面進(jìn)行寫(xiě)文件操作东亦。

    @Override
    protected void run() throws Throwable {
        
    }
主要用到的方法
    /**
     * 根據(jù)當(dāng)前文件獲取對(duì)應(yīng)的class文件
     * @param editor
     * @param file
     * @return
     */
    protected PsiClass getTargetClass(Editor editor, PsiFile file) {
        int offset = editor.getCaretModel().getOffset();
        PsiElement element = file.findElementAt(offset);
        if(element == null) {
            return null;
        } else {
            PsiClass target = PsiTreeUtil.getParentOfType(element, PsiClass.class);
            return target instanceof SyntheticElement ?null:target;
        }
    }
  • mClass.findMethodsByName("onCreate", false)判斷類(lèi)是否包含某方法
  • JavaPsiFacade.getInstance(mProject).findClass("android.app.Activity", new EverythingGlobalScope(mProject));根據(jù)類(lèi)名查找類(lèi)
  • PsiUtilBase.getPsiFileInEditor(mEditor, project);方法為獲取當(dāng)前文件杏节;
  • psiclass.add(JavaPsiFacade.getElementFactory(mProject).createMethodFromText(sbInitView.toString(), psiclass))方法為類(lèi)創(chuàng)建方法;
  • mFactory.mFactory.createMethodFromText(method.toString(), mClass)方法添加字段;
  • onCreate.getBody().addAfter(mFactory.createStatementFromText("initView();", mClass), setContentViewStatement);方法為方法體添加內(nèi)容奋渔。
具體創(chuàng)建內(nèi)容
import com.intellij.codeInsight.actions.ReformatCodeProcessor;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.command.WriteCommandAction.Simple;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.search.EverythingGlobalScope;
import entity.Element;
import org.apache.http.util.TextUtils;

import java.util.List;

public class IdCreator extends Simple {

    private PsiFile mFile;
    private Project mProject;
    private PsiClass mClass;
    private List<Element> mElements;
    private PsiElementFactory mFactory;
    private String mSelectText;

    public IdCreator(PsiFile psiFile, PsiClass psiClass, String command, List<Element> elements, String selectText) {
        super(psiClass.getProject(), command);

        mFile = psiFile;
        mProject = psiClass.getProject();
        mClass = psiClass;
        mElements = elements;
        // 獲取Factory
        mFactory = JavaPsiFacade.getElementFactory(mProject);
        mSelectText = selectText;
    }

    @Override
    protected void run() throws Throwable {
        generateFields();
        generateFindViewById();
        // 重寫(xiě)class
        JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(mProject);
        styleManager.optimizeImports(mFile);
        styleManager.shortenClassReferences(mClass);
        new ReformatCodeProcessor(mProject, mClass.getContainingFile(), null, false).runWithoutProgress();
    }

    /**
     * 創(chuàng)建變量
     */
    private void generateFields() {
        for (Element element : mElements) {

            // remove duplicate field
            PsiField[] fields = mClass.getFields();
            boolean duplicateField = false;
            for (PsiField field : fields) {
                String name = field.getName();
                if (name != null && name.equals(element.getFieldName())) {
                    duplicateField = true;
                    break;
                }
            }

            if (duplicateField) {
                continue;
            }
            // 設(shè)置變量
            String text = element.xml.getAttributeValue("android:text");
            // text
            String fromText = "private " + element.name + " " + element.getFieldName() + ";";
            if (!TextUtils.isEmpty(text)) {
                fromText = "/** " + text + " */\n" + fromText;
            }
            // 添加到class
            mClass.add(mFactory.createFieldFromText(fromText, mClass));
        }
    }

    /**
     * 設(shè)置變量的值FindViewById镊逝,Activity和Fragment
     */
    private void generateFindViewById() {
        // 根據(jù)類(lèi)名查找類(lèi)
        PsiClass activityClass = JavaPsiFacade.getInstance(mProject).findClass("android.app.Activity", new EverythingGlobalScope(mProject));
        PsiClass activityCompatClass = JavaPsiFacade.getInstance(mProject).findClass("android.support.v7.app.AppCompatActivity", new EverythingGlobalScope(mProject));
        PsiClass fragmentClass = JavaPsiFacade.getInstance(mProject).findClass("android.app.Fragment", new EverythingGlobalScope(mProject));
        PsiClass fragmentV4Class = JavaPsiFacade.getInstance(mProject).findClass("android.support.v4.app.Fragment", new EverythingGlobalScope(mProject));
        // 判斷mClass是不是繼承activityClass或者activityCompatClass
        if ((activityClass != null && mClass.isInheritor(activityClass, true))
                || (activityCompatClass != null && mClass.isInheritor(activityCompatClass, true))
                || mClass.getName().contains("Activity")) {
            // 判斷是否有onCreate方法
            if (mClass.findMethodsByName("onCreate", false).length == 0) {
                StringBuilder method = new StringBuilder();
                method.append("@Override protected void onCreate(android.os.Bundle savedInstanceState) {\n");
                method.append("super.onCreate(savedInstanceState);\n");
                method.append("\t// TODO:run FindViewById again To setValue in initView method\n");
                method.append("\tsetContentView(R.layout.");
                method.append(mSelectText);
                method.append(");\n");
                method.append("}");
                // 添加
                mClass.add(mFactory.createMethodFromText(method.toString(), mClass));
            } else {
                // 獲取setContentView
                PsiStatement setContentViewStatement = null;
                // onCreate是否存在initView方法
                boolean hasInitViewStatement = false;

                PsiMethod onCreate = mClass.findMethodsByName("onCreate", false)[0];
                for (PsiStatement psiStatement : onCreate.getBody().getStatements()) {
                    // 查找setContentView
                    if (psiStatement.getFirstChild() instanceof PsiMethodCallExpression) {
                        PsiReferenceExpression methodExpression = ((PsiMethodCallExpression) psiStatement.getFirstChild()).getMethodExpression();
                        if (methodExpression.getText().equals("setContentView")) {
                            setContentViewStatement = psiStatement;
                        } else if (methodExpression.getText().equals("initView")) {
                            hasInitViewStatement = true;
                        }
                    }
                }

                if (!hasInitViewStatement && setContentViewStatement != null) {
                    // 將initView()寫(xiě)到setContentView()后面
                    onCreate.getBody().addAfter(mFactory.createStatementFromText("initView();", mClass), setContentViewStatement);
                }

                generatorLayoutCode(null);
            }

            // 判斷mClass是不是繼承fragmentClass或者fragmentV4Class
        } else if ((fragmentClass != null && mClass.isInheritor(fragmentClass, true))
                || (fragmentV4Class != null && mClass.isInheritor(fragmentV4Class, true))
                || mClass.getName().contains("Fragment")) {
            // 判斷是否有onCreateView方法
            if (mClass.findMethodsByName("onCreateView", false).length == 0) {
                StringBuilder method = new StringBuilder();
                method.append("@Override public View onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup container, android.os.Bundle savedInstanceState) {\n");
                method.append("\t// TODO: run FindViewById again To setValue in initView method\n");
                method.append("\tView view = View.inflate(getActivity(), R.layout.");
                method.append(mSelectText);
                method.append(", null);");
                method.append("return view;");
                method.append("}");
                // 添加
                mClass.add(mFactory.createMethodFromText(method.toString(), mClass));

            } else {
                // 查找onCreateView
                PsiReturnStatement returnStatement = null;
                // view
                String returnValue = null;
                // onCreateView是否存在initView方法
                boolean hasInitViewStatement = false;

                PsiMethod onCreate = mClass.findMethodsByName("onCreateView", false)[0];
                for (PsiStatement psiStatement : onCreate.getBody().getStatements()) {
                    if (psiStatement instanceof PsiReturnStatement) {
                        // 獲取view的值
                        returnStatement = (PsiReturnStatement) psiStatement;
                        returnValue = returnStatement.getReturnValue().getText();
                    } else if (psiStatement.getFirstChild() instanceof PsiMethodCallExpression) {
                        PsiReferenceExpression methodExpression = ((PsiMethodCallExpression) psiStatement.getFirstChild()).getMethodExpression();
                        if (methodExpression.getText().equals("initView")) {
                            hasInitViewStatement = true;
                        }
                    }
                }

                if (!hasInitViewStatement && returnStatement != null && returnValue != null) {
                    onCreate.getBody().addBefore(mFactory.createStatementFromText("initView(" + returnValue + ");", mClass), returnStatement);
                }
                generatorLayoutCode(returnValue);
            }
        }
    }

    /**
     * 寫(xiě)initView方法
     *
     * @param findPre Fragment的話要view.findViewById
     */
    private void generatorLayoutCode(String findPre) {
        // 判斷是否已有initView方法
        PsiMethod[] initViewMethods = mClass.findMethodsByName("initView", false);
        if (initViewMethods.length > 0 && initViewMethods[0].getBody() != null) {
            PsiCodeBlock initViewMethodBody = initViewMethods[0].getBody();
            for (Element element : mElements) {
                String pre = TextUtils.isEmpty(findPre) ? "" : findPre + ".";
                String s2 = element.getFieldName() + " = (" + element.name + ") " + pre + "findViewById(" + element.getFullID() + ");";
                initViewMethodBody.add(mFactory.createStatementFromText(s2, initViewMethods[0]));
            }
        } else {
            StringBuilder initView = new StringBuilder();
            if (TextUtils.isEmpty(findPre)) {
                initView.append("private void initView() {\n");
            } else {
                initView.append("private void initView(View " + findPre + ") {\n");
            }

            for (Element element : mElements) {
                String pre = TextUtils.isEmpty(findPre) ? "" : findPre + ".";
                initView.append(element.getFieldName() + " = (" + element.name + ")" + pre + "findViewById(" + element.getFullID() + ");\n");
            }
            initView.append("}\n");
            mClass.add(mFactory.createMethodFromText(initView.toString(), mClass));
        }

    }
}

使用插件

導(dǎo)出插件Build->Prepare All Plugin Modules For Deployment

使用插件

Android Studio導(dǎo)入插件,當(dāng)前是本地嫉鲸,直接通過(guò)Install plugin from disk...導(dǎo)入撑蒜。

導(dǎo)入插件

博客的代碼不是完整的,更多內(nèi)容可以到GitHub上下載查看GitHub玄渗,此插件以后會(huì)繼續(xù)更新座菠,歡迎Start,Issuse

發(fā)布插件

發(fā)布到IntelliJ Plugin倉(cāng)庫(kù)捻爷,支持在plugin中搜索安裝辈灼,參考:
http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/publishing_plugin.html

主要的步驟就是注冊(cè)賬號(hào),提交相應(yīng)的jar文件也榄,然后填寫(xiě)信息巡莹,最后等待審核就可以了。

等著審核

感謝

學(xué)會(huì)編寫(xiě)Android Studio插件 別停留在用的程度了

http://blog.csdn.net/zhangke3016/article/details/53245530

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子循帐,更是在濱河造成了極大的恐慌寞埠,老刑警劉巖读规,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)额嘿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)劣挫,“玉大人册养,你說(shuō)我怎么就攤上這事⊙构蹋” “怎么了球拦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)帐我。 經(jīng)常有香客問(wèn)我坎炼,道長(zhǎng),這世上最難降的妖魔是什么拦键? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任谣光,我火速辦了婚禮,結(jié)果婚禮上芬为,老公的妹妹穿的比我還像新娘抢肛。我一直安慰自己狼钮,他們只是感情好碳柱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布捡絮。 她就那樣靜靜地躺著,像睡著了一般莲镣。 火紅的嫁衣襯著肌膚如雪福稳。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,046評(píng)論 1 285
  • 那天瑞侮,我揣著相機(jī)與錄音的圆,去河邊找鬼。 笑死半火,一個(gè)胖子當(dāng)著我的面吹牛越妈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钮糖,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼梅掠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了店归?” 一聲冷哼從身側(cè)響起阎抒,我...
    開(kāi)封第一講書(shū)人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎消痛,沒(méi)想到半個(gè)月后且叁,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秩伞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年逞带,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纱新。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡展氓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怒炸,到底是詐尸還是另有隱情带饱,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布阅羹,位于F島的核電站勺疼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏捏鱼。R本人自食惡果不足惜执庐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望导梆。 院中可真熱鬧轨淌,春花似錦迂烁、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至躏结,卻和暖如春却盘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背媳拴。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工黄橘, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屈溉。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓塞关,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親子巾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子帆赢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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