Arouter框架原理淺解

一遮咖、什么是路由?

路由是指路由器從一個(gè)接口上收到數(shù)據(jù)包造虏,根據(jù)數(shù)據(jù)路由包的目的地址進(jìn)行定向并轉(zhuǎn)發(fā)到另一個(gè)接口的過(guò)程御吞。—百度百科

以Android為例漓藕,各個(gè)組件module可以看成一個(gè)個(gè)網(wǎng)絡(luò)陶珠,而路由就可以看成是各個(gè)模塊頁(yè)面跳轉(zhuǎn)的中轉(zhuǎn)站,除了中轉(zhuǎn)以外享钞,路由還可以過(guò)濾或攔截一些不安全或不規(guī)范的跳轉(zhuǎn)揍诽。

二、為什么用阿里ARouter

官方定義:A framework for assisting in the renovation of Android componentization (幫助 Android App 進(jìn)行組件化改造的路由框架)

其實(shí)Android的跳轉(zhuǎn)已經(jīng)提供了Intent,Intent不僅可以顯示跳轉(zhuǎn)栗竖,也可以隱式跳轉(zhuǎn)暑脆。那么ARouter為什么還會(huì)存在呢?讓我們對(duì)比一下原生跳轉(zhuǎn)和路由跳轉(zhuǎn):

1狐肢、原生顯示跳轉(zhuǎn)必須依賴(lài)于類(lèi)添吗,強(qiáng)耦合,而隱式跳轉(zhuǎn)需要都在AndroidManifest里集中注冊(cè)份名,協(xié)作麻煩碟联;而路由跳轉(zhuǎn)通過(guò)URL索引,低耦合僵腺,協(xié)作方便鲤孵;

2、原生在通過(guò)startActivity啟動(dòng)跳轉(zhuǎn)后就交由系統(tǒng)控制辰如,并不能做攔截普监;路由跳轉(zhuǎn)可以對(duì)跳轉(zhuǎn)做過(guò)濾或攔截。

6eb2eafbd4c419c493dbbfb1b05064087cda2173.png

通過(guò)對(duì)比琉兜,我們發(fā)現(xiàn)路由特別適合用來(lái)做跳轉(zhuǎn)(尤其是組件間的通信及跳轉(zhuǎn))凯正,下面就來(lái)看一下阿里ARouter的集成及使用吧~

三、ARouter概述

ARouter 是一個(gè)用于幫助 Android App 進(jìn)行組件化改造的框架 —— 支持模塊間的路由呕童、通信漆际、解耦

適用于以下場(chǎng)景:

從外部URL映射到內(nèi)部頁(yè)面,以及參數(shù)傳遞與解析

跨模塊頁(yè)面跳轉(zhuǎn)夺饲,模塊間解耦

攔截跳轉(zhuǎn)過(guò)程奸汇,處理登陸、埋點(diǎn)等邏輯

跨模塊API調(diào)用往声,通過(guò)控制反轉(zhuǎn)來(lái)做組件解耦

主要包括這些功能:

支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn)擂找,并自動(dòng)注入?yún)?shù)到目標(biāo)頁(yè)面中

支持多模塊工程使用

支持添加多個(gè)攔截器,自定義攔截順序

支持依賴(lài)注入浩销,可單獨(dú)作為依賴(lài)注入框架使用

支持InstantRun

支持MultiDex(Google方案)

映射關(guān)系按組分類(lèi)贯涎、多級(jí)管理,按需初始化

支持用戶(hù)指定全局降級(jí)與局部降級(jí)策略

頁(yè)面慢洋、攔截器塘雳、服務(wù)等組件均自動(dòng)注冊(cè)到框架

支持多種方式配置轉(zhuǎn)場(chǎng)動(dòng)畫(huà)

支持獲取Fragment

完全支持Kotlin以及混編(配置見(jiàn)文末 其他#5)

支持第三方 App 加固(使用 arouter-register 實(shí)現(xiàn)自動(dòng)注冊(cè))

支持生成路由文檔

提供 IDE 插件便捷的關(guān)聯(lián)路徑和目標(biāo)類(lèi)

ARouter使用

這里只介紹ARouter最基礎(chǔ)的用法–Activity跳轉(zhuǎn)陆盘,其他用法如通過(guò)URL跳轉(zhuǎn)、解析參數(shù)败明、聲明攔截器隘马、處理跳轉(zhuǎn)結(jié)果、聲明服務(wù)等妻顶,請(qǐng)?jiān)敿?xì)閱讀ARouter官網(wǎng)使用說(shuō)明

1酸员、添加依賴(lài)和配置

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}
dependencies {
    // 替換成最新版本, 需要注意的是api
    // 要與compiler匹配使用,均使用最新版可以保證兼容
    compile 'com.alibaba:arouter-api:x.x.x'
    annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
}
// 舊版本gradle插件(< 2.2)讳嘱,可以使用apt插件幔嗦,配置方法見(jiàn)文末'其他#4'
// Kotlin配置參考文末'其他#5'

2、添加注解

// 在支持路由的頁(yè)面上添加注解(必選)
// 這里的路徑需要注意的是至少需要有兩級(jí)沥潭,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}

3邀泉、初始化SDK

if (isDebug()) {           // 這兩行必須寫(xiě)在init之前,否則這些配置在init過(guò)程中將無(wú)效
    ARouter.openLog();     // 打印日志
    ARouter.openDebug();   // 開(kāi)啟調(diào)試模式(如果在InstantRun模式下運(yùn)行叛氨,必須開(kāi)啟調(diào)試模式呼渣!線上版本需要關(guān)閉,否則有安全風(fēng)險(xiǎn))
}
ARouter.init(mApplication); // 盡可能早,推薦在Application中初始化

4寞埠、發(fā)起路由操作

// 1. 應(yīng)用內(nèi)簡(jiǎn)單的跳轉(zhuǎn)(通過(guò)URL跳轉(zhuǎn)在'進(jìn)階用法'中)
ARouter.getInstance().build("/test/activity").navigation();
// 2. 跳轉(zhuǎn)并攜帶參數(shù)
ARouter.getInstance().build("/test/1")
            .withLong("key1", 666L)
            .withString("key3", "888")
            .withObject("key4", new Test("Jack", "Rose"))
            .navigation();

四屁置、Arouter核心思路

ARouter通過(guò)Apt技術(shù)(APT,就是Annotation Processing Tool 的簡(jiǎn)稱(chēng)仁连,就是可以在代碼編譯期間對(duì)注解進(jìn)行處理蓝角,并且生成Java文件,減少手動(dòng)的代碼輸入饭冬。)使鹅,生成保存路徑(路由path)和被注解(@Router)的組件類(lèi)的映射關(guān)系的類(lèi),利用這些保存了映射關(guān)系的類(lèi)昌抠,Arouter根據(jù)用戶(hù)的請(qǐng)求postcard(明信片)尋找到要跳轉(zhuǎn)的目標(biāo)地址(class),使用Intent跳轉(zhuǎn)患朱。
原理很簡(jiǎn)單,可以看出來(lái)炊苫,該框架的核心是利用apt生成的映射關(guān)系裁厅,這里要用到Apt技術(shù),讀者可以自行搜索了解一下侨艾。

分析

在自定義Application中進(jìn)行初始化:
// 盡可能早执虹,推薦在Application中初始化

ARouter.init(Application.this);

最終調(diào)用了LogisticsCenter.init(mContext, executor)

//LogisticsCenter.java
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {

      Set<String> routerMap;
      
      //1、遍歷“com.alibaba.android.arouter.routes”路徑下的類(lèi)并把其加入到set中
      if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
          // These class was generated by arouter-compiler.
          routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
          if (!routerMap.isEmpty()) {
             context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
          }
         // Save new version name when router map update finishes.
         PackageUtils.updateVersion(context);    
      } else {
          logger.info(TAG, "Load router map from cache.");
          routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
            }
            
            
      //2唠梨、遍歷set袋励,將root、group、provider分類(lèi)并填充到Warehouse路由表中
      for (String className : routerMap) {
          if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
              // This one of root elements, load root.
              ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
          } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
              // Load interceptorMeta
              ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
          } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
              // Load providerIndex
              ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
          }
      }
  }
}

LogisticsCenter.init方法比較長(zhǎng)茬故,上面只保留了核心代碼盖灸,我們來(lái)看上面這種加載方式,PackageUtils.isNewVersion(context)中判斷SharedPreferences(后面簡(jiǎn)稱(chēng)sp)里面是否有存儲(chǔ)versionName及versionCode均牢,如果沒(méi)有或者他們有更新的時(shí)候糠雨,需要重新加載一次com.alibaba.android.arouter.routes這個(gè)路徑下的類(lèi)名并填充到Set中,否則直接從sp中取數(shù)據(jù)并賦值到Set中去徘跪。接著就開(kāi)始遍歷這個(gè)Set,并通過(guò)Class.forName(className)這種反射方式去實(shí)例化類(lèi)并調(diào)用類(lèi)中的loadInto方法將注解對(duì)應(yīng)的索引信息添加到Warehouse路由表中。畫(huà)個(gè)圖來(lái)總結(jié)一下:


587163-75a58236057aacee.webp.jpg

ARouter跳轉(zhuǎn)

ARouter跳轉(zhuǎn)時(shí)琅攘,直接使用ARouter.getInstance().build("xxx/xxx").navigation()即可完成跳轉(zhuǎn)垮庐,那我們就來(lái)看一下源碼,看看里面都做了什么坞琴,首先是build方法:

/**
 * Build postcard by path and default group
 */
protected Postcard build(String path) {
    if (TextUtils.isEmpty(path)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        PathReplaceService pService = navigation(PathReplaceService.class);
        if (null != pService) {
            path = pService.forString(path);
        }
        return build(path, extractGroup(path));
    }
}

/**
 * Build postcard by uri
 */
protected Postcard build(Uri uri) {
    if (null == uri || TextUtils.isEmpty(uri.toString())) {
        throw new HandlerException(Consts.TAG + "Parameter invalid!");
    } else {
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            uri = pService.forUri(uri);
        }
        return new Postcard(uri.getPath(), extractGroup(uri.getPath()), uri, null);
    }
}

/**
 * Build postcard by path and group
 */
protected Postcard build(String path, String group) {
    if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            path = pService.forString(path);
        }
        return new Postcard(path, group);
    }
}

三個(gè)方法中都有哨查,PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class),這個(gè)類(lèi)是用來(lái)預(yù)處理path和uri的剧辐,調(diào)用方需要實(shí)現(xiàn)PathReplaceService就可以做預(yù)處理寒亥,如果不實(shí)現(xiàn),默認(rèn)pService==null荧关,那么直接走下面的去初始化Postcard實(shí)體類(lèi)溉奕。

接著來(lái)看navigation方法,因?yàn)閎uild方法返回的是PostCard類(lèi)忍啤,所以調(diào)用的是PostCard類(lèi)的navigation方法加勤,經(jīng)過(guò)一系列跳轉(zhuǎn),最終來(lái)到_ARouter.getInstance().navigation(mContext, postcard, requestCode, callback) :

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    try {
        LogisticsCenter.completion(postcard);
      } catch (NoRouteFoundException ex) {
        logger.warning(Consts.TAG, ex.getMessage());
     }
        return null;
    }

    if (null != callback) {
        callback.onFound(postcard);
    }

    if (!postcard.isGreenChannel()) {   
       // It must be run in async thread, maybe interceptor cost too mush time made ANR.
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
     
            @Override
            public void onContinue(Postcard postcard) {
                _navigation(context, postcard, requestCode, callback);
            }

            @Override
            public void onInterrupt(Throwable exception) {
                if (null != callback) {
                    callback.onInterrupt(postcard);
                }
            }
        });
    } else {
        return _navigation(context, postcard, requestCode, callback);
    }
    return null;
}

去除了部分無(wú)關(guān)代碼同波,只保留了核心代碼鳄梅,首先調(diào)用了LogisticsCenter.completion方法,我們追進(jìn)去看看:

//LogisticsCenter.java
/**
 * Completion the postcard by route metas
 *
 * @param postcard Incomplete postcard, should complete by this method.
 */
public synchronized static void completion(Postcard postcard) {
    // 從倉(cāng)庫(kù)的路由地址清單列表中拿到對(duì)應(yīng)的RouteMeta
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {    
        // 根據(jù)一級(jí)地址未檩,拿到對(duì)應(yīng)的路由地址清單的文件類(lèi)(ARouter$$Root$$工程名)
        Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
        if (null == groupMeta) {
            throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
        } else {
            // Load route and cache it into memory, then delete from metas.
            try {
                IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                iGroupInstance.loadInto(Warehouse.routes);
                Warehouse.groupsIndex.remove(postcard.getGroup());

            } catch (Exception e) {
                throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
            }

            completion(postcard);   // Reload
        }
    } else {
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());

        Uri rawUri = postcard.getUri();
        if (null != rawUri) {   // Try to set params into bundle.
            Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
            Map<String, Integer> paramsType = routeMeta.getParamsType();

            if (MapUtils.isNotEmpty(paramsType)) {
                // Set value by its type, just for params which annotation by @Param
                for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                    setValue(postcard,
                            params.getValue(),
                            params.getKey(),
                            resultMap.get(params.getKey()));
                }

                // Save params name which need auto inject.
                postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
            }

            // Save raw uri
            postcard.withString(ARouter.RAW_URI, rawUri.toString());
        }

        switch (routeMeta.getType()) {
            case PROVIDER:  // if the route is provider, should find its instance
                // Its provider, so it must implement IProvider
                Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                IProvider instance = Warehouse.providers.get(providerMeta);
                if (null == instance) { // There's no instance of this provider
                    IProvider provider;
                    try {
                        provider = providerMeta.getConstructor().newInstance();
                        provider.init(mContext);
                        Warehouse.providers.put(providerMeta, provider);
                        instance = provider;
                    } catch (Exception e) {
                        throw new HandlerException("Init provider failed! " + e.getMessage());
                    }
                }
                postcard.setProvider(instance);
                postcard.greenChannel();    // Provider should skip all of interceptors
                break;
            case FRAGMENT:
                postcard.greenChannel();    // Fragment needn't interceptors
            default:
                break;
        }
    }
}

這個(gè)類(lèi)很長(zhǎng)戴尸,但是邏輯還是很清晰的:首先從Warehouse路由表的routes中獲取RouteMeta,但是第一次獲取的時(shí)候?yàn)榭眨ㄒ驗(yàn)閕nit時(shí)只填充了Warehouse路由表的groupsIndex冤狡、interceptorsIndex孙蒙、providersIndex,還記得嗎?)筒溃,接著從Warehouse.groupsIndex中根據(jù)group的名字找到對(duì)應(yīng)的group索引马篮,并將生成的索引類(lèi)的map數(shù)據(jù)加載到Warehouse.routes中,然后把Warehouse.groupsIndex中對(duì)應(yīng)的group刪除掉怜奖,以免重復(fù)加載數(shù)據(jù)浑测,然后調(diào)用了completion(postcard)進(jìn)行重新加載。此時(shí)Warehouse.routes已經(jīng)不為空,根據(jù)path獲取對(duì)應(yīng)的RouteMeta迁央,就會(huì)走到else邏輯中掷匠,先是對(duì)PostCard設(shè)置了一堆屬性,最后對(duì)IProvider的子類(lèi)進(jìn)行了初始化并加載到Warehouse.providers中岖圈,同時(shí)也設(shè)置到PostCard中讹语,并給PROVIDER和FRAGMENT設(shè)置了綠色通道(不會(huì)被攔截)》淇疲總結(jié)一下:主要邏輯就是通過(guò)Warehouse.groupsIndex找到對(duì)應(yīng)的group并進(jìn)行加載顽决,實(shí)現(xiàn)了分組加載路由表。

我們繼續(xù)回到navigation方法中往下走导匣,首先通過(guò)postcard.isGreenChannel()判斷是否會(huì)攔截才菠,如果攔截,就會(huì)走interceptorService的邏輯(interceptorService是在afeterInit中初始化的)贡定,否則就走到了_navigation邏輯中赋访,那么來(lái)看_navigation方法:

private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    final Context currentContext = null == context ? mContext : context;

    switch (postcard.getType()) {
        case ACTIVITY:
            // Build intent
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());

            // Set flags.
            int flags = postcard.getFlags();
            if (-1 != flags) {
                intent.setFlags(flags);
            } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }

            // Set Actions
            String action = postcard.getAction();
            if (!TextUtils.isEmpty(action)) {
                intent.setAction(action);
            }

            // Navigation in main looper.
            runInMainThread(new Runnable() {
                @Override
                public void run() {
                    startActivity(requestCode, currentContext, intent, postcard, callback);
                }
            });

            break;
        case PROVIDER:
            return postcard.getProvider();
        case BOARDCAST:
        case CONTENT_PROVIDER:
        case FRAGMENT:
            Class fragmentMeta = postcard.getDestination();
            try {
                Object instance = fragmentMeta.getConstructor().newInstance();
                if (instance instanceof Fragment) {
                    ((Fragment) instance).setArguments(postcard.getExtras());
                } else if (instance instanceof android.support.v4.app.Fragment) {
                    ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                }

                return instance;
            } catch (Exception ex) {
                logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
            }
        case METHOD:
        case SERVICE:
        default:
            return null;
    }

    return null;
}

private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
    if (requestCode >= 0) {  
        // Need start for result
        if (currentContext instanceof Activity) {
            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
        }
    } else {
        ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
    }

    if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
        ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
    }

    if (null != callback) { // Navigation over.
        callback.onArrival(postcard);
    }
}

最終ARouter跳轉(zhuǎn)Activity最終也是用原生的Intent實(shí)現(xiàn)的,如果navigation()不傳入context缓待,則使用初始化時(shí)Application作為context蚓耽,如果是FRAGMENT、PROVIDER旋炒、CONTENT_PROVIDER步悠、BOARDCAST,通過(guò)反射方式初始化并返回即可国葬。

源碼分析之?dāng)r截處理

參考文檔

鏈接:路由框架ARouter使用及源碼解析
鏈接:ARouter源碼解析
鏈接:ARouter 官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贤徒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子汇四,更是在濱河造成了極大的恐慌接奈,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件通孽,死亡現(xiàn)場(chǎng)離奇詭異序宦,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)背苦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)互捌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人行剂,你說(shuō)我怎么就攤上這事秕噪。” “怎么了厚宰?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵腌巾,是天一觀的道長(zhǎng)遂填。 經(jīng)常有香客問(wèn)我,道長(zhǎng)澈蝙,這世上最難降的妖魔是什么吓坚? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮灯荧,結(jié)果婚禮上礁击,老公的妹妹穿的比我還像新娘。我一直安慰自己逗载,他們只是感情好哆窿,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著撕贞,像睡著了一般更耻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捏膨,一...
    開(kāi)封第一講書(shū)人閱讀 52,255評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音食侮,去河邊找鬼号涯。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锯七,可吹牛的內(nèi)容都是我干的链快。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼眉尸,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼域蜗!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起噪猾,我...
    開(kāi)封第一講書(shū)人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤霉祸,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后袱蜡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體丝蹭,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年坪蚁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奔穿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡敏晤,死狀恐怖贱田,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嘴脾,我是刑警寧澤男摧,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響彩倚,放射性物質(zhì)發(fā)生泄漏筹我。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一帆离、第九天 我趴在偏房一處隱蔽的房頂上張望蔬蕊。 院中可真熱鬧,春花似錦哥谷、人聲如沸岸夯。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)猜扮。三九已至,卻和暖如春监婶,著一層夾襖步出監(jiān)牢的瞬間旅赢,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工惑惶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留煮盼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓带污,卻偏偏與公主長(zhǎng)得像僵控,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鱼冀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

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