插件式換膚框架搭建 - setContentView源碼閱讀

1. 概述


內(nèi)涵段子架構(gòu)第一階段已經(jīng)更新完了嗜浮,后面我們主要是以google源碼為主李滴,今天我?guī)Т蠹襾砜匆幌聅etContentView的源碼,請先看一下如果繼承自Activity去打印一個TextView與繼承自AppCompatActivity去打印一個TextView分別是這樣的:

繼承自Activity:  
android.widget.TextView{ac5cd17 V.ED..... ......ID 0,0-0,0 #7f0b002c app:id/text_view}

繼承自AppCompatActivity:  
android.support.v7.widget.AppCompatTextView{392562b V.ED..... ......ID 0,0-0,0 #7f0b0055 app:id/text_view}

誰能告訴我這到底是怎么啦再悼?我布局里面明明是TextView為什么繼承自AppCompatActivity就變成了AppCompatTextView缎除,那么接下來我們就來看一下源碼到底是怎么把我的TextView給拐走的。

所有分享大綱:2017Android進(jìn)階之路與你同行

視頻講解地址:https://pan.baidu.com/s/1qYl2AOO

2. Activity的setContentView源碼閱讀


2.1 很多人都問過我怎么看源碼荒辕,我只想說怎么看汗销?當(dāng)然是坐著點進(jìn)去看懊⒋狻!

    public void setContentView(@LayoutRes int layoutResID) {
        // 獲取Window 調(diào)用window的setContentView方法大溜,發(fā)現(xiàn)是抽象類化漆,所以需要找具體的實現(xiàn)類PhoneWindow
        getWindow().setContentView(layoutResID);
    }

    // PhoneWindow 中的 setContentView方法
    @Override
    public void setContentView(int layoutResID) {
        // 如果mContentParent 等于空,調(diào)用installDecor();
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        // 把我們自己的布局layoutId加入到mContentParent钦奋,我們set進(jìn)來的布局原來是放在這里面的Soga
        mLayoutInflater.inflate(layoutResID, mContentParent)座云;
    }

2.2 installDecor(),這個之前已經(jīng)帶大家看過一遍了付材,不過沒辦法再進(jìn)來看看吧:

    // This is the top-level view of the window, containing the window decor.
    // 看到這解釋木有朦拖?
    private DecorView mDecor;

    private void installDecor() {    
        if (mDecor == null) {
           // 先去創(chuàng)建一個  DecorView 
           mDecor = generateDecor(-1);
        }
        // ......
        // 省略調(diào)一些代碼,看著暈厌衔,不過這也太省了璧帝。
        if (mContentParent == null) {
           mContentParent = generateLayout(mDecor);
        }
    }
    
    // generateDecor 方法
    protected DecorView generateDecor(int featureId) {
        // 就是new一個DecorView ,DecorView extends FrameLayout 不同版本的源碼有稍微的區(qū)別富寿,
        // 低版本DecorView 是PhoneWindow的內(nèi)部類睬隶,高版本是一個單獨的類,不過這不影響页徐。
        return new DecorView(context, featureId, this, getAttributes());
    }

    protected ViewGroup generateLayout(DecorView decor) {
        // Inflate the window decor.
        // 我看你到底怎么啦
        int layoutResource;
        // 都是一些判斷苏潜,發(fā)現(xiàn) layoutResource = 系統(tǒng)的一個資源文件,
        if(){}else if(){}else if(){
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple;
            // System.out.println("Simple!");
        }
        
        mDecor.startChanging();
        // 把布局解析加載到  DecorView 而加載的布局是一個系統(tǒng)提供的布局,不同版本不一樣
        // 某些源碼是 addView() 其實是一樣的
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        //  ID_ANDROID_CONTENT 是 android.R.id.content变勇,這個View是從DecorView里面去找的恤左,
        //  也就是    從系統(tǒng)的layoutResource里面找一個id是android.R.id.content的一個FrameLayout
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        
        // 返回
        return contentParent;
    }

其實看源碼一定要帶著最初的出發(fā)點來看,要不然里面太多了根本找不到方向搀绣,如果帶著思想來看那么就算跑偏了也可以從新再回來飞袋,我目前就是想弄清楚我們的 setContentView() 系統(tǒng)到底把我們的布局加到哪里去了。我先用文字總結(jié)一下链患,然后去畫一張圖:

  • Activity里面設(shè)置setContentView()巧鸭,我們的布局顯示主要是通過PhoneWindow,PhoneWindow獲取實例化一個DecorView锣险。
  • 實例化DecorView蹄皱,然后做一系列的判斷然后去解析系統(tǒng)的資源layoutId文件览闰,至于解析哪一個資源文件會做判斷比如有沒有頭部等等芯肤,把它解析加載到DecorView,資源layout里面有一個View的id是android.R.id.content压鉴。
  • 我們自己通過setContentView設(shè)置的布局id其實是解析到mParentContent里面的崖咨,也就是那個id叫做android.R.id.content的FarmeLayout,好了就這么多了油吭。

3. AppCompatActivity的setContentView

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        // 跟我在網(wǎng)上看的完全不一樣
        getDelegate().setContentView(layoutResID);
    }
   

    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
    
    // window 還是那個window 击蹲,留意一下就行 , 不同的版本返回 AppCompatDelegateImpl署拟,但是都是相互繼承
    // 最終繼承都是繼承  AppCompatDelegateImplV9 有的版本V7有的V9 好麻煩 嗨!
    private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        final int sdk = Build.VERSION.SDK_INT;
        if (BuildCompat.isAtLeastN()) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (sdk >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (sdk >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (sdk >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);
        }
    }

    // 下面其實就沒啥好看的了歌豺,一個一個點進(jìn)去推穷,仔細(xì)看看就好了。與Activity沒啥區(qū)別了
    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }

    private void ensureSubDecor() {
        mSubDecor = createSubDecor();
    }

4. AppCompatViewInflater源碼分析


看到這里還是不知道為什么我的TextView變成了AppCompatTextView类咧,找啊找啊就找了這么個方法:

    @Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            // 把LayoutInflater 的 Factory設(shè)置為了this馒铃,也就說待會創(chuàng)建View就會走自己的onCreateView方法
            // 如果看不懂還需要看一下 LayoutInflater 的源碼,我們的LayoutInflater.from(mContext)其實是一個單例
            // 如果設(shè)置了Factory那么每次創(chuàng)建View都會先執(zhí)行Factory的onCreateView方法
            LayoutInflaterCompat.setFactory(layoutInflater, this);
        } else {
            if (!(LayoutInflaterCompat.getFactory(layoutInflater)
                    instanceof AppCompatDelegateImplV7)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }

    @Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        // 看一下是不是 5.0 痕惋,5.0 都自帶什么效果我就不說了
        final boolean isPre21 = Build.VERSION.SDK_INT < 21;

        if (mAppCompatViewInflater == null) {
            mAppCompatViewInflater = new AppCompatViewInflater();
        }

        // We only want the View to inherit its context if we're running pre-v21
        final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent);
        // 通過 AppCompatViewInflater 去創(chuàng)建View
        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */
                true, /* Read read app:theme as a fallback at all times for legacy reasons */
                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
        );
    }

其實在講數(shù)據(jù)庫優(yōu)化我們已經(jīng)看過一次AppCompatViewInflater的源碼了区宇,創(chuàng)建View都是用的反射,只不過做了緩存和優(yōu)化而已值戳,我們寫代碼其實可以仿照源碼來议谷,給我們很好的思路。

public final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {

        View view = null;
        // 果真找到你了堕虹,哈哈 卧晓,做了替換
        // We need to 'inject' our tint aware Views in place of the standard framework versions
        switch (name) {
            case "TextView":
                view = new AppCompatTextView(context, attrs);
                break;
            case "ImageView":
                view = new AppCompatImageView(context, attrs);
                break;
            case "Button":
                view = new AppCompatButton(context, attrs);
                break;
            case "EditText":
                view = new AppCompatEditText(context, attrs);
                break;
            // .........
        }

        if (view == null && originalContext != context) {
            // If the original context does not equal our themed context, then we need to manually
            // inflate it using the name so that android:theme takes effect.
            view = createViewFromTag(context, name, attrs);
        }
        return view;
    }

private View createView(Context context, String name, String prefix)
            throws ClassNotFoundException, InflateException {
        // 先從構(gòu)造緩存里面獲取
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        try {
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                Class<? extends View> clazz = context.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                // 利用反射創(chuàng)建一個構(gòu)造函數(shù)
                constructor = clazz.getConstructor(sConstructorSignature);
                sConstructorMap.put(name, constructor);
            }
            constructor.setAccessible(true);
            // 利用反射創(chuàng)建View的實例
            return constructor.newInstance(mConstructorArgs);
        } catch (Exception e) {
            // We do not want to catch these, lets return null and let the actual LayoutInflater
            // try
            return null;
        }
}

5. LayoutInflater源碼分析


LayoutInflater的源碼我們分三個步驟去看相對來說會更加的系統(tǒng):
  4. 1 如何獲取LayoutInflater?
  4. 2 如何使用LayoutInflater赴捞?
  4. 3 布局的View是如何被實例化的禀崖?

先來看看我們平時都是怎么去獲取LayoutInflater的,這個我們其實并不陌生LayoutInflater.from(context):

    /**
    * Obtains the LayoutInflater from the given context.
    */
    // 是一個靜態(tài)的方法
    public static LayoutInflater from(Context context) {
        // 通過context獲取系統(tǒng)的服務(wù)
        LayoutInflater LayoutInflater =
                // context.getSystemService()是一個抽象類螟炫,所以我們必須找到實現(xiàn)類ContextImpl
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

   
    // ContextImpl 里面的實現(xiàn)方法
    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

    /**
     * Gets a system service from a given context.
     */
    // SystemServiceRegistry 里面的getSystemService方法
    public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
    
    // 這是一個靜態(tài)的HashMap集合
    private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
    
    // 靜態(tài)的代碼塊中
    static{
         // 注冊LayoutInflater服務(wù)
         registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
         }
         // 注冊很多的其他服務(wù)......
    }

接下來大致的整理一下獲取LayoutInflater的思路波附,通過Context的實現(xiàn)類ContextImpl獲取的,最終是通過SystemServiceRegistry.getSystemService()方法昼钻,而SYSTEM_SERVICE_FETCHERS是一個靜態(tài)的HashMap掸屡,初始化是在靜態(tài)代碼塊中通過registerService注冊了很多服務(wù)。所以到目前為止我們有兩個思想對于我們后面插件化的皮膚框架有很大的關(guān)系然评,第一LayoutInflater其實是一個系統(tǒng)的服務(wù)仅财,第二每次通過LayoutInflater.form(context)是一個靜態(tài)的單例類無論在哪里獲取都是同一個對象。接下來我們來看一下加載布局的三種方式:

1.View.inflate(context,layoutId,parent);
2.LayoutInflater.from(context).inflate(layoutId,parent);
3.LayoutInflater.from(context).inflate(layoutId,parent,attachToRoot);

1.View.inflate(context,layoutId,parent);

    // 其實就是調(diào)用的  LayoutInflater.from(context).inflate(layoutId,parent);
    public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
        LayoutInflater factory = LayoutInflater.from(context);
        return factory.inflate(resource, root);
    }

2.LayoutInflater.from(context).inflate(layoutId,parent);

    // 其實就是調(diào)用的 LayoutInflater.from(context).inflate(layoutId,parent,attachToRoot);
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
         return inflate(resource, root, root != null);
    }

3.LayoutInflater.from(context).inflate(layoutId,parent,attachToRoot); 其實最終都是調(diào)用的該方法盏求,我們關(guān)鍵是要弄清楚這個參數(shù)的概念亿眠,尤其是attachToRoot:

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }
        // 獲取一個 XmlResourceParser 解析器,這個應(yīng)該并不陌生纳像,就是待會需要去解析我們的layoutId.xml文件
        // 這個到后面的插件化架構(gòu)再去詳細(xì)講解
        final XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        ......
        //保存?zhèn)鬟M(jìn)來的這個view
        View result = root;

        try {
            // Look for the root node.
            int type;
            //在這里找到root標(biāo)簽
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }

            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }
            //獲取這個root標(biāo)簽的名字
            final String name = parser.getName();
             ......

            //判斷是否merge標(biāo)簽
            if (TAG_MERGE.equals(name)) {
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }
                //這里直接加載頁面,忽略merge標(biāo)簽,直接傳root進(jìn)rInflate進(jìn)行加載子view
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                //通過標(biāo)簽來獲取view
                //先獲取加載資源文件中的根view
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                //布局參數(shù)          
                ViewGroup.LayoutParams params = null;

                //關(guān)鍵代碼A
                if (root != null) {
                    params = root.generateLayoutParams(attrs);
                    if (!attachToRoot) {
                        //temp設(shè)置布局參數(shù)
                        temp.setLayoutParams(params);
                    }
                }
                  ......
                //關(guān)鍵代碼B
                //在這里憔购,先獲取到了temp,再把temp當(dāng)做root傳進(jìn)去rInflateChildren
                //進(jìn)行加載temp后面的子view
                rInflateChildren(parser, temp, attrs, true);
                  ......

                 //關(guān)鍵代碼C
                if (root != null && attachToRoot) {
                    //把view添加到root中并設(shè)置布局參數(shù)
                    root.addView(temp, params);
                }

                //關(guān)鍵代碼D
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

        } catch (XmlPullParserException e) {
            ......
        } catch (Exception e) {
            ......
        } finally {
            ......
        }

        return result;
       }
    }
    // 創(chuàng)建View
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        // ......
        try {
            // 創(chuàng)建我們的View
            View view;
            if (mFactory2 != null) {
                // 先通過mFactory2 創(chuàng)建宫峦,其實在 AppCompatActivity里面會走這個方法,也就會去替換某些控件
                // 所以我們就 看到了上面的內(nèi)容
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                // 走mFactory 
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }
            // ......省略
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    // 判斷是不是自定義View玫鸟,自定義View在布局文件中com.hc.BannerView是個全類名导绷,
                    // 而系統(tǒng)的View在布局文件中不是全類名 TextView
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch (InflateException e) {
            // ........
        }
    }
    // 創(chuàng)建View
    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        // 做一些反射的性能優(yōu)化

        try {
            // 先從緩存中拿,這是沒拿到的情況
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                // 加載 clazz
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                
                // 創(chuàng)建View的構(gòu)造函數(shù)
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                // 加入緩存集合集合
                sConstructorMap.put(name, constructor);
            } else {
                
            }
            // 通過反射創(chuàng)建View
            final View view = constructor.newInstance(args);
            return view;

        } catch (NoSuchMethodException e) {
            // ......省略部分代碼
        }
    }

這里有兩個思想比較重要第一個View的創(chuàng)建是通過當(dāng)前View的全類名反射實例化的View,第二個View的創(chuàng)建首先會走mFactory2屎飘,然后會走mFactory诵次,只要不為空先會去執(zhí)行Factory的onCreateView方法,最后才會走系統(tǒng)的LayoutInflater里面的createView()方法枚碗,所以我們完全可以自己去實例化View逾一,這對于我們的插件化換膚很有幫助。
  基于插件式換膚框架搭建 - 資源加載源碼分析插件式換膚框架搭建 - setContentView源碼閱讀這兩篇文章我們完全可以自己動手搭建一套換膚框架了肮雨,我們下期再見遵堵。

所有分享大綱:2017Android進(jìn)階之路與你同行

視頻講解地址:https://pan.baidu.com/s/1qYl2AOO

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市怨规,隨后出現(xiàn)的幾起案子陌宿,更是在濱河造成了極大的恐慌,老刑警劉巖波丰,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壳坪,死亡現(xiàn)場離奇詭異,居然都是意外死亡掰烟,警方通過查閱死者的電腦和手機(jī)爽蝴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纫骑,“玉大人蝎亚,你說我怎么就攤上這事∠裙荩” “怎么了发框?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長煤墙。 經(jīng)常有香客問我梅惯,道長仿野,這世上最難降的妖魔是什么设预? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任鳖枕,我火速辦了婚禮,結(jié)果婚禮上宾符,老公的妹妹穿的比我還像新娘。我一直安慰自己辣苏,他們只是感情好稀蟋,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布退客。 她就那樣靜靜地躺著萌狂,像睡著了一般怀泊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上务傲,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天树灶,我揣著相機(jī)與錄音天通,去河邊找鬼像寒。 笑死瓜贾,一個胖子當(dāng)著我的面吹牛祭芦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胃夏,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼照雁,長吁一口氣:“原來是場噩夢啊……” “哼答恶!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起污呼,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎映企,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挤渐,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡浴麻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了焚挠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡榛泛,死狀恐怖曹锨,靈堂內(nèi)的尸體忽然破棺而出沛简,到底是詐尸還是另有隱情,我是刑警寧澤给郊,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布撒顿,位于F島的核電站荚板,受9級特大地震影響跪另,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜唧席,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一淌哟、第九天 我趴在偏房一處隱蔽的房頂上張望徒仓。 院中可真熱鬧掉弛,春花似錦殃饿、人聲如沸芋肠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帮孔。三九已至,卻和暖如春晤斩,著一層夾襖步出監(jiān)牢的瞬間澳泵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留维苔,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像沸柔,于是被迫代替她去往敵國和親褐澎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

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