Android应用程序进程启动过程-源码分析

Android系统中,启动一个app首先要保证该app所在的进程已经被启动。本文就介绍下app进程,即应用程序进程的启动过程。这里顺便提一句,通常所说的启动应用程序,指的是其根Activity的启动,和应用程序进程的启动是两个概念。

  • 了解Android系统启动过程或者Zygote进程的同学都会知道,应用程序进程的创建是通过Zygote进程fork其自身而产生的。①
  • 了解Activity的启动过程的朋友,又知道在Activity的启动过程中,会有一个检查Activity所在进程是否已经启动,否则就会先创建并启动其所在进程,再启动Activity。②
    知道了上面两个过程,再来理解app进程启动过程,就相对容易了。先让大家有个大概的流程认识:

在Android系统的启动过程中,Zygote进程的Java框架层中创建一个Server端的Socket。这个Socket用来等待AMS发送请求,让Zygote进程创建新的app进程。Zygote进程接收到请求后,通过fork自身创建app进程。

具体的创建和启动过程步骤比较多,我们把上述过程分为两个部分来分别讲解。

AMS发送启动app进程的请求

先来看一下AMS发送启动app进程请求过程的时序图


image.png

AMS如果想要启动app进程,就需要向Zygote进程发送创建app进程的请求,AMS会通过调用startProcessLocked方法来开始这一过程。下面展示相关的重要代码

private final void startProcessLocked(ProcessRecord app, String hostingType,
            String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
    ...
    try {
            ...
            //获取要创建的app进程的用户id
            int uid = app.uid;
            ...
            //对用户组gids进行创建和赋值
            if (ArrayUtils.isEmpty(permGids)) {
                    gids = new int[3];
                } else {
                    gids = new int[permGids.length + 3];
                    System.arraycopy(permGids, 0, gids, 3, permGids.length);
                }
                gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
                gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid));
                gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid));
            ...
            // Start the process.  It will either succeed and return a result containing
            // the PID of the new process, or else throw a RuntimeException.
            //这里的entryPoint的值就是app进程主线程的类名。
            if (entryPoint == null) entryPoint = "android.app.ActivityThread";
            ...
            //调用Process.start方法,将上面得到的entryPoint,uid,gids传进去
            startResult = Process.start(entryPoint,
                        app.processName, uid, uid, gids, debugFlags, mountExternal,
                        app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                        app.info.dataDir, invokeWith, entryPointArgs);
}

关于uid、gid和进程
在Linux中,每个UID标识一个用户,每个用户又至少属于一个组,每个组都有一个唯一标识的GID。每个进程都拥有真实的用户、组(UID、GID)。在Android系统中,一个用户的UID表示一个app。app在安装时被分配了用户UID,其在设备上存续期间,用户UID保持不变。对于普通的用户程序,GID和UID相同。详细介绍可以参考Android 安全机制(1)uid 、 gid 与 pid

entryPoint 希望读者能够留意这个变量值,它关系到app进程创建后的初始化过程,后面会提到。

接下来查看Process的start方法,这里需要注意的是,此处的Process类不是java/lang/Process.java的Process类,而是android/os/Process.java的Process类

package android.os;
/**
 * Tools for managing OS processes.
 */
public class Process {
        ...
        public static final ProcessStartResult start(final String processClass,
                                  final String niceName,
                                  int uid, int gid, int[] gids,
                                  int runtimeFlags, int mountExternal,
                                  int targetSdkVersion,
                                  String seInfo,
                                  String abi,
                                  String instructionSet,
                                  String appDataDir,
                                  String invokeWith,
                                  String[] zygoteArgs) {
        return zygoteProcess.start(processClass, niceName, uid, gid, gids,
                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
    }
        ...
}

一目了然,其内部直接调用了ZygoteProcess的start方法

public final Process.ProcessStartResult start(final String processClass,
                                                  final String niceName,
                                                  int uid, int gid, int[] gids,
                                                  int runtimeFlags, int mountExternal,
                                                  int targetSdkVersion,
                                                  String seInfo,
                                                  String abi,
                                                  String instructionSet,
                                                  String appDataDir,
                                                  String invokeWith,
                                                  String[] zygoteArgs) {
        try {
            return startViaZygote(processClass, niceName, uid, gid, gids,
                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */,
                    zygoteArgs);
        } catch (ZygoteStartFailedEx ex) {
            Log.e(LOG_TAG,
                    "Starting VM process through Zygote failed");
            throw new RuntimeException(
                    "Starting VM process through Zygote failed", ex);
        }
    }

它又调用了startViaZygote方法

/**
* Starts a new process via the zygote mechanism.
*/
private Process.ProcessStartResult startViaZygote(final String processClass,
                                                      final String niceName,
                                                      final int uid, final int gid,
                                                      final int[] gids,
                                                      int runtimeFlags, int mountExternal,
                                                      int targetSdkVersion,
                                                      String seInfo,
                                                      String abi,
                                                      String instructionSet,
                                                      String appDataDir,
                                                      String invokeWith,
                                                      boolean startChildZygote,
                                                      String[] extraArgs)
                                                      throws ZygoteStartFailedEx {
        ArrayList<String> argsForZygote = new ArrayList<String>();

        // --runtime-args, --setuid=, --setgid=,
        // and --setgroups= must go first
        argsForZygote.add("--runtime-args");
        argsForZygote.add("--setuid=" + uid);
        argsForZygote.add("--setgid=" + gid);
        argsForZygote.add("--runtime-flags=" + runtimeFlags);
        if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
            argsForZygote.add("--mount-external-default");
        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
            argsForZygote.add("--mount-external-read");
        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
            argsForZygote.add("--mount-external-write");
        }
        argsForZygote.add("--target-sdk-version=" + targetSdkVersion);

        // --setgroups is a comma-separated list
        if (gids != null && gids.length > 0) {
            StringBuilder sb = new StringBuilder();
            sb.append("--setgroups=");

            int sz = gids.length;
            for (int i = 0; i < sz; i++) {
                if (i != 0) {
                    sb.append(',');
                }
                sb.append(gids[i]);
            }
            argsForZygote.add(sb.toString());
        }
        if (niceName != null) {
            argsForZygote.add("--nice-name=" + niceName);
        }
        if (seInfo != null) {
            argsForZygote.add("--seinfo=" + seInfo);
        }
        if (instructionSet != null) {
            argsForZygote.add("--instruction-set=" + instructionSet);
        }
        if (appDataDir != null) {
            argsForZygote.add("--app-data-dir=" + appDataDir);
        }
        if (invokeWith != null) {
            argsForZygote.add("--invoke-with");
            argsForZygote.add(invokeWith);
        }
        if (startChildZygote) {
            argsForZygote.add("--start-child-zygote");
        }
        argsForZygote.add(processClass);
        if (extraArgs != null) {
            for (String arg : extraArgs) {
                argsForZygote.add(arg);
            }
        }
        synchronized(mLock) {
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
        }
    }

这个方法前面一段是创建字符串列表argsForZygote,并将app进程的启动参数放在argsForZygote中。方法最后调用了zygoteSendArgsAndGetResult方法。需要注意的是zygoteSendArgsAndGetResult方法的第一个参数openZygoteSocketIfNeeded,稍后会讲到。第二个参数就是argsForZygote。先看看zygoteSendArgsAndGetResult方法的代码

/**
     * Sends an argument list to the zygote process, which starts a new child
     * and returns the child's pid. Please note: the present implementation
     * replaces newlines in the argument list with spaces.
     *
     * @throws ZygoteStartFailedEx if process start failed for any reason
     */
@GuardedBy("mLock")
    private static Process.ProcessStartResult zygoteSendArgsAndGetResult(
            ZygoteState zygoteState, ArrayList<String> args)
            throws ZygoteStartFailedEx {
        try {
            int sz = args.size();
            for (int i = 0; i < sz; i++) {
                if (args.get(i).indexOf('\n') >= 0) {
                    throw new ZygoteStartFailedEx("embedded newlines not allowed");
                }
            }
            ... 
            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();
            ...
            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);
        }
    }

zygoteSendArgsAndGetResult方法的主要作用就是将传入的app进程的启动参数argsForZygote写入到zygoteState中,zygoteState是ZygoteProcess的静态内部类,用于表示与Zygote进程的通信状态。现在再回过头去看,startViaZygote的return语句,我们可以知道,这个zygoteState对象是openZygoteSocketIfNeeded方法返回的。

/**
     * Tries to open socket to Zygote process if not already open. If
     * already open, does nothing.  May block and retry.  Requires that mLock be held.
     */
    @GuardedBy("mLock")
    private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
        Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held");
        if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
            try {
                primaryZygoteState = ZygoteState.connect(mSocket);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
            }
            maybeSetApiBlacklistExemptions(primaryZygoteState, false);
             maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
        }
        if (primaryZygoteState.matches(abi)) {
            return primaryZygoteState;
        }
        // The primary zygote didn't match. Try the secondary.
        if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
            try {
                secondaryZygoteState = ZygoteState.connect(mSecondarySocket);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
            }
            maybeSetApiBlacklistExemptions(secondaryZygoteState, false);        maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
        }
        if (secondaryZygoteState.matches(abi)) {
            return secondaryZygoteState;
        }
        throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
    }

在Zygote进程启动过程中,Zygote的main方法中会创建name为"zygote"的Server端Socket。在上面的代码中,我们也看到了调用ZygoteState的connect方法。这个方法就是用来和Zygote进程建立Socket连接的。其中的mSocket,是一个LocalSocketAddress对象,表示和Zygote进程通信的Socket名称。在这里指的就是Zygote进程里的Socket名称。
我们再来看看ZygoteState类,就能更容易理解这个Socket通信过程。

public static class ZygoteState {
        final LocalSocket socket;
        final DataInputStream inputStream;
        final BufferedWriter writer;
        final List<String> abiList;
        ...
        public static ZygoteState connect(LocalSocketAddress address) throws IOException {
            DataInputStream zygoteInputStream = null;
            BufferedWriter zygoteWriter = null;
            final LocalSocket zygoteSocket = new LocalSocket();

            try {
                zygoteSocket.connect(address);

                zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());

                zygoteWriter = new BufferedWriter(new OutputStreamWriter(
                        zygoteSocket.getOutputStream()), 256);
            } catch (IOException ex) {
                try {
                    zygoteSocket.close();
                } catch (IOException ignore) {
                }

                throw ex;
            }

            String abiListString = getAbiList(zygoteWriter, zygoteInputStream);
            Log.i("Zygote", "Process: zygote socket " + address.getNamespace() + "/"
                    + address.getName() + " opened, supported ABIS: " + abiListString);

            return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter,
                    Arrays.asList(abiListString.split(",")));
        }
        ...
}

可以看到在ZygoteState的connect方法里面,调用了LocalSocket的connect方法。而这个LocalSocket就是Android里面的Socket实现,和java里面的Socket类一样,实现了Closeable接口。
至此,我们已经可以看出,AMS和Zygote进程建立Socket连接的过程和基于Java的Socket连接过程是很相似的。

Zygote接收请求并创建app进程

同样,我们先看一下这部分的时序图
image.png

我们先从,Zygote进程启动过程中会创建一个Server端的Socket,这个Socket用来等待AMS发送请求讲起。
这个过程是在ZygoteInit的main方法中执行的。

public static void main(String argv[]) {
        ...
        String socketName = "zygote";
        ...
        zygoteServer.registerServerSocketFromEnv(socketName);
        if (!enableLazyPreload) {
                bootTimingsTraceLog.traceBegin("ZygotePreload");
                EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
                    SystemClock.uptimeMillis());
                //预加载资源
                preload(bootTimingsTraceLog);
          ...
          //创建SystemServer进程
          if (startSystemServer) {
                Runnable r = forkSystemServer(abiList, socketName, zygoteServer);
          ...
          // The select loop returns early in the child process after a fork and
            // loops forever in the zygote.
            //等待AMS的请求
            caller = zygoteServer.runSelectLoop(abiList);
          ...  
}

这段代码中,首先通过registerServerSocketFromEnv方法创建了一个Server段的Socket,这个name为"zygote"的Socket用来等待AMS请求Zygote,以创建新的app进程。最后调用zygoteServer的runSelectLoop方法来等待或者说监听AMS请求创建新的app进程。接下来看看zygoteServer的runSelectLoop方法

/**
     * Runs the zygote process's select loop. Accepts new connections as
     * they happen, and reads commands from connections one spawn-request's
     * worth at a time.
     */
    Runnable runSelectLoop(String abiList) {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
        ...
        while (true) {
            StructPollfd[] pollFds = new StructPollfd[fds.size()];
            for (int i = 0; i < pollFds.length; ++i) {
            ...
            ZygoteConnection connection = peers.get(i);
                        final Runnable command = connection.processOneCommand(this);
            ...
}

runSelectLoop方法中,通过一个while(true)的循环,不停的遍历ArrayList<ZygoteConnection>类型的peers列表,执行其中每个ZygoteConnection对象的processOneCommand方法。我们接着看ZygoteConnection的processOneCommand方法

/**
     * Reads one start command from the command socket. If successful, a child is forked and a
     * {@code Runnable} that calls the childs main method (or equivalent) is returned in the child
     * process. {@code null} is always returned in the parent process (the zygote).
     *
     * If the client closes the socket, an {@code EOF} condition is set, which callers can test
     * for by calling {@code ZygoteConnection.isClosedByPeer}.
     */
    Runnable processOneCommand(ZygoteServer zygoteServer) {
        ...
        //读取app进程的启动参数
        args = readArgumentList();
        ...
        parsedArgs = new Arguments(args);
        ...
        pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
                parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
                parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,
                parsedArgs.instructionSet, parsedArgs.appDataDir);
       try {
            //当前代码是在子进程中执行的
            if (pid == 0) {
                // in child
                zygoteServer.setForkChild();

                zygoteServer.closeServerSocket();
                IoUtils.closeQuietly(serverPipeFd);
                serverPipeFd = null;

                return handleChildProc(parsedArgs, descriptors, childPipeFd,
                        parsedArgs.startChildZygote);
            } else {
                // In the parent. A pid < 0 indicates a failure and will be handled in
                // handleParentProc.
                IoUtils.closeQuietly(childPipeFd);
                childPipeFd = null;
                handleParentProc(pid, descriptors, serverPipeFd);
                return null;
            }
        } finally {
            IoUtils.closeQuietly(childPipeFd);
            IoUtils.closeQuietly(serverPipeFd);
        }
    }

上面这段代码中,首先调用readArgumentList方法获取app进程的启动参数,并封装到Arguments类型的parsedArgs参数中,然后会调用Zygote的forkAndSpecialize方法来创建app进程,参数就是parsedArgs中的app进程启动参数,然后返回pid。forkAndSpecialize方法主要是通过fork当前进程,也就是Zygote进程来创建一个子进程的。如果pid为0,那接下去的代码就是在新创建的子进程中执行。这时会调用handleChildProc来处理App进程。

   /**
     * Handles post-fork setup of child proc, closing sockets as appropriate,
     * reopen stdio as appropriate, and ultimately throwing MethodAndArgsCaller
     * if successful or returning if failed.
     */
private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
            FileDescriptor pipeFd, boolean isZygote) {
        //关闭LocalSocket
        closeSocket();
        ...
        if (!isZygote) {
                return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
                        null /* classLoader */);
        ...
}

在handleChildProc方法中,需要重点关注的代码就是调用了ZygoteInit的zogoteInit方法。

/**
     * The main function called when started through the zygote process. This
     * could be unified with main(), if the native code in nativeFinishInit()
     * were rationalized with Zygote startup.
     *
     */
    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
        if (RuntimeInit.DEBUG) {
            Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
        }

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
        RuntimeInit.redirectLogStreams();

        RuntimeInit.commonInit();
        //创建Binder线程池
        ZygoteInit.nativeZygoteInit();
        return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
    }

zygoteInit方法最后调用了RuntimeInit的applicationInit方法。下面是applicationInit方法的代码

protected static Runnable applicationInit(int targetSdkVersion, String[] argv,
            ClassLoader classLoader) {
        nativeSetExitWithoutCleanup(true);
        VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
        VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
        final Arguments args = new Arguments(argv);
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        // Remaining arguments are passed to the start class's static main
        return findStaticMain(args.startClass, args.startArgs, classLoader);
    }

RuntimeInit的ApplicationInit方法最后调用了findStaticMain方法,

/**
     * Invokes a static "main(argv[]) method on class "className".
     * Converts various failing exceptions into RuntimeExceptions, with
     * the assumption that they will then cause the VM instance to exit.
     */
    protected static Runnable findStaticMain(String className, String[] argv,
            ClassLoader classLoader) {
        Class<?> cl;

        try {
            cl = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    "Missing class when invoking static main " + className,
                    ex);
        }

        Method m;
        try {
            m = cl.getMethod("main", new Class[] { String[].class });
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException(
                    "Missing static main on " + className, ex);
        } catch (SecurityException ex) {
            throw new RuntimeException(
                    "Problem getting static main on " + className, ex);
        }

        int modifiers = m.getModifiers();
        if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
            throw new RuntimeException(
                    "Main method is not public and static on " + className);
        }

        /*
         * This throw gets caught in ZygoteInit.main(), which responds
         * by invoking the exception's run() method. This arrangement
         * clears up all the stack frames that were required in setting
         * up the process.
         */
        return new MethodAndArgsCaller(m, argv);
    }

源码中给出的注释,findStaticMain方法的工作是,根据类名className,调用其指定类的main方法。这个className指的就是本文开头提到过的android.app.ActivityThread。所以findStaticMain方法的实际作用就是调用进程中的ActivityThread类的main方法。
然后,看到上面代码的最后一行,创建了一个MethodAndArgsCaller对象并返回。MethodAndArgsCaller实现了Runnable接口,是一个可执行的任务。到这里,我们要根据方法调用往回看,最终会看到MethodAndArgsCaller对象的run方法是在ZygoteInit的main方法里面

public static void main(String argv[]) {
        ...
        caller = zygoteServer.runSelectLoop(abiList);
        ...
        // We're in the child process and have exited the select loop. Proceed to execute the
        // command.
        if (caller != null) {
            caller.run();
        }
}

接下来,我们来看看MethodAndArgsCaller的run方法

public void run() {
            try {
                mMethod.invoke(null, new Object[] { mArgs });
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InvocationTargetException ex) {
                Throwable cause = ex.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else if (cause instanceof Error) {
                    throw (Error) cause;
                }
                throw new RuntimeException(ex);
            }
        }

这里的mMethod方法就是ActivityThrea的main方法,调用mMethod的invoke方法后,ActivityThread的main方法就会被动态调用,app进程就进入了ActivityThread的main方法中,讲到这里,app进程的启动过程就算完成了。

总结

本文主要从源码分析的角度,介绍app进程的启动过程,这对于理解应用程序的启动机制以及相关的优化方面,都是很有意义的。面试的时候,也会经常被问到。个人认为其中比较重要的几个关键点是:

  • AMS与Zygote进程之间的Socket通信,传递app进程的启动参数
  • Zygote进程通过fork自身,创建app进程
  • app进程的初始化,创建Binder线程池,启动ActivityThread线程

本文参考:
《Android进阶解密》第三章
Android 安全机制(1)uid 、 gid 与 pid

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

推荐阅读更多精彩内容