Android中Gradle詳細實用指南

Android中Gradle實用指南

Gradle讓Android中的依賴管理、庫管理、渠道管理以及一些動態地編譯配置變得極為方便!!
本文是對Gradle在Android項目中進階使用的知識點整理和簡要講解
較為詳細的Gradle教科書Github
Gradle腳本配置文檔:Google Github


目錄

  • Android工程中的Gradle
  • 常用的Gradle Task
  • 動態參數配置
  • Project:build.gradle
    • 全局屬性配置
    • 整體結構和描述
  • Module: build.gradle
    • defaucltConfig
    • signConfigs
    • buidlTypes
    • sourceSets
    • productFlavors
    • compileOptions
    • lintOptions
    • build.gradle圖形化配置
  • dependencies項目依賴
  • 自定義方法def
  • Gradle編譯提速優化
    • 性能檢測
    • 禁用Task達到提速
    • AAPT
    • Gradle編譯優化
  • 認識Task
  • 自定義Plugin
    • build.gradle中直接定義
    • 當前工程中定義
    • 獨立Module自定義插件
  • 最后

Android工程中的Gradle

下面簡述對我們工程最重要的幾個Gradle文件,后續也會圍繞他們進行詳細講解和補充
(請仔細看代碼中的注釋哈)

  • 工程Project 中的 build.gradle : 工程控制Gradle編譯配置
  • 模塊module中的 build.gradle : 控制每個Module的編譯過程
  • gradle.properties : gradle動態參數的配置文件
  • local.properties : 本地的配置,如:SDK位置
  • gradle-wrapper.properties :gradle本地代理,聲明了指向目錄和版本
    • distributionUrl : 指定gradle版本不存在時,就從Value的地址中去下載。很多時候,我們只要版本換成我們本地存在的gradle版本就可以了
  • settings.gradle : 配置Gradle中的Module管理

常用Gradle Task

~ 表示 gradlew
gradlew是包裝器,自動下載包裝里定義好的gradle 版本,保證編譯環境統一
gradle 是用本地的gradle

  • gradlew task -all : 羅列出所有Task ,同時攜帶具體作用和相互關系

  • gradlew assembleDebug : 導出所有渠道測試包

    • ~ assembleRelease : 導出所有渠道正式包
    • ~ assembleBaiduDebug --stacktrace : 導出指定渠道測試包,同時攜帶異常信息
  • ~ --stop : 立即停止編譯

  • ~ check : 檢查任務

  • ~ build : 執行了 check和assemble

  • ~ clean : 清除所有中間編譯結果

動態參數配置

在Gradle中動態配置資源參數
我們可以根據各自的需求在不同的領域(如:buildType 的debug, defaultConfig ...)下去動態替換或配置我們項目中所使用到的資源,如 log 開關, 針對不同渠道的對應內容字段,不同版本定義引入的不同值等等

首先說清一點,對于動態資源在build.gradle中多個領域中的使用,會遵循一下順序來進行覆蓋:
buildType > productFlavor > defaultConfig > Manifest中的配置 > 依賴的第三方庫的配置 > 任意領域中的默認值(也就是沒有設置值)

  1. Manifest占位符: 可以動態配置Manifest的參數
        在Manifest的Application節點下
        //這里以友盟為例
        <!-- 友盟統計相關meta-data -->
        <meta-data
            android:name="UMENG_APPKEY"
            android:value="balabalabala" />
        <meta-data
            android:name="UMENG_CHANNEL"
            android:value="${UMENG_CHANNEL_VALUE}" />

在build.gradle中對參數進行動態配置
 productFlavors {
        baidu {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
        }
}
  1. gradle.properties 的使用
    • 系統屬性:
      配置: systemProp.proName=123
      使用:System.properties['proName']
    • 自定義屬性 :
      配置: 有key: ray.proName=123 ,無key : proName = 123
      使用: 有key: project。property('proName') , 無key : proName
gradle.properties中的配置

#使用系統參數配置
systemProp.keyStore=ray.jks
#使用key/value鍵值對配置
ray.keyPassword=123456
#屬性配置
mKeyAlias=ray

build.gradle中的使用
//簽名打包
        release {
            //簽名文件所在路徑
            storeFile file(System.properties['keyStore'])
            //簽名密碼
            storePassword "111111"
            //別名
            keyAlias mKeyAlias
            keyPassword project.property('ray.keyPassword')
        }


  1. BuildConfig文件
    • app/build/generated/source/buildConfig文件夾下面
    • 使用: buildConfigField "String" , "key" , "\"value\""
      配置buildConfig中的屬性參數 String : 參數類型(int,boolean...), key : 屬性的名字, value : 屬性的值, \ 為轉義字符
  1. resValue 動態修改工程資源
    • 和buildConfig 類似, resValue("string","key","value")
    • 其中string 表示 會在 app/build/generated/res/resValue/.../generated.xml中生成對應的String 的 key 和Value, 代碼中可以直接getResources().getString(R.string.key);獲取到value
    • 注意: 因為Gradle編譯的時候會將腳本配置和string.xml文件中配置進行merge,所以string.xml中已經存在的key在編譯前要刪除,否則會報錯
//下面模擬在不同渠道下修改資源參數
productFlavors{
baidu{
  buildConfigField "String" , "productCode" , "\"baidu 1.0\""
  resValue("string","productName","baidu")
}
}

Project : build.gradle

全局屬性配置

  1. 在project : build.gradlebuildscript中聲明ext和自定義屬性,然后在其他module中就可以直接使用這個屬性了
    如下:

Project : build.gradle
buildscript {
    //自定義工程使用的屬性
    ext {
        kotlin_version = '1.1.0'
        compile_version = 25
    }

    //聲明依賴Android Gradle 插件版本
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

Module : build.gradle 中使用
Android
{
compileSdkVersion compile_version
}
dependencies
{
 compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
 }
  1. 新建自己的Gradle屬性文件 text.gradle , 然后在Project中引入,最后就可以在其他的module中直接使用了,具體應用可以參考印象筆記Github
    如下:
text.gradle 編寫如下:
ext {
    kotlinVersion = "1.1.0"
    rxjavaLibVersion = "1.2.0"

    dependencies = [
            // Rx
            rxJava: "io.reactivex:rxjava:$rxjavaLibVersion"
    ]
}

Project : build.gradle 中引入
//就是引入他的相對根目錄路徑
apply from: 'config/text.gradle'

Module : build.gradle 中使用
rootProject.ext.XXXXX

dependencies {

 def dependencies=rootProject.ext.dependencies

    compile dependencies.rxJava

    compile "org.jetbrains.kotlin:kotlin-stdlib:$rootProject.ext.kotlinVersion"

}

整體結構與描述

//聲明引入的參數配置文件
apply from: 'config/dependencies.gradle'
apply from: 'config/text.gradle'

//編譯配置
buildscript {

    //自定義參數
    ext {
        kotlin_version = '1.1.0'
        compile_version = 25
    }

    //Gradle指定使用jcenter代碼倉庫
    repositories {
        jcenter()
    }
    //聲明依賴Android Gradle 插件版本
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

//這里可以為項目整體配置屬性
 allprojects{
    repositories {
        jcenter()
    }
}

//任務:每次構建的時候刪除指定目錄
task clean(type: Delete) {
    delete rootProject.buildDir
}

Module : build.gradle

控制每個Module的編譯過程以及具體的參數配置

defaultConfig

基本配置信息

  //默認配置
    defaultConfig {
        //包名
        applicationId "com.rayhahah.gradledemo"
        //最低版本
        minSdkVersion 19
        //目標版本
        targetSdkVersion 25
        //版本代碼
        versionCode getVersinCode()
        //版本
        versionName "1.0"
        //自動化測試
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        resValue "int","test","1"
    }

signConfigs

簽名配置信息

signingConfigs {

        //debug模式簽名文件
        debug {}
        //簽名打包
        release {
            //簽名文件所在路徑
            storeFile file("ray.jks")
            //簽名密碼
            storePassword "111111"
            //別名
            keyAlias "rayhahah"
            keyPassword "111111"
        }
        
        //自定義簽名配置
        ray{
            //和上面的屬性一致,根據個人需求實現不同配置
        }
    }

buildTypes

編譯類型 : 指定編譯不同類型情況下的不同配置信息
這里是列舉了部分屬性和方法,全部的方法和屬性請看官網文檔

//構建配置
    buildTypes {
        release {

            //是否啟用資源優化
            minifyEnabled
             //啟用舍棄無用資源,只有當開啟混淆才能夠啟用
            shrinkResources false
            //指定混淆文件
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

            //指定我們release包的輸出文件名就是我們的渠道名字
            applicationVariants.all { variant ->
                variant.outputs.each { output ->
                    def outputFile = output.outputFile
                    if (outputFile != null && outputFile.name.endsWith(".apk")) {

                        def fileName = "${variant.productFlavors[0].name}" + ".apk"
                        output.outputFile = new File(outputFile.parent, fileName);
                    }
                }
            }
        }

        debug {

        }

        //繼承
//        rayhahah.initWith(debug)

        //自定義buildType
        rayhahah {
            //指定簽名配置文件
            signingConfig signingConfigs.debug
            //包名增加后綴
            applicationIdSuffix ".ray"

        }
    }

sourceSets

配置資源邏輯組

  • 通過指定資源 文件夾路徑構建自己的工程目錄
指定Android所需要文件夾所在具體路徑

sourceSets {
//這樣的配置適用于將Eclipse中的項目結構遷移到AndroidStudio中
        main {
            //指定src資源目標目錄
            java.srcDirs = ['src']
            //指定asset的目標目錄
            assets.srcDirs = ['assets']
            //指定res的目標目錄
            res.srcDirs = ['res']
            //指定依賴C文件的目標目錄
            jni.srcDirs = ['jni']
             //指定依賴so文件的目標目錄
            jniLibs.srcDirs = ['libs']
            //指定Manifest的目標文件路徑
            manifest.srcFile 'AndroidManifest.xml'
        }
 }
  • 常用! 給自己的layout分包管理??!
1. 在res下新建文件夾 layouts(其實叫什么都無所謂)
2. 然后在 layouts下 新建你要分的包 如: activity,fragment 或按照業務模塊來分
3. 在分包內新建Android resource directory -> layout 不要改名字
4. 在module:build gradle 如下配置
5. 然后將以前的layout文件拷貝到對應分包的layout下就可以使用了

PS:只有在Project目錄才能看到,Android目錄結構是看不到的

 sourceSets {
        main {
            res.srcDirs = [
                    'src/main/res/layouts/activity',
                    'src/main/res/layouts/fragment',
                    'src/main/res/layouts',
                    'src/main/res'

            ]
        }
    }

productFlavors

打包渠道配置信息(仔細看代碼注釋)

//多渠道打包配置
//利用Manifest占位符動態參數配置
productFlavors {
        baidu {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "baidu"]
        }

        wandoujia {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "wandoujia"]
        }

        googleplayer {
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "googleplayer"]
        }
        
        //不想每一個都去配置渠道名稱也可以用下面這個函數
        //這個函數可以將 Manifest中的占位符替換成 每個渠道的名字
        productFlavors.all {
            flavor -> flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
        }
    }

compileOptions

 //編譯配置項
    //主要配置Java編譯版本
    compileOptions {
        sourceCompatibility org.gradle.api.JavaVersion.VERSION_1_8
        targetCompatibility org.gradle.api.JavaVersion.VERSION_1_8
    }

lintOptions


    //lint配置項
    lintOptions {

        //啟用出錯停止grgradle構建
      abortOnError false

      // true--檢查所有問題點,包含其他默認關閉項
      checkAllWarnings true

      // 關閉指定問題檢查
      disable 'TypographyFractions','TypographyQuotes'
      // 打開指定問題檢查
      enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
      // 僅檢查指定問題
      check 'NewApi', 'InlinedApi'

      // true--生成HTML報告(帶問題解釋,源碼位置,等)
      htmlReport true
      // html報告可選路徑(構建器默認是lint-results.html )
      htmlOutput file("lint-report.html")

      // 忽略指定問題的規則(同關閉檢查)
      ignore 'TypographyQuotes'
    }

build.gradle 圖形化配置

AndroidStuido 給我們提供了十分友好地對于Build.gradle 圖形化配置的界面,使用如下:

  • File -> Project Structure -> 選擇需要配置的 Module —> 選擇需要配置的領域
    如圖十分直觀:
圖形化界面.png

dependencies 項目依賴

  1. build.gradle中dependencies中的配置解析
//dependencies : 當前Android Module構建過程中所依賴的所有庫
dependencies {
    //依賴指定目錄下所有指定后綴的文件
    compile fileTree(dir: 'libs', include: ['*.jar'])
    //測試工具依賴
    testCompile 'junit:junit:4.12'
    //遠程庫的依賴 (當從遠程庫中下載一次過后,就會緩存到本地了)
    //默認遠程庫配置為 jcenter() 
    compile 'com.android.support:appcompat-v7:25.2.0'

    //依賴指定文件(這里依賴的是jar包)
    compile file('libs/test-1.0.0.jar')
    //依賴本地項目庫
    compile project(':testLibrary')

    //格式:  groupId: com.squareup.retrofit2
    //       artifactId : retrofit
    //       version:  2.1.0
    // SNAPSHOT : 表示依賴 retrofit 及其依賴的所有項目,如果他所依賴的項目在本項目中重復出現依賴,則只依賴retrofit項目中的。
    // @aar :  表示只依賴retrofit,不依賴他所依賴的項目
    compile ('com.squareup.retrofit2:retrofit:2.1.0-SNAPSHOT@aar')
    {
         //強制刷新遠程庫,避免遠程庫刷新,本地未更新
         transitive = true        
          //exclude : 單獨去除okhttp3的依賴
         exclude module : 'com.squareup.okhttp3:okhttp:3.3.0' 
    }

}
  1. so庫依賴

    • src/main目錄下創建jniLibs,然后將so文件拷貝進去就可以了
    • 當然也可以通過sourceSet指定jniLib的目標目錄來自定義管理依賴的so文件存放
  2. 本地Module依賴

    • build.gradledependencies 領域中添加 compile project(':testLibrary')
    • setting.gradle 中添加module到include中 如:include ':app',':testLibrary'

方法def定義

在Gradle中你可以寫方法供 配置信息動態調用

//自定義函數
def getVersinCode() {
//    ......
}

Android{
    defaultConfig{
        versionCode getVersinCode()
    }
}

Gradle編譯提速優化

檢測

  • gradlew build -profile : 編譯工程同時生成編譯性能分析文件,在根目錄build/reports/profile/profile-xxxx.xxx....html ,通過瀏覽器打開以后
    如圖: 我們需要優化的就是Task Execution
profile.png
  • 往下拉,可以看到TaskExcution的詳細參數
TaskExcution.png

可以看到lint耗時最多,然后我們就可以根據自己項目中的具體情況來做優化

禁用Task達到提速

  • 根據上面的耗時來對應禁用Task來達到提速的效果
  • Project:build.gradle 中的 buildScript中動態配置編譯時禁用即可, 代碼:gradle.startParameter.excludedTaskNames.add('lint') 就可以實現禁用了,具體需要繼續禁用的可以根據項目輸出的編譯分析文件來作出添加和調整

AAPT

aapt即Android Asset Packaging Tool,在SDK的build-tools目錄下。該工具可以查看,創建, 更新ZIP格式的文檔附件(zip, jar, apk)。也可將資源文件編譯成二進制文件,盡管你可能沒有直接使用過aapt工具,但是build scripts和IDE插件會使用這個工具打包apk文件構成一個Android 應用程序(百度百科)

  • 在Debug模式下,我們需要優化AAPT來大量提速我們的編譯(記得在release下改回來)
aaptOtions{
    cruncherEnabled = false
}

Gradle編譯優化

  • 提升Gradle本身編譯速度
    • gradle.properties中配置
//開啟守護線程支持
org.gradle.daemon=true

//開啟并行編譯
org.gradle.parallel=true
//按需編譯
org.gradle.configureondemand=true

//手動配置Gradle編譯時內存分配
# Default value: -Xmx10248m -XX:MaxPermSize=256m
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8

# 開啟JNI編譯支持過時API
android.useDeprecatedNdk=true
  • build.gradle中配置
//增加編譯內存到4g
dexOptions{
    incremental true    
    javaMaxHeapSize "4g"
}

認識Task

  • Task基本使用:

task testTask << {

    println 'testTask << print'

    // 表示在task最前面來執行的過程
    doFirst {
        println 'testTask do first'}
    // << 和 doLast表示對當前task追加執行過程,效果是一樣的
    doLast{
        println 'testTask do last!'}
}

//task之間的依賴 dependsOn
//當執行存在依賴的task時,會先執行他的父類也就是依賴目標
task testDependsOn(dependsOn:testTask){
    println 'testDependsOn default print '
}
//或者
testDependsOn.dependsOn testTask
//當執行testDependsOn是 打印順序: testDependsOn  default print -> testTask do first -> testTask << print -> testTask do last!
//順序總結為:
//1.不加doLast和doFirst的最先執行
//2.依賴task優先級高于自己的doFirst和doLast
//3.同一個task中的doLast按從上向下順序執行
//4.同一個task中的doFirst按從下到上倒序執行
//5.同一個task的doFirst優先級高于doLast

//顯示聲明類型為Copy, 不聲明默認為defaultTask
task testCopy(type : Copy){
    //將當前gradle文件從src目錄拷貝到dst目錄
    from "src"
    into "dst"
}
//每一個特定的Task類型還可以含有特定的Property,比如Copy的from和to等。

//自定義property
ext.testProperty = ""

task testExtProperty << {
    //直接使用自定義的property
    println testProperty
}

  • 自定義Task:
//局部自定義Task
//直接在build.gradle中自定義Task
//但是也只能在當前module中引用
class TestCustomTask extends DefaultTask {
    //@Optional,表示在配置該Task時,message是可選的。
    @Optional
    String message = 'I am jjx'
    //@TaskAction表示該Task要執行的動作,即在調用該Task時,hello()方法將被執行
    @TaskAction
    def hello() {
        println "hello world $message"
    }
}

//hello使用了默認的message值
task hello(type: TestCustomTask)

//重新設置了message的值
task helloOne(type: TestCustomTask) {
    message = "I am a android developer"
}


全局自定義Task
如果需要自定義大量的Task,就要新建一個Gradle文件來統一管理
通過apply來引入使用
//這是插件
apply plugin: 'com.android.application'
//這里gradle-quality.gradle就是另外單獨定義了task的gradle
apply from: '../build-config/gradle-quality.gradle'

自定義Plugin

自定義Plugin可以讓我們在工程編譯根據需求中自動去完成一些操作
下面就是一個編譯后自動打印當前時間的Plugin

build.gradle中直接定義

與自定義Task十分類似
可以在build.gradle中自定義plugin
apply plugin: DateAndTimePlugin

dateAndTime {
    timeFormat = 'HH:mm:ss.SSS'
    dateFormat = 'MM/dd/yyyy'
}

class DateAndTimePlugin implements Plugin<Project> {
    //該接口定義了一個apply()方法,在該方法中,我們可以操作Project,
    //比如向其中加入Task,定義額外的Property等。
    void apply(Project project) {
        //加載Extension
        project.extensions.create("dateAndTime", DateAndTimePluginExtension)

        //使用Extension配置信息
        project.task('showTime') << {
            println "Current time is " + new Date().format(project.dateAndTime.timeFormat)
        }

        project.tasks.create('showDate') << {
            println "Current date is " + new Date().format(project.dateAndTime.dateFormat)
        }
    }
}
//每個Gradle的Project都維護了一個ExtenionContainer,
//我們可以通過project.extentions進行訪問
//比如讀取額外的Property和定義額外的Property等。
//向Project中定義了一個名為dateAndTime的extension
//并向其中加入了2個Property,分別為timeFormat和dateFormat
class DateAndTimePluginExtension {
    String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS"
    String dateFormat = "yyyy-MM-dd"
}

工程中定義

其實本質上就是對上面的自定義Plugin結構化拆解

  1. Plugin目錄創建

    • 在根目錄下創建buildSrc
    • 子目錄結構如下:buildSrc/src/main/groovy/com/raybuildSrc/src/main/resources/META-INF/gradle-plugins
  2. 創建buildSrc/build.gradle , 配置如下

apply plugin:'groovy'

dependecies{
    compile gradleApi()
    compile localGroovy()
}
  1. Plugin 邏輯實現
    自定義Plugin的主要實現邏輯
    buildSrc/src/main/groovy/com/ray 下創建DateAndTimePlugin
class DateAndTimePlugin implements Plugin<Project> {
    void apply(Project project) {
        //加載Extension
        project.extensions.create("dateAndTime", DateAndTimePluginExtension)

        //使用Extension中的配置信息
        project.task('showTime') << {
            println "Current time is " + new Date().format(project.dateAndTime.timeFormat)
        }

        project.tasks.create('showDate') << {
            println "Current date is " + new Date().format(project.dateAndTime.dateFormat)
        }
    }
}
  1. Extension實現
    Extension相當于Gradle配置信息(相當于實體類),然后主項目的build.gradle 通過Extension傳遞配置(相當于賦值)

同樣在buildSrc/src/main/groovy/com/ray下創建DateAndTimePluginExtension

//每個Gradle的Project都維護了一個ExtenionContainer,
//我們可以通過project.extentions進行訪問
//比如讀取額外的Property和定義額外的Property等。
//向Project中定義了一個名為dateAndTime的extension
//并向其中加入了2個Property,分別為timeFormat和dateFormat
class DateAndTimePluginExtension {
    String timeFormat = "MM/dd/yyyyHH:mm:ss.SSS"
    String dateFormat = "yyyy-MM-dd"
}
  1. Plugin命名
    自定義外部引用時Plugin的名字
    buildSrc/src/main/resources/META-INF/gradle-plugins 下創建
    timePlugin.properties ,內容只有一行代碼 : implementation-class = com.ray.DateAndTimePlugin

  2. Plugin的使用
    主項目中apply plugin:'timePlugin'
    可選:配置Extension :

timePlugin{
    //動態修改和配置Extension屬性
    //這里修改了日期格式
    timeFormat = 'MM/dd/yyyy'
}

獨立Module自定義插件

  1. 新建plugin目錄結構如圖:
module_plugin目錄.png
  1. module_name/build.gradle的修改 如下:
apply plugin: 'groovy'
//增加Maven的支持
apply plugin: 'maven'

version = 1.0
group = 'com.ray.plugin'
archivesBaseName = 'timeplugin'
repositories.mavenCentral()

dependencies {
    compile gradleApi()
    groovy localGroovy()
}

//將插件部署到repo目錄下
uploadArchives {
    repositories.mavenDeployer {
        repository(url: uri('../repo'))
    }
}
  1. 發布:和其他Module一樣發布到中央庫中
  2. 使用插件,主項目中配置如下:
apply plugin: 'timePlugin'

buildscript {
    repositories {
        maven {
            url uri('../repo')
        } }
    dependencies {
        classpath group: 'com.ray.plugin', name: 'timePlugin',
                version: '1.0'
    }
}

最后

以上就是總結的Gradle實用的進階指南,讓我們可以更加隨心所欲地去管理我們的工程。以后如果有一些新的認識或者想法,我也會在這里實時更新的。也算是自己對Gradle認識和學習的總結整理吧。
正確使用Gradle的配置是為了讓我們開發更加便捷、效率更高,千萬不要本末倒置了。
如果哪里理解錯誤,歡迎大家指出糾正
如果文章對你有用的話,請點贊鼓勵一下哈O(∩_∩)O~~

參考和感謝以下博文和項目:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,828評論 18 139
  • 前言 為什么需要學Gradle? Gradle 是 Android 現在主流的編譯工具,雖然在Gradle 出現之...
    真笨笨魚閱讀 1,512評論 0 0
  • 前言 為什么需要學Gradle? Gradle 是 Android 現在主流的編譯工具,雖然在Gradle 出現之...
    Liuuuuuuzi閱讀 2,009評論 0 18
  • 有一句話叫做:細節決定成敗。還有一句話叫做:成大事者不拘小節。 以前,從來沒有把這兩句話放在一起來看,今天,在看了...
    芳的樹閱讀 641評論 0 4
  • 三月三帶女兒回到家鄉桂林,應女兒要求,帶她去吃地道桂林小吃——鹵菜粉。 根據兒時的回憶,我帶女兒找到桂...
    無理525閱讀 852評論 0 0