Gradle系列第(三)篇---Android Studio與Gradle那些事兒

版權聲明:本文為LooperJing原創文章,轉載請注明出處!

·

Android中的gradle.jpg

本來這篇要寫Android性能優化的,個人時間比較少,每天加班到很晚,寫博客的時間就很少了,但是Gradle系列的文章還沒有寫完,所以補一篇,在Gradle系列第(二)篇---Gradle編程主要對象主要寫了Gradle中的幾個對象(Project,Settings,Gradle,Task、Action),現在聊一聊Android Studio中的gradle常見的功能需求。如果你還沒有閱讀過我的前兩篇博客Gradle系列第(一)篇---Groovy語法初探Gradle系列2---Gradle編程主要對象,可以先看一下,有助于本文的理解,好啦,各位看官準備好瓜子花生,接下來一大篇文章嘩啦啦的來了。不過不用擔心,這篇博客仍然是面向基礎。

讀完這篇博客,你會了解到這些內容

  • 1、Android的構建文件
  • 2、全局參數配置
  • 3、用腳本更改項目結構
  • 4、多種apk的生成
  • 5、簽名的配置與使用
  • 6、項目混淆(Proguard)
  • 7、gradle多渠道打包
  • 8、APK需求定制的案例
  • 9、動態參數配置
  • 10、gradle依賴管理
  • 11、gradle.properties文件配置
  • 12、jar文件輸出
一、AS項目構建文件的簡單解釋

一個AS項目結構大概像下面這樣子

項目構建文件.jpg

如藍色條所示,項目中總共包含了6個構建文件(不算Library中的gradle),我們先從宏觀的方面了解一下,每個構建文件的作用是啥?

  • 1、這個文件是app文件夾下這個Module的gradle配置文件,也可以算是整個項目最主要的gradle配置文件,比如自動打包debug,release,beta等環境,簽名,多渠道打包,混淆等操作都可以在這里面寫。每一個Module都需要有一個gradle配置文件。
  • 2、我們主要看下gradle-wrapper.properties這個文件的內容
#Mon Dec 28 10:00:20 PST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip

可以看到里面聲明了gradle的目錄與下載路徑以及當前項目使用的gradle版本,這些默認的路徑我們一般不會更改的,有時候導入一個新項目,gradle版本不對,可以在這里修改。

  • 3、這個文件是整個項目的gradle基礎(全局)配置文件,內容主要包含了兩個方面:一個是聲明倉庫的源,這里可以看到是指明的jcenter(), 之前版本則是mavenCentral(), jcenter可以理解成是一個新的中央遠程倉庫,兼容maven中心倉庫,而且性能更優。另一個是聲明了android gradle plugin的版本。allprojects:中定義的屬性會被應用到所有 moudle 中,但是為了保證每個項目的獨立性,我們一般不會在這里面操作太多共有的東西。

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

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

所有通過gradle導入的jar包都是從http://bintray.com/bintray/jcenter這個中央倉庫上扒下來的。如果你需要的jar包在這個網站上沒有,那就無法通過gradle的方式來導入哦。

  • 4、這個里面可以配置參數,然后在其他build.gradle中引用,后面會講例子,如何動態配置參數。
  • 5、這里主要指定了ndk和SDK的路徑
ndk.dir=G\:\\Users\\wangjing\\AppData\\Local\\Android\\sdk\\ndk-bundle
sdk.dir=G\:\\Users\\wangjing\\AppData\\Local\\Android\\sdk
  • 6、setting.gradle最關鍵的內容就是告訴Gradle這個multiprojects包含哪些子projects,當你的app只有一個模塊的時候,你的setting.gradle將會是這樣子的:
include ':app'

當你的app有多個模塊的時候,你的setting.gradle將會是這樣子的

include ':app', ':library',。。。。

setting.gradle文件將會在gradle初始化時期執行,關于初始化時期,可以查看上一篇博客,并且定義了哪一個模塊將會被構建。舉個例子,上述setting.gradle包含了app模塊,setting.gradle是針對多模塊操作的,所以單獨的模塊工程完全可以刪除掉該文件。在這之后,Gradle會為我們創建一個Setting對象,每一個settings.gradle都會轉換成一個Settings對象,并為其包含必要的方法,你不必知道Settings類的詳細細節,但是你最好能夠知道這個概念。另外可以在settings做一些初始化的工作,后面介紹。

讀到這里做個總結

  • build.gradle:控制每個Module的構建過程
  • gradle.properties:設置gradle腳本中的參數
  • local.properties:gradle的SDK和NDK環境變量配置
  • gradle.properties:用于配置參數信息
  • setting.gradle :配置gradle的多項目管理
二、實用技能精講
1、全局參數配置

通常我們的項目都有很多的Module,像我現在公司的項目就有十幾個,那么每個Module里面的gradle文件通常都有類似這樣的配置。

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"
    defaultConfig {
        applicationId "com.zhangwan.www.gradle"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}

這些配置對于每個Module來說,最好統一,我把它定義在項目根目錄的gradle文件中,如下。

//全局配置
ext {
    minSdkVersion =15
    targetSdkVersion =24
    compileSdkVersion =24
    buildToolsVersion ="24.0.0"
    versionCode =1
    versionName="1.0"
}

定義好了,我們可以在各個Module的gradle文件文件中引用,如下:

android {
    compileSdkVersion rootProject.ext.compileSdkVersion
    buildToolsVersion  rootProject.ext.buildToolsVersion

    defaultConfig {
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode rootProject.ext.versionCode
        versionName rootProject.ext.versionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}

利用Gradle全局變量,對于多Module有很大的好處,方便統一,除了上面的列子,在舉個例子。上面全部按照單個的屬性配置的,對于相關的屬性,可以將他們寫到一個列表中,下面定義了一個dependencies_config的列表。

ext{
    dependencies_config=[supportv7:"com.android.support:appcompat-v7:25.0.0"]
}

在Module中,這樣引用

dependencies {
    compile rootProject.ext.dependencies_config.supportv7
     .....
}
2、項目結構更改

sourceSets 的作用是重新定義資源文件位置,比如

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.0"
 
    sourceSets{
        main{
            res.srcDirs=['src/main/res','src/main/res/layout/activity','src/main/res/layout/fragment']
        }
    }
}

在你Sync Now之后,會出現activity和fragment兩個文件夾


更改項目目錄.png

最常見的是下面這塊代碼,當Eclipse項目轉到Studio的時候,需要重新指定一些文件的位置。

sourceSets {  
    main {  
        manifest.srcFile 'AndroidManifest.xml'  
        java.srcDirs = ['src']  
        resources.srcDirs = ['src']  
        aidl.srcDirs = ['src']  
        renderscript.srcDirs = ['src']  
        res.srcDirs = ['res']  
        assets.srcDirs = ['assets']  
        jniLibs.srcDirs = ['libs']  
    }  
}  

sourceSets的用法就是這樣,可以重新指定文件目錄,但是讀到這,可能有些人心中有個問題,為什么sourceSets ,defaultConfig這樣的東東要寫在android的大括號中。換言之,android這個大括號里面還能寫什么東西,我來列舉一下。

android {
    defaultConfig {
        //默認配置項,defaultConfig就是程序的默認配置,注意,如果在   AndroidMainfest.xml里面定義了與這里相同的屬性,會以這里的為主。
    }

    buildTypes {
      // 編譯配置,release或debug版本的內容
    }

    compileOptions {
      // Java 的版本配置
    }

    sourceSets {
        //源碼設置(項目目錄結構的設置)
    }

    packagingOptions {
       //打包時的相關配置  
    }

    lintOptions {
        //編譯的 lint 開關,程序在buid的時候,會執行lint檢查,有任何的錯誤或者警告提示,都會終止構建,我們可以將其關掉。
        //abortOnError false  
    }

    productFlavors {
        //產品發布的一些東西,比如渠道、包名等
        flavor1 {
        }

        flavor2 {
        }
    }

    signingConfigs {
        //簽名的配置
        release {
        }
    }

    testOptions{
        //測試配置,TestOptions類型
    } 
    aaptOptions{
      //aapt配置,AaptOptions類型 
    } 
     lintOptions{
       //lint配置,LintOptions類型
    } 
    dexOptions{
       //dex配置,DexOptions類型
    } 
    compileOptions{
     // 編譯配置,CompileOptions類型
    } 
    packagingOptions{
       // PackagingOptions類型
    } 
    jacoco{ 
       //JacocoExtension類型。 用于設定 jacoco版本
    } 
    splits{
       //Splits類型。
    } 
}

在DSL文檔中,以上每個類型都有它的詳細配置選項,一般常見的設置就是上面啦,如果你覺得有的不太了解,看下面之后就了解了。

3、多種apk的生成

默認studio生成的buildTypes是像下面這樣的,但是呢,我還想要其他的變種類型。

 buildTypes {
        release {
            minifyEnabled false// 不混淆
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

我們可以這樣添加

 buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        r1{
            applicationIdSuffix ".r1"
        }
        r2{
            applicationIdSuffix ".r2"
        }
        r3{
            applicationIdSuffix ".r3"
        }
    }

通過這樣就可以得到多種變種app,執行assemble這個task,打出所有apk。

build_types.png

總共得到系統默認有的release和debug兩個apk,額外還有r1,r2,r3三個不同的apk。
那么applicationIdSuffix是什么呢,逆向r1包看看。

r1包逆向結果.png

系統通過包名來區分應用,這種方式無非就是在包名后面加上了一個后綴r1。

4、簽名的配置與使用

上面打出的包都是沒有指定簽名的,我們要配置一個簽名,首先需要生成簽名文件。我生成的簽名文件是1.jks

 signingConfigs{
        signR1{
            storeFile file("build/1.jks");
            storePassword "123456"
            keyAlias "xxx"
            keyPassword "123456"
        }
        signR2{
            storeFile file("build/2.jks");
            storePassword "123456"
            keyAlias "xxx"
            keyPassword "123456"
        }
    }

簽名在signingConfigs中配置,signR1,signR2是簽名的名字,在buildTypes中使用。

 buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        r1{
            signingConfig signingConfigs.signR1
            applicationIdSuffix ".r1"
        }
        r2{
            signingConfig signingConfigs.signR2
            applicationIdSuffix ".r2"
        }

    }

加上簽名后打的包是這樣,跟未加簽名相比較,多了app-r1.apk,app-r2.apk。


簽名apk生成.png
5、項目混淆(Proguard)

面對眾多的渠道,打包也有很多不同的需求。 比如 debug版,release版,dev版等等。 有時候不同的版本中使用到的不同的服務端api域名也不相同。 比如 debug_api.com,release_api.com,dev_api.com等等。不同的版本對應了不同的 api 域名,還可能對應不同的 icon 等。渠道首發包通常需要要求在歡迎頁添加渠道的logo等。下面我們開始進行打包。首先進行混淆設置,混淆需要buildTypes中配置,在上面說過,默認生成的buildTypes是這樣子的

  buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

其中,proguard-android.txt是在你的sdk\tools\proguard目錄下。minifyEnabled:表示是否開啟混淆,默認為false;proguardFiles:混淆配置文件,一般就采用項目中默認的proguard-rules.pro文件。在這個文件中寫我們的混淆規則,比如:

-keepclasseswithmembernames class * {                                           # 保持 native 方法不被混淆
    native <methods>;
}

-keepclassmembers enum * {                                                      # 保持枚舉 enum 類不被混淆
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keep class * implements android.os.Parcelable {                                # 保持 Parcelable 不被混淆
  public static final android.os.Parcelable$Creator *;
}

有這些還不夠,還需要在gradle中開啟混淆

   buildTypes {
        release {
           // 不顯示 Log 
            buildConfigField "boolean", "LOG_DEBUG", "false"
            shrinkResources true
            signingConfig signingConfigs.release
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard.cfg'
          
        }
        debug {
          // 顯示 Log 
            buildConfigField "boolean", "LOG_DEBUG", "true"
            signingConfig signingConfigs.debug
        }
    }

我們設置minifyEnabled true,就會在打包的時候進行代碼混淆處理. 其中proguard-android.txt不用管,在sdk目錄里面,我們主要是配置了proguard.cfg文件。可能大家直接在android studio創建項目不會有這個文件,而是proguard-rules.pro文件,其實一樣的,我這里是因為項目是從eclipse遷移過來的,之前在eclipse上混淆是proguard.cfg文件.

6、gradle多渠道打包
  • 1、第一步 在AndroidManifest.xml里配置PlaceHolder
 <meta-data
            android:name="MY_CHANNEL"
            android:value="${MY_CHANNEL}" />
  • 2、第二步 在build.gradle設置productFlavors
   productFlavors{
        xiaomi {
            //用gradle修改AndroidManifest.xml中的meta-data元素值
            manifestPlaceholders = [MY_CHANNEL: "xiaomi"]
        }

        _360 {
            manifestPlaceholders = [MY_CHANNEL: "_360"]
        }
        baidu {
            manifestPlaceholders = [MY_CHANNEL: "baidu"]
        }

        huawei{
            manifestPlaceholders = [MY_CHANNEL: "huawei"]
        }
    }

或者批量修改

 productFlavors{
        xiaomi {}
        _360 {}
        baidu {}
        huawei{}
    }

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

最后,最好在defaultConfig中定義一個默認的渠道

defaultConfig{
        manifestPlaceholders = [ MY_CHANNEL:"xiaomi" ]
}

到此配置完成,可以執行命令了。

  • 3、去工程的根目錄,也就是有gradlew文件的目錄,打開命令行,輸入命令:
    ./gradlew assemble
    這時候你去app/build/outputs/apk中就能看到自動打好的渠道包了。
    ./gradlew assembleRelease
    只打Release包
    ./gradlew assembleDebug
    只打Debug包
    ./gradlew assemblebaidu
    只打360的渠道包
    ./gradlew assemblebaiduRelease

不想敲命令行的,調起下面這個面板打包

打包Task.png
7、APK需求定制

上面說了一下打包,在打包的時候,一些特殊化的操作,比如修改指定apk Logo,apk重命名,這些怎么搞?

  • 渠道包重命名
android.applicationVariants.all { variant ->
        variant.outputs.each { output ->
            def outputFile = output.outputFile
            if (outputFile != null && outputFile.name.endsWith('.apk')) {
                File outputDirectory = new File(outputFile.parent);
                def fileName
                if (variant.buildType.name == "release") {
                    fileName = "wangjing_${variant.productFlavors[0].name}.apk"
                } else {
                    fileName = "wangjing_${variant.productFlavors[0].name}_beta.apk"
                }
                output.outputFile = new File(outputDirectory, fileName)
            }
        }
    }
  • 根據渠道修改APP名稱
buildTypes {
        debug {
            // 顯示Log
            buildConfigField "boolean", "LOG_DEBUG", "true"
            //重命名
            resValue("string","app_name","DEBUG")
            versionNameSuffix "-debug"
            minifyEnabled false
            zipAlignEnabled false
            shrinkResources false
            signingConfig signingConfigs.debug
        }
        release {
            // 不顯示Log
            buildConfigField "boolean", "LOG_DEBUG", "false"
            //重命名
            resValue("string","app_name","DEBUG")
            //混淆
            minifyEnabled true
            //加載默認混淆配置文件
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            //簽名
            signingConfig signingConfigs.release

        }
    }

其中 resValue("string","app_name","DEBUG") 表示一個string 類型的變量app_name的值是DEBUG,做了上面的配置之后,需要將string.xml的app_name刪掉,因為gradle編譯的時候,會將腳本中的配置跟string.xml的合并。

8、動態參數配置
signingConfigs{
        release{
            storeFile file("build/mykey.jks")
            storePassword   "123456"
            keyAlias "123456"
            keyPassword   "123456"
        }
    }

上面的這段配置,有個缺點,就是值直接寫死了,我們可以動態配置參數。在哪里配置呢,一開始就說了,在gradle.properties中配置參數。如下:

 systemPro.keyAliasPassword=123456
systemPro.keyAlias=123456
systemPro.keyStorePassword=123456
systemPro.keyStore=mykey.jks

配置好了,就可以“到處”使用了

 signingConfigs{
        release{
            storeFile       System.properties["keyStore"]
            storePassword   System.properties["keyStorePassword"]
            keyAlias        System.properties["keyAlias"]
            keyPassword     System.properties["keyAliasPassword"]
        }
        debug{
            storeFile file("mykey.jks")
            storePassword  "123456"
            keyAlias"123456"
            keyPassword  "123456"
        }
    }

9、gradle依賴管理

比如我們想依賴個support-v4包,直接一句話:

compile 'com.android.support:support-v4:23.1.1'

一個依賴需要定義三個元素:group,name和version。group意味著創建該library的組織名,通常這會是包名,name是該library的唯一標示。
上述的代碼是基于groovy語法的,所以其完整的表述應該是這樣的:

compile group: 'com.android.support:', name: 'support-v4', version:'23.1.1'

有些時候,你可能需要和sdk協調工作。為了能順利編譯你的代碼,你需要添加SDK到你的編譯環境。你不需要將sdk包含在你的APK中,因為它早已經存在于設備中,不需要在compile,我們總共有5個不同的配置:

  • compile是默認的那個,其含義是包含所有的依賴包,即在APK里,compile的依賴會存在。

  • apk的意思是apk中存在,但是不會加入編譯中,這個貌似用的比較少。

  • provided的意思是提供編譯支持,但是不會寫入apk。

  • testCompileandroidTestCompile會添加額外的library支持針對測試。

通常項目的Module很多,依賴也非常多,為了方便管理,我們應該將這些依賴寫到一個全局的地方,可以供其他module使用。這種思想也是第一小節所提的全局參數的配置。依賴管理可以參考:http://stormzhang.com/android/2016/03/13/gradle-config/

10、gradle.properties文件配置
  • gradle.properties常見配置比如有:
    開啟并行編譯:加快gradle 的編譯
    org.gradle.parallel=true

  • 開啟編譯守護進程:該進程在第一次啟動后回一直存在,當你進行二次編譯的時候,可以重用該進程。
    org.gradle.daemon=true

  • 加大可用編譯內存:
    org.gradle.jvmargs=-Xms256m -Xmx1024m

11、jar文件輸出

android Studio常常有輸出jar包的需求,只要下面這段代碼即可:

task makeJar(type: Copy) {
    delete 'build/libs/my.jar'
    from('build/intermediates/bundles/release/')
    into('build/libs/')
    include('classes.jar')
    rename ('classes.jar', 'my.jar')
}

OK,Gradle研究了一個多星期,這篇博客耗時兩個晚上,終于結束,另外如果時間來的急,在寫一篇Gradle系列4或5,因為感覺自己還沒有講清楚,側重多個Module中gradle的使用與Gradle常用命令的使用,下篇博客繼續性能優化系列的更新,每一次寫博客都花費很多的時間和精力也是一次鍛煉,跟他人分享自己的學習成果,最后附上參考資料,比我寫的好。

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

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,630評論 25 708
  • 1.介紹 如果你正在查閱build.gradle文件的所有可選項,請點擊這里進行查閱:DSL參考 1.1新構建系統...
    Chuckiefan閱讀 12,157評論 8 72
  • 這一章主要針對項目中可以用到的一些實用功能來介紹Android Gradle,比如如何隱藏我們的證書文件,降低風險...
    acc8226閱讀 7,653評論 3 25
  • ① 朗費羅說:“不要老嘆息過去,它是不再回來的;要明智地改善現在。 人生是條無名的河,是深是淺...
    玉兒說閱讀 717評論 3 3
  • 這是最近打磨出來的一個小技巧,也是之前思考時間概念時,迸發出來的一個小感悟。 干貨總結語:穿越到現在的未來的自己,...
    果大喵喵閱讀 722評論 0 0