大變活人—攔截View的創(chuàng)建

再開始之前,我們有必要先看這么一個問題,同樣一個TextView,如果我們的MainActivity 分別繼承Activity袋励,AppCompatActivity,我們打印一下我們的TextView末购。


<!--繼承自Activity:-->

android.widget.TextView id/text_view

<!--繼承自AppCompatActivity: -->

android.support.v7.widget.AppCompatTextView id/text_view

發(fā)現(xiàn)一個很有趣的現(xiàn)象,明明是TextView虎谢,為什么繼承AppCompatActivity 就變成AppCompatTextView了盟榴?到底發(fā)生了什么?

setContentView源碼分析

可以看到婴噩,setContentView所做的事情擎场,就是通過PhoneWindow實(shí)例化一個DecorView羽德,同時解析一系列系統(tǒng)自帶的資源文件,把它添加到DecorView中顶籽,這里面有一個id為 android.R.id.content的FrameLayout,這個正是我們的activity的直接父容器玩般,而setContentView把自己的資源文件添加進(jìn)去银觅。

也可以看到繼承AppComatActivity的setContentView方法其實(shí)跟繼承Activity的實(shí)現(xiàn)沒什么太大的區(qū)別礼饱。
到這里,我們還是沒發(fā)現(xiàn)是什么導(dǎo)致TextView變成AppCompatTextView的究驴。

我們看AppCompatActivity onCreate方法()

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        delegate.onCreate(savedInstanceState);
        if (delegate.applyDayNight() && mThemeId != 0) {
            // If DayNight has been applied, we need to re-apply the theme for
            // the changes to take effect. On API 23+, we should bypass
            // setTheme(), which will no-op if the theme ID is identical to the
            // current theme ID.
            if (Build.VERSION.SDK_INT >= 23) {
                onApplyThemeResource(getTheme(), mThemeId, false);
            } else {
                setTheme(mThemeId);
            }
        }
        super.onCreate(savedInstanceState);
    }

在調(diào)用super.onCreate(savedInstanceState)之前镊绪,其實(shí)調(diào)用了delegate.installViewFactory()和 delegate.onCreate(savedInstanceState)這兩個方法。

看注釋

 /**
     * Installs AppCompat's {@link android.view.LayoutInflater} Factory so that it can replace
     * the framework widgets with compatible tinted versions. This should be called before
     * {@code super.onCreate()} as so:
     * <pre class="prettyprint">
     * protected void onCreate(Bundle savedInstanceState) {
     *     getDelegate().installViewFactory();
     *     getDelegate().onCreate(savedInstanceState);
     *     super.onCreate(savedInstanceState);
     *
     *     // ...
     * }
     * </pre>
     * If you are using your own {@link android.view.LayoutInflater.Factory Factory} or
     * {@link android.view.LayoutInflater.Factory2 Factory2} then you can omit this call, and instead call
     * {@link #createView(android.view.View, String, android.content.Context, android.util.AttributeSet)}
     * from your factory to return any compatible widgets.
     */
    public abstract void installViewFactory();

安裝AppCompat的( android.view洒忧。LayoutInflater)工廠蝴韭,以便它可以用兼容版本b并替換框架小部件。(比如TextView 替換成AppCompatTextView)榄鉴。

如果你使用自己的Factory蛉抓,或者Factory2,則可以跳過這個調(diào)用,調(diào)用自己工廠的createView(View view巷送,String name, Context context, AttributeSet attrs)}來返回任何兼容的小部件驶忌。

看來原因就在這里面笑跛。

我們找到他的實(shí)現(xiàn)

//AppCompatDelegateImplV9中

AppCompatDelegateImplV9 implements LayoutInflater.Factory2

  @Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
        //在這里注冊了自己的工廠。
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else {
            if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }


public interface Factory2 extends Factory {
        /**
         * Version of {@link #onCreateView(String, Context, AttributeSet)}
         * that also supplies the parent that the view created view will be
         * placed in.
         *
         * @param parent The parent that the created view will be placed
         * in; <em>note that this may be null</em>. 創(chuàng)建視圖的父節(jié)點(diǎn)將被放置在其中 可能為空
         * @param name Tag name to be inflated.被創(chuàng)建的標(biāo)簽名稱飞蹂。比如TextView,livesun.io.MyTextView等几苍。
         * @param context The context the view is being created in. 上下文
         * @param attrs Inflation attributes as specified in XML file.
         *在XML文件中指定的詳細(xì)屬性:如textColor width 等
         * @return View Newly created view. Return null for the default
         *         behavior.視圖新創(chuàng)建的視圖
         */
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
    }
    
public interface Factory {
        /**
         * Hook you can supply that is called when inflating from a LayoutInflater.
         * You can use this to customize the tag names available in your XML
         * layout files.
         * 
         * <p>
         * Note that it is good practice to prefix these custom names with your
         * package (i.e., com.coolcompany.apps) to avoid conflicts with system
         * names.
         * 
         * @param name Tag name to be inflated.
         * @param context The context the view is being created in.
         * @param attrs Inflation attributes as specified in XML file.
         * 
         * @return View Newly created view. Return null for the default
         *         behavior.
         */
        public View onCreateView(String name, Context context, AttributeSet attrs);
    }
    

接著看這兩個回掉方法是如何實(shí)現(xiàn)的

 /**
     * From {@link LayoutInflater.Factory2}.
     */
    @Override
    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        // First let the Activity's Factory try and inflate the view
        final View view = callActivityOnCreateView(parent, name, context, attrs);
        if (view != null) {
            return view;
        }

        // If the Factory didn't handle it, let our createView() method try
        return createView(parent, name, context, attrs);
    }

    /**
     * From {@link LayoutInflater.Factory2}.
     */
    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        return onCreateView(null, name, context, attrs);
    }


//這里看了半天妻坝,只能看出mOriginalWindowCallback 其實(shí)是Window.Callback芥颈。
// mAppCompatWindowCallback = wrapWindowCallback(mOriginalWindowCallback);
//mWindow.setCallback(mAppCompatWindowCallback);
//通過這兩句代碼 重新設(shè)置了一個新的Callback 
//但是并沒用看到跟LayoutInflater.Factory有什么關(guān)系
//難道我們自己設(shè)置callBack的時候 同時讓其實(shí)現(xiàn)LayoutInflater.Factory才行爬坑??
//但是不管怎么說售担,目前這里是走不了。我們接著看return的createView(parent, name, context, attrs);方法岩四。

View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) {
        // Let the Activity's LayoutInflater.Factory try and handle it
        if (mOriginalWindowCallback instanceof LayoutInflater.Factory) {
            final View result = ((LayoutInflater.Factory) mOriginalWindowCallback)
                    .onCreateView(name, context, attrs);
            if (result != null) {
                return result;
            }
        }
        return null;
    }

createView(parent, name, context, attrs)方法是個關(guān)鍵的方法哥攘。

private static final boolean IS_PRE_LOLLIPOP = Build.VERSION.SDK_INT < 21;

 @Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        if (mAppCompatViewInflater == null) {
            mAppCompatViewInflater = new AppCompatViewInflater();
        }

        boolean inheritContext = false;
        if (IS_PRE_LOLLIPOP) {
            inheritContext = (attrs instanceof XmlPullParser)
                    // If we have a XmlPullParser, we can detect where we are in the layout
                    //如果我們有一個XmlPullParser逝淹,我們就可以檢測布局中的位置
                    ? ((XmlPullParser) attrs).getDepth() > 1
                    // Otherwise we have to use the old heuristic
                    //否則我們就得使用古老的啟發(fā)式
                    : shouldInheritContext((ViewParent) parent);
        }

        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP, /* 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 */
        );
    }

先判斷mAppCompatViewInflater為空栅葡,然后獲取其對象,如果小于5.0规脸,然后再判斷inheritContext為true還是false莫鸭。如果為true則使用父容器的context网棍。


 public final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;
        
        //一系列的判斷到底使用誰的context
        // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
        // by using the parent's context
        if (inheritContext && parent != null) {
            context = parent.getContext();
        }
        if (readAndroidTheme || readAppTheme) {
            // We then apply the theme on the context, if specified
            context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
        }
        if (wrapContext) {
            context = TintContextWrapper.wrap(context);
        }

        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;
            case "Spinner":
                view = new AppCompatSpinner(context, attrs);
                break;
            case "ImageButton":
                view = new AppCompatImageButton(context, attrs);
                break;
            case "CheckBox":
                view = new AppCompatCheckBox(context, attrs);
                break;
            case "RadioButton":
                view = new AppCompatRadioButton(context, attrs);
                break;
            case "CheckedTextView":
                view = new AppCompatCheckedTextView(context, attrs);
                break;
            case "AutoCompleteTextView":
                view = new AppCompatAutoCompleteTextView(context, attrs);
                break;
            case "MultiAutoCompleteTextView":
                view = new AppCompatMultiAutoCompleteTextView(context, attrs);
                break;
            case "RatingBar":
                view = new AppCompatRatingBar(context, attrs);
                break;
            case "SeekBar":
                view = new AppCompatSeekBar(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.
            //如果原始上下文不等于我們的主題上下文氏身,
            //那么我們需要用這個名稱手動infalte蛋欣,以便android:theme 生效如贷。
            
            //這里調(diào)用的createViewFromTag 其實(shí)跟Inflate的createViewFromTag是差不多一樣的,只是少了三個Factory的判斷
            view = createViewFromTag(context, name, attrs);
        }

        if (view != null) {
            // If we have created a view, check its android:onClick
            // 如果我們創(chuàng)建了一個視圖尚猿,檢查它的android:onClick
            checkOnClickListener(view, attrs);
        }

        return view;
    }
    

到這里凿掂,明白了為什么會出現(xiàn)這樣類似變魔術(shù)的情況了吧。

 private View createViewFromTag(Context context, String name, AttributeSet attrs) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        try {
            mConstructorArgs[0] = context;
            mConstructorArgs[1] = attrs;

            if (-1 == name.indexOf('.')) {
                // try the android.widget prefix first...
                //這里是走的widget包 適配 inflate 則是android.view包
                return createView(context, name, "android.widget.");
            } else {
                return createView(context, name, null);
            }
        } catch (Exception e) {
            // We do not want to catch these, lets return null and let the actual LayoutInflater
            // try
            return null;
        } finally {
            // Don't retain references on context.
            mConstructorArgs[0] = null;
            mConstructorArgs[1] = null;
        }
    }

LayoutInflate的createViewFromTag方法


 View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
      ....

        try {
            View view;
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch (InflateException e) {
            throw e;

        } catch (ClassNotFoundException e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;

        } catch (Exception e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        }
    }


也就是說只要我們再onCreat方法的時候,setFactory援奢,那么視圖的創(chuàng)建就不會走系統(tǒng)的集漾,而按我們自己的來锉罐,至于其中原因,我們看下inflate的源碼。

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        // Apply a theme wrapper, if allowed and one is specified.
        if (!ignoreThemeAttr) {
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }

        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        try {
            View view;
            //如果factort2 不為null,就會調(diào)用factort2的onCreateView方法侨舆。
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
            //如果factort 不為null,就會調(diào)用factort的onCreateView方法挨下。
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }
            // 如果factort2和factortweinull 而私有的不為null,就會調(diào)用mPrivateFactory的onCreateView方法脐湾。
            這個mPrivateFactory是在Activity中賦值秤掌,算是默認(rèn)的
            if (view == null && mPrivateFactory != null) {
            
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }
            //如果還為null,才會調(diào)用默認(rèn)的onCreateView

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                    //如系統(tǒng)控件 TextView ImagView
                        view = onCreateView(parent, name, attrs);
                    } else {
                    //自定義控件,如livesun.io.MyTextView
                    //其實(shí)是通過放射來實(shí)現(xiàn)初始化的
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch (InflateException e) {
            throw e;

        } catch (ClassNotFoundException e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;

        } catch (Exception e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        }
    }

Activity的源碼中可以看到,在attach方法中孟岛,為mPrivateFactory設(shè)了值渠羞。

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        attachBaseContext(context);

       ....
        
        //這里的this ,是Factory2
        mWindow.getLayoutInflater().setPrivateFactory(this);
        
      ....

看他的實(shí)現(xiàn)方法,如果name!="fragment",就調(diào)用自身的onCreateView 否則,調(diào)用fragment的onCreateView方法荧恍。這下清楚了Fragment的onCreateView的由來渗蟹。


 @Nullable
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        return null;
    }

    /**
     * Standard implementation of
     * {@link android.view.LayoutInflater.Factory2#onCreateView(View, String, Context, AttributeSet)}
     * used when inflating with the LayoutInflater returned by {@link #getSystemService}.
     * This implementation handles <fragment> tags to embed fragments inside
     * of the activity.
     *
     * @see android.view.LayoutInflater#createView
     * @see android.view.Window#getLayoutInflater
     */
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        if (!"fragment".equals(name)) {
            return onCreateView(name, context, attrs);
        }

        return mFragments.onCreateView(parent, name, context, attrs);
    }

所以攔截View的創(chuàng)建的本質(zhì),其實(shí)就是在LayoutInfalte中辨嗽,通過pull解析器解析整個XML視圖文件糟需,通過createViewFromTag方法谷朝,創(chuàng)建一個個View時,在之前通過Factory是否為null的判斷,結(jié)合view的值杈帐,來創(chuàng)建view专钉。這時跃须,如果我們改變前期的判斷,整個view在創(chuàng)建之前尽楔,我們就可以做很多事情第练,其實(shí)就是layoutInflate留好位置,改變它就行了垦缅。

來那我們變個魔術(shù)

重寫Activity的onCreateView方法驹碍,因?yàn)槲覀冎繟ctivity已經(jīng)實(shí)現(xiàn)了Factory2 (mWindow.getLayoutInflater().setPrivateFactory(this);)


 @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {

        if (name.equals("TextView"))
        {
            EditText editText=new EditText(this);
            editText.setHint("大變TextView");
            return editText;
        }

        return null;
    }

這是我們的布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:id="@+id/root"
   >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ddddd"
        />
</RelativeLayout>

運(yùn)行結(jié)果

image

當(dāng)然 還可以這么寫怔球,自己設(shè)置Factory2浮还、Factory 他們優(yōu)先級更高

記得要在setContentView之前,應(yīng)為setContentView就會調(diào)用inflat方法担汤。

 @Override
    protected void onCreate(Bundle savedInstanceState) {

        mLayoutInflater = LayoutInflater.from(this);
        LayoutInflaterCompat.setFactory2(mLayoutInflater, new LayoutInflater.Factory2() {
            @Override
            public View onCreateView(String s, Context context, AttributeSet attributeSet) {
                return null;
            }

            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attributeSet) {
                if (name.equals("TextView"))
                {
                    EditText editText=new EditText(MainActivity.this);
                    editText.setHint("大變TextView2.0");
                    return editText;
                }
                return null;
            }
        });
        setContentView(R.layout.scroll_demo);
        super.onCreate(savedInstanceState);

    }

結(jié)果

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末隅很,一起剝皮案震驚了整個濱河市率碾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌绒尊,老刑警劉巖仔粥,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異勘究,居然都是意外死亡斟冕,警方通過查閱死者的電腦和手機(jī)缅阳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門十办,熙熙樓的掌柜王于貴愁眉苦臉地迎上來向族,“玉大人,你說我怎么就攤上這事再扭∫勾#” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵罢荡,是天一觀的道長区赵。 經(jīng)常有香客問我,道長漱受,這世上最難降的妖魔是什么患整? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任各谚,我火速辦了婚禮,結(jié)果婚禮上赴穗,老公的妹妹穿的比我還像新娘膀息。我一直安慰自己,他們只是感情好甸赃,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布埠对。 她就那樣靜靜地躺著裁替,像睡著了一般。 火紅的嫁衣襯著肌膚如雪襟沮。 梳的紋絲不亂的頭發(fā)上昌腰,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天剥哑,我揣著相機(jī)與錄音株婴,去河邊找鬼暑认。 笑死大审,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的粮彤。 我是一名探鬼主播导坟,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼圈澈,長吁一口氣:“原來是場噩夢啊……” “哼康栈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起登舞,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤悬荣,失蹤者是張志新(化名)和其女友劉穎氯迂,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡驰坊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年拳芙,在試婚紗的時候發(fā)現(xiàn)自己被綠了皮璧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡睹限,死狀恐怖羡疗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情柳刮,我是刑警寧澤痒钝,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布送矩,位于F島的核電站,受9級特大地震影響梢灭,放射性物質(zhì)發(fā)生泄漏蒸其。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望靠汁。 院中可真熱鬧蝶怔,春花似錦、人聲如沸澳叉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至遥椿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間愈捅,已是汗流浹背慈鸠。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工青团, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芦昔。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓咕缎,卻偏偏與公主長得像料扰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子嫂伞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

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