Android組件化架構(gòu) —— 基礎(三) - ARouter

xwzz.jpg

前篇回顧

上篇文章揖铜,我們了解到“路由”是如何進行工作的彤委,并嘗試手寫了實現(xiàn)代碼裳食;

本篇,以ARouter為例辨赐,我們將剖析它是如何完成工作的,并通過源碼了解其原理京办。

還是以Activity跳轉(zhuǎn)掀序、Fragment獲取跨模塊功能調(diào)用三個方向為出發(fā)點惭婿,看看ARouter將如何實現(xiàn)這些功能不恭。

集成ARouter

咱們先快速將ARouter集成到項目中,當然你也可以參照官方文檔來操作财饥。

第一篇文章中换吧,我們將業(yè)務模塊的依賴關(guān)系集中到了config.gradle文件中,現(xiàn)修改如下:

  • 1钥星、config.gradle中管理依賴版本
ext { 
    // ARouter 版本號
    arouter_api_version = "1.5.2"
    arouter_compiler_version = "1.5.2"

    //各模塊引入的第三方公共庫 
    dependenciesImport = [
            ...
            ...
            arouter_api     : "com.alibaba:arouter-api:$arouter_api_version"
    ]

    //注解處理器 
    dependenciesKapt = [
            ...
            ...
            arouter: "com.alibaba:arouter-compiler:$arouter_compiler_version"
    ]
}
  • 2沾瓦、子模塊的build.gradle中,傳遞APT所需參數(shù)谦炒,并引入依賴
def dependenciesImport = rootProject.ext.dependenciesImport
def dependenciesKapt = rootProject.ext.dependenciesKapt

android {
    defaultConfig {
        ...
        ...
        // ARouter APT 傳參
        kapt {
            arguments {
                arg("AROUTER_MODULE_NAME", project.getName())
            }
        }   
    }
}

dependencies {

    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation project(":lib_comm")

    // 導入依賴庫
    dependenciesImport.each { k, v -> implementation(v) }
    // 導入依賴的注解處理器
    dependenciesKapt.each { k, v -> kapt(v) }
}
  • 3贯莺、初始化ARouter
class App : Application() {

    override fun onCreate() {
        super.onCreate()
        initRouter()
    }

    private fun initRouter() {
        if (BuildConfig.DEBUG) {
            ARouter.openLog()   // 打印日志
            ARouter.openDebug() // 開啟調(diào)試模式
        }
        ARouter.init(this) // 初始化
    }
}
  • 4、運行查看Log記錄:
2021-09-13 17:47:07.911 10088-10088/com.ljb.component I/ARouter::: ARouter init success![ ] 

ARouter - Activity跳轉(zhuǎn)

根據(jù)官方文檔宁改,只需2步便可完成功能:

  • 1缕探、注解標記需要跳轉(zhuǎn)的Activity
@Route(path = "/user/UserMainActivity" , group =  "user")
class UserMainActivity : AppCompatActivity() {
      ...
}
  • 2、發(fā)起跳轉(zhuǎn)
ARouter.getInstance().build("/user/UserMainActivity").navigation()

很神奇还蹲!與前篇我們實現(xiàn)的路由相比爹耗,ARouter的代碼要簡潔很多,完全不需要手動注冊路由就可完成跳轉(zhuǎn)谜喊,它是怎么做到的呢潭兽?

通過跟進navigation()函數(shù)調(diào)用過程,我們把目光聚焦到兩個容器中:

// ARouter源碼
class Warehouse {
    // Cache route and metas
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
    static Map<String, RouteMeta> routes = new HashMap<>();
    
    ...
}
  • Warehouse.groupsIndex: 用于存儲所有的路由組
public interface IRouteGroup {
    /**
     * Fill the atlas with routes in group.
     * atlas用于存儲當前組里的所有路由锅论,實際傳入的就是Warehouse.routes
     */
    void loadInto(Map<String, RouteMeta> atlas);
}
  • Warehouse.routes:用于存儲已注冊的所有路由
 // 路由包裝類讼溺,路由目標的Class對象就存儲在這里面
public class RouteMeta {
    private RouteType type;         // Type of route
    private Element rawType;        // Raw type of route
    private Class<?> destination;   // Destination
    private String path;            // Path of route
    private String group;           // Group of route
    private int priority = -1;      // The smaller the number, the higher the priority
    private int extra;              // Extra data
    private Map<String, Integer> paramsType;  // Param type
    private String name;
    
    ...
}

ARouter對路由提出了分組概念,上面 UserMainActivity就屬于user組下最易,當路由path存在2級及以上時怒坯,group字段也可以省略炫狱,ARouter默認會使用第一個反斜杠后面的path作為組名。

// group可省略不寫
@Route(path = "/user/UserMainActivity")
class UserMainActivity : AppCompatActivity() {
      ...
}

一般情況下剔猿,我們會將同一模塊的路由劃分在同一個組下视译,例如App模塊下的所有路由都在“app”這個分組下 , user模塊的路由都在“user”分組下;當然归敬,同一模塊擁有多個分組也是完全可行的酷含,只要保證與其它模塊中的路由分組不重名即可。

通過翻閱源碼汪茧,分析這兩個容器的作用椅亚,大致如下:

  • 1、當傳入path進行跳轉(zhuǎn)時舱污,優(yōu)先從Warehouse.routes中直接獲取路由對象呀舔;
  • 2、路由對象不存在扩灯,就需要通過Warehouse.groupsIndex路由組來完成注冊功能媚赖;
  • 3、注冊成功后珠插,當前path所在組的所有路由都將存儲到Warehouse.routes中惧磺;
  • 4、回到第1步捻撑,獲取路由對象磨隘;
  • 5、讀取路由對象信息布讹;
  • 6琳拭、完成跳轉(zhuǎn)。

下方是ARouter源碼中的實現(xiàn)代碼描验,我把不重要的部分進行了刪減:

//ARouter源碼 
public class LogisticsCenter {
    ...

    public synchronized static void completion(Postcard postcard) {
        
        //1白嘁、當傳入path進行跳轉(zhuǎn)時,優(yōu)先從Warehouse.routes中直接獲取路由包裝對象膘流;
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());

        if (null == routeMeta) {
            
            //2絮缅、包裝對象不存在,就需要通過Warehouse.groupsIndex路由組來完成注冊功能呼股;
            addRouteGroupDynamic(postcard.getGroup(), null);
            
            //4耕魄、回到第1步,獲取包裝對象彭谁;
            completion(postcard);   
        } else {
            // 5吸奴、讀取包裝對象信息;
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            
            ...
        }
    
    
    public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        
        if (Warehouse.groupsIndex.containsKey(groupName)){
            //3、注冊成功后,當前path所在組的所有路由都將存儲到Warehouse.routes中则奥;
            Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
            Warehouse.groupsIndex.remove(groupName);
        }


        if (null != group) {
            // 注冊本組路由
            group.loadInto(Warehouse.routes);
        }
    }

    ...
}

final class _ARouter {

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
            ...

            LogisticsCenter.completion(postcard);
            
            ...         
            // 6考润、完成跳轉(zhuǎn)。
            switch (postcard.getType()) {
                case ACTIVITY:
                    // Build intent
                    final Intent intent = new Intent(currentContext, postcard.getDestination());
                    intent.putExtras(postcard.getExtras());
                    
                    //...
                    // Navigation in main looper.
                    runInMainThread(new Runnable() {
                        @Override
                        public void run() {
                            startActivity(requestCode, currentContext, intent, postcard, callback);
                        }
                    });
                    break读处;
                
                ...
            }
        ...
    
    }
}

整個跳轉(zhuǎn)的源代碼過程不難理解糊治,但關(guān)鍵點在于:

  • Warehouse.groupsIndex容器是何時將路由組加載至內(nèi)存的?
  • group.loadInto(Warehouse.routes)又是如何完成注冊的罚舱?

回答這兩個問題井辜,需回過頭來看ARouter初始化都做了什么。

 ARouter.init(this)   // ARouter初始化

隨著代碼的跟進管闷,我們會發(fā)現(xiàn)下面這幾行代碼:

//ARouter源碼 
public final class Consts {

  public static final String SDK_NAME = "ARouter";
  public static final String SEPARATOR = "$$";
  public static final String SUFFIX_ROOT = "Root";
  public static final String DOT = ".";
  public static final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes";
}
  
public class LogisticsCenter {

    public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
    
            // 獲取com.alibaba.android.arouter.routes下的所有class文件
            routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
            
    
            for (String className : routerMap) {
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {

                    // 將名為ARouter$$Root開頭的Class對象創(chuàng)建出來粥脚,并調(diào)用loadInto(),將分組信息加載進Warehouse.groupsIndex中
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                }
                ...
            }
    }

}

源碼中會從com.alibaba.android.arouter.routes包下讀取所有的Class文件包个,并將文件名以ARouter$$Root為開頭的Class文件挑選出來阿逃,最后通過反射創(chuàng)建對象,并調(diào)用其loadInto()函數(shù)赃蛛,注意這里傳入?yún)?shù)正是Warehouse.groupsIndex,我們的路由組容器搀菩,從而將分組信息載入Warehouse.groupsIndex中。

果不其然肪跋,我們在user模塊的build文件夾下找到了這些文件:

ARouter生成的文件.png
  • 關(guān)于ARouter$$Root 前綴文件歧蒋,可以看到其后綴是當前模塊名module_user,而其loadInto()函數(shù)主要是將user模塊下涉及到的所有路由組存儲到Warehouse.groupsIndex中州既;
public class ARouter$$Root$$module_user implements IRouteRoot {

  @Override
  public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    routes.put("user", ARouter$$Group$$user.class);
  }
}
  • 關(guān)于ARouter$$Group前綴文件谜洽,其后綴是當前路由所屬的的分組名user,前面在梳理Activity跳轉(zhuǎn)的過程中吴叶,我們知道當路由未注冊時阐虚,會通過分組名獲取到組,并創(chuàng)建組對象調(diào)用其loadInto()函數(shù)完成對Warehouse.routes的注冊蚌卤,此文件中編寫的既是具體的注冊過程实束。
//3、注冊成功后逊彭,當前path所在組的所有路由都將存儲到Warehouse.routes中咸灿;
Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);


public class ARouter$$Group$$user implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    // loadInto被調(diào)用時,完成對Warehouse.routes的注冊
    atlas.put("/user/AppMainActivity", RouteMeta.build(RouteType.ACTIVITY, AppMainActivity.class, "/user/appmainactivity", "user", null, -1, -2147483648));
  }
}

ARouter - APT技術(shù)

通過上述分析侮叮,我們知道路由的注冊離不開ARouter生成的這兩個文件避矢,那這兩文件又是怎么來的呢?

這里不得不提到APT技術(shù)( Annotation Processing Tool),譯:注解處理器审胸。如果對該技術(shù)有所耳聞亥宿,你應該知道在Android中我們常用的EventBusButterknife歹嘹、Dagger2等框架都有它的身影箩绍。如果你是第一次聽說也沒關(guān)系,暫時只需知道通過這門技術(shù)可以做到“在編譯期時尺上,可使編譯器為我們生成具有一定規(guī)則的模板性代碼”即可材蛛。

顯然,這兩個文件就是ARouter通過APT生成的模板代碼怎抛,至于具體的實現(xiàn)過程僚饭,本篇中不做過多闡述搓劫,后續(xù)處理自定義路由時,我們會嘗試編寫一個自己的注解處理器,到時再細聊APT技術(shù)的相關(guān)細節(jié)炫欺。
不過我把ARouter注解處理器中生成這兩個文件的核心代碼貼在下方,看懂它的思路即可:

//ARouter 注解處理器源碼 
@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor {

   @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        // 獲取所有被 @Route 注解標記的類元素
        Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
        
        //解析這些類元素
        this.parseRoutes(routeElements);
        
    }
    
    
    private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
    
        // 解析元素
        for (Element element : routeElements) {
        
            if (types.isSubtype(tm, type_Activity)) {
                // Activity              
                routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);  
                        
                Set<RouteMeta> routeMetas = groupMap.get(routeMete.getGroup()); 
                routeMetaSet.add(routeMete);
                groupMap.put(routeMete.getGroup(), routeMetaSet);               
            }
        }
        
        // 根據(jù)解析元素生成Java文件
        for (Map.Entry<String, Set<RouteMeta>> entry : groupMap.entrySet()) {
          // 生成ARouter$$Group$$前綴文件
          String groupFileName = NAME_OF_GROUP + groupName;
          JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                TypeSpec.classBuilder(groupFileName)
                        .addJavadoc(WARNING_TIPS)
                        .addSuperinterface(ClassName.get(type_IRouteGroup))
                        .addModifiers(PUBLIC)
                        .addMethod(loadIntoMethodOfGroupBuilder.build())
                        .build()
            ).build().writeTo(mFiler);
        }
        
        // 生成ARouter$$Root$$前綴文件
        String groupFileName = NAME_OF_GROUP + groupName;
        JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
                TypeSpec.classBuilder(groupFileName)
                        .addJavadoc(WARNING_TIPS)
                        .addSuperinterface(ClassName.get(type_IRouteGroup))
                        .addModifiers(PUBLIC)
                        .addMethod(loadIntoMethodOfGroupBuilder.build())
                        .build()
        ).build().writeTo(mFiler);
    }
}

至此筒狠,整個Activity跳轉(zhuǎn)涉及到的源碼大致就是這些汪诉,總結(jié)下ARouter都做了什么:

  • 1、在編譯期椭赋,ARouter注解處理器將所有@Route注解標記的類查找出來抚岗,并根據(jù)這些類的相關(guān)信息生成ARouter$$相關(guān)文件;
  • 2哪怔、ARouter.init()初始化時宣蔚,將ARouter$$Group前綴文件涉及到的路由組存儲到Warehouse.groupsIndex中;
  • 3认境、調(diào)用ARouter...navigation()跳轉(zhuǎn)頁面時胚委,通過Warehouse.groupsIndex查詢路由所在組,將路由組中的所有路由注冊到Warehouse.routes中叉信,從Warehouse.routes中取出路由信息(包含Activity.class對象)亩冬,完成跳轉(zhuǎn)。

Fragment獲取

依舊根據(jù)文檔茉盏,獲取Fragment歩奏也只需2步:

  • 1鉴未、注解標記需要獲取的Fragemnt
@Route(path = "/user/UserFragment")
class UserFragment : Fragment() {
    ...
}
  • 2、獲取Fragment
val f  = ARouter.getInstance().build("/user/UserFragment").navigation() as Fragment

有了對Activity跳轉(zhuǎn)源碼分析的鋪墊鸠姨,F(xiàn)ragment的獲取原理基本是一樣的铜秆,只是在最后一步發(fā)生跳轉(zhuǎn)的代碼,變?yōu)榉祷谾ragment對象:

final class _ARouter {

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        ...         
        // 6讶迁、完成跳轉(zhuǎn)连茧。
        switch (postcard.getType()) {
            
            // Activity跳轉(zhuǎn)
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());
                
                //...
                // Navigation in main looper.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });
                break;
            
            // Fragment獲取
            case FRAGMENT:
                Class<?> fragmentMeta = postcard.getDestination();
                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;
            ...
        }
        ...
    }
}

跨模塊功能調(diào)用

ARouter的方案和我們之前的實現(xiàn)的方案原理是一樣的,都需將對外的服務暴露到lib_comm中啸驯,只是如Activity和Fragment一樣注冊的功能通過APT技術(shù)來實現(xiàn)客扎。

  • 1、lib_comm中定義暴露的接口
interface IUserService2 : IProvider {

    /**
     * 是否登錄
     */
    fun isLogin(): Boolean
}
  • 2罚斗、業(yè)務模塊實現(xiàn)接口徙鱼,并通過@Route注解標記
@Route(path = "/user/UserService")
class IUserServiceImpl2 : IUserService2 {

    override fun init(context: Context?) {

    }

    override fun isLogin(): Boolean {
        return UserUtils.isLogin()
    }

}
  • 3、獲取服務
ARouter.getInstance().build("/user/UserService").navigation() as IUserService2

和Fragment類似针姿,只不過在獲取時袱吆,多了一步緩存操作,緩存相關(guān)的核心代碼我貼在了下方:

class Warehouse {
    // Cache provider
    static Map<Class, IProvider> providers = new HashMap<>();
}

// 創(chuàng)建服務距淫,并緩存
 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) {
                logger.error(TAG, "Init provider failed!", e);
                throw new HandlerException("Init provider failed!");
            }
        }
        postcard.setProvider(instance);
        postcard.greenChannel();    // Provider should skip all of interceptors
        break;
}

獲取時绞绒,只需從postcard.getProvider()中取即可:

final class _ARouter {

    protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        ...         
        // 6、完成跳轉(zhuǎn)榕暇。
        switch (postcard.getType()) {
            
            // Activity跳轉(zhuǎn)
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());
                
                //...
                // Navigation in main looper.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });
                break蓬衡;
            
            // Fragment獲取
            case FRAGMENT:
                Class<?> fragmentMeta = postcard.getDestination();
                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;

            // 獲取Service
            case PROVIDER:
                return postcard.getProvider();
            ...
        }
        ...
    }
}

OK,ARouter路由核心三劍客功能已摸了個底朝天彤枢,與我們自己實現(xiàn)的路由框架相比狰晚,其核心思想是一樣的,但在細節(jié)上ARouter的處理明顯優(yōu)于我們缴啡,例如:

  • 通過APT來實現(xiàn)路由注冊家肯,省掉手動注冊的繁瑣,且無需在App啟動時注冊所有路由(與我們startup方案相比)盟猖,僅在使用到某組路由時才進行注冊,避免不必要的內(nèi)存開銷换棚。
  • 對外提供功能的Service對象式镐,在使用時進行緩存,避免重復創(chuàng)建固蚤。

小結(jié)

本篇對ARouter的Activity跳轉(zhuǎn)娘汞、Fragment獲取、擴模塊功能調(diào)用花費了大幅篇章來講解源碼夕玩,是希望讀者能了解其基本原理你弦,后續(xù)我在處理自定義路由時,會參考ARouter實現(xiàn)來造輪子燎孟,也方便讀者對后續(xù)篇章的理解禽作。

ARouter還支持很多功能,例如:攔截器揩页、標準Scheme URL跳轉(zhuǎn)旷偿、全局降級策略、動態(tài)路由注冊等,這些功能后續(xù)篇章中一部分會講到萍程,一部分也可能使用不到幢妄,但并不代表這些功能沒用,每個項目或團隊對組件化架構(gòu)的理解都會有所不同茫负,最后落地的項目結(jié)構(gòu)蕉鸳、路由交互方案也就各具特色。無論是怎樣的項目忍法,個人認為:

  • 組件化潮尝,它是由實際業(yè)務開發(fā)過程中,開發(fā)人員通過經(jīng)驗積累缔赠,設計出的一套適用于多人協(xié)作開發(fā)的架構(gòu)模式衍锚;
  • 具備可復用性、可替代性嗤堰、熱插拔戴质、可獨立調(diào)試特性的模塊,就是組件化模塊(組件)踢匣;
  • 由多個組件化模塊組成的項目架構(gòu)告匠,就是組件化架構(gòu)。

下篇离唬,聊聊組件通訊之Scheme URL后专,我們下篇再見!

Android組件化架構(gòu) —— 基礎(四) URL Scheme

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末输莺,一起剝皮案震驚了整個濱河市戚哎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嫂用,老刑警劉巖型凳,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嘱函,居然都是意外死亡甘畅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門往弓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疏唾,“玉大人,你說我怎么就攤上這事函似』痹啵” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵撇寞,是天一觀的道長准给。 經(jīng)常有香客問我泄朴,道長,這世上最難降的妖魔是什么露氮? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任祖灰,我火速辦了婚禮,結(jié)果婚禮上畔规,老公的妹妹穿的比我還像新娘局扶。我一直安慰自己,他們只是感情好叁扫,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布三妈。 她就那樣靜靜地躺著,像睡著了一般莫绣。 火紅的嫁衣襯著肌膚如雪畴蒲。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天对室,我揣著相機與錄音模燥,去河邊找鬼。 笑死掩宜,一個胖子當著我的面吹牛蔫骂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播牺汤,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼辽旋,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了檐迟?” 一聲冷哼從身側(cè)響起补胚,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎追迟,沒想到半個月后糖儡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡怔匣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了桦沉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片每瞒。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖纯露,靈堂內(nèi)的尸體忽然破棺而出剿骨,到底是詐尸還是另有隱情,我是刑警寧澤埠褪,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布浓利,位于F島的核電站挤庇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏贷掖。R本人自食惡果不足惜嫡秕,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望苹威。 院中可真熱鬧昆咽,春花似錦、人聲如沸牙甫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽窟哺。三九已至泻轰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間且轨,已是汗流浹背浮声。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留殖告,地道東北人阿蝶。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像黄绩,于是被迫代替她去往敵國和親羡洁。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

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