CC 原理简单分析

一、CC 的基本使用方式

1、在模块A中定义Component

/**
 * @author billy.qi
 */
public class ComponentA implements IComponent {
    @Override
    public String getName() {
        //组件的名称,调用此组件的方式:
        // CC.obtainBuilder("ComponentA")...build().callAsync()
        return "demo.ComponentA";
    }

    /**
     * 组件被调用时的入口
     * 要确保每个逻辑分支都会调用到CC.sendCCResult,
     * 包括try-catch,if-else,switch-case-default,startActivity
     * @param cc 组件调用对象,可从此对象中获取相关信息
     * @return true:将异步调用CC.sendCCResult(...),用于异步实现相关功能,例如:文件加载、网络请求等
     *          false:会同步调用CC.sendCCResult(...),即在onCall方法return之前调用,否则将被视为不合法的实现
     */
    @Override
    public boolean onCall(CC cc) {
        String actionName = cc.getActionName();
        switch (actionName) {
            case "showActivityA":
                openActivity(cc);
                break;
            case "getLifecycleFragment":
                //demo for provide fragment object to other component
                getLifecycleFragment(cc);
                break;
            case "lifecycleFragment.addText":
                lifecycleFragmentDoubleText(cc);
                break;
            case "getInfo":
                getInfo(cc);
                break;
            default:
                //这个逻辑分支上没有调用CC.sendCCResult(...),是一种错误的示例
                //并且方法的返回值为false,代表不会异步调用CC.sendCCResult(...)
                //在LocalCCInterceptor中将会返回错误码为-10的CCResult
                break;
        }
        return false;
    }

    private void lifecycleFragmentDoubleText(CC cc) {
        LifecycleFragment lifecycleFragment = cc.getParamItem("fragment");
        if (lifecycleFragment != null) {
            String text = cc.getParamItem("text", "");
            lifecycleFragment.addText(text);
            CC.sendCCResult(cc.getCallId(), CCResult.success());
        } else {
            CC.sendCCResult(cc.getCallId(), CCResult.error("no fragment params"));
        }
    }

    private void getLifecycleFragment(CC cc) {
        CC.sendCCResult(cc.getCallId(), CCResult.successWithNoKey(new LifecycleFragment()));
    }

    private void getInfo(CC cc) {
        String userName = "billy";
        CC.sendCCResult(cc.getCallId(), CCResult.success("userName", userName));
    }

    private void openActivity(CC cc) {
        CCUtil.navigateTo(cc, ActivityA.class);
        CC.sendCCResult(cc.getCallId(), CCResult.success());
    }
}

2、在主工程中调用A模块中的Component

 public static final String COMPONENT_NAME_A = "demo.ComponentA";
  
 IComponentCallback printResultCallback = new IComponentCallback() {
    @Override
    public void onResult(CC cc, CCResult result) {
        showResult(cc, result);
    }
};
    
CC.obtainBuilder(COMPONENT_NAME_A)
                        .setActionName("showActivityA")
                        .build().callAsyncCallbackOnMainThread(printResultCallback);
                        
                        

二、CC的调用流程

1、CC 是一次组件接口调用的载体类(data)

承载接口调用的各种参数,调用结果。

也可以理解为是一个装饰器类。


public class CC {
    private static final String TAG = "ComponentCaller";
    private static final String VERBOSE_TAG = "ComponentCaller_VERBOSE";
    public static final String CC_NULL_KEY = "CC_NULL_KEY";
    /**
     * 默认超时时间为2秒
     */
    private static final long DEFAULT_TIMEOUT = 2000;
    static boolean DEBUG = false;
    static boolean VERBOSE_LOG = false;
    /**
     * 是否响应跨app的组件调用
     * 为了方便开发调试,默认设置为允许响应跨app组件调用
     * 为了安全,app上线时可以将此值设置为false,避免被恶意调用
     */
    private static boolean REMOTE_CC_ENABLED = false;

    private volatile CCResult result;

    private final byte[] wait4resultLock = new byte[0];

    private static Application application;

    WeakReference<Activity> cancelOnDestroyActivity;

    WeakReference<Fragment> cancelOnDestroyFragment;

    private volatile boolean waiting;

    static {
        Application app = CCUtil.initApplication();
        if (app != null) {
            init(app);
        }
    }

    /**
     * 初始化方法
     * 仅初始化CC,不初始化组件和拦截器
     * 在Application.onCreate(...)中调用
     * @param app Application
     */
    public static synchronized void init(Application app) {
        init(app, false, false);
    }

    /**
     * 初始化方法
     * 同时初始化组件和全局拦截器
     * @param app Application
     * @param initComponents 如果设置为true则同时初始化组件
     * @param initGlobalInterceptors 如果设置为true则同时初始化全局拦截器
     */
    public static synchronized void init(Application app, boolean initComponents, boolean initGlobalInterceptors) {
        if (application == null && app != null) {
            application = app;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                application.registerActivityLifecycleCallbacks(new CCMonitor.ActivityMonitor());
            }
        }
        if (initComponents) {
            ComponentManager.init();
        }
        if (initGlobalInterceptors) {
            GlobalCCInterceptorManager.init();
        }
    }
    
    

    private WeakReference<Context> context;
    /**
     * 组件名称
     */
    private String componentName;
    /**
     * 组件中某个功能的名称,用以区别同一个组件中不同功能的调用
     */
    private String actionName;
    private final Map<String, Object> params = new HashMap<>();
    /**
     * 回调对象
     */
    private IComponentCallback callback;
    /**
     * 是否异步执行
     */
    private boolean async;
    private final List<ICCInterceptor> interceptors = new ArrayList<>();
    private boolean callbackOnMainThread;
    /**
     * 调用超时时间,默认值(同步调用:2000, 异步调用:0)
     */
    private long timeout = -1;
    long timeoutAt;
    private final AtomicBoolean finished = new AtomicBoolean(false);
    private String callId;
    private volatile boolean canceled = false;
    private volatile boolean timeoutStatus = false;
    private boolean withoutGlobalInterceptor = false;

    private CC(String componentName) {
        this.componentName = componentName;
    }

    /**
     * 创建CC对象的Builder<br>
     * <b>此对象会被CC框架复用,请勿在程序中保存</b>
     * @param componentName 要调用的组件名称
     * @return 创建CC对象的Builder
     */
    public static Builder obtainBuilder(String componentName) {
        return BUILDER_POOL.get(componentName);
    }

    /**
     * 获取当前app的Application对象
     * @return application对象
     */
    public static Application getApplication() {
        return application;
    }

    @Override
    public String toString() {
        JSONObject json = new JSONObject();
        put(json, "callId", callId);
        put(json, "context", getContext());
        put(json, "componentName", componentName);
        put(json, "actionName", actionName);
        put(json, "timeout", timeout);
        put(json, "withoutGlobalInterceptor", withoutGlobalInterceptor);
        put(json, "callbackOnMainThread", callbackOnMainThread);
        put(json, "params", CCUtil.convertToJson(params));
        put(json, "interceptors", interceptors);
        put(json, "callback", getCallback());
        return json.toString();
    }

    public Context getContext() {
        if (context != null) {
            Context context = this.context.get();
            if (context != null) {
                return context;
            }
        }
        return application;
    }

    void forwardTo(String componentName) {
        this.componentName = componentName;
    }

    public String getActionName() {
        return actionName;
    }

    /**
     * get all params
     * @return all params as map
     */
    public Map<String, Object> getParams() {
        return params;
    }

    /**
     * 获取通过 {@link Builder#setParamWithNoKey(Object)} 设置的参数
     * @param defaultValue 默认值
     * @param <T> 泛型,返回值的类型
     * @return 未设置key(使用默认key)的参数
     */
    public <T> T getParamItemWithNoKey(T defaultValue) {
        return getParamItem(CC_NULL_KEY, defaultValue);
    }

    /**
     * 获取通过 {@link Builder#setParamWithNoKey(Object)} 设置的参数
     * @param <T> 泛型,返回值的类型
     * @return 未设置key(使用默认key)的参数
     */
    public <T> T getParamItemWithNoKey() {
        return getParamItem(CC_NULL_KEY);
    }

    /**
     * get param(auto class casted) by key
     * @param key key for param
     * @param defaultValue default value if not found or class cast error
     * @param <T> class to cast for param
     * @return class casted param
     */
    public <T> T getParamItem(String key, T defaultValue) {
        T item = getParamItem(key);
        if (item == null) {
            return defaultValue;
        }
        return item;
    }

    /**
     * get param(auto class casted) by key
     * @param key key for param
     * @param <T> class to cast for param
     * @return class casted param
     */
    public <T> T getParamItem(String key) {
        try {
            return (T) params.get(key);
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    boolean isAsync() {
        return async;
    }

    boolean isCallbackOnMainThread() {
        return callbackOnMainThread;
    }

    long getTimeout() {
        return timeout;
    }

    public String getCallId() {
        return callId;
    }

    boolean isCanceled() {
        return canceled;
    }

    /**
     * 判断是否需要中止运行,本次调用被手动取消或已超时。
     * 组件在处理耗时操作时,要根据此状态进行判断,以免进行无效操作
     * @return <code>true</code>:需要中止继续执行;false:可以继续运行
     */
    public boolean isStopped() {
        return canceled || timeoutStatus;
    }

    boolean isTimeout() {
        return timeoutStatus;
    }

    boolean isWithoutGlobalInterceptor() {
        return withoutGlobalInterceptor;
    }

    CCResult getResult() {
        return result;
    }

    void setResult(CCResult result) {
        finished.set(true);
        this.result = result;
    }

    void setResult4Waiting(CCResult result) {
        try {
            synchronized (wait4resultLock) {
                if (VERBOSE_LOG) {
                    verboseLog(callId, "setResult" + (waiting ? "4Waiting" : "")
                            + ". CCResult:" + result);
                }
                setResult(result);
                if (waiting) {
                    waiting = false;
                    wait4resultLock.notifyAll();
                }
            }
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    void wait4Result() {
        //等待调用CC.sendCCResult(callId, result)
        synchronized (wait4resultLock) {
            if (!isFinished()) {
                try {
                    verboseLog(callId, "start waiting for CC.sendCCResult(...)");
                    waiting = true;
                    wait4resultLock.wait();
                    verboseLog(callId, "end waiting for CC.sendCCResult(...)");
                } catch (InterruptedException ignored) {
                }
            }
        }
    }

    IComponentCallback getCallback() {
        return callback;
    }

    /**
     * 在onDestroy后,自动cancel
     */
    void cancelOnDestroy(Object reason) {
        if (!isFinished()) {
            if (VERBOSE_LOG) {
                verboseLog(callId, "call cancel on " + reason + " destroyed");
            }
            cancel();
        }
    }

    void addCancelOnFragmentDestroyIfSet() {
        if (cancelOnDestroyFragment == null) {
            return;
        }
        Fragment fragment = cancelOnDestroyFragment.get();
        if (fragment == null) {
            return;
        }
        FragmentManager manager = fragment.getFragmentManager();
        if (manager != null) {
            manager.registerFragmentLifecycleCallbacks(
                    new CCMonitor.FragmentMonitor(this)
                    , false);
        }
    }

    public String getComponentName() {
        return componentName;
    }

    List<ICCInterceptor> getInterceptors() {
        return interceptors;
    }

    /**
     * 异步调用,且不需要回调
     * @return callId,可用于取消调用的任务
     */
    public String callAsync() {
        return callAsync(null);
    }
    /**
     * 异步调用,在异步线程执行回调
     * @param callback 回调函数
     * @return callId 用于取消
     */
    public String callAsync(IComponentCallback callback) {
        this.callbackOnMainThread = false;
        return processCallAsync(callback);
    }
    /**
     * 异步调用,在主线程执行回调
     * @param callback 回调函数
     * @return callId 用于取消
     */
    public String callAsyncCallbackOnMainThread(IComponentCallback callback) {
        this.callbackOnMainThread = true;
        return processCallAsync(callback);
    }

    private String processCallAsync(IComponentCallback callback) {
        if (callback != null) {
            this.callback = callback;
        }
        this.async = true;
        //调用方未设置超时时间,默认为无超时时间
        if (timeout < 0) {
            timeout = 0;
        }
        setTimeoutAt();
        this.callId = nextCallId();
        this.canceled = false;
        this.timeoutStatus = false;
        if (VERBOSE_LOG) {
            verboseLog(callId, "start to callAsync:" + this);
        }
        ComponentManager.call(this);
        return callId;
    }

    /**
     * 同步调用
     * @return CCResult
     */
    public CCResult call() {
        this.callback = null;
        this.async = false;
        boolean mainThreadCallWithNoTimeout = timeout == 0 && Looper.getMainLooper() == Looper.myLooper();
        //主线程下的同步调用必须设置超时时间,默认为2秒
        if (mainThreadCallWithNoTimeout || timeout < 0) {
            timeout = DEFAULT_TIMEOUT;
        }
        setTimeoutAt();
        this.callId = nextCallId();
        this.canceled = false;
        this.timeoutStatus = false;
        //加上开关判断,防止开关关闭的情况下也执行this.toString()方法
        if (VERBOSE_LOG) {
            verboseLog(callId, "start to call:" + this);
        }
        return ComponentManager.call(this);
    }

   
    /**
     * 在任意位置回调结果
     * 组件的onCall方法被调用后,<b>必须确保所有分支均会调用</b>到此方法将组件调用结果回调给调用方
     * @param callId 回调对象的调用id
     * @param ccResult 回调的结果
     */
    public static void sendCCResult(String callId, CCResult ccResult) {
        if (VERBOSE_LOG) {
            verboseLog(callId, "CCResult received by CC.sendCCResult(...).CCResult:" + ccResult);
        }
        CC cc = CCMonitor.getById(callId);
        if (cc != null) {
            if (cc.markFinished()) {
                if (ccResult == null) {
                    ccResult = CCResult.defaultNullResult();
                    logError("CC.sendCCResult called, But ccResult is null, set it to CCResult.defaultNullResult(). "
                            + "ComponentName=" + cc.getComponentName());
                }
                cc.setResult4Waiting(ccResult);
            } else {
                logError("CC.sendCCResult called, But ccResult is null. "
                        + "ComponentName=" + cc.getComponentName());
            }
        } else {
            log("CCResult received, but cannot found callId:" + callId);
        }
    }

    /**
     * 在任意位置回调结果
     * @param callId 回调对象的调用id
     * @param result 回调的结果
     * @deprecated use {@link #sendCCResult(String, CCResult)}
     */
    @Deprecated
    public static void invokeCallback(String callId, CCResult result) {
        sendCCResult(callId, result);
    }

    /**
     * 获取当前app内是否含有指定的组件
     * @param componentName 组件名称
     * @return true:有, false:没有
     */
    public static boolean hasComponent(String componentName) {
        return ComponentManager.hasComponent(componentName);
    }

    /**
     * 开关跨app调用组件支持,默认为关闭状态
     *  1. 某个componentName当前app中不存在时,是否尝试调用其它app的此组件
     *  2. 接收到跨app调用时,是否执行本次调用
     *  3. 建议仅在开发阶段调试时打开,正式发布时关闭
     * @param enable 开关(true:会执行; false:不会)
     */
    public static void enableRemoteCC(boolean enable) {
        REMOTE_CC_ENABLED = enable;
        if (enable && application != null) {
            RemoteCCInterceptor.getInstance().enableRemoteCC();
        }
    }

    public static boolean isRemoteCCEnabled() {
        return REMOTE_CC_ENABLED;
    }

    /**
     * 当前进程是否以包名在运行(当前进程是否为主进程)
     */
    public static boolean isMainProcess(){
        return CCUtil.isMainProcess();
    }

    public static boolean isDebugMode() {
        return DEBUG;
    }
}

其核心方法 call(),其实是代理到了
ComponentManager.call(this)中。

   /**
     * 同步调用
     * @return CCResult
     */
    public CCResult call() {
        this.callback = null;
        this.async = false;
        boolean mainThreadCallWithNoTimeout = timeout == 0 && Looper.getMainLooper() == Looper.myLooper();
        //主线程下的同步调用必须设置超时时间,默认为2秒
        if (mainThreadCallWithNoTimeout || timeout < 0) {
            timeout = DEFAULT_TIMEOUT;
        }
        setTimeoutAt();
        this.callId = nextCallId();
        this.canceled = false;
        this.timeoutStatus = false;
        //加上开关判断,防止开关关闭的情况下也执行this.toString()方法
        if (VERBOSE_LOG) {
            verboseLog(callId, "start to call:" + this);
        }
        return ComponentManager.call(this);
    }

2、ComponentManager 类是CC 中的总线类,或者说仓库类。维护着CC 中的路由信息

<组件名字符串,Component组件对象>

    private static final ConcurrentHashMap<String, IComponent> COMPONENTS = new ConcurrentHashMap<>();

组件映射关系的收集是通过gradle插件 cc-register 来完成的。

https://github.com/luckybilly/AutoRegister

scanInterface : (必须)字符串,接口名(完整类名),所有直接实现此接口的类将会被收集
codeInsertToClassName : (必须)字符串,类名(完整类名),通过编译时生成代码的方式将收集到的类注册到此类的codeInsertToMethodName方法中
codeInsertToMethodName: 字符串,方法名,注册代码将插入到此方法中。若未指定,则默认为static块,(方法名为:)
registerMethodName : (必须)字符串,方法名,静态方法,方法的参数为 scanInterface
scanSuperClasses : 字符串或字符串数组,类名(完整类名),所有直接继承此类的子类将会被收集
include : 数组,需要扫描的类(正则表达式,包分隔符用/代替,如: com/billy/android/.*),默认为所有的类
exclude : 数组,不需要扫描的类(正则表达式,包分隔符用/代替,如: com/billy/android/.*),

自动收集原理是:

  • gradle插件包含了一个叫Transform的API,这个API允许第三方插件在class文件转为为dex文件前操作编译好的class文件。
  • 在Tansform中 收集相关类,编译期间修改字节码,加入到管理类中。
/**
 * 组件调用管理类
 * @author billy.qi
 * @since 17/6/28 20:14
 */
class ComponentManager {
    /** 当前进程中的组件集合 */
    private static final ConcurrentHashMap<String, IComponent> COMPONENTS = new ConcurrentHashMap<>();
    /**
     * 组件名称对应的进程名称集合
     * 当前进程为主进程:包含当前app内的所有静态组件和动态组件的(名称 - 进程名)的映射表
     * 当前进程为子进程:包含当前app内的所有静态组件和当前进程内注册的动态组件的(名称 - 进程名)的映射表
     */
    private static final ConcurrentHashMap<String, String> COMPONENT_PROCESS_NAMES = new ConcurrentHashMap<>();
    private static final String SUB_PROCESS_SEPARATOR = ":";

    static {
        registerComponent(new DynamicComponentOption());
        //加载类时自动调用初始化:注册所有组件
        //通过auto-register插件生成组件注册代码
        //生成的代码如下:
        //registerComponent(new ComponentA());
        //registerComponent(new ComponentAA());
    }

    /**
     * 提前初始化所有全局拦截器
     */
    static void init(){
        //调用此方法时,虚拟机会加载ComponentManager类
        //会自动执行static块中的组件自动注册,调用组件类的无参构造方法
        //如果不提前调用此方法,static块中的代码将在第一次进行组件调用时(cc.callXxx())执行
    }
}

核心调用方法call(),交给了一个拦截器调用链。

 /**
     * 组件调用统一入口
     * @param cc 组件调用指令
     * @return 组件调用结果(同步调用的返回值)
     */
    static CCResult call(CC cc) {
        String callId = cc.getCallId();
        Chain chain = new Chain(cc);
        if (!cc.isWithoutGlobalInterceptor()) {
            chain.addInterceptors(INTERCEPTORS);
        }
        chain.addInterceptors(cc.getInterceptors());
        // 有效性校验放在自定义拦截器之后执行,优先执行自定义拦截器,让其可以拦截到所有组件调用
        // 执行实际调用的拦截器在校验有效性结束后再添加
        chain.addInterceptor(ValidateInterceptor.getInstance());
        ChainProcessor processor = new ChainProcessor(chain);
        //异步调用,放到线程池中运行
        if (cc.isAsync()) {
            if (CC.VERBOSE_LOG) {
                CC.verboseLog(callId, "put into thread pool");
            }
            CC_THREAD_POOL.submit(processor);
            //异步调用时此方法返回null,CCResult通过callback回调
            return null;
        } else {
            //同步调用,直接执行
            CCResult ccResult;
            try {
                ccResult = processor.call();
            } catch (Exception e) {
                ccResult = CCResult.defaultExceptionResult(e);
            }
            if (CC.VERBOSE_LOG) {
                CC.verboseLog(callId, "cc finished.CCResult:" + ccResult);
            }
            //同步调用的返回结果,永不为null,默认为CCResult.defaultNullResult()
            return ccResult;
        }
    }

Chain 实现了一个拦截器调用链:


/**
 * 组件调用链,用于管理拦截器的运行顺序
 * @author billy.qi
 */
public class Chain {
    private final List<ICCInterceptor> interceptors = new ArrayList<>();
    private final CC cc;
    private int index;

    Chain(CC cc) {
        this.cc = cc;
        this.index = 0;
    }

    void addInterceptors(Collection<? extends ICCInterceptor> interceptors) {
        if (interceptors != null && !interceptors.isEmpty()) {
            this.interceptors.addAll(interceptors);
        }
    }
    void addInterceptor(ICCInterceptor interceptor) {
        if (interceptor != null) {
            this.interceptors.add(interceptor);
        }
    }

    public CCResult proceed() {
        if (index >= interceptors.size()) {
            return CCResult.defaultNullResult();
        }
        ICCInterceptor interceptor = interceptors.get(index++);
        //处理异常情况:如果为拦截器为null,则执行下一个
        if (interceptor == null) {
            return proceed();
        }
        String name = interceptor.getClass().getName();
        String callId = cc.getCallId();
        CCResult result;
        if (cc.isFinished()) {
            //timeout, cancel, CC.sendCCResult(callId, ccResult), cc.setResult, etc...
            result = cc.getResult();
        } else {
            if (CC.VERBOSE_LOG) {
                CC.verboseLog(callId, "start interceptor:" + name + ", cc:" + cc);
            }
            try {
                result = interceptor.intercept(this);
            } catch(Throwable e) {
                //防止拦截器抛出异常
                result = CCResult.defaultExceptionResult(e);
            }
            if (CC.VERBOSE_LOG) {
                CC.verboseLog(callId, "end interceptor:" + name + ".CCResult:" + result);
            }
        }
        //拦截器理论上不应该返回null,但为了防止意外(自定义拦截器返回null,此处保持CCResult不为null
        //消灭NPE
        if (result == null) {
            result = CCResult.defaultNullResult();
        }
        cc.setResult(result);
        return result;
    }

    public CC getCC() {
        return cc;
    }
}

image.png

三、CC 库有点可以值得我们仔细研究和学习。

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

推荐阅读更多精彩内容