微北洋開(kāi)發(fā)白皮書

微北洋開(kāi)發(fā)白皮書

v1.0.0
2018-5-5
微北洋Android

這份文檔旨在講述微北洋的基礎(chǔ)架構(gòu)的設(shè)計(jì),以及其用法和新模塊的開(kāi)發(fā)流程規(guī)范粮宛。

模塊參考

目前代碼質(zhì)量較高的質(zhì)量有GPA(gpa2),課程表模塊(schedule2)。里面的代碼涵蓋了基礎(chǔ)庫(kù)的用法饵沧,Kotlin的高級(jí)使用方式,架構(gòu)的抽象封裝赌躺,自定義View之類狼牺,吃透兩個(gè)模塊的代碼,基本上就沒(méi)有什么可以擔(dān)心了礼患。
如果不知道從哪里做起是钥,可以先從GPA的Model層看起掠归,然后一步步追溯到View層,看處理方式悄泥》鞯剑看代碼的話,可以用兩種方法:自頂向下和自下而上的方法码泞,選用哪一種都OK兄旬。

基礎(chǔ)庫(kù)研究

內(nèi)容量最大,在做自己新功能之前所需要了解的就是:commons庫(kù)的用法余寥,進(jìn)一步也需要多研究研究commons的代碼設(shè)計(jì)领铐,閱讀完代碼一定會(huì)受益匪淺。

開(kāi)發(fā)指南

開(kāi)發(fā)指南從用法的角度來(lái)闡述微北洋的基礎(chǔ)庫(kù)

網(wǎng)絡(luò)請(qǐng)求

網(wǎng)絡(luò)請(qǐng)求再微北洋中統(tǒng)一使用Retrofit來(lái)做宋舷,至于Retrofit的用法绪撵,比如說(shuō)接口寫法,自行查閱官方文檔

我們需要根據(jù)后端組提供的API接口來(lái)寫對(duì)應(yīng)的interface
一般來(lái)講一個(gè)請(qǐng)求的URL是由BaseUrl + Path組成祝蝠。比如說(shuō)https://open.twtstudio.com/api/v1/auditClass/audit這個(gè)網(wǎng)絡(luò)請(qǐng)求音诈,我們規(guī)定BaseUrl = https://open.twtstudio.com/api/,同時(shí)Path = v1/auditClass/audit。因此我們的接口就可以這樣子寫:

interface AuditApi {
    @GET("v1/auditClass/audit")
    fun getMyAudit(@Query("user_number") userNumber: String = CommonPreferences.studentid): Deferred<CommonBody<List<AuditCourse>>>

    @GET("v1/auditClass/popular")
    fun getPopluarAudit(): Deferred<CommonBody<List<AuditPopluar>>>

    @POST("v1/auditClass/audit")
    @FormUrlEncoded
    fun audit(@Field("user_number") userNumber: String, @Field("course_id") courseId: Int, @Field("info_ids") infoIds: String): Deferred<CommonBody<String>>

    @DELETE("v1/auditClass/audit")
    fun cancelAudit(@Query("user_number") userNumber: String, @Query("ids") ids: String): Deferred<CommonBody<String>>

    companion object : AuditApi by ServiceFactory() // 暫時(shí)不要管這一行的意思 忽略就好了
}

這基本上就是Retrofit的寫法绎狭,需要提及一點(diǎn)是接口方法的返回值
比如說(shuō)這幾個(gè)例子中我們的返回值類型是Deferred<T>是協(xié)程的返回類型(可以await那種)细溅,之所以可以用這類型返回值,是因?yàn)槲覀冊(cè)赗etrofit初始化的時(shí)候儡嘶,加入了對(duì)應(yīng)的CallAdapter

.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory())
//see code: com/twt/wepeiyang/commons/experimental/network/ServiceFactory.kt

不添加額外的CallAdapterFactory的話喇聊,我們?cè)诮涌谥兄荒軐?code>Call<T>的返回值,現(xiàn)在我們可以寫Deferred<T>,Observable<T>的返回值蹦狂。例如

    @GET("v1/auditClass/audit")
    fun getMyAudit(@Query("user_number") userNumber: String = CommonPreferences.studentid): Call<CommonBody<List<AuditCourse>>>

    @GET("v1/auditClass/popular")
    fun getPopluarAudit(): Observable<CommonBody<List<AuditPopluar>>>

再說(shuō)泛型包裝

先從微北洋的Api返回結(jié)構(gòu)說(shuō)起誓篱,規(guī)范的Api返回結(jié)構(gòu)是

{
   "error_code": -1, // 這里是錯(cuò)誤碼,比如說(shuō)記錄Token過(guò)期凯楔,辦公網(wǎng)綁定錯(cuò)誤之類
   "message": "", // 這里是服務(wù)器返回的錯(cuò)誤Message窜骄,比如說(shuō) ‘Token已過(guò)期’
   "data": <這里是你真正需要的數(shù)據(jù)>
}

對(duì)于這種返回格式,我們客戶端也做了一套封裝來(lái)對(duì)應(yīng)

/**
 * A common wrapper class of respond data from open.twtstudio.com/api.
 *
 * @see AuthService
 */
data class CommonBody<out T>(
        val error_code: Int,
        val message: String,
        val data: T?
)
//所有的微北洋Api規(guī)范請(qǐng)求都要用這個(gè)東西來(lái)做
//see code : com/twt/wepeiyang/commons/experimental/network/ServiceFactory.kt

<強(qiáng)制使用>因?yàn)槟阏嬲胍臄?shù)據(jù)在data字段里面摆屯,所有你只需要單獨(dú)寫data內(nèi)容對(duì)于的bean類即可邻遏,然后用泛型包裝。例如蹭課系統(tǒng)中我的一個(gè)請(qǐng)求返回的內(nèi)容是

{
   "error_code": -1,
   "message": "",
   "data": [
      {
         "course_id": 53,
         "course_name": "數(shù)據(jù)庫(kù)原理(雙語(yǔ))",
         "college": "軟件學(xué)院",
         "semester": "1",
         "year": "2018",
         "infos": [
            {
               "id": 417,
               "course_id": "53",
               "course_name": "數(shù)據(jù)庫(kù)原理(雙語(yǔ))",
               "course_id_in_tju": "3665",
               "start_week": "9",
               "end_week": "16",
               "start_time": "1",
               "course_length": "2",
               "week_day": "3",
               "week_type": "3",
               "building": "45",
               "room": "B307",
               "teacher_type": "副教授",
               "teacher": "王征"
            }
         ]
      },
      {
         "course_id": 3,
         "course_name": "有機(jī)化學(xué)B",
         "college": "理學(xué)院",
         "semester": "1",
         "year": "2018",
         "infos": [
            {
               "id": 42,
               "course_id": "3",
               "course_name": "有機(jī)化學(xué)B",
               "course_id_in_tju": "3171",
               "start_week": "1",
               "end_week": "16",
               "start_time": "2",
               "course_length": "2",
               "week_day": "5",
               "week_type": "3",
               "building": "50",
               "room": "A124",
               "teacher_type": "講師",
               "teacher": "王元欣"
            }
         ]
      }
   ]
}

返回的data字段是一個(gè)List鸥拧,因此我們只需要去寫這個(gè)List內(nèi)對(duì)象的對(duì)應(yīng)類即可

data class AuditCourse(val college: String,
                       @SerializedName("course_id") val courseId: Int,
                       @SerializedName("course_name") val courseName: String,
                       val year: String,
                       val semester: String,
                       val infos: List<InfoItem>)

同時(shí)在Retrofit的Api里面寫

@GET("v1/auditClass/audit")
    fun getMyAudit(@Query("user_number") userNumber: String = CommonPreferences.studentid): Deferred<CommonBody<List<AuditCourse>>>

// 重點(diǎn):
Deferred<CommonBody<List<AuditCourse>>> //這種情況是CommonBody里面包著List<T>

當(dāng)然Data字段里面不是List就直接泛型包裹即可党远,比如說(shuō)辦公網(wǎng)課程表那邊

    @GET("v1/classtable")
    fun getClassTable(): Deferred<CommonBody<Classtable>>

所以給你講了那么多削解,別給我瞎雞兒寫B(tài)ean類了富弦,照著規(guī)范來(lái)

網(wǎng)絡(luò)請(qǐng)求Interface怎么用

Retrofit對(duì)于網(wǎng)絡(luò)請(qǐng)求的接口采用了動(dòng)態(tài)代理的機(jī)制,commons庫(kù)中對(duì)其進(jìn)行了封裝氛驮,具體用法:

  • 在類外寫

    val api: TjuCourseApi by ServiceFactory() // 可以寫在一個(gè)object里面腕柜,單例化
    api.getClassTable()
    
  • 可以在interface里寫一個(gè)伴生對(duì)象(同時(shí)實(shí)現(xiàn)該接口,同時(shí)被代理)

    interface TjuCourseApi {
    
        @GET("v1/classtable")
        fun getClassTable(): Deferred<CommonBody<Classtable>>
    
        companion object : TjuCourseApi by ServiceFactory() // 伴生對(duì)象實(shí)現(xiàn)單例
    
    }
    
    TjuCourseApi.getClassTable() // 就可以調(diào)用
    

推薦使用第二種,對(duì)應(yīng)的源碼在com/twt/wepeiyang/commons/experimental/network/ServiceFactory.kt盏缤,如果你想要看懂這些的話砰蠢,你需要學(xué)Retrofit,Kotlin 代理,invoke() 運(yùn)算符重載

Async/await來(lái)處理網(wǎng)絡(luò)請(qǐng)求以及異步任務(wù)

目前微北洋里面的異步任務(wù)都是使用Kotlin協(xié)程來(lái)寫的。當(dāng)然如果你實(shí)在不會(huì)用唉铜,你也可以使用

Thread {
    uiThread{
        
    }
}

當(dāng)然這樣子我會(huì)Diss你
作為一個(gè)合格的項(xiàng)目開(kāi)發(fā)者台舱,你應(yīng)該使用Kotlin協(xié)程所提供的async/await來(lái)處理問(wèn)題。在之前的文檔中潭流,我們的網(wǎng)絡(luò)請(qǐng)求的返回值是Deferred<T>類型竞惋,至于這是個(gè)啥,請(qǐng)自己上網(wǎng)查謝謝灰嫉。附一個(gè)文章
一般的做發(fā)是拆宛,在普通環(huán)境(非協(xié)程環(huán)境)使用launch(UI){}方法啟動(dòng)一個(gè)協(xié)程,在協(xié)程里面使用async/await來(lái)控制讼撒。

launch (exceptionHandler + UI) {
                val result = AuditApi.searchCourse(courseName).await()
                // do something 這里的代碼會(huì)等待網(wǎng)絡(luò)請(qǐng)求出結(jié)果才執(zhí)行
        }

比如說(shuō)我們添加一些邏輯

launch (exceptionHandler + UI) {
                val result: CommonBody<String> = AuditApi.searchCourse(courseName).await()
                // do something
                if (result.error_code == -1) textView.text = result.data 
        }

協(xié)程里面的上下文:通用的有UI CommonPool表示協(xié)程運(yùn)行在哪個(gè)線程中浑厚,CommonPool是一個(gè)線程池,耗時(shí)操作要在這里完成根盒,然后通過(guò)await來(lái)協(xié)調(diào)先后钳幅。示例一段偽代碼(看個(gè)意思就行)

launch (exceptionHandler + UI) { // 主線程開(kāi)啟
                val result: CommonBody<String> = AuditApi.searchCourse(courseName).await()
                // do something
                if (result.error_code == -1) textView.text = result.data 
                val dbTask = async(CommonPool) { // 異步開(kāi)啟
                    ScheduleDb.queryAllCourses() // 耗時(shí)操作
                }
                val courseList = dbTask.await()
                courseList.foreach {
                    textView.text += it.courseName
                }
        }

這一段看不懂的話,多補(bǔ)補(bǔ)協(xié)程炎滞。菜是原罪

到這里贡这,你已經(jīng)會(huì)使用微北洋的基礎(chǔ)框架來(lái)發(fā)起一個(gè)網(wǎng)絡(luò)請(qǐng)求了。請(qǐng)不要另辟蹊徑厂榛,謝謝盖矫!
繼續(xù)閱讀下去,你會(huì)學(xué)到:緩存框架的用法

構(gòu)建有緩存的響應(yīng)式網(wǎng)絡(luò)請(qǐng)求

有一部分的網(wǎng)絡(luò)請(qǐng)求是需要通過(guò)添加緩存來(lái)提高用戶體驗(yàn)的击奶,比如說(shuō)新聞列表辈双,課程表之類。微北洋已經(jīng)對(duì)這種常用的緩存進(jìn)行了封裝柜砾。 這套封裝的架構(gòu)湃望,整體來(lái)講是基于LiveData來(lái)做的,所以如果要徹底了解這些痰驱,需要些預(yù)備知識(shí):LiveData鏈接

而如果僅僅是使用的話证芭,就非常簡(jiǎn)單了。

// 這是創(chuàng)建的過(guò)程
val GpaLocalCache = Cache.hawk<GpaBean>("GPA") //本地緩存使用Hawk存儲(chǔ)
// 遠(yuǎn)程數(shù)據(jù)的獲取方法:通過(guò)Retrofit網(wǎng)絡(luò)請(qǐng)求 -> 一個(gè)map操作只取data
val GpaRemoteCache = Cache.from(GpaService.Companion::get).map(CommonBody<GpaBean>::data) 
// 最后把它們放在一起担映,RefreshableLiveData來(lái)幫你處理剩下的緩存問(wèn)題
val GpaLiveData = RefreshableLiveData.use(GpaLocalCache, GpaRemoteCache)

因?yàn)檫@套緩存系統(tǒng)是基于LiveData的废士,而LiveData又是響應(yīng)式的可觀測(cè)數(shù)據(jù)流,就很好用蝇完。比如說(shuō)GPA里面使用:

GpaLiveData.bindNonNull(this) {
    // 每次GPALiveData的數(shù)據(jù)發(fā)生變化的時(shí)候官硝,這個(gè)閉包里面代碼就會(huì)被回調(diào)
    // 回調(diào)的時(shí)機(jī)就是 獲得緩存/獲得網(wǎng)絡(luò)數(shù)據(jù)刷新/手動(dòng)刷新
    it.stat.total.let {
        scoreTv.text = it.score.toString()
        gpaTv.text = it.gpa.toString()
        creditTv.text = it.credit.toString()
    }

    it.data.asSequence().map(Term::stat).map {
        GpaLineChartView.DataWithDetail(it.score, """
                加權(quán):${it.score}
                績(jī)點(diǎn):${it.gpa}
                學(xué)分:${it.credit}
                """.trimIndent())
    }.toList().let {
        gpaLineCv.dataWithDetail = it
    }

    // attempt to refresh chart view while new data coming
    selectedTermIndex = selectedTermIndex
}

如果你可以看懂RefreshableLiveData的源碼矗蕊,你可以看到它的自動(dòng)刷新時(shí)機(jī),Activity/Fragment在Active的時(shí)候啟動(dòng)(你可以粗略的理解成oncreate或者onresume)氢架,你也可以手動(dòng)刷新傻咖,甚至加上刷新回調(diào)。

fun Context.simpleCallback(success: String? = "加載成功", error: String? = "發(fā)生錯(cuò)誤", refreshing: String? = "加載中"): suspend (RefreshState<*>) -> Unit =
        with(this.asReference()) {
            {
                when (it) {
                    is RefreshState.Success -> if (success != null) Toasty.success(this(), success).show()
                    is RefreshState.Failure -> if (error != null) Toasty.error(this(), "$error ${it.throwable.message}岖研!${it.javaClass.name}").show()
                    is RefreshState.Refreshing -> if (refreshing != null) Toasty.normal(this(), "$refreshing...").show()
                }
            }
        }

refreshBtn = findViewById<ImageButton>(R.id.btn_refresh).apply {
    setOnClickListener {
        GpaLiveData.refresh(REMOTE, callback = simpleCallback()) // 核心刷新方法
    }
}

手動(dòng)調(diào)用refresh方法即可手動(dòng)刷新

auditPopluarLiveData.refresh(CacheIndicator.LOCAL, CacheIndicator.REMOTE)
// 后面的部分表示卿操,從哪里刷新 Remote就是遠(yuǎn)程(服務(wù)器)

目前的封裝已經(jīng)可以應(yīng)對(duì)大部分常用的網(wǎng)絡(luò)請(qǐng)求,在緩存邏輯復(fù)雜的時(shí)候孙援,你也可以通過(guò)繼承RefreshableLiveData來(lái)做這個(gè)事情硬纤。例如:

// see code : com/twt/service/schedule2/model/audit/AuditApi.kt
val auditCourseLiveData = object : RefreshableLiveData<List<AuditCourse>, CacheIndicator>() {

    override fun observe(owner: LifecycleOwner, observer: Observer<List<AuditCourse>>) {
        super.observe(owner, observer)
        AuditCourseManager.getAuditListLive().observe(owner, observer)
    }

    override fun refresh(vararg indicators: CacheIndicator, callback: suspend (RefreshState<CacheIndicator>) -> Unit) {
        if (indicators == CacheIndicator.REMOTE) {
            async(CommonPool) {
                try {
                    AuditCourseManager.refreshAuditClasstable()
                    callback(RefreshState.Success(CacheIndicator.REMOTE))
                } catch (e: Exception) {
                    e.printStackTrace()
                    callback(RefreshState.Failure(e))
                }
            }
        }
    }

    override fun onActive() {
        refresh(CacheIndicator.REMOTE)
    }

    override fun cancel() {
        // no need to impl
    }

}

組織結(jié)構(gòu)

微北洋的模塊開(kāi)發(fā)中,不會(huì)強(qiáng)制要求采用MVP架構(gòu)或者M(jìn)VVM架構(gòu)之類赃磨。在架構(gòu)的選擇上鼓勵(lì)有效的創(chuàng)新筝家,服從個(gè)人喜好。如果你對(duì)于項(xiàng)目結(jié)構(gòu)有些拿捏不定的話邻辉,可以參考gpa2模塊或者schedule2模塊溪王,里面做的還算不錯(cuò)。

一般來(lái)講值骇,Model層是一定要分開(kāi)的莹菱,就是說(shuō),如果是網(wǎng)絡(luò)請(qǐng)求吱瘩,數(shù)據(jù)庫(kù)操作道伟,或者緩存封裝之類的話,要單獨(dú)寫開(kāi)使碾。比如說(shuō)蜜徽,把網(wǎng)絡(luò)請(qǐng)求的interface,網(wǎng)絡(luò)請(qǐng)求緩存的包裝票摇,寫在一個(gè)kt文件中拘鞋。把各種bean類的定義(要用data class),寫在一個(gè)kt文件中矢门。把數(shù)據(jù)庫(kù)操作寫在一個(gè)kt文件中盆色。

建議最后暴露給上層(Activity/Presenter)使用的時(shí)候,做二次封裝祟剔,比如說(shuō)AuditCourseManager.kt隔躲,建議不要讓上層直接操作數(shù)據(jù)庫(kù)網(wǎng)絡(luò)請(qǐng)求之類,尤其是數(shù)據(jù)庫(kù)

參考:課程表模塊

Schedule2
├── extensions
│   ├── ClassTableExtensions.kt
│   ├── ScheduleNetworkExtensions.kt
│   ├── StringExtensions.kt
│   └── UIExtensions.kt
├── model
│   ├── AbsClasstableProvider.kt
│   ├── CommonClassTable.kt
│   ├── MergedClassTableProvider.kt
│   ├── ScheduleDb.kt
│   ├── SchedulePref.kt
│   ├── TableData.kt
│   ├── audit // 蹭課
│   │   ├── AuditApi.kt // 蹭課Api接口相關(guān)
│   │   ├── AuditCourseManager.kt // 暴露給外面的物延,對(duì)蹭課Model做二次封裝
│   │   ├── AuditData.kt // 蹭課的數(shù)據(jù)bean類 / 數(shù)據(jù)庫(kù)Dao
│   │   └── AuditPopluar.kt // bean類
│   ├── custom
│   │   ├── CustomCourse.kt
│   │   ├── CustomCourseManager.kt // 暴露給外面的宣旱,對(duì)自定義Model做二次封裝
│   │   └── CustomCourseProvider.kt
│   ├── school
│   │   └── TjuCourse.kt 
│   └── total
│       └── TotalCourseManager.kt // 暴露給外面的,對(duì)綜合的課程Model做二次封裝
└── view
    ├── adapter
    │   ├── CommonItem.kt
    │   └── ItemAdapter.kt
    ├── audit
    │   ├── AuditActivity.kt
    │   ├── AuditComponents.kt
    │   └── search
    │       ├── AnimationUtil.java
    │       └── SearchResultActivity.kt
    ├── custom
    │   ├── AddCustomCourseActivity.kt
    │   ├── CustomComponents.kt
    │   └── CustomSettingBottomFragment.kt
    ├── detail
    │   ├── CourseDetailAdapter.kt
    │   ├── CourseDetailBottomFragment.kt
    │   ├── DetailComponents.kt
    │   ├── MultiCourseDetailFragment.kt
    │   └── WrapContentViewPager.java
    ├── schedule
    │   ├── CourseRefreshBottomFragment.kt
    │   ├── RefreshComponents.kt
    │   ├── ScheduleActivity.kt
    │   ├── ScheduleAdapter.kt
    │   └── ScheduleDecoration.kt
    ├── theme
    │   ├── ColorCircleView.kt
    │   └── SpreadChainLayout.kt
    └── week
        ├── WeekSelectAdapter.kt
        ├── WeekSquareView.kt
        └── WeekTestActivity.kt

16 directories, 44 files

資源命名

一定要加前綴
一定要加前綴
一定要加前綴

比如說(shuō)GPA模塊中的一個(gè)layout

layout
├── gpa2_activity_evaluate.xml
├── gpa2_activity_gpa.xml
├── gpa2_component_sort.xml
├── gpa2_component_term.xml
├── gpa2_component_total.xml
├── gpa2_item_course.xml
└── gpa2_tv_selected_term.xml

Drawable教届,其他資源文件同理

依賴

  • 不在特別需要的時(shí)候响鹃,不建議隨意使用第三方框架。

  • 不在特別需要的時(shí)候案训,不適用注解處理器框架买置,實(shí)在需要的話,找組長(zhǎng)評(píng)估后考慮批準(zhǔn)/或被diss强霎。

  • 不允許使用第三方Gradle插件忿项,實(shí)在需要的話,找組長(zhǎng)評(píng)估后考慮批準(zhǔn)/或被diss城舞。

  • 使用第三方庫(kù)的時(shí)候轩触,未批準(zhǔn)不允許暴露給其他模塊,使用implementation家夺。

  • 對(duì)于微型第三方庫(kù)(只有一個(gè)或者幾個(gè)文件那種)脱柱,可以拷貝代碼將來(lái)直接使用(要保留代碼注釋頭部的開(kāi)源證書),而不是依賴模塊拉馋。

  • Support庫(kù)使用統(tǒng)一依賴榨为,使用方式參考gpa2/schedule2模塊的build.gradle

        [':auth', ':bike', ':commons', ':gpa2', ':schedule', ':tjulibrary', ':tjunet', ':party', ':schedule2'].each {
            implementation project(it)
        }
        implementation 'com.twt.service:fragmentation:1.1.0'
    
        [*supportLibraries, 'constraint-layout', 'multidex', *archLibraries, 'kotlin-stdlib', 'butterknife', 'anko'].each {
            implementation dependenciesMap[it]
        }
        ['lifecycle-compiler', 'butterknife-compiler'].each {
            annotationProcessor dependenciesMap[it]
        }
    

期望/計(jì)劃

  • Recyclerview DSL使用文檔
  • 普通DSL使用文檔
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市煌茴,隨后出現(xiàn)的幾起案子随闺,更是在濱河造成了極大的恐慌,老刑警劉巖蔓腐,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矩乐,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡回论,警方通過(guò)查閱死者的電腦和手機(jī)散罕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)傀蓉,“玉大人笨使,你說(shuō)我怎么就攤上這事×藕Γ” “怎么了硫椰?”我有些...
    開(kāi)封第一講書人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)萨蚕。 經(jīng)常有香客問(wèn)我靶草,道長(zhǎng),這世上最難降的妖魔是什么岳遥? 我笑而不...
    開(kāi)封第一講書人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任奕翔,我火速辦了婚禮,結(jié)果婚禮上浩蓉,老公的妹妹穿的比我還像新娘派继。我一直安慰自己宾袜,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布驾窟。 她就那樣靜靜地躺著庆猫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绅络。 梳的紋絲不亂的頭發(fā)上月培,一...
    開(kāi)封第一講書人閱讀 52,696評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音恩急,去河邊找鬼杉畜。 笑死,一個(gè)胖子當(dāng)著我的面吹牛衷恭,可吹牛的內(nèi)容都是我干的此叠。 我是一名探鬼主播,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼随珠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼拌蜘!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起牙丽,我...
    開(kāi)封第一講書人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤简卧,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后烤芦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體举娩,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年构罗,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铜涉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡遂唧,死狀恐怖芙代,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情盖彭,我是刑警寧澤纹烹,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站召边,受9級(jí)特大地震影響铺呵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜隧熙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一片挂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦音念、人聲如沸沪饺。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)整葡。三九已至,卻和暖如春肝谭,著一層夾襖步出監(jiān)牢的瞬間掘宪,已是汗流浹背蛾扇。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工攘烛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人镀首。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓坟漱,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親更哄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子芋齿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,324評(píng)論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件成翩、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,124評(píng)論 4 61
  • 字符串 常見(jiàn)使用 函數(shù) 1.定義函數(shù)關(guān)鍵字def 觅捆,命名規(guī)則和變量名的規(guī)則一樣2.定義函數(shù)的參數(shù)相當(dāng)于數(shù)學(xué)里面的函...
    信賴_ec3b閱讀 161評(píng)論 0 0
  • 1.立春 最近楚熙然迷上了奇奇怪怪的各種app栅炒。 __卡路里測(cè)量?jī)x。她每次吃東西之前都...
    青衫依人閱讀 573評(píng)論 0 2
  • 遠(yuǎn)山 向遠(yuǎn)方走去 牧羊人 把最后一絲光亮 趕進(jìn)羊圈 像羊一樣 咀嚼柔軟 咀嚼牙齒間過(guò)剩的思念 我收回 收回 打撈所...
    道夫123閱讀 407評(píng)論 18 30