自己動(dòng)手打造一套IOC注解框架

1.概述


這是我們的內(nèi)涵段子系統(tǒng)架構(gòu)的第一期分享都伪,希望大家可以先去了解一下這一期的內(nèi)容:2017Android進(jìn)階之路與你同行呕乎。在介紹內(nèi)涵段子整個(gè)項(xiàng)目的時(shí)候我們也說(shuō)好了會(huì)分析系統(tǒng)源碼設(shè)計(jì)模式,第三方框架源碼解析陨晶,然后自己動(dòng)手一點(diǎn)一點(diǎn)打造一套內(nèi)涵段子框架楣嘁。這一期的內(nèi)容對(duì)于部分哥們可能有點(diǎn)麻煩,如果覺(jué)得抽象請(qǐng)看視頻講解珍逸。
  那么什么是IOC逐虚,控制反轉(zhuǎn)(Inversion of Control,英文縮寫(xiě)為IOC)谆膳,其實(shí)就是反射加注解如果你學(xué)過(guò)Java后臺(tái)這個(gè)在三大框架中會(huì)經(jīng)常使用叭爱。過(guò)多的去解釋其實(shí)也沒(méi)什么意思,我們主要來(lái)看有什么用處漱病。
  附視頻講解地址:http://pan.baidu.com/s/1kVFMRQJ

2.第三方IOC框架源碼解析


今天主要講的就是Android中IOC框架就是注入控件和布局或者說(shuō)是設(shè)置點(diǎn)擊監(jiān)聽(tīng)买雾,如果你用過(guò)xUtils,afinal杨帽,butterknife類(lèi)的框架漓穿,你肯定不陌生~
  我們挑兩個(gè)做一下對(duì)比和源碼分析,我們就挑xUtils和butterknife這兩個(gè)代表:

2.1 xUtils的IOC注解使用

xutils如果大家使用過(guò)的話它里面的內(nèi)容會(huì)比較多注盈,包括網(wǎng)絡(luò)晃危,數(shù)據(jù)庫(kù),IOC注入,網(wǎng)絡(luò)圖片使用僚饭,那么我們這里主要看看xutils3.0的IOC注解:https://github.com/wyouflf/xUtils3

public class MainActivity extends AppCompatActivity {

    @ViewInject(R.id.icon)
    private ImageView mIconIv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        x.view().inject(this);

        mIconIv.setImageResource(R.drawable.icon);
    }

    /**
     * 1. 方法必須私有限定,
     * 2. 方法參數(shù)形式必須和type對(duì)應(yīng)的Listener接口一致.
     * 3. 注解參數(shù)value支持?jǐn)?shù)組: value={id1, id2, id3}
     * 4. 其它參數(shù)說(shuō)明見(jiàn){@link org.xutils.view.annotation.Event}類(lèi)的說(shuō)明.
     **/
    @Event(value = R.id.icon,
            type = View.OnClickListener.class/*可選參數(shù), 默認(rèn)是View.OnClickListener.class*/)
    private void iconIvClick(View view) {
        Toast.makeText(this, "圖片被點(diǎn)擊了", Toast.LENGTH_LONG).show();
    }
}

這里寫(xiě)圖片描述

  
  我就是設(shè)置一張圖片和一個(gè)點(diǎn)擊事件而已震叮,其實(shí)主要解決的就是我們不再需要findViewById()和setOnClickListener(),我們簡(jiǎn)單的來(lái)看一下源碼到底是怎么實(shí)現(xiàn)的:
  
 2.2 xUtils的IOC注解源碼解析 
  
  我就挑一些關(guān)鍵的代碼來(lái)分析一下鳍鸵,視頻里面會(huì)給大家講得非常詳細(xì)苇瓣,我們主要看一下x.view().inject(this);到底是干了什么:

    @Override
    public void inject(Activity activity) {
        //獲取Activity的ContentView的注解
        Class<?> handlerType = activity.getClass();
        try {
            // 找到ContentView這個(gè)注解,在activity類(lèi)上面獲取
            ContentView contentView = findContentView(handlerType);
            if (contentView != null) {
                int viewId = contentView.value();
                if (viewId > 0) {
                   // 如果有注解獲取layoutId的值偿乖,利用反射調(diào)用activity的setContentView方法注入視圖
                    Method setContentViewMethod = 
                        handlerType.getMethod("setContentView", int.class);
                    setContentViewMethod.invoke(activity, viewId);
                }
            }
        } catch (Throwable ex) {
            LogUtil.e(ex.getMessage(), ex);
        }
        // 處理 findViewById和setOnclickListener的注解
        injectObject(activity, handlerType, new ViewFinder(activity));
    }
private static void injectObject(Object handler, Class<?> handlerType, ViewFinder finder) {
     // .......

        // 從父類(lèi)到子類(lèi)遞歸
        injectObject(handler, handlerType.getSuperclass(), finder);

        // inject view  注入控件View
        Field[] fields = handlerType.getDeclaredFields();
        if (fields != null && fields.length > 0) {
            for (Field field : fields) {
              // ......
               // 獲取viewInject 注解
                ViewInject viewInject = field.getAnnotation(ViewInject.class);
                if (viewInject != null) {
                    try {
                       // 其實(shí)最終還是調(diào)用findViewById的方法
                        View view = finder.findViewById(viewInject.value(),
                             viewInject.parentId());
                        if (view != null) {
                            // 利用反射 把View注入到該屬性中
                            field.setAccessible(true);
                            field.set(handler, view);
                        } else {
                           // ......
                        }
                    } catch (Throwable ex) {
                        LogUtil.e(ex.getMessage(), ex);
                    }
                }
            }
        } // end inject view

        // inject event  注入事件
        Method[] methods = handlerType.getDeclaredMethods();
        if (methods != null && methods.length > 0) {
            for (Method method : methods) {
          // ......
                //檢查當(dāng)前方法是否是event注解的方法
                Event event = method.getAnnotation(Event.class);
                if (event != null) {
                    try {
                        // id參數(shù)
                        int[] values = event.value();
                        int[] parentIds = event.parentId();
                        int parentIdsLen = parentIds == null ? 0 : parentIds.length;
                        //循環(huán)所有id击罪,生成ViewInfo并添加代理反射   主要使用了動(dòng)態(tài)代理的設(shè)計(jì)模式
                        for (int i = 0; i < values.length; i++) {
                            int value = values[i];
                            if (value > 0) {
                                ViewInfo info = new ViewInfo();
                                info.value = value;
                                info.parentId = parentIdsLen > i ? parentIds[i] : 0;
                                method.setAccessible(true);
                                // EventListenerManager 動(dòng)態(tài)代理執(zhí)行相應(yīng)的方法
                                EventListenerManager.addEventMethod(
                                    finder, info, event, handler, method);
                            }
                        }
                    } catch (Throwable ex) {
                        LogUtil.e(ex.getMessage(), ex);
                    }
                }
            }
        } // end inject event

    }

關(guān)鍵的源碼大概就這么多,動(dòng)態(tài)代理的部分沒(méi)有貼出來(lái)贪薪,這個(gè)寫(xiě)可能寫(xiě)不清楚大家可以自己去看看源碼或者自己去網(wǎng)上搜搜動(dòng)態(tài)代理的設(shè)計(jì)模式分析外邓,視頻里面會(huì)給大家講清楚。動(dòng)態(tài)代理我記得我剛剛自學(xué)那會(huì)還真是一道坎但是這個(gè)坎我們得邁過(guò)去古掏,后面我們講Android的Hook技術(shù)以及插件開(kāi)發(fā)會(huì)反復(fù)的講到损话。
  xutils相對(duì)來(lái)說(shuō)應(yīng)該不難理解吧,其實(shí)就是我們利用類(lèi)的反射循環(huán)獲取屬性的注解值然后通過(guò)findViewById之后槽唾,動(dòng)態(tài)的注入到控件屬性里面丧枪;事件注入也是類(lèi)似首先f(wàn)indViewById然后利用動(dòng)態(tài)代理去反射執(zhí)行方法。

2.3 butterknife的使用
  
  相比于xutils來(lái)講它可能更受大家的歡迎庞萍,第一在性能方面xutils完全是利用的反射拧烦,butterknife是輕量級(jí)的反射使用的注解都是編譯時(shí)注解,而且它還提供了一個(gè)Android Studio的插件不需要我們?nèi)?xiě)任何的代碼钝计,作者JakeWharton很出名的寫(xiě)過(guò)很多大型的第三方框架恋博,https://github.com/JakeWharton/butterknife

public class MainActivity extends AppCompatActivity {

    @Bind(R.id.icon)
    ImageView icon;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
    }

    @OnClick(R.id.icon)
    public void onClick() {
        Toast.makeText(this, "圖片點(diǎn)擊了", Toast.LENGTH_LONG).show();
    }
}

上面是由插件給我們自動(dòng)生成的代碼,我們?cè)僖膊挥檬謩?dòng)去寫(xiě)這些代碼了私恬。屬性是不能private,onClick()方法也不能private,否則會(huì)報(bào)錯(cuò)的待會(huì)我們看源碼的實(shí)現(xiàn)就知道為什么只能這樣债沮,插件自動(dòng)生成的名字和方法總感覺(jué)怪怪的和我們的Android源碼規(guī)范不一致,當(dāng)然后面我們會(huì)自己去寫(xiě)一個(gè)Android Studio插件來(lái)配合我們自己的IOC注解框架本鸣。

2.4 butterknife的源碼解析

源碼閱讀相對(duì)于xutils的也麻煩很多疫衩,如果我們按照常規(guī)的邏輯去找ButterKnife.bind(this);會(huì)發(fā)現(xiàn)里面好像沒(méi)什么東西只有這個(gè):

static void bind(Object target, Object source, Finder finder) {
    Class<?> targetClass = target.getClass();
    try {
      if (debug) Log.d(TAG, "Looking up view binder for " + targetClass.getName());
      // 找到viewBinder
      ViewBinder<Object> viewBinder = findViewBinderForClass(targetClass);
      if (viewBinder != null) {
        // 直接執(zhí)行方法
        viewBinder.bind(finder, target, source);
      }
    } catch (Exception e) {
      throw new RuntimeException("Unable to bind views for " + 
          targetClass.getName(), e);
    }
  }

如果從這里看我們好像看不到任何的東西,其實(shí)工作流程是怎樣的呢荣德?我們可以看一下Bind這個(gè)Annotation注解類(lèi)和ButterKnifeProcessor這兩個(gè)類(lèi)其實(shí)就能找到線索:

@Retention(CLASS) @Target(FIELD)
public @interface Bind {
  /** View ID to which the field will be bound. */
  int[] value();
}

@Retention 為 CLASS 的 Annotation闷煤,由apt(Annotation Processing Tool) 解析自動(dòng)解析。ButterKnife便是用了Java Annotation Processing技術(shù)涮瞻,就是在Java代碼編譯成Java字節(jié)碼的時(shí)候就已經(jīng)處理了@Bind鲤拿、@OnClick(ButterKnife還支持很多其他的注解)這些注解了。

你可以你定義注解署咽,并且自己定義解析器來(lái)處理它們近顷。Annotation processing是在編譯階段執(zhí)行的,它的原理就是讀入Java源代碼,解析注解幕庐,然后生成新的Java代碼。新生成的Java代碼最后被編譯成Java字節(jié)碼家淤,注解解析器(Annotation Processor)不能改變讀入的Java 類(lèi)异剥,比如不能加入或刪除Java方法。下面我們應(yīng)該就大概知道工作流程了吧絮重?

2.5 ButterKnife 工作流程

當(dāng)你編譯你的Android工程時(shí)冤寿,ButterKnife工程中ButterKnifeProcessor類(lèi)的process()方法會(huì)執(zhí)行以下操作:

  • 開(kāi)始它會(huì)掃描Java代碼中所有的ButterKnife注解@Bind、@OnClick青伤、@OnItemClicked等督怜。

  • 當(dāng)它發(fā)現(xiàn)一個(gè)類(lèi)中含有任何一個(gè)注解時(shí),ButterKnifeProcessor會(huì)幫你生成一個(gè)Java類(lèi)狠角,名字類(lèi)似$$ViewBinder号杠,這個(gè)新生成的類(lèi)實(shí)現(xiàn)了ViewBinder接口。

  • 這個(gè)ViewBinder類(lèi)中包含了所有對(duì)應(yīng)的代碼丰歌,比如@Bind注解對(duì)應(yīng)findViewById(), @OnClick對(duì)應(yīng)了view.setOnClickListener()等等姨蟋。

  • 最后當(dāng)Activity啟動(dòng)ButterKnife.bind(this)執(zhí)行時(shí),ButterKnife會(huì)去加載對(duì)應(yīng)的ViewBinder類(lèi)調(diào)用它們的bind()方法立帖。

現(xiàn)在我們總該明白為什么我們的生成的屬性和方法不能私有了吧眼溶?我們最后看一下編譯時(shí)生成的class類(lèi)吧

public class MainActivity$$ViewBinder<T extends MainActivity> implements ViewBinder<T> {
  @Override public void bind(final Finder finder, final T target, Object source) {
    View view;
    view = finder.findRequiredView(source, 2131427372, "field 'icon' and method 'onClick'");
    target.icon = finder.castView(view, 2131427372, "field 'icon'");
    view.setOnClickListener(
      new butterknife.internal.DebouncingOnClickListener() {
        @Override public void doClick(View p0) {
           target.onClick();
        }
      });
  }

  @Override public void unbind(T target) {
    target.icon = null;
  }
}

如果現(xiàn)在要我們來(lái)選,我肯定會(huì)選butterknife這個(gè)主要是因?yàn)樗幸粋€(gè)插件我完全不用寫(xiě)任何的代碼晓勇,而且沒(méi)有利用不是全反射在性能方面也有一點(diǎn)提升堂飞,但是有的時(shí)候我想加一個(gè)檢測(cè)網(wǎng)絡(luò)的注解,這就有點(diǎn)麻煩了绑咱,插件生成的屬性名稱和方法名也有點(diǎn)蛋疼绰筛,我們自己來(lái)試著寫(xiě)寫(xiě)吧。

3.自己動(dòng)手豐衣足食


接下來(lái)我們自己來(lái)實(shí)現(xiàn)一套IOC注解框架吧描融,采用的方式反射加注解和Xutils類(lèi)似别智,但我們盡量不寫(xiě)那么麻煩,也不打算采用動(dòng)態(tài)代理稼稿,我們擴(kuò)展一個(gè)檢測(cè)網(wǎng)絡(luò)的注解薄榛,比如沒(méi)網(wǎng)的時(shí)候我們不去執(zhí)行方法而是給予沒(méi)有網(wǎng)絡(luò)的提示同時(shí)也不允許用戶反復(fù)點(diǎn)擊。
  這個(gè)時(shí)候有人就開(kāi)始噴了让歼,明知道反射會(huì)影響性能為什么還要用敞恋?這里我就隨便說(shuō)說(shuō)吧,我承認(rèn)反射會(huì)影響性能但是問(wèn)題不大我們可以自己去測(cè)試反射1萬(wàn)次大概會(huì)怎樣谋右,如果你非得去糾結(jié)那我也沒(méi)辦法硬猫,我們還是多花時(shí)間在UI渲染和Bitmap以及Service和Handler上面吧,我還從來(lái)沒(méi)有遇到過(guò)反射調(diào)用gc或者內(nèi)存溢出的情況,而且后面講插件化開(kāi)發(fā)的時(shí)候也會(huì)用到反射那砸門(mén)就不做了啸蜜?不管了開(kāi)工坑雅。
  
 3.1 控件屬性注入
  
  這里我就不在介紹Annotation的使用了,如果對(duì)于這個(gè)不是特別了解的大家可以自己去查一查資料或者看一下我錄制的視頻吧衬横,先來(lái)處理控件屬性的注入裹粤,但是需要考慮各種情況:

/**
 * Created by Darren on 2017/2/4.
 * Email: 240336124@qq.com
 * Description:  IOC的View屬性注解類(lèi)
 */
// RUNTIME 運(yùn)行時(shí)檢測(cè),CLASS 編譯時(shí)butterKnife使用是這個(gè)  SOURCE 源碼資源的時(shí)候
@Retention(RetentionPolicy.RUNTIME)
// FIELD 注解只能放在屬性上    METHOD 方法上  TYPE 類(lèi)上  CONSTRUCTOR 構(gòu)造方法上
@Target(ElementType.FIELD)
public @interface ViewById {
    // 代表可以傳值int類(lèi)型  使用的時(shí)候:ViewById(R.id.xxx)
    int value();
}
/**
 * Created by Darren on 2017/2/4.
 * Email: 240336124@qq.com
 * Description: IOC 注入 ViewUtils
 */
public class ViewUtils {

    public static void inject(Activity activity) {
        inject(new ViewFinder(activity), activity);
    }

    // 兼容View
    public static void inject(View view) {
        inject(new ViewFinder(view), view);
    }

    // 兼容Fragment
    public static void inject(View view, Object object) {
        inject(new ViewFinder(view), object);
    }

    private static void inject(ViewFinder viewFinder, Object object) {
        injectFiled(viewFinder, object);
        injectEvent(viewFinder, object);
    }

    // 注入事件
    private static void injectEvent(ViewFinder viewFinder, Object object) {

    }

    /**
     * 注入屬性
     */
    private static void injectFiled(ViewFinder viewFinder, Object object) {
        // object --> activity or fragment or view 是反射的類(lèi)
        // viewFinder --> 只是一個(gè)view的findViewById的輔助類(lèi)

        // 1. 獲取所有的屬性
        Class<?> clazz = object.getClass();
        // 獲取所有屬性包括私有和公有
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            // 2. 獲取屬性上面ViewById的值
            ViewById viewById = field.getAnnotation(ViewById.class);

            if (viewById != null) {
                // 獲取ViewById屬性上的viewId值
                int viewId = viewById.value();
                // 3. 通過(guò)findViewById獲取View
                View view = viewFinder.findViewById(viewId);

                if (view != null) {
                    // 4. 反射注入View屬性
                    // 設(shè)置所有屬性都能注入包括私有和公有
                    field.setAccessible(true);
                    try {
                        field.set(object, view);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                } else {
                    throw new RuntimeException("Invalid @ViewInject for "
                            + clazz.getSimpleName() + "." + field.getName());
                }
            }
        }
    }
}

3.2 點(diǎn)擊事件注入
 
 事件的注入我們只打算setOnclickListener其他不常見(jiàn)的我們先不管蜂林,也不打算采用動(dòng)態(tài)代理的設(shè)計(jì)模式遥诉。

// 事件注入
private static void injectEvent(ViewFinder viewFinder, Object object) {
        // 1.獲取所有方法
        Class<?> clazz = object.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        // 2.獲取方法上面的所有id
        for (Method method : methods) {
            OnClick onClick = method.getAnnotation(OnClick.class);
            if (onClick != null) {
                int[] viewIds = onClick.value();
                if (viewIds.length > 0) {
                    for (int viewId : viewIds) {
                        // 3.遍歷所有的id 先f(wàn)indViewById然后 setOnClickListener
                        View view = viewFinder.findViewById(viewId);
                        if (view != null) {
                            view.setOnClickListener(new DeclaredOnClickListener(method, object));
                        }
                    }
                }
            }
        }
    }


    private static class DeclaredOnClickListener implements View.OnClickListener {
        private Method mMethod;
        private Object mHandlerType;

        public DeclaredOnClickListener(Method method, Object handlerType) {
            mMethod = method;
            mHandlerType = handlerType;
        }

        @Override
        public void onClick(View v) {
            // 4.反射執(zhí)行方法
            mMethod.setAccessible(true);
            try {
                mMethod.invoke(mHandlerType, v);
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    mMethod.invoke(mHandlerType, null);
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
    }
public class MainActivity extends AppCompatActivity {

    @ViewById(R.id.icon)
    private ImageView mIconIv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewUtils.inject(this);
        mIconIv.setImageResource(R.drawable.icon);
    }

    @OnClick(R.id.icon)
    private void onClick(View view) {
        int i = 2 / 0;
        Toast.makeText(this, "圖片點(diǎn)擊了"+i, Toast.LENGTH_LONG).show();
    }
}

使用起來(lái)和xutils類(lèi)似,方法和屬性可以私有噪叙,但是有一點(diǎn)我們?cè)贠nclick點(diǎn)擊事件的方法里面無(wú)論做什么操作都是不會(huì)報(bào)錯(cuò)的矮锈,所以如果發(fā)現(xiàn)bug需要留意警告日志,這不是坑嗲嗎睁蕾?其實(shí)在我們的開(kāi)發(fā)過(guò)程給用戶或者老板玩的時(shí)候我們最怕的是閃退苞笨,現(xiàn)在我們就算有Bug也不會(huì)出現(xiàn)閃退的情況只是調(diào)試的時(shí)候需要留意警告日志還是蠻不錯(cuò)的。
 
 3.3 擴(kuò)展動(dòng)態(tài)檢測(cè)網(wǎng)絡(luò)注解

我們最后擴(kuò)展一下加一個(gè)檢測(cè)網(wǎng)絡(luò)的注解子眶,有的時(shí)候我們?cè)邳c(diǎn)擊的方法里面需要去檢測(cè)網(wǎng)絡(luò)猫缭,比如登陸注冊(cè),我們?nèi)绻麤](méi)網(wǎng)就沒(méi)必要去調(diào)接口啟動(dòng)線程了壹店,只需要提示用戶當(dāng)前無(wú)網(wǎng)絡(luò)即可猜丹。當(dāng)然這只是一個(gè)擴(kuò)展而已。

public class MainActivity extends AppCompatActivity {

    @ViewById(R.id.icon)
    private ImageView mIconIv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewUtils.inject(this);
        mIconIv.setImageResource(R.drawable.icon);
    }

    @OnClick(R.id.icon)
    @CheckNet          // 檢測(cè)網(wǎng)絡(luò)
    private void onClick(View view) {
        Toast.makeText(this, "圖片點(diǎn)擊了", Toast.LENGTH_LONG).show();
    }
}

擴(kuò)展我們就寫(xiě)這么個(gè)例子吧硅卢,這是內(nèi)涵段子框架搭建的第一期分享射窒,希望大家可以先去了解一下我們所有的分享內(nèi)容2017Android進(jìn)階之路與你同行,

附視頻講解地址:http://pan.baidu.com/s/1kVFMRQJ

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市将塑,隨后出現(xiàn)的幾起案子脉顿,更是在濱河造成了極大的恐慌,老刑警劉巖点寥,帶你破解...
    沈念sama閱讀 219,589評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艾疟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡敢辩,警方通過(guò)查閱死者的電腦和手機(jī)蔽莱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)戚长,“玉大人盗冷,你說(shuō)我怎么就攤上這事⊥” “怎么了仪糖?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,933評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵柑司,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我锅劝,道長(zhǎng)攒驰,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,976評(píng)論 1 295
  • 正文 為了忘掉前任故爵,我火速辦了婚禮玻粪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘稠集。我一直安慰自己奶段,他們只是感情好饥瓷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布剥纷。 她就那樣靜靜地躺著,像睡著了一般呢铆。 火紅的嫁衣襯著肌膚如雪晦鞋。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,775評(píng)論 1 307
  • 那天棺克,我揣著相機(jī)與錄音悠垛,去河邊找鬼。 笑死娜谊,一個(gè)胖子當(dāng)著我的面吹牛确买,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播纱皆,決...
    沈念sama閱讀 40,474評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼湾趾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了派草?” 一聲冷哼從身側(cè)響起搀缠,我...
    開(kāi)封第一講書(shū)人閱讀 39,359評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤著淆,失蹤者是張志新(化名)和其女友劉穎护戳,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體缭付,經(jīng)...
    沈念sama閱讀 45,854評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鉴竭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評(píng)論 3 338
  • 正文 我和宋清朗相戀三年歧譬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搏存。...
    茶點(diǎn)故事閱讀 40,146評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缴罗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出祭埂,到底是詐尸還是另有隱情面氓,我是刑警寧澤兵钮,帶...
    沈念sama閱讀 35,826評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站舌界,受9級(jí)特大地震影響掘譬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜呻拌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評(píng)論 3 331
  • 文/蒙蒙 一葱轩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧藐握,春花似錦靴拱、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,029評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至初家,卻和暖如春偎窘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背溜在。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,153評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工陌知, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掖肋。 一個(gè)月前我還...
    沈念sama閱讀 48,420評(píng)論 3 373
  • 正文 我出身青樓仆葡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親志笼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子沿盅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評(píng)論 2 356

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

  • 每次寫(xiě)代碼的時(shí)候,看到一大堆FindViewById()都挺煩的,沒(méi)有沒(méi)什么方法能幫我擺脫這種煩惱呢?當(dāng)然是有的,...
    dreamruner閱讀 1,238評(píng)論 0 12
  • 什么是注解注解分類(lèi)注解作用分類(lèi) 元注解 Java內(nèi)置注解 自定義注解自定義注解實(shí)現(xiàn)及使用編譯時(shí)注解注解處理器注解處...
    Mr槑閱讀 1,079評(píng)論 0 3
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,193評(píng)論 25 707
  • 偶然讀到莊子的一番話:泉涸,魚(yú)雙與處于陸籽腕。相掬以濕嗡呼,相濡以沫,不如相忘于江湖皇耗。 沒(méi)有生在江湖義氣風(fēng)發(fā)的年代南窗,對(duì)我來(lái)...
    長(zhǎng)亭changting閱讀 212評(píng)論 0 0
  • 所謂愛(ài)情
    yqg2227694_73d4閱讀 169評(píng)論 0 0