Android从开机到点击Icon(桌面图标)中间发生了什么?

系列文章

前言

上文Android Activity生命周期,启动模式,启动过程详解我们讲解了Activity的生命周期,以及Activity启动过程的详细步骤,Activity承载着App对用户的内容展示,当我们在手机桌面上点开一个图标便打开了一个App,App上显示的界面对应背后的Activity,那么为什么点击图标便能打开App呢,其实桌面应用程序(Launcher)我们也可以理解为一个App,此Activity上承载着各种App图标,点击图标便有一个响应事件,这个响应事件就是打开图标对应的App。那么Launcher程序又是如何启动的呢?自然是和手机开机有关,因为我们知道手机开机后便是默认桌面,所以本文我们将从手机开机说起,梳理一下从手机开机,到启动Launcher程序,到点击Icon打开APP这个过程,下面分别介绍以上三个过程。

本文源码基于Android-25版本

开机过程分析

在看Android系统启动流程前,我们先了解下计算机PC启动过程,大概分为以为四步:

  • BIOS:主要做硬件自检,转移控制权等工作;
  • 主引导记录:BIOS把控制权转交给排在第一位的储存设备(主引导记录),共512字节;
  • 硬盘启动:计算机的控制权由硬盘的某个分区控制;
  • 操作系统:计算机的控制权转交给操作系统,内核首先被载入内存,以Linux为例,其第一个运行的程序是/sbin/init。它根据配置文件产生init进程,init是Linux启动后的第一个进程,pid进程编号为1,其他进程都是它的后代。然后,init线程加载系统的各个模块,比如窗口程序和网络程序,直至执行/bin/login程序,跳出登录界面,等待用户输入用户名和密码。

以上摘自 计算机是如何启动的?[阮一峰]

因为Android系统是基于Linux内核的,所以系统的开机过程肯定涉及到Linux内核的启动。但是Android系统属于嵌入式系统,没有计算机那样的BIOS引导程序,取而代之的是系统引导Bootloader,为启动系统内核做好准备。Android中也没有硬盘,取而代之的是ROM,类似硬盘存放操作系统和应用程序等。整体开机流程如下图所示:

Android开机流程

第一步:上电复位

开机时,通电产生一个CPU复位信号,CPU开始执行指令,第一条指令是设定好的(固化在ROM上),将引导程序(Bootloader)加载到RAM中;

第二步:Bootloader

Bootloader启动,引导进入Linux内核;引导程序是运行的第一个程序,不同的主板和芯片具有不同引导程序,不同的手机厂商使用不同的程序,这部分不属于Android操作系统,因此可能是厂商加锁的地方;

第三步:Linux内核

Linux内核启动后主要做一些初始化工作,比如初始化软硬件环境,加载驱动程序,挂载根文件系统,内核加载的最后阶段启动第一个进程init进程;

第四步:init进程

init进程(/system/core/init/*)是系统第一个进程,进程号为1,该进程会首先加载一个init.rc配置文件,init.rc文件是Android系统的重要配置文件,位于(/system/core/rootdir/init.rc),其主要功能是定义了系统启动时需要执行的一系列动作,设置环境变量,生成系统运行所需要的文件或目录,执行特定Services等。

Android针对init.rc有特定的格式和规则,它由Android Init Language语言编写而成,Android Init Language主要包含四种声明:Actions(动作),Commands(命令),Services(服务),Options(选项)。

通过init.rc脚本主要启动了以下服务:

Action/Service 描述
on early-init 设置init进程以及它创建的子进程的优先级,设置init进程的安全环境
on init 设置全局环境,为cpu accounting创建cgroup(资源控制)挂载点
on fs 挂载mtd分区
on post-fs 改变系统目录的访问权限
on post-fs-data 改变/data目录以及它的子目录的访问权限
on boot 基本网络的初始化,内存管理等等
service servicemanager 启动系统管理器管理所有的本地服务,比如位置、音频、Shared preference等等…
service zygote 启动zygote作为应用进程

本表来自Android启动过程深入解析

init.rc最重要的的任务是启动一个Zygote(孵化器)进程,此进程负责启动Android应用进程的启动工作。init.rc通过include引入init.zygote.rc创建Zygote进程。

init.rc的具体语法可以参考深入分析AIL语言及init.rc文件

第五步:Zygote进程

Zygote进程孵化了所有Android应用进程,是Android Framework的基础。在Java中,不同的虚拟机实例会为不同的应用分配不同的内存。Android应用程序是运行在Dalvik虚拟机里面的,并且每一个应用程序对应有一个单独的Dalvik虚拟机实例。但是Android应用程序中的Dalvik虚拟机实例实际上是从Zygote进程的地址空间拷贝而来的,这样就可以加快Android应用程序的启动速度,Zygote让Dalvik虚拟机共享代码,低内存占用,最小启动时间成为可能。

Zygote其实是一个虚拟机进程,它会完成虚拟机的初始化、库的加载、预制类库、核心类库的初始化等,当系统需要一个新的虚拟机时,它会迅速复制自己,提供给系统。每当我们打开一个新的APP时都会fork之前的Zygote进程。下面介绍下Zygote启动过程:

App_main.main()

此函数位于(frameworks/base/cmds/app_process/App_main.cpp)中,主要添加了Android运行环境,即创建一个AppRuntime变量runtime,而AppRuntime继承自AndroidRuntime,在App_main.main()方法中执行了runtime.start()方法,也就是执行了AndroidRuntime类的start方法,由于在init.rc文件中设置了启动app_process的参数为:--zygote--start-system-server,因此实际上在main函数里最终会执行下面语句:

runtime.start("com.android.internal.os.ZygoteInit", args, zygote);

AndroidRuntime.start()

此函数位于(frameworks/base/core/jni/AndroidRuntime.cpp)中,主要做了以下三件事情:

  • startVm():创建虚拟机,主要是关于虚拟机参数的设置;
  • startReg(): 注册JNI方法;
  • env->GetStaticMethodID(): 通过JNI调用Java函数,进入Java代码中,实际上最终调用了com.android.internal.os.ZygoteInit的main函数。

ZygoteInit.main()

此函数位于(frameworks/base/core/java/com/android/internal/os/ZygoteInit.java)中:

public static void main(String argv[]) {
    try {
        
        ......
        
        String socketName = "zygote";

        ......

        registerZygoteSocket(socketName);
        preload(); 
        
        ......

        if (startSystemServer) {
            startSystemServer(abiList, socketName);
        } 
        runSelectLoop(abiList); 
        closeServerSocket();
    } catch (MethodAndArgsCaller caller) {
        ......
        
    } catch (RuntimeException ex) {
        ......
        
    }
}

主要做了以下几件事情:

  • registerZygoteSocket():创建一个名为Zygote的socket接口,用来和ActivityManagerService通讯
  • preload():预加载通用类等资源,如res(drawable,xml信息,strings)等
  • startSystemServer(): 启动SystemServer等服务,在此函数内部Zygote进程通过Zygote.forkSystemServer函数来创建一个新的进程来启动SystemServer组件,如下所示:
private static boolean startSystemServer(){
    
    ....
    // SystemServer是由Zygote通过Zygote.forkSystemServer函数fork出来的
    pid = Zygote.forkSystemServer(
                parsedArgs.uid, parsedArgs.gid,
                parsedArgs.gids, debugFlags, null,
                parsedArgs.permittedCapabilities,
                parsedArgs.effectiveCapabilities);

    ....
    
    // 子进程返回0,即SystemServer
    if (pid == 0) {
        ....
        handleSystemServerProcess(parsedArgs);
    }
}

private static void handleSystemServerProcess(
            ZygoteConnection.Arguments parsedArgs)
            throws ZygoteInit.MethodAndArgsCaller {
    // 关闭这里继承来的socket,因为这里的子进程并不会使用到socket      
    closeServerSocket();
    
    // 继续启动SystemServer的操作
    RuntimeInit.zygoteInit(parsedArgs.remainingArgs);
    /* should never reach here */
}

public static final void zygoteInit(String[] argv)  
            throws ZygoteInit.MethodAndArgsCaller { 
            
    ....
    // 初始化Binder进程间通信机制
    zygoteInitNative();  

    ....
}  
  • runSelectLoopMode(): 轮询监听socket,不断处理来自客户端的AMS请求,然后交给runOnce()处理

至此,Zygote进程就启动完成了,总结如下:

  • init进程创建Zygote进程,而Zygote负责整个后续Android 应用程序的创建
  • Zygote进程启动时会创建SystemServer进程,SystemServer进程负责启动系统的关键服务,如ActivityManagerService,PowerManagerService,PackageManagerService等
  • 当我们准备启动一个APP时,AMS通过Socket和Zygote进程间通信,通知Zygote fork子进程,加载需要的类。

整体过程如下图所示:

Zygote启动过程

SystemServer启动过程

上述启动Zygote进程的过程中,我们提到了启动SystemServer这个Android系统核心进程,为了更清楚理解Android系统工作,这里我们再将启动SystemServer进程的过程单独列为一小节说明。

SystemServer由Zygote fork而成,进程名为system_server,承载着framework服务。

具体的SystemServer启动过程较为复杂,通过一系列方法最终转移到SystemServer类的main()方法中,如下图所示:


SystemServer启动过程

图片来自 作者:Gityuan博客 来源:Android系统启动-SystemServer上篇
链接:http://gityuan.com/2016/02/14/android-system-server/

SystemServer.main()方法主要的流程如下:

SystemServer.main()           // 初始化SystemServer对象,再调用run()方法
SystemServer.run()            // 调用以下方法
    createSystemContext()     // 创建system_server进程的上下文信息
    startBootstrapServices(); // 创建AMS,PMS,LightService,DisplayManagerService等服务
    startCoreServices();      // 启动BatteryService,UsageStatsService,WebViewUpdateService服务
    startOtherServices();     // 显示启动界面,调用AMS.systemReady()方法
    Looper.loop();            // 开启消息循环

在上面的startOtherServices()方法中有一段代码如下:

mActivityManagerService.systemReady(new Runnable() {
    public void run() {
      ...
    }
});

public final class ActivityManagerService{

    public void systemReady(final Runnable goingCallback) {
        
        ....
        
        startHomeActivityLocked(mCurrentUserId, "systemReady"); //启动桌面
        mStackSupervisor.resumeTopActivitiesLocked(); //恢复栈顶的Activity
    }
}

AMS的systemReady()方法中启动了WebView,SystemUI,开启WatchDog,启动桌面Launcher 程序。
启动Launcher的过程如下:首先通过Zygote进程fork一个子进程作为APP进程,然后创建Application,再启动Activity,最后显示出实际画面。
完整的启动过程如下:


Android完整开机过程

本图片摘自 作者: Jeanboydev 来源:CSDN
链接 https://blog.csdn.net/freekiteyu/article/details/79318031

Launcher

从上面分析我们知道,AMS在完成服务注册等初始化工作后,在main()方法中调用systemReady()方法,进而调用startHomeActivityLocked()来启动Launcher。

boolean startHomeActivityLocked(int userId, String reason) {
    if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL
            && mTopAction == null) {
        return false;
    }
    // 获取启动Launcher的intent信息
    Intent intent = getHomeIntent();
    // 通过PackageManagerService获得Launcher的Activity描述信息Info
    ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
    if (aInfo != null) {
        intent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
        aInfo = new ActivityInfo(aInfo);
        aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId);
        ProcessRecord app = getProcessRecordLocked(aInfo.processName,
                aInfo.applicationInfo.uid, true);
        if (app == null || app.instrumentationClass == null) {
            intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
            mActivityStarter.startHomeActivityLocked(intent, aInfo, reason);
        }
    } else {
        Slog.wtf(TAG, "No home screen found for " + intent, new Throwable());
    }
    return true;
}

启动Launcher的Intent对象中添加了Intent.CATEGORY_HOME常量,代表将要启动Home界面。
获得intent和ActivityInfo后,后续方法调用顺序为:

ActivityStarter.startHomeActivityLocked()
ActivityStackSupervisor.moveHomeStackTaskToTop() // 把Launcher的堆栈移到顶部
ActivityStarter.startActivityLocked()
......

进入ActivityStarter.startActivityLocked()方法后,后续过程就和启动一个普通的Activity没什么区别,启动一个普通Activity的过程可以参考我的上一篇文章Android Activity生命周期,启动模式,启动过程详解
第一次启动Launcher时,执行到ActivityStackSupervisor的startSpecificActivityLocked()方法时,会判断Launcher Activity所在进程是否已经存在,如果不存在则会创建一个进程容纳Activity,创建进程的流程就是通过AMS向Zygote发起请求,Zygote收到请求后fork一个子进程,然后再继续启动Launcher。创建子进程的过程,我们会在下文中点击Icon启动一个APP的过程中进行分析。

总而言之,Launcher的启动过程就是类似一个启动Activity的过程。但是,我们知道Launcher上放着各种应用程序图标,有着不同的状态,因此其Activity实现具体原理还可以进一步探究。可以参考Android Launcher加载流程源码分析Android M Launcher3主流程源码浅析Android系统启动流程(四)Launcher启动过程与系统启动流程

应用安装的时候,通过PackageManagerService解析apk的AndroidManifest.xml文件,提取出这个apk的信息写入packages.xml中,包括权限、包名、icon、apk安装位置、版本、userID等信息,Launcher为已安装的程序在桌面上生成图标,点击图标便可启动应用,下面分析启动应用的过程。

本段来自 Jeanboydev CSDN博客 一篇文章看明白 Android 从点击应用图标到界面显示的过程

点击Icon启动APP

其实点击Icon启动APP的过程就是启动APP主Activity的过程,即启动MainActivity的过程,从而把APP启动起来,但是当我们新启动一个APP过程时,需要判断此APP所在进程是否已经存在,如果不存在则需要fork一个子进程。下面详细介绍这个具体过程。

Launcher.startActivitySafely

点击图标时,桌面程序Launcher.java做出响应,调用Launcher.startActivitySafely来启动一个Activity

public class Launcher extends Activity
        implements LauncherExterns, View.OnClickListener, OnLongClickListener,
                   LauncherModel.Callbacks, View.OnTouchListener, LauncherProviderChangeListener,
                   AccessibilityManager.AccessibilityStateChangeListener {
 
    ......

    public void onClick(View v) {
        Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {
            onClickAppShortcut(v);
        } 
        
        ......
    }
    
    protected void onClickAppShortcut(final View v) {
        
        ......
        
        startAppShortcutOrInfoActivity(v);
    }
    
    private void startAppShortcutOrInfoActivity(View v) {
        ItemInfo item = (ItemInfo) v.getTag();
        Intent intent = item.getIntent();
        
        ......
        
        boolean success = startActivitySafely(v, intent, item);

        ......
    }
    
    public boolean startActivitySafely(Intent intent, Object tag) {
        ......
        
        // 表示在新的Task中启动此Activity
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        ......
        try {
        
            ......
            
            startActivity(intent);
        } catch (ActivityNotFoundException e) {
            ......
        } catch (SecurityException e) {
            ......
        }
    }
 
    ......
 
}

Activity.startActivity

Launcher继承自Activity,因此上段代码中startActivity(intent)便调用了Activity类的该方法:

public void startActivity(Intent intent) {
    this.startActivity(intent, null);
}

public void startActivity(Intent intent, @Nullable Bundle options) {
    if (options != null) {
        startActivityForResult(intent, -1, options);
    } else {
        startActivityForResult(intent, -1);
    }
}

Activity的StartActivity()方法最终调用了startActivityForResult()方法,这和我之前一篇文章Android Activity生命周期,启动模式,启动过程详解相同,后续过程基本没有区别。和前文类似,当执行到ActivityStackSupervisor的startSpecificActivityLocked()方法时,会判断APP所在进程是否已经存在,如果不存在则会fork一个进程。下面介绍fork新进程的过程:

fork新进程

Android创建进程的流程图大概如下:


Android创建进程

图片来自 作者:Gityuan博客 来源:理解Android进程创建流程
链接:http://gityuan.com/2016/03/26/app-process-create/

ActivityManagerService.startProcessLocked()

ActivityStackSupervisor的startSpecificActivityLocked()的方法中有下面一段代码

ProcessRecord app = mService.getProcessRecordLocked(r.processName, r.info.applicationInfo.uid, true);

第一次启动APP时,很明显上面获得的变量app=null,在配置文件AndroidManifest.xml中我们如果没有指定process属性,系统会默认使用package的名称当做process名。而且每一个程序都有自己的uid,uid+process的组合就可以为每一个应用程序创建一个ProcessRecord,每次新建进程前都会判断此ProcessRecord是否存在,如果已经存在则不会新建进程。
如果不存在,则新建进程:

mService.startProcessLocked(r.processName, r.info.applicationInfotrue, 0,
        "activity", r.intent.getComponent(), false, false, true);

因为startProcessLocked有多个重载形式,上述方法最终调用的方法为:

private final void startProcessLocked(ProcessRecord app, String hostingType,
        String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
    
    ......
        
    Process.ProcessStartResult startResult = Process.start(entryPoint,
            app.processName, uid, uid, gids, debugFlags, mountExternal,
            app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
            app.info.dataDir, entryPointArgs);
    ......
    
}

最终转到Process的start()方法中。

Process.start()

public static final ProcessStartResult start(final String processClass,
                              final String niceName,
                              int uid, int gid, int[] gids,
                              int debugFlags, int mountExternal,
                              int targetSdkVersion,
                              String seInfo,
                              String abi,
                              String instructionSet,
                              String appDataDir,
                              String[] zygoteArgs) {
    try {
        return startViaZygote(processClass, niceName, uid, gid, gids,
                debugFlags, mountExternal, targetSdkVersion, seInfo,
                abi, instructionSet, appDataDir, zygoteArgs);
    } catch (ZygoteStartFailedEx ex) {
        ......
    }
}

又继续调用Process.startViaZygote()方法:

private static ProcessStartResult startViaZygote(final String processClass,
                              final String niceName,
                              final int uid, final int gid,
                              final int[] gids,
                              int debugFlags, int mountExternal,
                              int targetSdkVersion,
                              String seInfo,
                              String abi,
                              String instructionSet,
                              String appDataDir,
                              String[] extraArgs)
                              throws ZygoteStartFailedEx {
    synchronized(Process.class) {
        
        ......
        
        // openZygoteSocketIfNeeded(abi)方法是根据当前的abi来选择与zygote还是zygote64来进行通信
        return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
    }
}

继续调用zygoteSendArgsAndGetResult()方法:

private static ProcessStartResult zygoteSendArgsAndGetResult(
        ZygoteState zygoteState, ArrayList<String> args)
        throws ZygoteStartFailedEx {
    try {
        
        ......

        final BufferedWriter writer = zygoteState.writer;
        final DataInputStream inputStream = zygoteState.inputStream;

        writer.write(Integer.toString(args.size()));
        writer.newLine();

        for (int i = 0; i < sz; i++) {
            String arg = args.get(i);
            writer.write(arg);
            writer.newLine();
        }

        writer.flush();

        ProcessStartResult result = new ProcessStartResult();
        
        // 等待Zygote的socket返回新进程的pid
        result.pid = inputStream.readInt();
        result.usingWrapper = inputStream.readBoolean();

        if (result.pid < 0) {
            throw new ZygoteStartFailedEx("fork() failed");
        }
        return result;
    } catch (IOException ex) {
        zygoteState.close();
        throw new ZygoteStartFailedEx(ex);
    }
}

通过socket向Zygote发送一系列参数,然后进入阻塞等待状态,直到远端的socket服务端发送回来新创建的进程pid才返回。Zygote收到请求后,开始工作,我们又回到了ZygoteInit.main()方法中。

ZygoteInit.main()

ZygoteInit.main()方法中主要停留在runSelectLoop函数中,等待AMS的请求,收到请求时会调用ZygoteConnection的runOnce函数来处理请求,后序函数调用逻辑为:

ZygoteConnection.runOnce()
    Zygote.forkAndSpecialize()              // fork当前进程来创建一个子进程
    ZygoteConnection.handleChildProc()      // 启动上面fork的子进程,并切换到子进程中执行后续代码 
        RuntimeInit.zygoteInit()            // 创建Binder线程池,一些初始化工作
            RuntimeInit.invokeStaticMain()  // 利用反射ActivityThread.main()方法
ActivityThread.main()                       // 创建消息循环,进入消息循环状态

ActivityThread.main()

public static void main(String[] args) {

    ......
    // 创建消息循环
    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // 进入消息循环
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

此时只创建了应用程序的 ActivityThread 和 ApplicationThread,和开启了 Handler 消息循环机制,其他的都还未创建, ActivityThread.attach(false) 又会最终到 ActivityMangerService 的 attachApplication,这个方法其实是将本地的 ApplicationThread 传递到 ActivityMangerService。然后 ActivityMangerService 就可以通过 ApplicationThread 的代理 ApplicationThreadProxy 来调用应用程序 ApplicationThread.bindApplication,通知应用程序的 ApplicationThread 已和 ActivityMangerService 绑定,可以不借助其他进程帮助直接通信了。此时 Launcher 的任务也算是完成了。

本段来源
作者:Jeanboydev
来源:CSDN
原文:https://blog.csdn.net/freekiteyu/article/details/79318031

下面看下ActivityMangerService的attachApplication方法:

public final void attachApplication(IApplicationThread thread) {
    synchronized (this) {
        ......
        attachApplicationLocked(thread, callingPid);
        ......
    }
}

private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid) {

    ......

    // 检查顶层Activity是否等待被运行
    if (normalMode) {
        try {
            if (mStackSupervisor.attachApplicationLocked(app)) {
                didSomething = true;
            }
        } catch (Exception e) {
            Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
            badApp = true;
        }
    }

    // 寻找所有需要在该进程中运行的服务
    if (!badApp) {
        try {
            didSomething |= mServices.attachApplicationLocked(app, processName);
        } catch (Exception e) {
            Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
            badApp = true;
        }
    }

    // 检查是否在这个进程中有下一个广播接收者
    if (!badApp && isPendingBroadcastProcessLocked(pid)) {
        try {
            didSomething |= sendPendingBroadcastsLocked(app);
        } catch (Exception e) {
            // If the app died trying to launch the receiver we declare it 'bad'
            Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
            badApp = true;
        }
    }
    
    ......
    
    return true;
}

当发现有Activity需要被启动时,调用ActivityStackSupervisor.attachApplicationLocked()方法:

boolean attachApplicationLocked(ProcessRecord app) throws RemoteException {
    ......
    
    realStartActivityLocked(hr, app, true, true);
    
    ......
}

后续具体的启动步骤又回到了启动一个普通Activity的过程中,同样可以参考上篇文章Android Activity生命周期,启动模式,启动过程详解
最后,再引用一张图片结束本文:

Launcher启动Activity流程

图片来自 Jeanboydev 来源:CSDN
链接:https://blog.csdn.net/freekiteyu/article/details/79318031

至此,我们基本把Android从开机到点击Icon的过程理清楚了,但是中间每一部分都有很多细节值得深入,因此本文只给了一个大概流程,具体的实现细节可以分模块去查找探究,本文结束!

参考信息

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