Android Usb在framework的相关源码分析

Android USB 在framework相关源码分析

USB插拔这一块内容比较多,在实际开发过程中,暴露出来的问题也比较多,而且有些问题还比较不好解决,定位过程中不能一下就定位出是framework层还是kernal层部分的问题(就比如遇到的一个平板连接多设备,有打印机,2D扫描类Hub的方式)。因此,对于做frameowork开发来讲,深入理解这一块(至少在App/Framework层要理解透彻)是非常有必要的。

由于在写这篇博客的时候,是以写->补->写->补的方式来的,没有按照一定的顺序,比如遵循从kernal—>framework—>到app,或者app—>framework—>kernal有条理的来,而只是按照了一些小的流程线来说的,源码看到哪,就说哪。

第一节

Kernal 与 Framework层交互 UEventObserver;插入与拔出USB设备,事件监听以及上报.UEventObserver

涉及到的类文件

  • android_os_UEventObserver.cpp
    ./frameworks/base/core/jni/android_os_UEventObserver.cpp
  • UEventObserver.java
    ./frameworks/base/core/java/android/os/UEventObserver.java

插拔U盘,usb事件上报
在UsbDeviceManager.java的构造方法中,添加了USB_STATE_MATCH和ACCESSORY_START_MATCH监听。因此,下面我们就按照源码跟踪framework与kernal交互的这段边界部分。

    ...
    // Watch for USB configuration changes
    mUEventObserver.startObserving(USB_STATE_MATCH);    
    mUEventObserver.startObserving();
    ...

下面我们转到UEventObserver中,UEventObserver 是一个Interface,定义了一个回调方法,onUEvent,通过它将kernal 上报事件传递至app/framework层、

/*mUEventObserver的声明:
     * Listens for uevent messages from the kernel to monitor the USB state
     */
    private final UEventObserver mUEventObserver = new UEventObserver() {
        @Override
        public void onUEvent(UEventObserver.UEvent event) { //这里从UEventThead
            String state = event.get("USB_STATE");
            String accessory = event.get("ACCESSORY");
            if (state != null) {
                mHandler.updateState(state); //更新USB当前的状态,CONNECTED ,DISCONNECTED,CONFIGURED
            } else if ...
        }
    };

先看UEventObserver中的startObservering方法

    public final void startObserving(String match) {
        ...
        //The UEventThread is Singleton pattern.
        final UEventThread t = getThread();
        t.addObserver(match, this); //this参数,当onUEvent回调时,则回调到注册的这个Observer中。

    }

接下来,startObserving方法转到了UEventThread中。UEventThread是个线程是UEventObserver的内部类,run方法中是个死循环,不断地监听UEvent,当有事件从Kernal上报时,则通过Handler一步步上传。UEventThread 在UEventObserver中以单例模式存在。

//Create the thread and start it
    private static UEventThread getThread() {
        synchronized (UEventObserver.class) { //keep it sysncronized
            if (sThread == null) {
                sThread = new UEventThread(); //第一次创建并start
                sThread.start();
            }
            return sThread;
        }
    }

UEventThread:

 private static final class UEventThread extends Thread {
        /** Many to many mapping of string match to observer.
         *  Multimap would be better, but not available in android, so use
         *  an ArrayList where even elements are the String match and odd
         *  elements the corresponding(相关的) UEventObserver observer */
        private final ArrayList<Object> mKeysAndObservers = new ArrayList<Object>();
        private final ArrayList<UEventObserver> mTempObserversToSignal =
                new ArrayList<UEventObserver>();
        public UEventThread() {
            super("UEventObserver");
        }
        @Override
        public void run() {
            nativeSetup();
            while (true) {//   a loop to get Event everytime
             String message = nativeWaitForNextEvent();//wait for the next Event
                if (message != null) {
                    sendEvent(message);
                }
            }
        }
//向上层发送Event
        private void sendEvent(String message) {
            synchronized (mKeysAndObservers) {
                final int N = mKeysAndObservers.size();
                for (int i = 0; i < N; i += 2) {
                    final String key = (String)mKeysAndObservers.get(i); // the match String index is i
                    //遍历list中所有的match String
                    if (message.contains(key)) {
                        final UEventObserver observer =
                                (UEventObserver)mKeysAndObservers.get(i + 1); // then , the Observer object in ArrayList index is (i+1)
                        mTempObserversToSignal.add(observer);
                    }
                }
            }

            if (!mTempObserversToSignal.isEmpty()) {
                final UEvent event = new UEvent(message);
                //mTempObserversToSignal存储是的与该message相配套的Observer,所以这里遍历后,将message全部发出去。
                final int N = mTempObserversToSignal.size();
                for (int i = 0; i < N; i++) {
                    final UEventObserver observer = mTempObserversToSignal.get(i);
                    observer.onUEvent(event); //找到与回调回去了
                }
                //发出去完了,等待下一波
                mTempObserversToSignal.clear();
            }
        }
        // “matchA”,ObserverA ;"matchB",ObserverB ...
        // 这里的match String 和 Observer Object是成双成对的,如果我取到了match String index 为 i,则Observer object 的index 为 (i+1)
        // 这里 match是不会一致的,否则就重复了,但是Observer 是会有可能同一个
        public void addObserver(String match, UEventObserver observer) {
            synchronized (mKeysAndObservers) {
                //mKeyAndroidObservers key to value
                mKeysAndObservers.add(match); 
                mKeysAndObservers.add(observer);
                nativeAddMatch(match);
            }
        }
                ...

在UEventThread中,有两个ArrayList,一个是mKeysAndObservers,虽然是个List但是它扮演的是Map的角色,里面的match (String) , Observer(Object) 一一对应,这里就有疑问了?为何不直接使用map呢?map不正好是key-value对应的吗? 我的认为是,list里面match (String) 与Obsever的对应是存在重复的情况。但是map中是不允许key重复的。这应该是没有使用Map的原因吧。另外一个是mTempObserversToSignal ,作为临时变量。framework/base/core/jni/android_os_UEventObserver.cpp

那UEvent从哪里来呢?从nativeWaitForNextEvent这里来。后面的部分暂时不继续往下跟了。

static jstring nativeWaitForNextEvent(JNIEnv *env, jclass clazz) {
    char buffer[1024];
    for (;;) {
        int length = uevent_next_event(buffer, sizeof(buffer) - 1);
        if (length <= 0) {
            return NULL;
        }
        buffer[length] = '\0';
        ALOGV("Received uevent message: %s", buffer);
        if (isMatch(buffer, length)) {
            // Assume the message is ASCII.
            jchar message[length];
            for (int i = 0; i < length; i++) {
                message[i] = buffer[i];
            }
            return env->NewString(message, length);
        }
    }}
  1. UsbInterface,UsbConfigration,UsbDevice 的创建
  2. USB相关Notification的显示流程,源码解析

从上面的分析我们看到,当有新的UEvent从kernal 过来后,UEventObserver回调在UsbDeviceManager中,此时我们通过Handler消息机制,更新Notification的显示。具体过程如下:

 private final UEventObserver mUEventObserver = new UEventObserver() {
        @Override
        public void onUEvent(UEventObserver.UEvent event) { //这里从UEventThead
            if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());
            String state = event.get("USB_STATE");
            String accessory = event.get("ACCESSORY");
            if (state != null) {
                mHandler.updateState(state);
            } else if {...}
        }
    };
mHandler中的updateState:
       public void updateState(String state) {
            int connected, configured;
            if ("DISCONNECTED".equals(state)) {  //断开连接
                connected = 0;
                configured = 0;
            } else if ("CONNECTED".equals(state)) {  //连接上
                connected = 1;
                configured = 0;
            } else if ("CONFIGURED".equals(state)) {
                connected = 1;
                configured = 1;
            } else {
                Slog.e(TAG, "unknown state " + state);
                return;
            }
            removeMessages(MSG_UPDATE_STATE); //avoid MSG repeated
            Message msg = Message.obtain(this, MSG_UPDATE_STATE);
            msg.arg1 = connected;
            msg.arg2 = configured;
            // debounce disconnects to avoid problems bringing up USB tethering
            sendMessageDelayed(msg, (connected == 0) ? UPDATE_DELAY : 0);
        }

此处对MSG_UPDATE_STATE 的处理是关键。

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_UPDATE_STATE:
                    mConnected = (msg.arg1 == 1); 
                    mConfigured = (msg.arg2 == 1);
                    updateUsbNotification(); // 更新USB Notification
                    updateAdbNotification();
                    if (containsFunction(mCurrentFunctions,
                            UsbManager.USB_FUNCTION_ACCESSORY)) {
                        updateCurrentAccessory();
                    } else if (!mConnected) {
                        // restore defaults when USB is disconnected
                        setEnabledFunctions(getDefaultFunctions(), false);
                    }
                    if (mSystemReady) {
                        updateUsbState();
                        updateAudioSourceFunction();
                    }
                    break;
                    ...

接下来我们只看updateUsbNotification方法。这个方法很简单,就是更新Notification,就是我们平时插上USB连接线的时候,顶部通知栏会显示的通知。

如果你要添加声音或者振动,或者更改图标等,算是找对地方了。

  private void updateUsbNotification() {
            if (mNotificationManager == null || !mUseUsbNotification) return;
            int id = 0;
            Resources r = mContext.getResources();
            if (mConnected) {
                if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_MTP)) {
                    id = com.android.internal.R.string.usb_mtp_notification_title; //作为USB设备链接
                } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_PTP)) {
                    id = com.android.internal.R.string.usb_ptp_notification_title;//作为相机链接
                } else if (containsFunction(mCurrentFunctions,
                        UsbManager.USB_FUNCTION_MASS_STORAGE)) {
                    id = com.android.internal.R.string.usb_cd_installer_notification_title; //作为安装应用程序
                } else if (containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_ACCESSORY)) {
                    id = com.android.internal.R.string.usb_accessory_notification_title; // 已链接到USB配件
                } else {
                    // There is a different notification for USB tethering so we don't need one here
                    //if (!containsFunction(mCurrentFunctions, UsbManager.USB_FUNCTION_RNDIS)) {
                    //    Slog.e(TAG, "No known USB function in updateUsbNotification");
                    //}
                }
            }
            if (id != mUsbNotificationId) {
                // clear notification if title needs changing
                if (mUsbNotificationId != 0) { //当前正在显示的notification id
                    mNotificationManager.cancelAsUser(null,mUsbNotificationId,UserHandle.ALL);
                    mUsbNotificationId = 0;
                }
                if (id != 0) {
                    CharSequence message = r.getText(
                            com.android.internal.R.string.usb_notification_message); //触摸可以显示其他USB选项
                    CharSequence title = r.getText(id);

                    Notification notification = new Notification();
                    notification.icon = com.android.internal.R.drawable.stat_sys_data_usb;
                    notification.when = 0; 
                    notification.flags = Notification.FLAG_ONGOING_EVENT;
                    notification.tickerText = title;
                    //如果我们需要添加声音,是否振动或者更改显示的图标,可以修改这里。
                    notification.defaults = 0; // please be quiet
                    notification.sound = null;
                    notification.vibrate = null;
                    //设置优先级
                    notification.priority = Notification.PRIORITY_MIN;

                    //点击通知栏后的操作
                    Intent intent = Intent.makeRestartActivityTask(
                            new ComponentName("com.android.settings",
                                    "com.android.settings.UsbSettings"));
                    PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0,
                            intent, 0, null, UserHandle.CURRENT);
                    notification.color = mContext.getResources().getColor(
                            com.android.internal.R.color.system_notification_accent_color);
                    notification.setLatestEventInfo(mContext, title, message, pi);
                    notification.visibility = Notification.VISIBILITY_PUBLIC;
                    mNotificationManager.notifyAsUser(null, id, notification,
                            UserHandle.ALL);
                    mUsbNotificationId = id;
                }
            }
        }
        ......

Kernal —> Framework UEvent这部分,并插拔USB时,Notification的更新就先讲到这里了。

第二节

SystemServer启动UsbService

涉及到的类文件

  • UsbService.java:

    ./frameworks/base/services/usb/java/com/android/server/usb/UsbService.java

  • UsbManager.java:

    ./frameworks/base/core/java/android/hardware/usb/UsbManager.java

  • UsbDeviceManager.java:

    ./frameworks/base/services/usb/java/com/android/server/usb/UsbDeviceManager.java

  • UsbHostManager.java: ./frameworks/base/services/usb/java/com/android/server/usb/UsbHostManager.java

  • SystemServer.java

    ./frameworks/base/services/java/com/android/server/SystemServer.java

  • SystemServiceManager.java

    ./frameworks/base/services/core/java/com/android/server/SystemServiceManager.java

  • ActivityManagerService.java

    ./frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

首先我们看下SystemServer中启动UsbService服务以及UsbDeviceManager,UsbHostManager的初始化。

SystemServer.java 中UsbService启动代码:

            ...
            if (!disableNonCoreServices) {
                if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_USB_HOST)
                        || mPackageManager.hasSystemFeature(
                        PackageManager.FEATURE_USB_ACCESSORY)) {
                   //注意Feature_USB_HOST 和 FEATURE_USB_ACCESSORY
                    // Manage USB host and device support
                    mSystemServiceManager.startService(USB_SERVICE_CLASS);
                }

注意这里的USB_SERVICE_CLASS变量定义

private static final String USB_SERVICE_CLASS = "com.android.server.usb.UsbService$Lifecycle";

可以看到实际这个USB_SERVICE_CLASS,是Lifecycle这个内部类。

另外, SystemServiceManager的startService方法是通过反射机制,创建并调用对应方法,来初始化对应的Service. 实现代码重用,这个地方是和低版本有区别。因此,下面先看下通过反射创建实例并启动Service。

首先看到SystemServiceManager的startService方法,该方法仅仅创建了Class实例

    public SystemService startService(String className) {

        final Class<SystemService> serviceClass;
        try {
              //通过Class反射机制获得className所对应的Service class实例
              serviceClass = (Class<SystemService>) Class.forName(className);
        } catch (ClassNotFoundException ex) {
            ... //do not find balabala
        }
        return startService(serviceClass);
    }

然后将Class实例作为参数继续转到startService的重载方法:

isAssignableFrom 这个方法是检查主与参数的关系是否来自于同一个parent。放到这里来说,也就是参数serviceClass必须是SystemService的衍生类才行,否则就会抛出运行时异常。

public <T extends SystemService> T startService(Class<T> serviceClass) {
        final String name = serviceClass.getName();
        Slog.i(TAG, "Starting the name of the SystemService : " + name);
        // Create the service.
        //extends or not .
        if (!SystemService.class.isAssignableFrom(serviceClass)) {
            throw new RuntimeException("Failed to create " + name
                    + ": service must extend " + SystemService.class.getName());
        }
        final T service;
        try {
            //获得构造方法对象
         Constructor<T> constructor = serviceClass.getConstructor(Context.class);
            //创建实例
            service = constructor.newInstance(mContext);
        } catch ...

        // Register it.
        mServices.add(service); //添加到list中
        // Start it.
        try {
            service.onStart();//调用onStart方法,转到Service内部流程中...
        } catch ...
        return service;
    }

接下来我们转到UsbService.java&Lifecycle中 ,Lifecycle是UsbService.java的一个静态内部类,显式持有外部类UsbService的对象引用mUsbService。在它的onStart方法中创建UsbService,但是这里仅仅只创建UsbService类对象做一部分简单的初始化操作(创建UsbDeviceManager,注册广播等),而真正的初始化时发生在systemReady方法中,该方法在onBootPhase方法中调用。那service的onSystemReady何时被调用呢?

    public static class Lifecycle extends SystemService {
        private UsbService mUsbService;
        public Lifecycle(Context context) {
            super(context);
        }
        @Override
        public void onStart() { //创建Service实例,并添加到ServiceManager中。
            mUsbService = new UsbService(getContext());
            publishBinderService(Context.USB_SERVICE, mUsbService);
        }
        @Override
        public void onBootPhase(int phase) {
            if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
                mUsbService.systemReady();
            }
        }
    }

这里要说一下SystemServiceManager.java中startService中 mServices.add(service); 这句,从SystemServer.java中我们可以看到系统service添加到了mServices list中。

先看SystemServiceManager.java中startBootPhase方法

    /**
     * Starts the specified boot phase for all system services that have been started up to
     * this point.
     *
     * @param phase The boot phase to start.
     */
    public void startBootPhase(final int phase) {
        if (phase <= mCurrentPhase) {
            throw new IllegalArgumentException("Next phase must be larger than previous");
        }
        mCurrentPhase = phase; 
      //遍历mServices,分别调用Service的onBootPhase方法
        final int serviceLen = mServices.size();
        for (int i = 0; i < serviceLen; i++) {
            final SystemService service = mServices.get(i);
            try {
                service.onBootPhase(mCurrentPhase);
            } catch ...
        }

再看ActivityManagerService.java中的finishBooting方法,这个方法是开机启动完成后调用。

    final void finishBooting() {
        ...
        // Let system services know.
        mSystemServiceManager.startBootPhase(SystemService.PHASE_BOOT_COMPLETED);
        ...
        }

PHASE_BOOT_COMPLETED这个变量按照官方的解释是代替开机广播,开机即刻完成所有service的启动,减少了广播的延迟等待的时间。

    /**
     * After receiving this boot phase, services can allow user interaction with the device.
     * This phase occurs when boot has completed and the home application has started.
     * System services may prefer to listen to this phase rather than registering a
     * broadcast receiver for ACTION_BOOT_COMPLETED to reduce overall latency.
     *
     */
    public static final int PHASE_BOOT_COMPLETED = 1000;

第三节

Usb多设备连接(类似于HUB)

本来想把最近遇到的一个critical bug 放上来一起写一下,但是发现比较麻烦信息不太全就算了吧。
涉及到的类文件

  • UsbDeviceManager.java:
    ./frameworks/base/services/usb/java/com/android/server/usb/UsbDeviceManager.java
  • UsbHostManager.java: ./frameworks/base/services/usb/java/com/android/server/usb/UsbHostManager.java
  • UsbDevice.java
    ./framework/base/core/java/android/hardware/usb/UsbDevice.java

有2个点必须要提一下

(一)在UsbDevice.java中

    public static final Parcelable.Creator<UsbDevice> CREATOR =
        new Parcelable.Creator<UsbDevice>() {
        public UsbDevice createFromParcel(Parcel in) {
            ...
            Parcelable[] configurations = in.readParcelableArray(UsbInterface.class.getClassLoader());
            ...
            return device;
        }
        ...
    };

 public void writeToParcel(Parcel parcel, int flags) {
     ...
     parcel.writeParcelableArray(mConfigurations, 0);
}

从Parceable接口实现规则中可以知道,read和write是要对应的。而在这里,readParcelableArray和writeParcelableArray是明显不一致的。这样写是有问题的我认为。这里值得推敲一下。

(二) UsbHostManager.java代码分析

当新连接一个设备之后,UsbHostManager.java中会依次从jni回调beginUsbDeviceAdded—>addUsbConfiguration—>addUsbInterface—>addUsbEndpoint—>endUsbDeviceAdded 这几个方法,分别创建UsbDevice,UsbConfigration,UsbInterface,UsbEndpoint Parcebale 对象。而这些对象是一对多的方式(这样说应该是可以理解的吧,i think...),说白了就是类似于二叉树的方式。

其中在endUsbDeviceAdded这个方法最后没有把mNewInterface置为null ,没有把mNewConfigration置为null,并且在addUsbConfiguration和addUsbInterface加了判空,这里我的理解是,进入这些if块是在下一个UsbDevice添加的时候才会走进去,但是此时mNewInterface,mNewConfigration是上一轮数据而且后面有创建新的UsbConfigration和UsbInterface,并且在endUsbDeviceAdded方法的开头有当前一轮创建的Configration和Interface分别set到对象中,所以前面的判断显得多余。

private void addUsbInterface(int id, String name, int altSetting,
        int Class, int subClass, int protocol) {
     //有疑问代码块  start
    if (mNewInterface != null) {
        mNewInterface.setEndpoints(
                mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
        mNewEndpoints.clear();
    }
     //有疑问代码块  end
    ...
}
    private void addUsbConfiguration(int id, String name, int attributes, int maxPower) {
      //有疑问代码块  start
        if (mNewConfiguration != null) {
            mNewConfiguration.setInterfaces(
                    mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
            mNewInterfaces.clear();
        }
      //有疑问代码块  end
        ...
    }
/* Called from JNI in monitorUsbHostBus() to finish adding a new device */
private void endUsbDeviceAdded() {
    if (DEBUG) {
        Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()");
    }
    // 往Interface中添加endpoint start
    if (mNewInterface != null) {
        mNewInterface.setEndpoints(
                mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
    }
    // 往Interface中添加endpoint end
  
    //往Configration中添加Interface start
    if (mNewConfiguration != null) {
        mNewConfiguration.setInterfaces(
                mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
    }
    //往Configration中添加Interface start
  
    //往Device中添加Configration start
    synchronized (mLock) {
        if (mNewDevice != null) {
            mNewDevice.setConfigurations(
                    mNewConfigurations.toArray(new UsbConfiguration[mNewConfigurations.size()]));
            mDevices.put(mNewDevice.getDeviceName(), mNewDevice);
            Slog.d(TAG, "Added device " + mNewDevice);
            getCurrentSettings().deviceAttached(mNewDevice);
            mUsbAudioManager.deviceAdded(mNewDevice);
        } else {
            Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded");
        }
      //往Device中添加Configration end
        //这里,没有把mNewInterface置为null ,没有把mNewConfigration置为null
        mNewDevice = null;
        mNewConfigurations = null;
        mNewInterfaces = null;
        mNewEndpoints = null;
      // 修复 start
        mNewConfigration = null ;
        mNewInterface = null ;
      // 修复 end
    }
}

读者可以仔细阅读这个类文件的代码,不多也就那么几行。

第四节

从Activity中获取service

通常情况下我们从Activity中获取一个Service实例,然后调用它的方法。

 UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
 HashMap<Strinng,UsbDevice> deviceHashMap = usbManager.getDeviceList() ;

在ContextImpl.java类中有static初始化块。

    // This one's defined separately and given a variable name so it
    // can be re-used by getWallpaperManager(), avoiding a HashMap
    // lookup.
    private static ServiceFetcher WALLPAPER_FETCHER = new ServiceFetcher() {
            public Object createService(ContextImpl ctx) {
                return new WallpaperManager(ctx.getOuterContext(),
                        ctx.mMainThread.getHandler());
            }};
     ...
        registerService(CAPTIONING_SERVICE, new ServiceFetcher() {
                public Object getService(ContextImpl ctx) {
                    return new CaptioningManager(ctx);
                }});
      ...

在registerService方法中,将所有service缓存到SYSTEM_SERVICE_MAP中。

    private static void registerService(String serviceName, ServiceFetcher fetcher) {
        ...
        SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
    }

因此,getSystemService方法即是从map中取出service实例。

    @Override
    public Object getSystemService(String name) {
        ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
        return fetcher == null ? null : fetcher.getService(this);
    }

之前喜欢把一些工作总结写到CSDN,最近越来越喜欢MarkDown越来越喜欢简书,所以就开始放到这里来,这是处女篇,希望各位看官多多指点,你的批评是我进步的阶梯。
Thanks very much for your actions .

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

推荐阅读更多精彩内容