「打造自己的Library」SharedPreferences篇

Updated on 2016/1/26
歡迎轉(zhuǎn)載瞎嬉,但請(qǐng)保留作者鏈接:http://www.reibang.com/p/64ef6eb7406f
LitePreferences完整源碼傳送門(mén)GitHub

開(kāi)局閑談

SharedPreferences是Android之中的基礎(chǔ)內(nèi)容霉囚,是一種非常輕量化的存儲(chǔ)工具殴边。核心思想就是在xml文件中保存鍵值對(duì)搪桂。而正因?yàn)椴捎玫氖俏募x寫(xiě)康谆,所以它天生線程不安全赐劣。Google曾經(jīng)想要對(duì)其進(jìn)行一番擴(kuò)展以令其實(shí)現(xiàn)線程安全讀寫(xiě)柄慰,但最終以失敗告終鳍悠。后來(lái)于是有了民間替代方案,詳細(xì)可以參考GitHub上這個(gè)項(xiàng)目坐搔。
筆者本身對(duì)SharedPreferences是否線程安全是沒(méi)有需求的藏研,我主要是覺(jué)得它——
限、制概行、太蠢挡、多!使凳忙、用业踏、太、麻涧卵、煩勤家!

吐槽及預(yù)期

// get it
SharedPreferences p = mContext.getSharedPreferences("Myprefs", Context.MODE_PRIVATE);
// or
p = PreferenceManager.getDefaultSharedPreferences(mContext);

// read
p.getString("preference_key", "default value");

// write
p.edit().putString("preference_key", "new value").commit();
// or
p.edit().putString("preference_key", "new value").apply();

這里演示了String類(lèi)型的情況,其他也是類(lèi)似柳恐。
以上就是SharedPreferences的基本使用情況了伐脖,足以應(yīng)付絕大部分情況,看上去也就那么幾行乐设,挺簡(jiǎn)單讼庇、挺好用的嘛!
那好伤提,我們現(xiàn)在來(lái)看一下它究竟有哪些短板巫俺。

限制之一,使用之前必須拿到Context:

// get it
SharedPreferences p = mContext.getSharedPreferences("Myprefs", Context.MODE_PRIVATE);
// or
p = PreferenceManager.getDefaultSharedPreferences(mContext);

這里展示了兩種方式肿男,第一種的優(yōu)勢(shì)是可以自定義名稱介汹,并且如果需要的話可以指定全局讀寫(xiě)(雖然Google不推薦用SharedPreferences來(lái)跨應(yīng)用讀寫(xiě)却嗡,相關(guān)字段早就被置上了deprecated),如果不需要?jiǎng)t純粹成了消耗多余體力的代碼嘹承。
而且窗价,Context并不是永遠(yuǎn)都那么好拿的,所以有一種最簡(jiǎn)單粗暴的作法就是做一個(gè)自己的Application類(lèi)像是這樣:

public class App extends Application {
    private static Context sMe;
    public static Context getInstance() {
        return sMe;
    }
    @Override
    public void onCreate() {
        super.onCreate();
        sMe = this;
    }
}

但是殺雞焉用牛刀叹卷,你做這樣一個(gè)全局可得的ApplicationContext本就是為了不時(shí)之需撼港,拿來(lái)用SharedPreferences,每次還得這樣寫(xiě)App.getInstance()骤竹,逼格太低又很累啊帝牡。

限制之二,讀值為什么會(huì)要這么多代碼:

// read
p.getString("preference_key", "default value");

初看上去蒙揣,這似乎是無(wú)比正常的代碼:"default value"的存在確保了你永遠(yuǎn)可以取到值靶溜,但問(wèn)題就出在這個(gè)"default value"上了,在某種情況下懒震,你需要取某個(gè)值的地方很多罩息,而且全都可能還沒(méi)有初始化過(guò),也就是說(shuō)在這些地方實(shí)際第一次處理時(shí)使用到值的是"default value"个扰,假如某一天"default value"值需要變更瓷炮,你就要細(xì)心謹(jǐn)慎地把每個(gè)地方都改一輪了。

限制之三递宅,寫(xiě)值代碼也很多:

// write
p.edit().putString("preference_key", "new value").commit();
// or
p.edit().putString("preference_key", "new value").apply();

先拿到Editor內(nèi)部類(lèi)娘香,再操作,最后再提交办龄,雖然IDE自帶補(bǔ)全功能茅主,但補(bǔ)全三次也不是那么方便吧?源碼中的說(shuō)法是土榴,“so you can chain put calls together.”,因?yàn)槊看蝡utXXX()操作后仍舊返回同一個(gè)Editor內(nèi)部類(lèi)對(duì)象响牛,所以你能一次性put許多下最后再提交玷禽。可實(shí)際情況中使用到鏈?zhǔn)秸{(diào)用的機(jī)會(huì)還是挺少的呀打,畢竟很難出現(xiàn)Web上那種出現(xiàn)一整個(gè)表單給用戶填寫(xiě)矢赁,最后一次性提交的情況膨桥。

總的來(lái)說(shuō)芯勘,在不同的地方重復(fù)獲取SharedPreferences是沒(méi)有必要的,可以拿一個(gè)單例來(lái)解決鬼佣;讀值和寫(xiě)值太累贅了豺憔,要做下封裝……
不额获,這還不夠够庙,作為一個(gè)名有追求的工程師——
我們需要一個(gè)強(qiáng)有力的Library來(lái)解決這些問(wèn)題,力爭(zhēng)達(dá)到一經(jīng)寫(xiě)就抄邀,永久受益的效果耘眨。

常規(guī)解決方案

一般是做一個(gè)單例工具類(lèi),然后簡(jiǎn)單封裝一下方法境肾,這里截取了一下Notes中的部分代碼如下:

/**
 * Created by lgp on 2014/10/30.
 */
public class PreferenceUtils{

    private SharedPreferences sharedPreferences;

    private SharedPreferences.Editor shareEditor;

    private static PreferenceUtils preferenceUtils = null;

    public static final String NOTE_TYPE_KEY = "NOTE_TYPE_KEY";

    public static final String EVERNOTE_ACCOUNT_KEY = "EVERNOTE_ACCOUNT_KEY";

    public static final String EVERNOTE_NOTEBOOK_GUID_KEY = "EVERNOTE_NOTEBOOK_GUID_KEY";

    @Inject @Singleton
    protected PreferenceUtils(@ContextLifeCycle("App") Context context){
        sharedPreferences = context.getSharedPreferences(SettingFragment.PREFERENCE_FILE_NAME, Context.MODE_PRIVATE);
        shareEditor = sharedPreferences.edit();
    }

    public static PreferenceUtils getInstance(Context context){
        if (preferenceUtils == null) {
            synchronized (PreferenceUtils.class) {
                if (preferenceUtils == null) {
                    preferenceUtils = new PreferenceUtils(context.getApplicationContext());
                }
            }
        }
        return preferenceUtils;
    }

    public String getStringParam(String key){
        return getStringParam(key, "");
    }

    public String getStringParam(String key, String defaultString){
        return sharedPreferences.getString(key, defaultString);
    }

    public void saveParam(String key, String value)
    {
        shareEditor.putString(key,value).commit();
    }

    ......
}

可以看到其思想還是挺簡(jiǎn)單的剔难,基本上對(duì)于限制一二三全都照顧到了。
對(duì)于限制一奥喻,因?yàn)槭菃卫脊灰鞔_這個(gè)類(lèi)已經(jīng)初始化過(guò)一次了,后面就可以這樣來(lái)獲取實(shí)例PreferenceUtils.getInstance(null)——必須說(shuō)明這是一種取巧的手段环鲤,而且看上去非常丑陋——所以說(shuō)不需要依賴Context(另外我們還可以增加對(duì)于resId的支持纯趋,讓這種方式成為可能getStringParam(int resId)只要在這個(gè)類(lèi)中持有Context就能做到——但要注意為防內(nèi)存泄漏應(yīng)給這個(gè)類(lèi)傳ApplicationContext);關(guān)鍵是限制二的解決并不漂亮楔绞,因?yàn)椴煌脑O(shè)置項(xiàng)的default值多數(shù)情況下是不一樣的结闸,所以還是提供了一個(gè)二參方法getStringParam(String key, String defaultString),本質(zhì)上并沒(méi)有解決酒朵。

不過(guò)不管怎樣桦锄,我們的Library LitePreferences最起碼要包含以上這個(gè)工具類(lèi)的全部功能,然后再談突破蔫耽。

極致簡(jiǎn)約

既然是個(gè)單例结耀,那么在使用之前就必須調(diào)用getInstance()了,像是這樣:

LitePrefs.getInstance(mContext).getInt(R.string.tedious);

在這行代碼中匙铡,如果LitePrefs已經(jīng)初始化過(guò)一次了图甜,那么中間的getInstance(mContext)純粹就是毫無(wú)意義。我們希望代碼簡(jiǎn)約成這樣:

LitePrefs.getInt(R.string.tedious);

要達(dá)到這樣的效果鳖眼,只需讓getInt()是一個(gè)靜態(tài)方法即可黑毅。直接包裝一層:

public static int getInt(int resId) {
       return  getInstance().getIntLite(resId);
}

為什么這里的getInstance()無(wú)參?因?yàn)長(zhǎng)itePrefs構(gòu)造方法是這樣的:

private LitePrefs() {}

無(wú)參钦讳,什么也不做矿瘦。對(duì)于這個(gè)類(lèi)的初始化全都剝離到一個(gè)專(zhuān)門(mén)的初始化方法中去了。這意味著要使用這個(gè)類(lèi)之前愿卒,必須先初始化缚去。它們看上去像是這樣:

private boolean valid = false;

public static void init(Context ctx) {
     getInstance().initLite(ctx);
}

public void initLite(Context ctx) {
     // do something to initialize 
     
     valid = true;
}

    private void checkValid() {
        if (!valid) {
            throw new IllegalStateException("this should only be called when LitePrefs didn't initialize once");
        }
    }

記得用一個(gè)標(biāo)志位來(lái)保障工具類(lèi)已經(jīng)初始化過(guò)。
使用這種方式琼开,所有的操作都可以簡(jiǎn)化為L(zhǎng)itePrefs.靜態(tài)方法()易结。

支持文件配置

完成之后,我們的Library會(huì)擁有這樣的初始化技能:

        try {
            LitePrefs.initFromXml(context, R.xml.prefs);
        } catch (IOException | XmlPullParserException e) {
            e.printStackTrace();
        }

支持文件配置不僅會(huì)讓配置變得很方便,同時(shí)也繞過(guò)了限制二:依常理考慮搞动,一個(gè)設(shè)置項(xiàng)的默認(rèn)值應(yīng)該是惟一的躏精。那么,如果在第一次啟動(dòng)應(yīng)用時(shí)寫(xiě)一次初始值到SharedPreferences中滋尉,那么今后取值的時(shí)候不就永遠(yuǎn)有值了嗎玉控?那么上面那種單參封裝也就可以一直正常使用了。

既然要用文件讀寫(xiě)狮惜,那就開(kāi)搞吧高诺,很容易想到使用一個(gè)xml文件來(lái)放配置項(xiàng)像是這樣:

<?xml version="1.0" encoding="utf-8"?>
<prefs name="liteprefs">
    <pref>
        <key>preference_key</key>
        <def-value>default value</def-value>
        <description>Write some sentences if you want,
        the LitePrefs parser will not parse the tag "description"</description>
    </pref>
    <pref>
        <key>boolean_key</key>
        <def-value>false</def-value>
    </pref>
    <pref>
        <key>int_key</key>
        <def-value>233</def-value>
    </pref>
    <pref>
        <key>float_key</key>
        <def-value>3.141592</def-value>
    </pref>
    <pref>
        <key>long_key</key>
        <def-value>4294967296</def-value>
    </pref>
    <pref>
        <key>String_key</key>
        <def-value>this is a String</def-value>
    </pref>
</prefs>

由于xml解析器由我們自己來(lái)寫(xiě),所以非常自由碾篡。這里attribute"name"中寫(xiě)上了對(duì)應(yīng)的SharedPreferences使用的name虱而。tag也是各種隨意。而且多寫(xiě)幾個(gè)不解析的tag用來(lái)在配置文件中添加說(shuō)明也沒(méi)有問(wèn)題开泽,像是上面的"<description>","</description>"牡拇。
基本數(shù)據(jù)類(lèi)型全都可以很容易寫(xiě)出來(lái),處理也容易穆律,就是Set<String>不是太好處理惠呼,但SharedPreferences中這個(gè)支持用到的場(chǎng)合還是非常少的,目前我在Android源碼中從未見(jiàn)過(guò)使用的例子峦耘。

考慮一個(gè)問(wèn)題:上面怎么說(shuō)也有五種類(lèi)型的數(shù)據(jù)剔蹋,我們要怎么讀?只有兩個(gè)tag顯然不足以判斷這一項(xiàng)的具體類(lèi)型是int還是String辅髓,難道我們要加一個(gè)tag專(zhuān)門(mén)來(lái)區(qū)分嗎泣崩?
雖然可以這樣做,但這樣寫(xiě)model類(lèi)又會(huì)是老大難的問(wèn)題——要寫(xiě)一個(gè)model類(lèi)讓它持有標(biāo)志類(lèi)型的flag洛口,再加上持有五種類(lèi)型的域矫付?這也太恐怖了吧!

話說(shuō)回來(lái)第焰,寫(xiě)入配置到xml這一步真的是必要的嗎买优?
因?yàn)?strong>SharedPreferences要寫(xiě)過(guò)之后才有值,所以我們想要在第一次運(yùn)行應(yīng)用時(shí)讀配置文件然后把值寫(xiě)進(jìn)xml挺举,之后運(yùn)行則不再需要進(jìn)行這樣的操作——這就是原定計(jì)劃了而叼,但這其實(shí)是存在漏洞的,漏洞出在SharedPreferences中的兩個(gè)方法上:remove(String key)豹悬,clear()
這兩個(gè)方法會(huì)把值清空液荸,用戶來(lái)一發(fā)恢復(fù)默認(rèn)設(shè)置的時(shí)候就是它們登場(chǎng)的時(shí)候瞻佛。

既然如此,我們更改計(jì)劃:應(yīng)用啟動(dòng)時(shí)讀取配置文件并持有這些信息,在讀Preference項(xiàng)的時(shí)候伤柄,如該項(xiàng)未設(shè)置則返回配置文件中的默認(rèn)值绊困。
這樣一來(lái),無(wú)須考慮寫(xiě)文件操作的情況下适刀,我們讀文件時(shí)條件也可放寬了:根本就不需要知道Preference的數(shù)據(jù)類(lèi)型秤朗,全部用String類(lèi)型保存就好,編程者為正確使用它們而負(fù)責(zé)笔喉。

我們用一個(gè)Pref類(lèi)作為Preference項(xiàng)的模型取视,這樣設(shè)計(jì):

 public class Pref {

    public String key;

    /**
     * use String store the default value
     */
    public String defValue;

    /**
     * use String store the current value
     */
    public String curValue;

    /**
     * flag to show the pref has queried its data from SharedPreferences or not
     */
    public boolean queried = false;

    public Pref() {
    }

    public Pref(String key, String defValue) {
        this.key = key;
        this.defValue = defValue;
    }

    public Pref(String key, int defValue) {
        this.key = key;
        this.defValue = String.valueOf(defValue);
    }

   .......

    public int getDefInt() {
        return Integer.parseInt(defValue);
    }

    public String getDefString() {
        return defValue;
    }

   .......

    public int getCurInt() {
        return Integer.parseInt(curValue);
    }

    public String getCurString() {
        return curValue;
    }
    
    .......

    public void setValue(int value) {
        curValue = String.valueOf(value);
    }

    public void setValue(String value) {
        curValue = value;
    }
    
    ......

以上代碼片段展示了對(duì)于int及String類(lèi)型的處理,用一個(gè)defValue保存該P(yáng)ref項(xiàng)的默認(rèn)值常挚;用queried標(biāo)志是否該P(yáng)ref曾經(jīng)進(jìn)行過(guò)查詢作谭,假如有,那么其實(shí)際值保存在curValue之中奄毡。通過(guò)這樣的處理折欠,每一個(gè)Preference項(xiàng)最多只會(huì)查詢一次。

所以吼过,解析器可以非常簡(jiǎn)單地寫(xiě)成像是這樣:

public class ParsePrefsXml {

    private static final String TAG_ROOT = "prefs";
    private static final String TAG_CHILD = "pref";
    private static final String ATTR_NAME = "name";

    private static final String TAG_KEY = "key";
    private static final String TAG_DEFAULT_VALUE = "def-value";

    public static ActualUtil parse(XmlResourceParser parser)
            throws XmlPullParserException, IOException {
        Map<String, Pref> map = new HashMap<>();
        int event = parser.getEventType();

        Pref pref = null;
        String name = null;
        Stack<String> tagStack = new Stack<>();

        while (event != XmlResourceParser.END_DOCUMENT) {
            if (event == XmlResourceParser.START_TAG) {
                switch (parser.getName()) {
                    case TAG_ROOT:
                        name = parser.getAttributeValue(null, ATTR_NAME);
                        tagStack.push(TAG_ROOT);
                        if (null == name) {
                            throw new XmlPullParserException(
                                    "Error in xml: doesn't contain a 'name' at line:"
                                            + parser.getLineNumber());
                        }
                        break;
                    case TAG_CHILD:
                        pref = new Pref();
                        tagStack.push(TAG_CHILD);
                        break;
                    case TAG_KEY:
                        tagStack.push(TAG_KEY);
                        break;
                    case TAG_DEFAULT_VALUE:
                        tagStack.push(TAG_DEFAULT_VALUE);
                        break;
//                    default:
//                        throw new XmlPullParserException(
//                                "Error in xml: tag isn't '"
//                                        + TAG_ROOT
//                                        + "' or '"
//                                        + TAG_CHILD
//                                        + "' or '"
//                                        + TAG_KEY
//                                        + "' or '"
//                                        + TAG_DEFAULT_VALUE
//                                        + "' at line:"
//                                        + parser.getLineNumber());
                }

            } else if (event == XmlResourceParser.TEXT) {
                switch (tagStack.peek()) {
                    case TAG_KEY:
                        pref.key = parser.getText();
                        break;
                    case TAG_DEFAULT_VALUE:
                        pref.defValue = parser.getText();
                        break;
                }

            } else if (event == XmlResourceParser.END_TAG) {
                boolean mismatch = false;
                switch (parser.getName()) {
                    case TAG_ROOT:
                        if (!TAG_ROOT.equals(tagStack.pop())) {
                            mismatch = true;
                        }
                        break;
                    case TAG_CHILD:
                        if (!TAG_CHILD.equals(tagStack.pop())) {
                            mismatch = true;
                        }
                        map.put(pref.key, pref);
                        break;
                    case TAG_KEY:
                        if (!TAG_KEY.equals(tagStack.pop())) {
                            mismatch = true;
                        }
                        break;
                    case TAG_DEFAULT_VALUE:
                        if (!TAG_DEFAULT_VALUE.equals(tagStack.pop())) {
                            mismatch = true;
                        }
                        break;
                }

                if (mismatch) {
                    throw new XmlPullParserException(
                            "Error in xml: mismatch end tag at line:"
                                    + parser.getLineNumber());
                }

            }
            event = parser.next();
        }
        parser.close();
        return new ActualUtil(name, map);
    }
}

這里解析完成最后返回的ActualUtil是一個(gè)實(shí)際操作SharedPreferences的基礎(chǔ)工具類(lèi)锐秦,它的邏輯也很簡(jiǎn)單,像是這樣:

public class ActualUtil {
    private int editMode = LitePrefs.MODE_COMMIT;
    private String name;
    private SharedPreferences mSharedPreferences;
    private Map<String, Pref> mMap;

    public ActualUtil(String name, Map<String, Pref> map) {
        this.name = name;
        this.mMap = map;
    }

    public void init(Context context) {
        mSharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE);
    }

    public void setEditMode(int editMode) {
        this.editMode = editMode;
    }

    public void putToMap(String key, Pref pref) {
        mMap.put(key, pref);
    }

    private void checkExist(Pref pref) {
        if (null == pref) {
            throw new NullPointerException("operate a pref that isn't contained in data set,maybe there are some wrong in initialization of LitePrefs");
        }
    }

    private Pref readyOperation(String key) {
        Pref pref = mMap.get(key);
        checkExist(pref);
        return pref;
    }

    public int getInt(String key) {
        Pref pref = readyOperation(key);
        if (pref.queried) {
            return pref.getCurInt();
        } else {
            pref.queried = true;
            int ans = mSharedPreferences.getInt(key, pref.getDefInt());
            pref.setValue(ans);
            return ans;
        }
    }
    
    public boolean putInt(String key, int value) {
        Pref pref = readyOperation(key);
        pref.queried = true;
        pref.setValue(value);

        if (LitePrefs.MODE_APPLY == editMode) {
            mSharedPreferences.edit().putInt(key, value).apply();
            return true;
        }
        return mSharedPreferences.edit().putInt(key, value).commit();
    }

    ......
}

可擴(kuò)展性

無(wú)擴(kuò)展性盗忱、泛用性不夠的代碼只能作為一次性使用酱床。

UML

我們的結(jié)構(gòu)如圖中所示,ActualUtil持有SharedPreferences售淡,實(shí)際完成讀寫(xiě)操作斤葱,ParsePerfsXml提供解析方法將xml配置文件解析成相應(yīng)的ActualUtil,而提供給用戶的實(shí)際操作類(lèi)則為L(zhǎng)itePrefs揖闸。
看上去抽象程度還算不錯(cuò)揍堕,當(dāng)我們需要針對(duì)項(xiàng)目特性定制的時(shí)候只需要繼承LitePrefs就可以……問(wèn)題就出在這里,LitePrefs是個(gè)單例汤纸。

    private static volatile LitePrefs sMe;

    private LitePrefs() {

    }

    public static LitePrefs getInstance() {
        if (null == sMe) {
            synchronized (LitePrefs.class) {
                if (null == sMe) {
                    sMe = new LitePrefs();
                }
            }
        }
        return sMe;
    }

因?yàn)槭菃卫萌祝訪itePrefs的構(gòu)造方法為private,這保障了它不會(huì)在類(lèi)外部被創(chuàng)建贮泞。但這也同時(shí)使得其無(wú)法派生出子類(lèi)楞慈。這可不是一件好事。出于這個(gè)原由啃擦,我們特別設(shè)計(jì)一個(gè)不標(biāo)準(zhǔn)的單例BaseLitePrefs用于擴(kuò)展:

    private static volatile BaseLitePrefs sMe;

    protected BaseLitePrefs() {

    }

    public static BaseLitePrefs getInstance() {
        if (null == sMe) {
            synchronized (BaseLitePrefs.class) {
                if (null == sMe) {
                    sMe = new BaseLitePrefs();
                }
            }
        }
        return sMe;
    }

因?yàn)閷⒃L問(wèn)權(quán)限修改為了protected囊蓝,所以這個(gè)類(lèi)可以被順利繼承,雖然損失了一點(diǎn)嚴(yán)謹(jǐn)性令蛉,但這完全值得聚霜。

現(xiàn)在狡恬,我們可嘗試著寫(xiě)一個(gè)子類(lèi)看看:

public class MyLitePrefs extends BaseLitePrefs {
      public static final String THEME = "choose_theme_key";

      public static void initFromXml(Context context) {
          try {
                initFromXml(context, R.xml.prefs);
          } catch (IOException | XmlPullParserException e) {
                e.printStackTrace();
          }
      }

      public static ThemeUtils.Theme getTheme() {
          return ThemeUtils.Theme.mapValueToTheme(getInt(THEME));
      }

      public static boolean setTheme(int value) {
          return putInt(THEME, value);
      }
}

本篇至此結(jié)束,完整源碼鏈接在頂部蝎宇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末弟劲,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子姥芥,更是在濱河造成了極大的恐慌兔乞,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凉唐,死亡現(xiàn)場(chǎng)離奇詭異庸追,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)熊榛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)锚国,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人玄坦,你說(shuō)我怎么就攤上這事血筑。” “怎么了煎楣?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵豺总,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我择懂,道長(zhǎng)喻喳,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任困曙,我火速辦了婚禮表伦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘慷丽。我一直安慰自己蹦哼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布要糊。 她就那樣靜靜地躺著纲熏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锄俄。 梳的紋絲不亂的頭發(fā)上局劲,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音奶赠,去河邊找鬼鱼填。 笑死,一個(gè)胖子當(dāng)著我的面吹牛毅戈,可吹牛的內(nèi)容都是我干的苹丸。 我是一名探鬼主播塑猖,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼谈跛!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起塑陵,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤感憾,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后令花,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體阻桅,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年兼都,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嫂沉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡扮碧,死狀恐怖趟章,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情慎王,我是刑警寧澤蚓土,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站赖淤,受9級(jí)特大地震影響蜀漆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咱旱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一确丢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吐限,春花似錦鲜侥、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至搂赋,卻和暖如春赘阀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背脑奠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工基公, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宋欺。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓轰豆,卻偏偏與公主長(zhǎng)得像胰伍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子酸休,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理骂租,服務(wù)發(fā)現(xiàn),斷路器斑司,智...
    卡卡羅2017閱讀 134,659評(píng)論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法渗饮,類(lèi)相關(guān)的語(yǔ)法,內(nèi)部類(lèi)的語(yǔ)法宿刮,繼承相關(guān)的語(yǔ)法互站,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,639評(píng)論 18 399
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,161評(píng)論 25 707
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,822評(píng)論 6 342
  • 從三月份找實(shí)習(xí)到現(xiàn)在僵缺,面了一些公司胡桃,掛了不少,但最終還是拿到小米磕潮、百度翠胰、阿里、京東揉抵、新浪亡容、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,253評(píng)論 11 349