最熟悉的陌生人 -- Gradle

Gradle對于很多開發(fā)者來說有一種既熟悉又陌生的感覺穿铆,他是離我們那么近艺演,以至于我每天做項(xiàng)目都需要他,但是他又是離我們那么的遠(yuǎn)贱傀,因?yàn)槲覐膩矶疾恢浪锩鎸懙臇|西到底是啥意思惨撇。

對于這樣的問題我也同樣困惑,以前忙做這項(xiàng)目看到網(wǎng)上有一些教程拿過來就直接用了府寒,并沒有深入去理解魁衙,大部分精力還是消耗在了項(xiàng)目本身。

趕在這次GapYear的結(jié)尾株搔,準(zhǔn)備繼續(xù)開發(fā)Android剖淀,便想著應(yīng)該從Gradle下手,稍微深入理解下這東西纤房。

在開篇之前先放出我參考的幾篇博文:

Groovy腳本基礎(chǔ)全攻略
Gradle腳本基礎(chǔ)全攻略
深入理解Android之Gradle

有耐心的同學(xué)看完這三篇博文相信也不用繼續(xù)讀下面的內(nèi)容了纵隔,我是將Android相關(guān)的內(nèi)容總結(jié)了起來。不過添加了一些實(shí)例和理解在里面,希望能夠?qū)δ憷斫釭radle有幫助捌刮。

1 Gradle

想要了解Gradle的話碰煌,先要知道下面的一些基礎(chǔ)。

  1. Gradle使用的語言是Groovy绅作,Groovy語言相關(guān)內(nèi)容請查閱Groovy官網(wǎng)芦圾。
  2. Gradle有自己的DSL和構(gòu)建流程。Gradle DSL
  3. Gradle插件Android Plugin有自己的DSL俄认。Android Plugin DSL

其實(shí)如果你有時(shí)間有耐心有一定英文閱讀能力个少,看完這些文檔,相信對于Android開發(fā)下如何使用Gradle有非常深刻的理解梭依,但是按照這三個(gè)文檔的內(nèi)容稍算,估計(jì)沒有十天半個(gè)月很難啃的透。這里也不會完全去帶著你詳細(xì)的介紹每個(gè)接口的用法役拴,只是帶你有一個(gè)明確的認(rèn)識糊探。

按照這個(gè)結(jié)構(gòu)來一個(gè)一個(gè)講。

1.1 Groovy

想要對一門語言理解透徹哪是一小段文字就能說得清道得明的呢河闰,不過好在我們只是用他來做Gradle的配置科平,理解一些與Gradle相關(guān)的語法就可以了。

1.1.1 定義變量

Groovy作為一門動態(tài)語言姜性,所以肯定是支持動態(tài)類型瞪慧。那么在定義變量的時(shí)候是可以不指定變量類型的。

使用def關(guān)鍵字來定義變量部念,但是def關(guān)鍵字也是可以省略的弃酌。例如:

def a = 1           //使用def關(guān)鍵字定義變量a
a = 1               //省略def同樣可以
def int a = 1       //指定變量類型
int a = 1       //省略def關(guān)鍵字,并指定變量類型

但是這并沒有突出動態(tài)語言的特性儡炼,既然是動態(tài)語言妓湘,那么變量a可以是任意類型的,比如:

def a = 1       //聲明的時(shí)候是一個(gè)int類型
a = 'hello groovy'      //然后賦值一個(gè)String
assert a instanceof String      //斷言測試是沒有問題的

但是如果在定義變量的時(shí)候指定了變量類型乌询,那么這個(gè)變量就不在是動態(tài)變量榜贴,例如:

def int a = 1       //聲明變量的時(shí)候指定變量a是一個(gè)int型
a = 'hello groovy'      //將一個(gè)String賦值給a的時(shí)候會報(bào)錯(cuò)

1.1.2 類型

由于Groovy是基于Java的,所以Java常用類型都有妹田,而那些類型你肯定也都會用唬党,所以就不一一列舉了,這里先列出Groovy的類型鬼佣,然后我們說幾個(gè)Groovy中比較特別的部分驶拱。

  • String
  • char
  • byte
  • short
  • int
  • long
  • BigInteger
  • float
  • double
  • BigDecimal
  • boolean
  • List
  • Map
  • Array
1.1.2.1 String

Groovy里面的String比較的變態(tài),因?yàn)樗谋磉_(dá)方式太多晶衷,下面一一列舉一下String幾種形式:

def v = '支持占位符'

def a = '單引號形式'
def b = "雙引號形式屯烦,${v}"
def c = '''三個(gè)單引號形式
支持多行
不支持占位符'''
def d = """三個(gè)雙引號形式
支持多行
支持占位符坷随,${v}"""
def e = /反斜杠形式
支持多行
支持占位符,${v}/
def f = $/美刀反斜杠形式
支持多行
支持占位符驻龟,${v}/$

String的形式雖然很多温眉,但是還是建議你只使用'"這兩種形式,以防別人看不懂你寫的代碼翁狐,并不是大家都有同樣的Groovy語法基礎(chǔ)类溢,如果你一定要用的話,也請你注釋清楚這是什么意思露懒。

1.1.2.2 List

Groovy中的List使用的是java.util.List闯冷,默認(rèn)使用ArrayList,如果你想要使用LinkedList需要特別指明懈词;List是動態(tài)的蛇耀,里面可以同時(shí)放不同類型的數(shù)據(jù)。

//動態(tài)類型坎弯,可以同時(shí)存放任意類型的值
def list = [0, "list", 1.2, false]      

//指定LinkedList的兩種方式
LinkedList linkedList = [1, 2, 3]
def linkedList2 = [1, 2, 3] as java.util.LinkedList

//根據(jù)下標(biāo)獲得list數(shù)據(jù)的方式
assert list.get(2) == 1.2
assert list[2] == 1.2

關(guān)于List就說這么多纺涤,Java中的用法,這里基本都支持抠忘,所以也沒有必要多講撩炊,不過有關(guān)List的迭代我們到Closure的時(shí)候在講。

1.1.2.3 Array數(shù)組

Groovy中崎脉,使用Array數(shù)組和使用List最大的區(qū)別就是拧咳,Array數(shù)組必須指定類型。

//聲明數(shù)組的兩種方式囚灼,必須指定類型
def String[] arrStr = ["a", "b", "c"]
def arrNum = [1, 2, 3] as int[]
//創(chuàng)建一個(gè)有界限范圍的空數(shù)組
def String[] arrStr2 = new String[2]

//二維數(shù)組
def int[][] matrix2 = [[1, 2], [3, 4]]
1.1.2.4 Map

Groovy中的Map使用非常的簡單骆膝。

//定義一個(gè)map
def person = [name: '阿,希爸', age: 30, sex: 'male']
//獲取對應(yīng)的key值
assert person['name'] == '阿灶体,希爸'
assert person.age == 30
assert person.get('sex') == 'male'

//修改key對應(yīng)的值
person.age == 31
//添加新的鍵值對
person.country =  '中國'
person.put('province', '安徽')

//空map
def emptyMap = [:]

1.1.3 函數(shù)

Groovy中的函數(shù)可以不指定返回值類型谭网,可以不指定參數(shù)類型,函數(shù)代碼中的最后一行表達(dá)式就是返回值赃春,調(diào)用函數(shù)的時(shí)候可以省略圓括號。例如:

//定義一個(gè)不指定返回值類型的函數(shù)
def test(x, y){
    println "x=${x}, y=${y}"    //省略了圓括號
    x + y   //最后一行為返回值劫乱,想到于return x+y
}

//調(diào)用test時(shí)候省略圓括號织中,建議參數(shù)大于一個(gè)的時(shí)候還是加上的好
def result = test 1, 2
println result

1.1.4 Closure 閉包

首先說一下,這個(gè)閉包和javascript中的閉包不是一回事衷戈,如果你有javascript基礎(chǔ)狭吼,看見閉包兩個(gè)詞就覺得沒有看的必要那就得不償失了,Groovy中的閉包更像是一個(gè)匿名函數(shù)殖妇。
Groovy官方的解釋的話刁笙,閉包應(yīng)該是一個(gè)能夠接受參數(shù),能夠返回一個(gè)值,能夠賦予一個(gè)變量的匿名代碼塊疲吸。
ClosureGroovy使用率非常非常高的一個(gè)語法座每,我們在build.gradle中寫的大部分內(nèi)容都是Closure,所以想要理解好我們build.gradle的內(nèi)容摘悴,一定要好好理解Closure閉包這個(gè)概念峭梳。

1.1.4.1 定義一個(gè)閉包

定義一個(gè)閉包的語法如下:

{ [closureParameters -> ] statements }

[closureParameters -> ]部分能夠接受參數(shù),可以省略蹂喻,參數(shù)可以是動態(tài)無類型的葱椭,也可以是指定類型的,但是如果指定類型了就必須按照類型傳參口四。

如果你只有一個(gè)參數(shù)孵运,那么你可以不用顯示的指定這個(gè)參數(shù),Groovy中有一個(gè)it參數(shù)可以指代蔓彩。說出來可能有就繞治笨,看例子吧:

//聲明一個(gè)閉包,接受兩個(gè)參數(shù)粪小,返回他們相加的結(jié)果
{ a, b -> a + b }

//如果你只有一個(gè)參數(shù)大磺,則可以省略,直接用it指代這個(gè)參數(shù)
{ it -> println it}
//完全可以寫成
{ println it }
1.1.4.2 執(zhí)行閉包

我們可以像執(zhí)行函數(shù)一樣去執(zhí)行一個(gè)閉包探膊,也可以調(diào)用閉包的call方法來執(zhí)行閉包杠愧,看例子:

def closure = { a, b ->  a+b }
//執(zhí)行函數(shù)的方式
closure(1, 2)
//可以省略括號
closure 1,2
//使用call方法執(zhí)行
closure.call(3, 4)

//甚至可以這么寫
({ println it }) "I am closure" //相當(dāng)于匿名函數(shù)自執(zhí)行
//等同于
def closure2 = { println it }
closure2 "I am closure"

有一個(gè)地方注意一下,{ println it }是默認(rèn)有一個(gè)參數(shù)的逞壁,但是{ -> println it }這么寫在執(zhí)行的時(shí)候是會報(bào)錯(cuò)的流济,因?yàn)槟泔@示聲明了這個(gè)Closure沒有參數(shù)。

1.1.4.3 Closure到底是個(gè)啥腌闯?

Closure是一個(gè)groovy.lang.Closure對象绳瘟,這就解釋了為什么他可以賦值給一個(gè)變量,也就是說姿骏,我們可以用這個(gè)來寫函數(shù)式編程了糖声。

讓我們回到List,迭代List有兩個(gè)方法分瘦,eacheachWithIndex蘸泻,看例子:

[1, 2, 3].each {
    println "Item: $it"
}

['a', 'b', 'c'].eachWithIndex { it, i ->
    println "$i: $it"
}

我們可以看出,each方法接受一個(gè)類型是Closure類型的參數(shù)嘲玫,然后執(zhí)行這個(gè)參數(shù)悦施。上面的方法會直接打印出每一個(gè)參數(shù)的值,看似很神奇去团,雖然我沒看他源碼是如何實(shí)現(xiàn)的抡诞,但是我們可以猜想一下each函數(shù)的樣子穷蛹。

def myList = [1, 2, 3, 4, 5]
//使用一個(gè)閉包作為參數(shù)
def each(Closure c){
    //迭代list,將list的item作為參數(shù)執(zhí)行閉包
    for(int i = 0; i < myList.size(); i++){
        c(myList[i])
    }
}

def eachWithIndex(Closure c){
    for(int i = 0; i < myList.size(); i++){
        c(myList[i], i)
    }
}

//定義一個(gè)閉包昼汗,打印他的參數(shù)
def closure = { println it }
//執(zhí)行each肴熏,注意:圓括號是可以省略的
each(closure)
eachWithIndex(closure)

//也就相當(dāng)于
[1, 2, 3].each {
    println it
}

大概應(yīng)該就是這個(gè)樣子,如果你看到了源碼可以跟我分享一下乔遮。

在Gradle中扮超,基本上都是這樣的方式。

有關(guān)Groovy的部分就說這么多蹋肮,他還有很多功能語法出刷,這里都沒有涉及,畢竟我們不是要將他研究透坯辩,只是為了更好的理解Gradle馁龟,如果你有興趣,還是仔細(xì)閱讀一遍官網(wǎng)上的教程和說明漆魔,寫的還是挺詳細(xì)的坷檩,就是有點(diǎn)亂。你也可以看前面推薦的博客改抡,我覺得寫的還是挺細(xì)致的矢炼,我也是參考了很多。

1.2 Gradle基礎(chǔ)

我想了很久這塊應(yīng)該寫一些什么阿纤,Gradle的內(nèi)容很多句灌,但是,基本上都是寫給那些利用Gradle寫插件的人看的欠拾,比如開發(fā)Android Gradle插件的工程師們肯定要看胰锌,但是Google已經(jīng)幫我們實(shí)現(xiàn)好了,對于小項(xiàng)目的APP工程師真的沒有必要過多的去專研Gradle這東西藐窄,專研了之后其實(shí)你發(fā)現(xiàn)资昧,沒個(gè)屌用,別問我為什么知道荆忍。

如果你真的特別的想知道Gradle是怎么回事格带,去看下面的這篇文章:

Gradle腳本基礎(chǔ)全攻略

文章作者基本上用的都是官網(wǎng)的例子,自己重新整理了順序刹枉,還挺通熟易懂的叽唱。

如果你想親手感受一下,建議你還是把Gradle的運(yùn)行環(huán)境裝好嘶卧,親自在命令行里面敲一下Gradle命令,寫一些腳本文件執(zhí)行一下凉袱。

如果你對Gradle框架確實(shí)沒有什么太大興趣的話芥吟,這里簡單的說一下我認(rèn)為Android開發(fā)者需要了解Gradle的一些基礎(chǔ)內(nèi)容侦铜。

Gradle的生命周期分為階段:

  1. Initialization - 初始化階段
  2. Configuration - 配置階段
  3. Execution - 執(zhí)行階段

1.2.1 Initialization - 初始化階段

初始化階段會執(zhí)行項(xiàng)目根目錄下的settings.gradle文件,來分析哪些項(xiàng)目參與構(gòu)建钟鸵。

所以這個(gè)文件里面的內(nèi)容經(jīng)常是:

include ':app'
include ':libraries:someProject'

這是告訴Gradle這些項(xiàng)目需要編譯钉稍,所以我們引入一些開源的項(xiàng)目的時(shí)候,需要在這里填上對應(yīng)的項(xiàng)目名稱棺耍,來告訴Gradle這些項(xiàng)目需要參與構(gòu)建贡未。

1.2.2 Configuration - 配置階段

配置階段會去加載所有參與構(gòu)建的項(xiàng)目的build.gradle文件,會將每個(gè)build.gradle文件實(shí)例化為一個(gè)Gradle的project對象蒙袍。然后分析project之間的依賴關(guān)系俊卤,下載依賴文件,分析project下的task之間的依賴關(guān)系害幅。

他會先執(zhí)行根目錄下的build.gradle文件消恍,一般這個(gè)文件的內(nèi)容如下:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.2'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

buildscript中的dependencies是說這個(gè)項(xiàng)目依賴com.android.tools.build:gradle:2.2.2來構(gòu)建。
allprojects 后面是一個(gè)閉包以现,相當(dāng)于我們執(zhí)行allprojects這個(gè)函數(shù)狠怨,傳入了一個(gè)閉包作為參數(shù)。其實(shí)就是對所有的項(xiàng)目進(jìn)行迭代邑遏,指定所有參與構(gòu)建的項(xiàng)目使用的倉庫佣赖。

1.2.3 Execution - 執(zhí)行階段

執(zhí)行階段來執(zhí)行具體的task。

taskGradle中的最小執(zhí)行單元记盒,我們所有的構(gòu)建憎蛤,編譯,打包孽鸡,debug蹂午,test等都是執(zhí)行了某一個(gè)task,一個(gè)project可以有多個(gè)task彬碱,task之間可以互相依賴豆胸。例如我有兩個(gè)task,taskA和taskB巷疼,指定taskA依賴taskB晚胡,然后執(zhí)行taskA,這時(shí)會先去執(zhí)行taskB嚼沿,taskB執(zhí)行完畢后在執(zhí)行taskA估盘。

說到這可能會有疑問,我翻遍了build.gradle也沒看見一個(gè)task長啥樣骡尽,有一種被欺騙的趕腳遣妥!

其實(shí)不是,你點(diǎn)擊AndroidStudio右側(cè)的一個(gè)Gradle按鈕攀细,會打開一個(gè)面板箫踩,內(nèi)容差不多是這樣的:

里面的每一個(gè)條目都是一個(gè)task爱态,那這些task是哪來的呢?

一個(gè)是根目錄下的build.gradle中的

dependencies {
        classpath 'com.android.tools.build:gradle:2.2.2'
    }

一個(gè)是app目錄下的build.gradle中的

apply plugin: 'com.android.application'

這兩段代碼決定的境钟。也就是說锦担,Gradle提供了一個(gè)框架,這個(gè)框架有一些運(yùn)行的機(jī)制可以讓你完成編譯慨削,但是至于怎么編譯是由插件決定的洞渔。還好Google已經(jīng)給我們寫好了Android對應(yīng)的Gradle工具,我們使用就可以了缚态。

根目錄下的build.gradledependencies {classpath 'com.android.tools.build:gradle:2.2.2'}是Android Gradle編譯插件的版本磁椒。

app目錄下的build.gradle中的apply plugin: 'com.android.application'是引入了Android的應(yīng)用構(gòu)建項(xiàng)目,還有com.android.library和com.android.test用來構(gòu)建library和測試猿规。

所有Android構(gòu)建需要執(zhí)行的task都封裝在工具里碧囊,如果你有一些特殊需求的話信粮,也可以自己寫一些task憔杨。那么對于開發(fā)一個(gè)Android應(yīng)用來說北戏,最關(guān)鍵的部分就是如何來用AndroidGradle的插件了。

1.3 Android Gradle DSL

先扔出Android Gradle DSL的地址:

Android Gradle DSL

關(guān)于AndroidGradleDSL环葵,這東西的主要功能還是提供了與構(gòu)建相關(guān)的東西调窍,他并不是一個(gè)特別神奇的東西,只是根據(jù)開發(fā)者的需求提供了很多便捷的方式张遭,所以不用把他當(dāng)成一個(gè)特別痛苦的東西而拒絕邓萨。

讓我們從BuildType說起。

1.3.1 BuildType

BuildType的最大的應(yīng)用場景是當(dāng)你對測試版本和發(fā)布版本需要連接不同的服務(wù)的時(shí)候使用菊卷,他默認(rèn)其實(shí)就有debug版本只是沒有顯示在build.gradle中缔恳,當(dāng)然我們也可以根據(jù)需求創(chuàng)建多個(gè)buildType來使用。這里先貼出來Android生成的app目錄下的build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"
    defaultConfig {
        applicationId "com.axiba.gradleDemo"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:23.4.0'
    testCompile 'junit:junit:4.12'
}

可以看到默認(rèn)的buildTypes中就已經(jīng)添加了一個(gè)release洁闰,我們來手動添加一個(gè)自定義的buildType歉甚,內(nèi)容如下:

    buildTypes {
        myBuildType {
            //可調(diào)試
            debuggable true
            //給applicationId添加后綴,這樣可同時(shí)在一部手機(jī)上存在多個(gè)版本
            applicationIdSuffix ".debug"
            //添加自定義變量
            buildConfigField "boolean", "isMyBuildType", "true"
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

上面的代碼我們創(chuàng)建了一個(gè)自定義的buildType名為myBuildType扑眉,其中的內(nèi)容我相信你看注釋應(yīng)該就很明白了纸泄,其中的applicationIdSuffix是給你的包名添加一個(gè)后綴,這樣你的手機(jī)中就可以同時(shí)存在多個(gè)版本腰素,因?yàn)槟憧赡苷陂_發(fā)新的版本聘裁,但是你已經(jīng)發(fā)布的版本要在手機(jī)上隨時(shí)觀察,這樣你是需要同時(shí)有多個(gè)版本在手機(jī)上的弓千。

關(guān)于自定義變量和debuggable可能你會有疑惑衡便,這東西配置了怎么用啊?

當(dāng)修改了build.gradle文件之后我們需要sync一下镣陕,然后你會發(fā)現(xiàn)你的Gradle面板中征唬,:app結(jié)構(gòu)下的build中多了一個(gè)名為compileMyBuildTypeSources的task,如下圖

然后雙擊執(zhí)行這個(gè)task茁彭,完成之后我們到app/build/gengrated/source/buildConfig/myBuildType目錄,如圖

在該目錄下生成了一個(gè)BuildConfig.java文件扶歪,文件內(nèi)容如下:

public final class BuildConfig {
  public static final boolean DEBUG = Boolean.parseBoolean("true");
  public static final String APPLICATION_ID = "com.axiba.gradledemo.debug";
  public static final String BUILD_TYPE = "myBuildType";
  public static final String FLAVOR = "";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "1.0";
  // Fields from build type: myBuildType
  public static final boolean isMyBuildType = true;
}

可以看到我們在buildType定義的內(nèi)容在這里都體現(xiàn)了出來理肺,包括最后一行的自定義變量。但是除非你有特別的需要善镰,否則真的不建議你使用這個(gè)變量妹萨。

最常用的可能就是DEBUG了,例如有寫Log你只想在debug的時(shí)候顯示炫欺,release以后就不在顯示乎完,可以在你的代碼中這樣寫:

if (BuildConfig.DEBUG) {
    Log.d("tag", "something happened");
}

如果你每次都這么寫log覺得累的話,可以自己封裝一個(gè)log工具類品洛。

有關(guān)BuildType先說到這里树姨,后面涉及到的時(shí)候我們在慢慢補(bǔ)充。

關(guān)于BuildType更多的內(nèi)容可以看官方DSL文檔 -> BuildType

1.3.2 signingConfigs

如果你按照上面的內(nèi)容添加了一個(gè)myBuildType之后你會發(fā)現(xiàn)桥状,在你的Gradle面板中帽揪,:app下的build中是多了assembleMyBuildTypecompileMyBuildTypeSourcescompileMyBuildTypeUnitTestSources三個(gè)相關(guān)的Task辅斟,但是install結(jié)構(gòu)下卻找不到installMyBuildType這樣的Task转晰。原因是AndroidStudio不會給沒有指定簽名的BuildType生成install相關(guān)的Task。這里我們來為myBuildType添加一個(gè)簽名士飒,并介紹一下簽名相關(guān)的內(nèi)容查邢。

我們在android閉包中添加這么一段代碼:

    signingConfigs {
        debug {
            storeFile file("/Users/username/.android/debug.keystore")
        }
    }

這里的storeFile用的是AndroidStudio生成的一個(gè)默認(rèn)的debug.keystore。如果你的debug沒有特殊需求可以使用這個(gè)keystore文件酵幕。
Linux和MaxOS系統(tǒng)下扰藕,這個(gè)文件在~/.android/debug.keystore
Windows XP:C:\Documents and Settings<user>.android\debug.keystore
Windows Vista and Windows 7, 8, and 10:C:\Users<user>.android\debug.keystore

但是這么寫肯定是有問題的,如果你使用git多人開發(fā)的話每個(gè)人的地址是不一樣的裙盾,解決的辦法是這樣:

首先在app目錄下創(chuàng)建一個(gè)keystore.properties文件
內(nèi)容入下

debugKeyStoreFile=/Users/username/.android/debug.keystore

然后在.gitignore中添加keystore.properties实胸,將keystore.properties文件排除

這樣你的git中每個(gè)人可以根據(jù)自己的環(huán)境來配置keystore.properties這個(gè)文件。

最后在build.gradle中使用配置內(nèi)容番官,這里直接貼出完整的代碼:

apply plugin: 'com.android.application'

def keystoreProperties = new Properties()
//加載keystore.properties文件
keystoreProperties.load(new FileInputStream(file("keystore.properties")))


android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"
    defaultConfig {
        applicationId "com.axiba.gradledemo"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }


    signingConfigs {
        debug {
            //指定keystore    文件
            storeFile file(keystoreProperties['debugKeyStoreFile'])
//            storeFile file("/Users/username/.android/debug.keystore")
        }
    }

    buildTypes {
        myBuildType {
            //可調(diào)試
            debuggable true
            //給applicationId添加后綴庐完,這樣可同時(shí)在一部手機(jī)上存在多個(gè)版本
            applicationIdSuffix ".debug"
            //指定簽名信息
            signingConfig signingConfigs.debug
            //添加自定義變量
            buildConfigField "boolean", "isMyBuildType", "true"
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

如果沒有特殊要求,signingConfigsdebugrelease就夠了徘熔,release相關(guān)的配置信息你也可以寫在keystore.properties文件中门躯,如果你覺得不安全可以使用命令行輸入來輸入密碼。

keystore.properties文件內(nèi)容:

debugKeyStoreFile=/Users/username/.android/debug.keystore
releaseKeyStoreFile=yourReleaseKeyStoreFile
releaseStorePassword=yourStorePassword
releaseKeyAlias=yourKeyAliasName
releaseKeyPassword=yourKeyAliasPassword

build.gradle文件內(nèi)容:

apply plugin: 'com.android.application'

def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(file("keystore.properties")))

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"
    defaultConfig {
        applicationId "com.axiba.gradledemo"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }

    signingConfigs {
        debug {
            storeFile file(keystoreProperties['debugKeyStoreFile'])
//            storeFile file("/Users/liukun/.android/debug.keystore")
        }
        release {
            storeFile file(keystoreProperties['releaseKeyStoreFile'])
            storePassword keystoreProperties['releaseStorePassword']
            keyAlias keystoreProperties['releaseKeyAlias']
            keyPassword keystoreProperties['releaseKeyPassword']
        }
    }

    buildTypes {
        myBuildType {
            //可調(diào)試
            debuggable true
            //給applicationId添加后綴酷师,這樣可同時(shí)在一部手機(jī)上存在多個(gè)版本
            applicationIdSuffix ".debug"
            //指定簽名信息
            signingConfig signingConfigs.debug
            //添加自定義變量
            buildConfigField "boolean", "isMyBuildType", "true"
        }
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
    }
}

有關(guān)簽名的部分就說到這讶凉,更多相關(guān)的內(nèi)容請參考官方文檔染乌。

1.3.3 install之后自動打開

到目前為止,在你的Gradle -> :app -> install結(jié)構(gòu)下應(yīng)該可以找到installMyBuildType這個(gè)Task了懂讯,雙擊執(zhí)行這個(gè)Task荷憋,app會裝到目標(biāo)手機(jī)或者模擬器中,但是安裝是安裝了褐望,為什么沒有自動打開呢勒庄?我們點(diǎn)擊運(yùn)行按鈕的時(shí)候不都是自動打開安裝好的app的么。

其實(shí)點(diǎn)擊運(yùn)行之后瘫里,我們看一下輸出可以知道adb做了哪些事实蔽,下面的內(nèi)容是點(diǎn)擊運(yùn)行按鈕之后輸出的內(nèi)容。

11/13 22:24:52: Launching app
$ adb push /Users/username/Developer/workspace/androidStudio/GradleDemo/app/build/outputs/apk/app-debug.apk /data/local/tmp/com.axiba.gradledemo
$ adb shell pm install -r "/data/local/tmp/com.axiba.gradledemo"
    pkg: /data/local/tmp/com.axiba.gradledemo
Success

$ adb shell am start -n "com.axiba.gradledemo/com.axiba.gradledemo.MainActivity" -a android.intent.action.MAIN -c android.intent.category.LAUNCHER

他是先將編譯好的APK文件推送到你的目標(biāo)設(shè)備谨读,在從目標(biāo)設(shè)備中進(jìn)行安裝局装,安裝完成之后啟動。

install只會將文件安裝到目標(biāo)設(shè)備劳殖,并不會啟動這個(gè)APP铐尚。

這個(gè)章節(jié)我們就來根據(jù)前面學(xué)習(xí)過的知識,讓他啟動起來哆姻。
既然目標(biāo)是讓他啟動起來塑径,那么肯定是要在Task中執(zhí)行adb命令了,然后我們要在安裝完成之后啟動app填具,那這個(gè)Task就要依賴于install這個(gè)task统舀,我們在android域中添加一個(gè)task,名為openApp劳景,并讓他依賴installMyBuildType誉简,代碼如下:

android {
    ...
    task openApp (dependsOn: "installMyBuildType") << {
        
    }
}

這樣就創(chuàng)建好Task了,sync一下之后你可以在Gradle -> :app -> other結(jié)構(gòu)下找到這個(gè)Task盟广。接下來解決執(zhí)行adb命令的問題闷串。

其實(shí)我們是想讓adb執(zhí)行這段命令adb shell am start -n "com.axiba.gradledemo/com.axiba.gradledemo.MainActivity"來啟動MainActivity。讓Gradle在命令行中執(zhí)行命令可以使用project.exec這個(gè)函數(shù)筋量,內(nèi)容如下:

android {
    ...
    task openApp (dependsOn: "installMyBuildType") << {
        project.exec {
            executable = adbExecutable
            args 'shell'
            args 'am'
            args 'start'
            args '-n'
            args 'com.axiba.gradledemo/com.axiba.gradledemo.MainActivity'
        }
    }
}

其中的adbExecutable是你AndroidSDK中的adb工具所在的目錄路徑烹吵,后面的agrs是執(zhí)行命令的參數(shù),貌似只有有空格就要寫一個(gè)args桨武。

目前我們只是將命令行中的執(zhí)行命令復(fù)制過來肋拔,但是這里有一個(gè)問題,就是在buildType.myBuildType中我們添加了applicationIdSuffix ".debug"呀酸,這會導(dǎo)致我們最后一個(gè)路徑有問題凉蜂,我們可以這樣來修改

task openApp (dependsOn: "installMyBuildType") << {
    def applicationId = android.defaultConfig.applicationId

    if(buildTypes.myBuildType.applicationIdSuffix){
        applicationId += buildTypes.myBuildType.applicationIdSuffix
    }
    
    project.exec {
        executable = adbExecutable
        args 'shell'
        args 'am'
        args 'start'
        args '-n'
        args "${applicationId}/com.axiba.gradledemo.MainActivity"
    }
}

現(xiàn)在雙擊openApp這個(gè)Task可以安裝并執(zhí)行app了。

這里也許你會想,那我是不是每個(gè)BuildType都要跟著建立這樣一個(gè)task窿吩,多麻煩茎杂,我寧可到手機(jī)上點(diǎn)一下app去執(zhí)行。那你就圖樣圖森破了纫雁。Gradle提供了動態(tài)創(chuàng)建Task的功能煌往,我們可以寫一個(gè)函數(shù)來生成所有相關(guān)的Task。

build.gradle文件中添加這樣一段代碼轧邪,注意不要放到android的閉包里面携冤。

def initTasksOpenAndInstall(){

    //遍歷buildTypes
    android.buildTypes.each { buildType ->

        println buildType
        //buildType.name首字母大寫
        def buildTypeName = getFirstLetterUpper(buildType.name);

        //如果有后綴,添加后綴
        def applicationId = android.defaultConfig.applicationId

        if(buildType.applicationIdSuffix){
            applicationId += buildType.applicationIdSuffix

        }

        //創(chuàng)建task
        task "installAndOpen${buildTypeName}" (dependsOn: "install${buildTypeName}") {

            //將task添加到install分組中
            group "install"

            //通過adb來執(zhí)行啟動應(yīng)用
            doLast {

                //執(zhí)行adb命令來啟動app
                project.exec {
                    executable = android.adbExecutable
                    args 'shell'
                    args 'am'
                    args 'start'
                    args '-n'
                    args "${applicationId}/com.axiba.gradledemo.MainActivity"
                }
            }

        }
    }
}

initTasksOpenAndInstall()


//第一個(gè)字母大寫
def getFirstLetterUpper(name){

    def firstLetter = name.charAt(0).toUpperCase().toString();

    if(name.length() > 1){
        name = firstLetter + name.substring(1)
    } else {
        name = firstLetter
    }

    return name;
}

我們創(chuàng)建了一個(gè)initTasksOpenAndInstall函數(shù)闲勺,他的主要功能是遍歷buildTypes,給每個(gè)buildType添加一個(gè)installAndOpenTypeName的task扣猫,這個(gè)task依賴于installTypeName菜循,并將這個(gè)task添加到install分組之中,點(diǎn)擊sync按鈕之后申尤,你可以在Gradle -> :app -> install分組中發(fā)現(xiàn)多了幾個(gè)installAndOpen的task癌幕。雙擊執(zhí)行task,應(yīng)用安裝到了目標(biāo)設(shè)備并自動打開昧穿。

1.3.4 productFlavors

productFlavor的應(yīng)用場景最多的是為不同的渠道打包勺远,比如你在用友盟的時(shí)候要分析渠道來源,你要和應(yīng)用市場做首發(fā)活動的時(shí)候需要在啟動頁添加他們的LOGO时鸵。

創(chuàng)建一個(gè)productFlavor也是非常的簡單胶逢,在android的閉包中添加代碼:

    productFlavors {
        wandoujia {
            applicationIdSuffix '.wandoujia'
        }
    }

新增一個(gè)productFlavor名為wandoujia,我們希望這是專門在豌豆莢上線的應(yīng)用饰潜。注意這里加了一個(gè)applicationId的后綴初坠,他的功能是和buildType一樣的。

現(xiàn)在sync一下之后你會發(fā)現(xiàn)Gradle里面多了很多task彭雾,例如:installWandoujiaDebug碟刺、installWandoujiaReleaseinstallWandoujiaMyBuildType等task薯酝。這是因?yàn)锳ndroidGradle會將productFlavor對應(yīng)每一個(gè)buildType都生成一個(gè)版本半沽。那么問題來了,我在myBuildType中添加了一個(gè)applicationId的后綴.debug吴菠,現(xiàn)在我又給wandoujia添加了一個(gè)applicationId后綴.wandoujia者填,那么WandoujiaMyBuildType版本的applicationId應(yīng)該是什么呢?
我們這個(gè)項(xiàng)目生成的applicationId是這樣的
com.axiba.gradledemo.wandoujia.debug

也就是說做葵,他會先添加productFlavor的后綴幔托,在添加buildType的后綴。

productFlavor比較常用的一個(gè)用能就是用他來修改AndroidManifest.xml文件中的參數(shù)。

例如當(dāng)你用友盟分析渠道的時(shí)候重挑,你的AndroidManifest.xml文件中應(yīng)該有這樣的代碼:

<meta-data
            android:name="UMENG_CHANNEL"
            android:value="${UMENG_CHANNEL_VALUE}" />

注意value部分要使用占位符的方式嗓化。

然后修改productFlavor的wandoujia部分。

    productFlavors {
        wandoujia {
            applicationIdSuffix '.wandoujia'
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
        }
    }

如果你的渠道很多谬哀,可以通過遍歷productFlavors的方式來替換:

    productFlavors.all {
        flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
    }

另外可以在defaultConfig中添加一個(gè)默認(rèn)值

    defaultConfig {
        ....
        manifestPlaceholders = [UMENG_CHANNEL_VALUE: "axiba"]
    }

如果你想為你的輸出文件格式化名稱的話刺覆,可以參考下面的代碼。

    applicationVariants.all { variant ->
        variant.outputs.each { output ->
            def outputFile = output.outputFile
            if (outputFile != null && outputFile.name.endsWith('.apk')) {
                def fileName = "GradleDemo_v${defaultConfig.versionName}_${variant.productFlavors[0].name}_release.apk"
                output.outputFile = new File(outputFile.parent, fileName)
            }
        }
    }

有關(guān)Android Gradle Plugin相關(guān)的內(nèi)容就先寫到這里史煎,他的功能很多谦屑,而且還在擴(kuò)展新的功能,這里就不一一介紹了篇梭,主要的原因是很多東西我也不懂氢橙,就不誤導(dǎo)大家了。之前創(chuàng)建initTasksOpenAndInstall方法可以根據(jù)buildType來生成相關(guān)的Task恬偷,后面我將他補(bǔ)全了悍手,添加了productFlavor+buildType的形式來建立Task,有興趣的同學(xué)可以到我的git上面去看袍患。

GradleDemo

如果你在執(zhí)行Gradle的過程中坦康,發(fā)生了錯(cuò)誤,那么我建議你在終端(或者命令行)中輸入相關(guān)的Gradle命令诡延,如果出錯(cuò)了滞欠,他會給你詳細(xì)的錯(cuò)誤報(bào)告。

2 Gradle Warpper

為什么我在Github下了一個(gè)項(xiàng)目導(dǎo)入要這么久肆良?

Gradle到底在干點(diǎn)啥浪費(fèi)了我這么多青春筛璧?

為什么我對著硬盤里面的大姐姐擼了一發(fā)了還沒導(dǎo)入成功?

一般這種情況的罪魁禍?zhǔn)资?strong>Gradle Wapper惹恃,他的主要作用是來適配不同的Gradle版本的隧哮。

比如你在github上面下了一個(gè)項(xiàng)目,項(xiàng)目的目錄結(jié)構(gòu)大概是這樣的:

.
├── app
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
└── settings.gradle

其中gradlew文件是Linux和MacOS環(huán)境下運(yùn)行的座舍,gradlew.bat是window環(huán)境下運(yùn)行的沮翔。而定義項(xiàng)目的執(zhí)行版本在/gradle/wrapper/gradle-wrapper.properties文件中,內(nèi)容如下:

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip

這文件是說我這個(gè)項(xiàng)目需要你用2.10版本的gradle來編譯曲秉。
如果你當(dāng)前AndroidStudio所使用的Gradle版本與文件中指定的不一致采蚀,Gradle會去下載對應(yīng)的版本,下載好了用這個(gè)版本去編譯承二。

一個(gè)Gradle的壓縮文件大概50M以上榆鼠,如果你的網(wǎng)絡(luò)環(huán)境差一點(diǎn),是比較痛苦的亥鸠。

解決這個(gè)問題的方法有兩個(gè)妆够,一個(gè)是讓gradle-wrapper.properties中的Gradle版本與當(dāng)前AndroidStudio所使用的Gradle版本一致识啦,一個(gè)是自己下載好所需要的版本。但是兩個(gè)方法都會有一些問題神妹,我們來一個(gè)一個(gè)分析颓哮。

使用同一個(gè)版本的Gradle

首先查看你當(dāng)前AndroidStudio所使用的Gradle是什么版本,然后將gradle-wrapper.properties中的Gradle改成同一個(gè)版本鸵荠。

查看AndroidStudioGradle版本的方法如下圖:

這里建議將Gradle home配置成本地環(huán)境冕茅。也就是說自己下載一個(gè)Gradle然后配置到系統(tǒng)環(huán)境中,這樣在終端就可以使用了蛹找。

上圖中所使用的Gradle版本是2.14.1姨伤。而剛才我們列出來的gradle-wrapper.properties中的Gradle版本是2.10。

gradle-wrapper.properties中的Gradle版本是2.14.1就可以避免去下載2.10版本庸疾。但是這樣做有風(fēng)險(xiǎn)乍楚,如果新的版本對舊版本的一些語法不在支持了,那么就會編譯錯(cuò)誤届慈,結(jié)果會得不償失徒溪,除非你對版本變更了解的非常透徹,能夠手動將文件變動的地方重新修改拧篮,那么用這種方法沒有問題。但是如果你不了解牵舱,建議不要采用這種方法串绩。

接著介紹第二種。

手動下載Gradle版本

既然對Gradle的版本變更不熟悉芜壁,我們就按照gradle-wrapper.properties文件中的配置內(nèi)容礁凡,使用下載工具去下載,來避免他下載過慢的問題慧妄,而且如果我們積累了比較常用的版本顷牌,也就不用每次都去下載了。

gradle-wrapper.properties中基本都給出來了下載鏈接塞淹,https://services.gradle.org/distributions/可以查看所有版本窟蓝。
如果你的網(wǎng)絡(luò)訪問這個(gè)頁面或者使用下載工具還是下載很慢的話,可以到androiddevtools上面去下載相關(guān)的安裝包饱普。

但是這種做法也有一個(gè)問題运挫,那就是他的下載機(jī)制。還是用剛才的栗子套耕。

目前我的AndroidStudio使用的Gradle是2.14.1谁帕。
我的gradle-wrapper.properties中的Gradle版本是2.10。

那么執(zhí)行編譯冯袍,Gradle會先到gradle-wrapper.properties配置的目標(biāo)路徑下去找這個(gè)文件是否存在匈挖,其中的GRADLE_USER_HOME一般是對應(yīng)下圖中的Service directory path

那么連起來就是/Users/username/.gradle/wrapper/dists這個(gè)目錄碾牌,為了后面引用我們給這個(gè)路徑起名叫做wrapperPath

如果路徑下不存在這個(gè)版本的目錄儡循,那么Gradle會去創(chuàng)建相關(guān)文件目錄舶吗,并開始下載對應(yīng)版本的壓縮包。內(nèi)容如下圖贮折。

如果你現(xiàn)在嫌他下載太慢裤翩,刪除gradle-2.10-all目錄,然后將自己下載的壓縮文件解壓出來以為就萬事大吉了调榄,那就太天真了踊赠,當(dāng)年我就是這么天真的。

我們先來看他下載好了是什么樣的每庆。

我們可以看到多了一個(gè)gradle-2.10-all.zip.part變成了gradle-2.10-all.zip.ok筐带。同時(shí)多了文件壓縮包和解壓出來的對應(yīng)文件目錄。并且在一個(gè)目錄名為看似一串隨機(jī)碼的目錄下缤灵。

解釋一下伦籍,Gradle會在 /wrapperPath/gradle-2.10-all/隨機(jī)碼/ 目錄下去檢查是否有一個(gè)gradle-2.10-all.zip.ok文件,如果有就直接編譯腮出,即使你將gradle-2.10這個(gè)目錄刪除了帖鸦,他的編譯會報(bào)錯(cuò),但是他還是不會去下載胚嘲。
如果沒有這個(gè)文件作儿,那么Gradle就會開始去下載gradle-2.10-all.zip文件,gradle-2.10-all.zip.part文件就是正在下載的gradle-2.10-all.zip文件馋劈,下載完成自動解壓攻锰,在執(zhí)行編譯。

其中的隨機(jī)碼目錄根據(jù)官方的介紹應(yīng)該是由SHA-256 hash生成的妓雾,只是我的猜想娶吞。

如果我們想要騙過他就讓他先執(zhí)行下載,生成相關(guān)的文件目錄械姻,然后將你下載好的壓縮包解壓后放進(jìn)去妒蛇,在創(chuàng)建一個(gè)gradle-2.10-all.zip.ok文件,就可以了楷拳。

我將1.7以后的版本都手動配置好了材部。

以上,相信你能夠填上Gradle Wrapper給你挖的坑了唯竹。

3 加速編譯過程

3.1 Gradle Daemon

Gradle Daemon是一個(gè)長期在后臺執(zhí)行的一個(gè)進(jìn)程乐导,用來避免每次開始編譯在JVM啟動Gradle所消耗的時(shí)間,同時(shí)也會在內(nèi)存中保存一些你的項(xiàng)目數(shù)據(jù)來加速編譯過程浸颓。Gradle3.0默認(rèn)是開啟Daemon的物臂。

想要開啟Daemon功能可以在.gradle/gradle.properties文件中添加org.gradle.daemon = true旺拉。

3.2 Gradle Parallel

Gradle Parallel一般是對多個(gè)項(xiàng)目使用并行編譯,他會在配置階段對項(xiàng)目進(jìn)行預(yù)編譯棵磷,分析項(xiàng)目之間的依賴關(guān)系蛾狗,而且已經(jīng)編譯過的項(xiàng)目,如果沒有更改仪媒,直接用上次編譯好的去構(gòu)建目標(biāo)項(xiàng)目沉桌,例如我們Android開發(fā)時(shí)在libraries里面的項(xiàng)目,只要編譯一次就可以了算吩,不會每次都去編譯留凭。

可以通過在項(xiàng)目根目錄下的gradle.properties中添加org.gradle.parallel=true來開啟此功能。

3.3 Configuration on demand

Configuration on demand簡單的說就是能夠縮短multi-projects的配置時(shí)間偎巢。

可以通過在項(xiàng)目根目錄下的gradle.properties中添加org.gradle.configureondemand=true來開啟此功能蔼夜。

3.4 增加jvm進(jìn)程的最大堆內(nèi)存

通過修改項(xiàng)目根目錄下的gradle.properties中的org.gradle.jvmargs屬性來設(shè)置。根據(jù)你的配置可以改成org.gradle.jvmargs=-Xmx2048m 或者 org.gradle.jvmargs=-Xmx3072m

3.5 使用固定版本的依賴項(xiàng)

如果你在依賴項(xiàng)中使用了動態(tài)版本配置压昼,那么編譯的時(shí)候會去檢查是否有更新的版本求冷,如果有就會下載新版本的依賴項(xiàng)。所以盡量使用固定版本的依賴窍霞,減少 + 號的使用匠题。

最后

這篇文寫的稍微有點(diǎn)長了,也是超出了我的預(yù)料之外但金,前前后后寫了大概有10幾天韭山,翻遍了groovy的文檔,翻遍了gradle的文檔傲绣,也翻遍了AndroidGradle DSL的文檔掠哥,最后得出來的一些小心得巩踏,希望對你有幫助秃诵。文中的很多技巧和代碼你在其他地方也許都能找到,但是本文意在你能夠看懂Gradle里面的內(nèi)容到底是怎么回事塞琼,做了些什么菠净,遇到相關(guān)gradle的問題去哪翻文檔。

如果有人發(fā)現(xiàn)哪里有寫的不對的地方請聯(lián)系我彪杉,我盡快修改毅往,別誤導(dǎo)了他人。

如果這篇文字有幫助到你派近,請到github上賞我一個(gè)star攀唯。

最后感謝你能堅(jiān)持看到這里。

好了不多說了渴丸,哄我家希寶睡覺去了~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侯嘀,一起剝皮案震驚了整個(gè)濱河市另凌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌戒幔,老刑警劉巖吠谢,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異诗茎,居然都是意外死亡工坊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門敢订,熙熙樓的掌柜王于貴愁眉苦臉地迎上來王污,“玉大人,你說我怎么就攤上這事枢析∮竦В” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵醒叁,是天一觀的道長司浪。 經(jīng)常有香客問我,道長把沼,這世上最難降的妖魔是什么啊易? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮饮睬,結(jié)果婚禮上租谈,老公的妹妹穿的比我還像新娘。我一直安慰自己捆愁,他們只是感情好割去,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著昼丑,像睡著了一般呻逆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上菩帝,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天咖城,我揣著相機(jī)與錄音,去河邊找鬼呼奢。 笑死宜雀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的握础。 我是一名探鬼主播辐董,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼禀综!你這毒婦竟也來了简烘?” 一聲冷哼從身側(cè)響起他匪,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎夸研,沒想到半個(gè)月后邦蜜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亥至,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年悼沈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姐扮。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡絮供,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出茶敏,到底是詐尸還是另有隱情壤靶,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布惊搏,位于F島的核電站贮乳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏恬惯。R本人自食惡果不足惜向拆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酪耳。 院中可真熱鬧浓恳,春花似錦、人聲如沸碗暗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽言疗。三九已至晴圾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間洲守,已是汗流浹背疑务。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工沾凄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留梗醇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓撒蟀,卻偏偏與公主長得像叙谨,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子保屯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評論 25 707
  • 前言 為什么需要學(xué)Gradle? Gradle 是 Android 現(xiàn)在主流的編譯工具手负,雖然在Gradle 出現(xiàn)之...
    真笨笨魚閱讀 1,488評論 0 0
  • Android Studio作為Android應(yīng)用開發(fā)的官方IDE涤垫,默認(rèn)使用Gradle作為構(gòu)建工具,所以對于An...
    feil0n9wan9閱讀 1,655評論 1 6
  • 前言 為什么需要學(xué)Gradle? Gradle 是 Android 現(xiàn)在主流的編譯工具竟终,雖然在Gradle 出現(xiàn)之...
    Liuuuuuuzi閱讀 1,982評論 0 18
  • 你忘掉了所有的曾經(jīng) 你 便將未來 提前交給曾經(jīng) 期望是一種無償?shù)牡却?你 埋葬了曾經(jīng) 便擁有了過去的將來 現(xiàn)在是未...
    釋修堂閱讀 253評論 0 3