【0174】Android 面試- Android項目構建相關

1.Android 的構建流程

【說明】html

流程概述:
一、打包資源文件,生成R.java文件
二、處理aidl文件,生成相應java 文件
三、編譯工程源代碼,生成相應class 文件
四、轉換全部class文件,生成classes.dex文件
五、打包生成apk
六、對apk文件進行簽名
七、對簽名後的apk文件進行對其處理

 

打包過程使用的工具
名稱
功能介紹 在操做系統中的路徑 源碼路徑
aapt
(Android Asset Package Tool)
Android資源打包工具
${ANDROID_SDK_HOME} /build-tools/
 ANDROID_VERSION/aapt
frameworks\base\tools\aap
aidl
(android interface definition language)

Android接口描述語言,java

將aidl轉化爲.java文件的工具android

${ANDROID_SDK_HOME}/build-tools/
 ANDROID_VERSION/aidl
frameworks\base\tools\aidl
javac Java Compiler

${JDK_HOME}/javagit

c或/usr/bin/javacgithub

 
dex
轉化.class文件爲Davik VM
能識別的.dex文件
${ANDROID_SDK_HOME}/build-tools/
 ANDROID_VERSION/dx
 
apkbuilder
生成apk包
${ANDROID_SDK_HOME}/tools/
 apkbuilder
sdk\sdkmanager\libs\sdklib\
 src\com\android\sdklib\build\
 ApkBuilderMain.java
jarsigner .jar文件的簽名工具 ${JDK_HOME}/jarsigner或/usr/bin/jarsigner
 
zipalign 字節碼對齊工具

${ANDROID_SDK_HOME}/toolsweb

 /zipalign算法

 
 
第一步:打包資源文件,生成R.java文件。
【輸入】 Resource文件(就是工程中res中的文件)、Assets文件(至關於另一種資源,這種資源Android系統並不像對res中的文件那樣優化它)、AndroidManifest.xml文件(包名就是從這裏讀取的,由於生成R.java文件須要包名)、Android基礎類庫(Android.jar文件)
【工具】aapt工具
【輸出】打包好的資源(bin目錄中的resources.ap_文件)、R.java文件(gen目錄中)
打包資源的工具aapt,大部分文本格式的XML資源文件會被編譯成二進制格式的XML資源文件,除了assets和res/raw資源被原裝不動地打包進APK以外,其它的資源都會被編譯或者處理。 。
生成過程主要是調用了aapt源碼目錄下的Resource.cpp文件中的buildResource()函數,該函數首先檢查AndroidManifest.xml的合法性,而後對res目錄下的資源子目錄進行處理,處理的函數爲makeFileResource(),處理的內容包括資源文件名的合法性檢查,向資源表table添加條目等,處理完後調用compileResourceFile()函數編譯res與asserts目錄下的資源並生成resources.arsc文件,compileResourceFile()函數位於aapt源碼目錄的ResourceTable.cpp文件中,該函數最後會調用parseAndAddEntry()函數生成R.java文件,完成資源編譯後,接下來調用compileXmlfile()函數對res目錄的子目錄下的xml文件分別進行編譯,這樣處理過的xml文件就簡單的被「加密」了,最後將全部的資源與編譯生成的resorces.arsc文件以及「加密」過的AndroidManifest.xml文件打包壓縮成resources.ap_文件(使用Ant工具命令行編譯則會生成與build.xml中「project name」指定的屬性同名的ap_文件)。
關於這一步更詳細的流程可閱讀 http://blog.csdn.net/luoshengyang/article/details/8744683
 
第二步:處理aidl文件,生成相應的java文件。
【輸入】 源碼文件、aidl文件、framework.aidl文件
【工具】aidl工具
【輸出】對應的.java文件
對於沒有使用到aidl的android工程,這一步能夠跳過。aidl工具解析接口定義文件並生成相應的java代碼供程序調用。
 
第三步:編譯工程源代碼,生成下相應的class文件。
【輸入】 源碼文件(包括R.java和AIDL生成的.java文件)、庫文件(.jar文件)
【工具】javac工具
【輸出】.class文件
這一步調用了javac編譯工程src目錄下全部的java源文件,生成的class文件位於工程的bin\classes目錄下,上圖假定編譯工程源代碼時程序是基於android SDK開發的,實際開發過程當中,也有可能會使用android NDK來編譯native代碼,所以,若是可能的話,這一步還須要使用android NDK編譯C/C++代碼,固然,編譯C/C++代碼的步驟也能夠提早到第一步或第二步。
 
第四步:轉換全部的class文件,生成classes.dex文件。
【輸入】 .class文件(包括Aidl生成.class文件,R生成的.class文件,源文件生成的.class文件),庫文件(.jar文件)
【工具】javac工具
【輸出】.dex文件
前面屢次提到,android系統dalvik虛擬機的可執行文件爲dex格式,程序運行所需的classes.dex文件就是在這一步生成的,使用的工具爲dx,dx工具主要的工做是將java字節碼轉換爲dalvik字節碼、壓縮常量池、消除冗餘信息等。
 
第五步:打包生成apk。
【輸入】打包後的資源文件、打包後類文件(.dex文件)、libs文件(包括.so文件,固然不少工程都沒有這樣的文件,若是你不使用C/C++開發的話)
【工具】apkbuilder工具
【輸出】未簽名的.apk文件
打包工具爲apkbuilder,apkbuilder爲一個腳本文件,實際調用的是android-sdk\tools\lib\sdklib.jar文件中的com.android.sdklib.build.ApkBuilderMain類。它的代碼實現位於android系統源碼的sdk\sdkmanager\libs\sdklib\src\com\android\sdklib\build\ApkBuilderMain.java文件,代碼構建了一個ApkBuilder類,而後以包含resources.arsc的文件爲基礎生成apk文件,這個文件通常爲ap_結尾,接着調用addSourceFolder()函數添加工程資源,addSourceFolder()會調用processFileForResource()函數往apk文件中添加資源,處理的內容包括res目錄與asserts目錄中的文件,添加完資源後調用addResourceFromJar()函數往apk文件中寫入依賴庫,接着調用addNativeLibraries()函數添加工程libs目錄下的Native庫(經過android NDK編譯生成的so或bin文件),最後調用sealApk()關閉apk文件。
 
第六步:對apk文件進行簽名。
【輸入】未簽名的.apk文件
【工具】jarsigner
【輸出】簽名的.apk文件
android的應用程序須要簽名才能在android設備上安裝,簽名apk文件有兩種狀況:一種是在調試程序時進行簽名,使用eclipse開發android程序時,在編譯調試程序時會本身使用一個debug.keystore對apk進行簽名;另外一種是打包發佈時對程序進行簽名,這種狀況下須要提供一個符合android開發文檔中要求的簽名文件。簽名的方法也分兩種:一種是使用jdk中提供的jarsigner工具簽名;另外一種是使用android源碼中提供的signapk工具,它的代碼位於android系統源碼build\tools\signapk目錄下。
 
第七步:對簽名後的apk文件進行對齊處理。
【輸入】簽名後的.apk文件
【工具】zipalign工具
【輸出】對齊後的.apk文件
這一步須要使用的工具爲zipalign,它位於android-sdk\tools目錄,源碼位於android系統源碼的build\tools\zipalign目錄,它的主要工做是將spk包進行對齊處理,使spk包中的全部資源文件距離文件起始偏移爲4字節整數倍,這樣經過內存映射訪問apk文件時速度會更快,驗證apk文件是否對齊過的工做由ZipAlign.cpp文件的verify()函數完成,處理對齊的工做則由process()函數完成。

【手動】bootstrap

o爲了可以手動對齊程序包,Android 1.6及之後的SDK的tools/文件夾下都有zipalign工具。你可使用它來對齊任何版本下的程序包。你必須在簽名apk文件後進行,安全

        使用如下命令:zipalign -v 4 source.apk destination.apk性能優化

【驗證對齊】

o如下的命令用於檢查程序包是否進行了對齊:zipalign -c -v 4 application.apk

【使用Android studio】

android studio 中的build.gradle文件中加入zipAlignEnabled  true
相似於buildTypes {

              release { 

                 minifyEnabled false 

                 proguardFiles getDefaultProguardFile ('proguard-android.txt' ), 'proguard-rules.txt'

     zipAlignEnabled true

                     }

  }

2.jenkins的認識-構建或持續集成

【參考文章1】https://www.jianshu.com/p/b524b151d35f

【參考文章2】https://www.yiibai.com/jenkins/   Jenkins教程

 3.Git使用

 

【說明】通常的項目開發的使用流程以下:本身遠程倉庫與項目遠程倉庫分離;

當你想更正別人倉庫裏的錯誤時,要走一個流程:
  1. 先 fork 別人的倉庫,至關於拷貝一份,相信我,不會有人直接讓你改修原倉庫的
  2. clone 到本地分支,作一些 bug fix
  3. 發起 pull request 給原倉庫,讓他看到你修改的 bug
  4. 原倉庫 review 這個 bug,若是是正確的話,就會 merge 到他本身的項目中

至此,整個 pull request 的過程就結束了。

【實例演示】

理解了 pull request 的含義和流程,具體操做也就簡單了。以 Github 排名最高的 爲例說明。
1. 先點擊 fork 倉庫,項目如今就在你的帳號下了

 

2. 在你本身的機器上 git clone 這個倉庫,切換分支(也能夠在 master 下),作一些修改。
~  git clone https://github.com/beepony/bootstrap.git
~  cd bootstrap ~ git checkout -b test-pr ~ git add . && git commit -m "test-pr" ~ git push origin test-pr 

3. 完成修改以後,回到 test-pr 分支,點擊旁邊綠色的 Compare & pull request 按鈕

4. 添加一些註釋信息,確認提交
5. 倉庫做者看到,你提的確實是對的,就會 merge,合併到他的項目中

以上就是 pull reqesut 的整個流程

4.gradle 相關

 

5. Proguard

 

  1. 壓縮(Shrink):檢測並移除代碼中無用的類、字段、方法和特性(Attribute)。
  2. 優化(Optimize):對字節碼進行優化,移除無用的指令。
  3. 混淆(Obfuscate):使用a,b,c,d這樣簡短而無心義的名稱,對類、字段和方法進行重命名。
  4. 預檢(Preveirfy):在Java平臺上對處理後的代碼進行預檢,確保加載的class文件是可執行的。

【爲何須要進行混淆】

 java是一種跨平臺的解釋性語言,java的源碼會編譯成爲字節碼存在.class文件中,因爲跨平臺的須要,java的字節碼包含了許多的源碼的信息,包括變量名方法名等等;

 而且能夠經過這些名稱訪問變量名和方法,這些信息不少是無用的,可是容易被編譯成爲java源碼,防止被反編譯,須要對java源碼進行混淆;

混淆就是對release版本的程序進行從新的組織和處理;處理以後的代碼具備相同的功能,可是代碼是不同的,同時代碼不容易被反編譯,即便反編譯成功以後也不容易被讀懂;

被混淆以後的代碼仍然遵循原來的格式進行調用,執行的結果同樣;對外保證了程序的安全性;對內是透明的,執行的結果是同樣的;

 【參考文章】

ProGuard工做原理

ProGuar由shrink、optimize、obfuscate和preveirfy四個步驟組成,每一個步驟都是可選的,咱們能夠經過配置腳原本決定執行其中的哪幾個步驟。
 
混淆就是移除沒有用到的代碼,而後對代碼裏面的類、變量、方法重命名爲人可讀性不好的簡短名字。
那麼有一個問題,ProGuard怎麼知道這個代碼沒有被用到呢?
這裏引入一個Entry Point(入口點)概念, Entry Point是在ProGuard過程當中不會被處理的類或方法
在壓縮的步驟中,ProGuard會從上述的Entry Point開始遞歸遍歷,搜索哪些類和類的成員在使用,對於沒有被使用的類和類的成員,就會在壓縮段丟棄,在接下來的優化過程當中,那些非Entry Point的類、方法都會被設置爲private、static或final,不使用的參數會被移除,此外,有些方法會被標記爲內聯的,在混淆的步驟中,ProGuard會對非Entry Point的類和方法進行重命名。
那麼這個入口點怎麼來呢?就是 從ProGuard的配置文件來,只要這個配置了,那麼就不會被移除

如何編寫一個ProGuard文件

有個三步走的過程:
  • 基本混淆
  • 針對APP的量身定製
  • 針對第三方jar包的解決方案
基本混淆
混淆文件的基本配置信息,任何APP都要使用,能夠做爲模板使用,具體以下。
1,基本指令
複製代碼
# 代碼混淆壓縮比,在0和7之間,默認爲5,通常不須要改
-optimizationpasses 5
 
# 混淆時不使用大小寫混合,混淆後的類名爲小寫
-dontusemixedcaseclassnames
 
# 指定不去忽略非公共的庫的類
-dontskipnonpubliclibraryclasses
 
# 指定不去忽略非公共的庫的類的成員
-dontskipnonpubliclibraryclassmembers
 
# 不作預校驗,preverify是proguard的4個步驟之一
# Android不須要preverify,去掉這一步可加快混淆速度
-dontpreverify
 
# 有了verbose這句話,混淆後就會生成映射文件
# 包含有類名->混淆後類名的映射關係
# 而後使用printmapping指定映射文件的名稱
-verbose
-printmapping proguardMapping.txt
 
# 指定混淆時採用的算法,後面的參數是一個過濾器
# 這個過濾器是谷歌推薦的算法,通常不改變
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
 
# 保護代碼中的Annotation不被混淆,這在JSON實體映射時很是重要,好比fastJson
-keepattributes *Annotation*
 
# 避免混淆泛型,這在JSON實體映射時很是重要,好比fastJson
-keepattributes Signature
 
//拋出異常時保留代碼行號,在異常分析中能夠方便定位
-keepattributes SourceFile,LineNumberTable

-dontskipnonpubliclibraryclasses用於告訴ProGuard,不要跳過對非公開類的處理。默認狀況下是跳過的,由於程序中不會引用它們,有些狀況下人們編寫的代碼與類庫中的類在同一個包下,而且對包中內容加以引用,此時須要加入此條聲明。

-dontusemixedcaseclassnames,這個是給Microsoft Windows用戶的,由於ProGuard假定使用的操做系統是能區分兩個只是大小寫不一樣的文件名,可是Microsoft Windows不是這樣的操做系統,因此必須爲ProGuard指定-dontusemixedcaseclassnames選項
複製代碼

 2,須要保留的東西

複製代碼
# 保留全部的本地native方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}
 
# 保留了繼承自Activity、Application這些類的子類
# 由於這些子類,都有可能被外部調用
# 好比說,第一行就保證了全部Activity的子類不要被混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
 
# 若是有引用android-support-v4.jar包,能夠添加下面這行
-keep public class com.xxxx.app.ui.fragment.** {*;}
 
# 保留在Activity中的方法參數是view的方法,
# 從而咱們在layout裏面編寫onClick就不會被影響
-keepclassmembers class * extends android.app.Activity {
    public void *(android.view.View);
}
 
# 枚舉類不能被混淆
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
 
# 保留自定義控件(繼承自View)不被混淆
-keep public class * extends android.view.View {
    *** get*();
    void set*(***);
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
}
 
# 保留Parcelable序列化的類不被混淆
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}
 
# 保留Serializable序列化的類不被混淆
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}
 
# 對於R(資源)下的全部類及其方法,都不能被混淆
-keep class **.R$* {
    *;
}
 
# 對於帶有回調函數onXXEvent的,不能被混淆
-keepclassmembers class * {
    void *(**On*Event);
}
複製代碼
針對APP的量身定製
1,保留實體類和成員被混淆
對於實體,保留它們的set和get方法,對於boolean型get方法,有人喜歡命名isXXX的方式,因此不要遺漏。以下:
複製代碼
# 保留實體類和成員不被混淆
-keep public class com.xxxx.entity.** {
    public void set*(***);
    public *** get*();
    public *** is*();
}
複製代碼

 一種好的作法是把全部實體都放在一個包下進行管理,這樣只寫一次混淆就夠了,避免之後在別的包中新增的實體而忘記保留,代碼在混淆後由於找不到相應的實體類而崩潰。

2,內嵌類

內嵌類常常會被混淆,結果在調用的時候爲空就崩潰了,最好的解決方法就是把這個內嵌類拿出來,單獨成爲一個類。若是必定要內置,那麼這個類就必須在混淆的時候保留,好比以下:

# 保留內嵌類不被混淆
-keep class com.example.xxx.MainActivity$* { *; }

這個$符號就是用來分割內嵌類與其母體的標誌。

3,對WebView的處理

複製代碼
# 對WebView的處理
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String)
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.webView, java.lang.String)
}
複製代碼

4,對JavaScript的處理

# 保留JS方法不被混淆
-keepclassmembers class com.example.xxx.MainActivity$JSInterface1 {
    <methods>;
}

 其中JSInterface是MainActivity的子類

5,處理反射

在程序中使用SomeClass.class.method這樣的靜態方法,在ProGuard中是在壓縮過程當中被保留的,那麼對於Class.forName("SomeClass")呢,SomeClass不會被壓縮過程當中移除,它會檢查程序中使用的Class.forName方法,對參數SomeClass法外開恩,不會被移除。可是在混淆過程當中,不管是Class.forName("SomeClass"),仍是SomeClass.class,都不能矇混過關,SomeClass這個類名稱會被混淆,所以,咱們要在ProGuard.cfg文件中保留這個類名稱。
  • Class.forName("SomeClass")
  • SomeClass.class
  • SomeClass.class.getField("someField")
  • SomeClass.class.getDeclaredField("someField")
  • SomeClass.class.getMethod("someMethod", new Class[] {})
  • SomeClass.class.getMethod("someMethod", new Class[] { A.class })
  • SomeClass.class.getMethod("someMethod", new Class[] { A.class, B.class })
  • SomeClass.class.getDeclaredMethod("someMethod", new Class[] {})
  • SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class })
  • SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class, B.class })
  • AtomicIntegerFieldUpdater.newUpdater(SomeClass.class, "someField")
  • AtomicLongFieldUpdater.newUpdater(SomeClass.class, "someField")
  • AtomicReferenceFieldUpdater.newUpdater(SomeClass.class, SomeType.class, "someField")

在混淆的時候,要在項目中搜索一下上述方法,將相應的類或者方法的名稱進行保留而不被混淆。

 

6,對於自定義View的解決方案
但凡在Layout目錄下的XML佈局文件配置的自定義View,都不能進行混淆。爲此要遍歷Layout下的全部的XML佈局文件,找到那些自定義View,而後確認其是否在ProGuard文件中保留。有一種思路是,在咱們使用自定義View時,前面都必須加上咱們的包名,好比com.a.b.customeview,咱們能夠遍歷全部Layout下的XML佈局文件,查找全部匹配com.a.b的標籤便可。
 
針對第三方jar包的解決方案
咱們在Android項目中不可避免要使用不少第三方提供的SDK,通常而言,這些SDK是通過ProGuard混淆的,而咱們所須要作的就是避免這些SDK的類和方法在咱們APP被混淆。
1,針對android-support-v4.jar的解決方案
複製代碼
# 針對android-support-v4.jar的解決方案
-libraryjars libs/android-support-v4.jar
-dontwarn android.support.v4.**
-keep class android.support.v4.**  { *; }
-keep interface android.support.v4.app.** { *; }
-keep public class * extends android.support.v4.**
-keep public class * extends android.app.Fragment
複製代碼

 2,其餘的第三方jar包的解決方案

這個就取決於第三方包的混淆策略了,通常都有在各自的SDK中有關於混淆的說明文字,好比支付寶以下:

# 對alipay的混淆處理
-libraryjars libs/alipaysdk.jar
-dontwarn com.alipay.android.app.**
-keep public class com.alipay.**  { *; }
值得注意的是,不是每一個第三方SDK都須要-dontwarn 指令,這取決於混淆時第三方SDK是否出現警告,須要的時候再加上。

其餘注意事項

固然在使用ProGuard過程當中,還有一些注意的事項,以下。
1,如何確保混淆不會對項目產生影響
  • 測試工做要基於混淆包進行,才能儘早發現問題
  • 天天開發團隊的冒煙測試,也要基於混淆包
  • 發版前,重點的功能和模塊要額外的測試,包括推送,分享,打賞
2,打包時忽略警告
當導出包的時候,發現不少could not reference class之類的warning信息,若是確認App在運行中和那些引用沒有什麼關係,能夠添加-dontwarn 標籤,就不會提示這些警告信息了
 
3,對於自定義類庫的混淆處理
好比咱們引用了一個叫作AndroidLib的類庫,咱們須要對Lib也進行混淆,而後在主項目的混淆文件中保留AndroidLib中的類和類的成員。
 
4,使用annotation避免混淆
另外一種類或者屬性被混淆的方式是,使用annotation,好比這樣:
複製代碼
@keep
@keepPublicGetterSetters
public class Bean{
    public  boolean booleanProperty;
    public  int intProperty;
    public  String stringProperty;
}
複製代碼
5,在項目中指定混淆文件
到最後,發現沒有介紹如何在項目中指定混淆文件。在項目中有一個project.properties文件,在其中寫這麼一句話,就能夠確保每次手動打包生成的apk是混淆過的。
proguard.config=proguard.cfg
其中,proguard.cfg是混淆文件的名稱。

小結

總之ProGuard是一個比較枯燥的過程,但Android項目沒有了ProGuard就真不行了,這樣能夠保證咱們開發出的APK能夠更健壯,畢竟不少核心代碼質量也算是一個APK的核心競爭力吧。
 

閱讀擴展

源於對掌握的Android開發基礎點進行整理,羅列下已經總結的文章,從中能夠看到技術積累的過程。
相關文章
相關標籤/搜索