一文讀懂 AOP | 你想要的最全面 AOP 方法探討

AOP系列思維導(dǎo)圖

前前言

相信大家在入門(mén) AOP 時(shí),常常被繁多的術(shù)語(yǔ)嫌拣、方法和框架繞暈柔袁。AOP 好像有點(diǎn)耳熟?Javaseopt 是個(gè)什么亭罪?Javassist 又是啥瘦馍?Dexposed、APT 也是 AOP应役?本篇將輔助你快速理清概念情组,掌握 AOP 思想,找到最適合自己業(yè)務(wù)場(chǎng)景的 AOP 方法箩祥。

前言

上文 也談代碼 —— 重構(gòu)兩年前的代碼 中院崇,我們提到最佳的系統(tǒng)架構(gòu)由模塊化的關(guān)注面領(lǐng)域組成,每個(gè)關(guān)注面均用純 Java 對(duì)象實(shí)現(xiàn)袍祖。不同的領(lǐng)域之間用最不具有侵害性的「方面」或「類(lèi)方面」工具整合起來(lái)底瓣。

反思自己的項(xiàng)目,有很多模塊沒(méi)有做到恰當(dāng)?shù)厍蟹株P(guān)注面,往往在業(yè)務(wù)邏輯中耦合了業(yè)務(wù)埋點(diǎn)捐凭、權(quán)限申請(qǐng)拨扶、登陸狀態(tài)的判斷、對(duì)不可預(yù)知異常 try-catch 和一些持久化操作茁肠。

雖說(shuō)保證代碼最簡(jiǎn)單化和可運(yùn)行化很有必要患民,但我們還是可以嘗試小范圍的重構(gòu)。就如「代碼整潔之道」中所說(shuō):通過(guò)方面式的手段切分關(guān)注面的威力不可低估垦梆,假如你能用 POJO 編寫(xiě)應(yīng)用程序的領(lǐng)域邏輯匹颤,在代碼層面與架構(gòu)關(guān)注面分離開(kāi),就有可能真正地用測(cè)試來(lái)驅(qū)動(dòng)架構(gòu)托猩。

這里的切分關(guān)注面的思想就是 AOP印蓖。


一、AOP即面向切向編程

AOP 是 Aspect Oriented Programming 的縮寫(xiě)京腥,譯為面向切向編程赦肃。用我們最常用的 OOP 來(lái)對(duì)比理解:

縱向關(guān)系 OOP,橫向角度 AOP

舉個(gè)小例子:

設(shè)計(jì)一個(gè)日志打印模塊绞旅。按 OOP 思想摆尝,我們會(huì)設(shè)計(jì)一個(gè)打印日志 LogUtils 類(lèi),然后在需要打印的地方引用即可因悲。

public class ClassA {
    private void initView() {
        LogUtils.d(TAG, "onInitView");
    }
}

public class ClassB {
    private void onDataComplete(Bean bean) {
        LogUtils.d(TAG, bean.attribute);
    }
}

public class ClassC {
    private void onError() {
        LogUtils.e(TAG, "onError");
    }
}

看起來(lái)沒(méi)有任何問(wèn)題是吧堕汞?

但是這個(gè)類(lèi)是橫跨并嵌入眾多模塊里的,在各個(gè)模塊里分散得很厲害晃琳,到處都能見(jiàn)到讯检。從對(duì)象組織角度來(lái)講,我們一般采用的分類(lèi)方法都是使用類(lèi)似生物學(xué)分類(lèi)的方法卫旱,以「繼承」關(guān)系為主線人灼,我們稱(chēng)之為縱向,也就是 OOP顾翼。設(shè)計(jì)時(shí)只使用 OOP思想可能會(huì)帶來(lái)兩個(gè)問(wèn)題:

  1. 對(duì)象設(shè)計(jì)的時(shí)候一般都是縱向思維投放,如果這個(gè)時(shí)候考慮這些不同類(lèi)對(duì)象的共性,不僅會(huì)增加設(shè)計(jì)的難度和復(fù)雜性适贸,還會(huì)造成類(lèi)的接口過(guò)多而難以維護(hù)(共性越多灸芳,意味著接口契約越多)。

  2. 需要對(duì)現(xiàn)有的對(duì)象 動(dòng)態(tài)增加 某種行為或責(zé)任時(shí)非常困難拜姿。

而AOP就可以很好地解決以上的問(wèn)題烙样,怎么做到的?除了這種縱向分類(lèi)之外蕊肥,我們從橫向的角度去觀察這些對(duì)象谒获,無(wú)需再去到處調(diào)用 LogUtils 了,聲明哪些地方需要打印日志,這個(gè)地方就是一個(gè)切面批狱,AOP 會(huì)在適當(dāng)?shù)臅r(shí)機(jī)為你把打印語(yǔ)句插進(jìn)切面裸准。

// 只需要聲明哪些方法需要打印 log,打印什么內(nèi)容
public class ClassA {
    @Log(msg = "onInitView")
    private void initView() {
    }
}

public class ClassB {
    @Log(msg = "bean.attribute")
    private void onDataComplete(Bean bean) {
    }
}

public class ClassC {
    @Log(msg = "onError")
    private void onError() {
    }
}

如果說(shuō) OOP 是把問(wèn)題劃分到單個(gè)模塊的話精耐,那么 AOP 就是把涉及到眾多模塊的某一類(lèi)問(wèn)題進(jìn)行統(tǒng)一管理狼速。AOP的目標(biāo)是把這些功能集中起來(lái),放到一個(gè)統(tǒng)一的地方來(lái)控制和管理卦停。利用 AOP 思想,這樣對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行了隔離恼蓬,從而降低業(yè)務(wù)邏輯各部分之間的耦合惊完,提高程序的可重用性,提高開(kāi)發(fā)效率处硬。

OOP 與 AOP 的區(qū)別

  1. 面向目標(biāo)不同:簡(jiǎn)單來(lái)說(shuō) OOP 是面向名詞領(lǐng)域小槐,AOP 面向動(dòng)詞領(lǐng)域。

  2. 思想結(jié)構(gòu)不同:OOP 是縱向結(jié)構(gòu)荷辕,AOP 是橫向結(jié)構(gòu)凿跳。

  3. 注重方面不同:OOP 注重業(yè)務(wù)邏輯單元的劃分,AOP 偏重業(yè)務(wù)處理過(guò)程中的某個(gè)步驟或階段疮方。

OOP 與 AOP 的聯(lián)系

兩者之間是一個(gè)相互補(bǔ)充和完善的關(guān)系控嗜。


二、應(yīng)用場(chǎng)景

那AOP既然這么有用骡显,除了上面提到的打印日志場(chǎng)景疆栏,還有沒(méi)有其他用處呢?

當(dāng)然有惫谤!

只要系統(tǒng)的業(yè)務(wù)模塊都需要引用通用模塊壁顶,就可以使用AOP。以下是一些常用的業(yè)務(wù)場(chǎng)景:

1. 參數(shù)校驗(yàn)和判空

系統(tǒng)之間在進(jìn)行接口調(diào)用時(shí)溜歪,往往是有入?yún)鬟f的若专,入?yún)⑹墙涌跇I(yè)務(wù)邏輯實(shí)現(xiàn)的先決條件,有時(shí)入?yún)⒌娜笔Щ蝈e(cuò)誤會(huì)導(dǎo)致業(yè)務(wù)邏輯的異常蝴猪,大量的異常捕獲無(wú)疑增加了接口實(shí)現(xiàn)的復(fù)雜度调衰,也讓代碼顯得雍腫冗長(zhǎng),因此提前對(duì)入?yún)⑦M(jìn)行驗(yàn)證是有必要的拯腮,可以提前處理入?yún)?shù)據(jù)的異常窖式,并封裝好異常轉(zhuǎn)化成結(jié)果對(duì)象返回給調(diào)用方,也讓業(yè)務(wù)邏輯解耦變得獨(dú)立动壤。

2. Android API23+的權(quán)限控制

避免到處都是申請(qǐng)權(quán)限和處理權(quán)限的代碼

3. 無(wú)痕埋點(diǎn)

4. 安全控制

比如全局的登錄狀態(tài)流程控制萝喘。

5. 日志記錄

6. 事件防抖

防止View被連續(xù)點(diǎn)擊觸發(fā)多次事件

7. 性能統(tǒng)計(jì)

檢測(cè)方法耗時(shí)其實(shí)已經(jīng)有一些現(xiàn)成的工具,比如 trace view。痛點(diǎn)是這些工具使用起來(lái)都比較麻煩阁簸,效率低下爬早,而且無(wú)法針對(duì)某一個(gè)塊代碼或者某個(gè)指定的sdk進(jìn)行查看方法耗時(shí)∑裘茫可以采用 AOP 思想對(duì)每個(gè)方法做一個(gè)切點(diǎn)筛严,在執(zhí)行之后打印方法耗時(shí)。

8. 事務(wù)處理

聲明方法饶米,為特定方法加上事務(wù)桨啃,指定情況下(比如拋出異常)回滾事務(wù)

9. 異常處理

替代防御性的 try-Catch。

10. 緩存

緩存某方法的返回值檬输,下次執(zhí)行該方法時(shí)照瘾,直接從緩存里獲取。

11. 軟件破解

使用 Hook 修改軟件的驗(yàn)證類(lèi)的判斷邏輯丧慈。

12. 熱修復(fù)

AOP 可以讓我們?cè)趫?zhí)行一個(gè)方法的前插入另一個(gè)方法析命,運(yùn)用這個(gè)思路,我們可以把有 bug 的方法替換成我們下發(fā)的新方法逃默。


三鹃愤、AOP 方法

本篇為入門(mén)篇,重在理解 AOP 思想和應(yīng)用完域,輔助你快速進(jìn)行 AOP 方法選型软吐,所以 AOP 方法這塊暫不會(huì)深入原理和術(shù)語(yǔ)。

Android AOP 常用的方法有 JNI HOOK 和 靜態(tài)織入筒主。

動(dòng)態(tài)織入 Hook 方式

在運(yùn)行期关噪,目標(biāo)類(lèi)加載后,為接口動(dòng)態(tài)生成代理類(lèi)乌妙,將切面植入到代理類(lèi)中使兔。相對(duì)于靜態(tài)AOP更加靈活。但切入的關(guān)注點(diǎn)需要實(shí)現(xiàn)接口藤韵。對(duì)系統(tǒng)有一點(diǎn)性能影響虐沥。

  1. Dexposed

  2. Xposed

  3. epic
    在 native 層修改 java method 對(duì)應(yīng)的 native 指針

動(dòng)態(tài)字節(jié)碼生成

  1. Cglib + Dexmaker

Cglib 是一個(gè)強(qiáng)大的,高性能的 Code 生成類(lèi)庫(kù), 原理是在運(yùn)行期間目標(biāo)字節(jié)碼加載后泽艘,通過(guò)字節(jié)碼技術(shù)為一個(gè)類(lèi)創(chuàng)建子類(lèi)欲险,并在子類(lèi)中采用方法攔截的技術(shù)攔截所有父類(lèi)方法的調(diào)用,順勢(shì)織入橫切邏輯匹涮。由于是通過(guò)子類(lèi)來(lái)代理父類(lèi)天试,因此不能代理被 final 字段修飾的方法。

但是 Cglib 有一個(gè)很致命的缺點(diǎn):底層是采用著名的 ASM 字節(jié)碼生成框架然低,使用字節(jié)碼技術(shù)生成代理類(lèi)喜每,也就是通過(guò)操作字節(jié)碼來(lái)生成的新的 .class 文件务唐,而我們?cè)?Android 中加載的是優(yōu)化后的 .dex 文件,也就是說(shuō)我們需要可以動(dòng)態(tài)生成 .dex 文件代理類(lèi)带兜,因此 Cglib 不能在 Android 中直接使用枫笛。有大神根據(jù) Dexmaker 框架(dex代碼生成工具)來(lái)仿照 Cglib 庫(kù)動(dòng)態(tài)生成 .dex 文件,實(shí)現(xiàn)了類(lèi)似于 Cglib 的 AOP 的功能刚照。詳細(xì)的用法可參考:將cglib動(dòng)態(tài)代理思想帶入Android開(kāi)發(fā)

靜態(tài)織入方式

  • 在編譯期織入刑巧,切面直接以字節(jié)碼的形式編譯到目標(biāo)字節(jié)碼文件中,无畔,這要求使用特殊的 Java 編譯器啊楚。
  • 在類(lèi)裝載期織入,這要求使用特殊的類(lèi)裝載器檩互。

靜態(tài)織入對(duì)系統(tǒng)無(wú)性能影響特幔。但靈活性不夠。

  1. APT

  2. AspectJ

  3. ASM

  4. Javassist

  5. DexMaker

  6. ASMDEX

這么多方法闸昨?有什么區(qū)別?

方法作用期比對(duì)

一圖勝千言

AOP 方法作用時(shí)期比對(duì).png

AOP 是思想薄风,上面的方法其實(shí)都是工具饵较,只不過(guò)是插入時(shí)機(jī)和方式不同。

同:都可以織入邏輯遭赂,都體現(xiàn)了 AOP 思想
異:作用的時(shí)機(jī)不一樣循诉,且適用的注解的類(lèi)型不一樣。

方法優(yōu)缺點(diǎn)撇他、難點(diǎn)比對(duì)

方法 作用時(shí)機(jī) 操作對(duì)象 優(yōu)點(diǎn) 缺點(diǎn) 為了上手茄猫,我需要掌握什么?
APT 編譯期:還未編譯為 class 時(shí) .java 文件 1. 可以織入所有類(lèi)困肩;2. 編譯期代理划纽,減少運(yùn)行時(shí)消耗 1. 需要使用 apt 編譯器編譯;2. 需要手動(dòng)拼接代理的代碼(可以使用 Javapoet 彌補(bǔ))锌畸;3. 生成大量代理類(lèi) 設(shè)計(jì)模式和解耦思想的靈活應(yīng)用
AspectJ 編譯期勇劣、加載時(shí) .java 文件 功能強(qiáng)大,除了 hook 之外潭枣,還可以為目標(biāo)類(lèi)添加變量比默,接口。也有抽象盆犁,繼承等各種更高級(jí)的玩法命咐。 1. 不夠輕量級(jí);2. 定義的切點(diǎn)依賴(lài)編程語(yǔ)言谐岁,無(wú)法兼容Lambda語(yǔ)法醋奠;3. 無(wú)法織入第三方庫(kù)榛臼;4. 會(huì)有一些兼容性問(wèn)題,如:D8钝域、Gradle 4.x等 復(fù)雜的語(yǔ)法讽坏,但掌握幾個(gè)簡(jiǎn)單的,就能實(shí)現(xiàn)絕大多數(shù)場(chǎng)景
Javassist 編譯期:class 還未編譯為 dex 時(shí)或運(yùn)行時(shí) class 字節(jié)碼 1. 減少了生成子類(lèi)的開(kāi)銷(xiāo)例证;2. 直接操作修改編譯后的字節(jié)碼路呜,直接繞過(guò)了java編譯器,所以可以做很多突破限制的事情织咧,例如胀葱,跨 dex 引用,解決熱修復(fù)中 CLASS_ISPREVERIFIED 問(wèn)題笙蒙。 運(yùn)行時(shí)加入切面邏輯抵屿,產(chǎn)生性能開(kāi)銷(xiāo)。 1. 自定義 Gradle 插件捅位;2. 掌握groovy 語(yǔ)言
ASM 編譯期或運(yùn)行期字節(jié)碼注入 class 字節(jié)碼 小巧輕便轧葛、性能好,效率比Javassist高 學(xué)習(xí)成本高 需要熟悉字節(jié)碼語(yǔ)法艇搀,ASM 通過(guò)樹(shù)這種數(shù)據(jù)結(jié)構(gòu)來(lái)表示復(fù)雜的字節(jié)碼結(jié)構(gòu)尿扯,并利用 Push 模型來(lái)對(duì)樹(shù)進(jìn)行遍歷,在遍歷過(guò)程中對(duì)字節(jié)碼進(jìn)行修改焰雕。
ASMDEX 編譯期和加載時(shí):轉(zhuǎn)化為 .dex 后 Dex 字節(jié)碼衷笋,創(chuàng)建 class 文件 可以織入所有類(lèi) 學(xué)習(xí)成本高 需要對(duì) class 文件比較熟悉,編寫(xiě)過(guò)程復(fù)雜矩屁。
DexMaker 同ASMDEX Dex 字節(jié)碼辟宗,創(chuàng)建 dex 文件 同ASMDEX 同ASMDEX 同ASMDEX
Cglib 運(yùn)行期生成子類(lèi)攔截方法 字節(jié)碼 沒(méi)有接口也可以織入 1. 不能代理被final字段修飾的方法;2. 需要和 dexmaker 結(jié)合使用 --
xposed 運(yùn)行期hook -- 能hook自己應(yīng)用進(jìn)程的方法吝秕,能hook其他應(yīng)用的方法泊脐,能hook系統(tǒng)的方法 依賴(lài)三方包的支持,兼容性差郭膛,手機(jī)需要root --
dexposed 運(yùn)行期hook -- 只能hook自己應(yīng)用進(jìn)程的方法晨抡,但無(wú)需root 1. 依賴(lài)三方包的支持,兼容性差则剃;2. 只能支持 Dalvik 虛擬機(jī) --
epic 運(yùn)行期hook -- 支持 Dalvik 和 Art 虛擬機(jī) 只適合在開(kāi)發(fā)調(diào)試中使用耘柱,碎片化嚴(yán)重有兼容性問(wèn)題 --

四、常用的 AOP 方法介紹

業(yè)務(wù)中常用的 AOP 方式為靜態(tài)織入棍现,接下來(lái)詳細(xì)介紹靜態(tài)織入中最常用的三種方式:APT调煎、AspectJ、Javassist己肮。

1. APT

APT (Annotation Processing Tool )即注解處理器士袄,是一種處理注解的工具悲关,確切的說(shuō)它是 javac 的一個(gè)工具,它用來(lái)在編譯時(shí)掃描和處理注解娄柳。注解處理器以 Java 代碼( 或者編譯過(guò)的字節(jié)碼)作為輸入寓辱,生成 .java 文件作為輸出。簡(jiǎn)單來(lái)說(shuō)就是在編譯期赤拒,通過(guò)注解生成 .java 文件秫筏。使用的 Annotation 類(lèi)型是 SOURCE。

代表框架:DataBinding挎挖、Dagger2这敬、ButterKnife、EventBus3蕉朵、DBFlow崔涂、AndroidAnnotation

為什么這些框架注解實(shí)現(xiàn) AOP 要使用 APT?

目前 Android 注解解析框架主要有兩種實(shí)現(xiàn)方法始衅,一種是運(yùn)行期通過(guò)反射去解析當(dāng)前類(lèi)冷蚂,注入相應(yīng)要運(yùn)行的方法。另一種是在編譯期生成類(lèi)的代理類(lèi)汛闸,在運(yùn)行期直接調(diào)用代理類(lèi)的代理方法帝雇,APT 指的是后者。

如果不使用APT基于注解動(dòng)態(tài)生成 java 代碼蛉拙,那么就需要在運(yùn)行時(shí)使用反射或者動(dòng)態(tài)代理,比如大名鼎鼎的 butterknife 之前就是在運(yùn)行時(shí)反射處理注解彻亲,為我們實(shí)例化控件并添加事件孕锄,然而這種方法很大的一個(gè)缺點(diǎn)就是用了反射,導(dǎo)致 app 性能下降苞尝。所以后面 butterknife 改為 apt 的方式畸肆,可以留意到,butterknife 會(huì)在編譯期間生成一個(gè) XXX_ViewBinding.java宙址。雖然 APT 增加了代碼量轴脐,但是不再需要用反射,也就無(wú)損性能抡砂。

APT 的缺點(diǎn)改進(jìn)

性能問(wèn)題解決了大咱,又帶來(lái)新的問(wèn)題了。我們?cè)谔幚碜⒔饣蛟獢?shù)據(jù)文件的時(shí)候注益,往往有自動(dòng)生成源代碼的需要碴巾。難道我們要手動(dòng)拼接源代碼嗎?不不不丑搔,這不符合代碼的優(yōu)雅厦瓢,JavaPoet 這個(gè)神器就是來(lái)解決這個(gè)問(wèn)題的提揍。

JavaPoet

JavaPoet 是 square 推出的開(kāi)源 java 代碼生成框架,提供 Java Api 生成 .java 源文件煮仇。這個(gè)框架功能非常有用劳跃,我們可以很方便的使用它根據(jù)注解、數(shù)據(jù)庫(kù)模式浙垫、協(xié)議格式等來(lái)對(duì)應(yīng)生成代碼刨仑。通過(guò)這種自動(dòng)化生成代碼的方式,可以讓我們用更加簡(jiǎn)潔優(yōu)雅的方式要替代繁瑣冗雜的重復(fù)工作绞呈。本質(zhì)上就是用建造者模式來(lái)替代手工拼寫(xiě)源文件贸人。

JavaPoet詳細(xì)用法可參考:javapoet——讓你從重復(fù)無(wú)聊的代碼中解放出來(lái)

2. AspectJ

目前最好、最方便佃声、最火的 AOP 實(shí)現(xiàn)方式當(dāng)屬 AspectJ艺智,它是一種幾乎和 Java 完全一樣的語(yǔ)言,而且完全兼容 Java圾亏。

但是在 Android 上集成 AspectJ 是比較復(fù)雜的十拣。

我們需要使用 andorid-library gradle 插件在編譯時(shí)做一些 hook。使用 AspectJ 的編譯器(ajc志鹃,一個(gè)java編譯器的擴(kuò)展)對(duì)所有受 aspect 影響的類(lèi)進(jìn)行織入夭问。在 gradle 的編譯 task 中增加一些額外配置,使之能正確編譯運(yùn)行曹铃。等等等等……

有很多庫(kù)幫助我們完成這些工作缰趋,可以方便快捷接入 AspectJ。

AspectJ 框架選型

庫(kù) 大小 兼容性 缺點(diǎn) 備注
Hugo 131kb -- 不支持AAR或JAR切入 --
gradle-android-aspectj-plugin -- -- 無(wú)法兼容databinding陕见,不支持AAR或JAR切入 該庫(kù)已經(jīng)棄用
AspectJx(推薦) 44kb 會(huì)和有transform功能的插件沖突秘血,如:retroLambda 在前兩者基礎(chǔ)上擴(kuò)展支持AAR, JAR及Kotlin的應(yīng)用 僅支持annotation的方式展融,不支持 *.aj 文件的編譯

3. Javassist

代表框架:熱修復(fù)框架HotFix 晴音、Savior(InstantRun)

Javassist 是一個(gè)編輯字節(jié)碼的框架,作用是修改編譯后的 class 字節(jié)碼漏益,ASM也有這個(gè)功能忍坷,不過(guò) Javassist 的 Java 風(fēng)格 API 要比 ASM 更容易上手粘舟。

既然是修改編譯后的 class 字節(jié)碼,首先我們得知道什么時(shí)候編譯完成佩研,并且我們要在 .class文件被轉(zhuǎn)為 .dex 文件之前去做修改柑肴。在 Gradle Transfrom 這個(gè) api 出來(lái)之前,想要監(jiān)聽(tīng)項(xiàng)目被打包成 .dex 的時(shí)機(jī)韧骗,就必須自定義一個(gè) Gradle Task嘉抒,插入到 predex 或者 dex 之前,在這個(gè)自定義的 Task 中使用 Javassist 或者 ASM 對(duì) class 字節(jié)碼進(jìn)行操作袍暴。而 Transform 更為方便些侍,我們不再需要插入到某個(gè)Task前面隶症。Tranfrom 有自己的執(zhí)行時(shí)機(jī),一經(jīng)注冊(cè)便會(huì)自動(dòng)添加到 Task 執(zhí)行序列中岗宣,且正好是 class 被打包成dex之前蚂会。


五、總結(jié)

AOP 重在理解這種思想:

  1. 先考慮要在什么期間插入代碼耗式,選用合適的 AOP 方法胁住;
  2. 找準(zhǔn)切入點(diǎn)也就是代碼可注入的點(diǎn),比如一個(gè)方法的調(diào)用處或者方法內(nèi)部刊咳;
  3. 接著考慮怎么過(guò)濾方法彪见,找到注入點(diǎn)的描述,比如注入到所有onClick方法:call(* view.onClick(..))娱挨;
  4. 接著要考慮以怎樣的方式處理代碼余指,是在代碼執(zhí)行前?執(zhí)行后跷坝?還是包裹代碼酵镜?還是替換目標(biāo)代碼?

任何的技術(shù)都需要有業(yè)務(wù)依托和落地柴钻,想要一步步實(shí)現(xiàn) AOP 應(yīng)用落地淮韭?請(qǐng)戳 一文應(yīng)用 AOP | 最全選型考量 + 邊剖析經(jīng)典開(kāi)源庫(kù)邊實(shí)踐,美滋滋贴届。


六靠粪、還想了解更多?

博文推薦

書(shū)籍推薦


我是 FeelsChaotic毫蚓,一個(gè)寫(xiě)得了代碼 p 得了圖庇配,剪得了視頻畫(huà)得了畫(huà)的程序媛,致力于追求代碼優(yōu)雅绍些、架構(gòu)設(shè)計(jì)和 T 型成長(zhǎng)。

歡迎關(guān)注 FeelsChaotic 的簡(jiǎn)書(shū)掘金耀鸦,如果我的文章對(duì)你哪怕有一點(diǎn)點(diǎn)幫助柬批,歡迎 ??!你的鼓勵(lì)是我寫(xiě)作的最大動(dòng)力袖订!

最最重要的氮帐,請(qǐng)給出你的建議或意見(jiàn),有錯(cuò)誤請(qǐng)多多指正洛姑!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末上沐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子楞艾,更是在濱河造成了極大的恐慌参咙,老刑警劉巖龄广,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蕴侧,居然都是意外死亡择同,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)净宵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)敲才,“玉大人,你說(shuō)我怎么就攤上這事择葡〗粑洌” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵敏储,是天一觀的道長(zhǎng)阻星。 經(jīng)常有香客問(wèn)我,道長(zhǎng)虹曙,這世上最難降的妖魔是什么迫横? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮酝碳,結(jié)果婚禮上矾踱,老公的妹妹穿的比我還像新娘。我一直安慰自己疏哗,他們只是感情好呛讲,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著返奉,像睡著了一般贝搁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芽偏,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天雷逆,我揣著相機(jī)與錄音,去河邊找鬼污尉。 笑死膀哲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的被碗。 我是一名探鬼主播某宪,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锐朴!你這毒婦竟也來(lái)了兴喂?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎衣迷,沒(méi)想到半個(gè)月后畏鼓,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蘑险,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年滴肿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佃迄。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡泼差,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呵俏,到底是詐尸還是另有隱情堆缘,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布普碎,位于F島的核電站吼肥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏麻车。R本人自食惡果不足惜缀皱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望动猬。 院中可真熱鬧啤斗,春花似錦、人聲如沸赁咙。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)彼水。三九已至崔拥,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間凤覆,已是汗流浹背链瓦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盯桦,地道東北人澡绩。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像俺附,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子溪掀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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

  • 1. AOP與OOP的區(qū)別 平時(shí)我接觸多的就是OOP(Object Oriented Programming面向?qū)?..
    生椰拿鐵錘閱讀 2,388評(píng)論 3 22
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,182評(píng)論 25 707
  • AOP:面向切面編程(Aspect-Oriented Programming)事镣。如果說(shuō),OOP如果是把問(wèn)題劃分到單...
    North_2016閱讀 46,761評(píng)論 38 269
  • 以后的日子 要忙里偷閑 要乏味中取樂(lè) 雖然寫(xiě)的不好 盡量一日一寫(xiě)吧
    難_閱讀 121評(píng)論 0 0
  • 六一兒童節(jié),陪女兒觀看某高校學(xué)前教育學(xué)院舉辦的鋼琴音樂(lè)會(huì)“為兒童的音樂(lè)”璃哟。 觀演期間氛琢,女兒就像只毛毛蟲(chóng),動(dòng)來(lái)動(dòng)去随闪,...
    小淑文閱讀 815評(píng)論 0 5