Android 中能夠作為 Log 開關的一些操作以及安全性淺談

亦楓發表於2017-09-01

自定義常量


開發階段利用 Log 日誌方便程式碼除錯是再常見不過的事情。出於安全考慮,這種做法僅限於 Debug 模式,Release 模式下打包釋出時一定要關掉。所以在我們的專案中,一定會有一個工具類或者方法來控制 Log 日誌的使用,比如:

public class LogUtils {

    public static final Boolean DEBUG_MODE = true;

    public static void d(String message) {
        if (DEBUG_MODE) {
            Log.d("TAG", message);       
        }
    }

}複製程式碼

常見的做法便是像上面這樣,自定義一個布林型別的常量作為開關來控制是否列印日誌。但是這種做法有一個弊端,那就是每次釋出 Release 包時都需要手動修改這個常量的值為 false,然後下一次開發階段再手動修改為 true。

雖然是很簡單的手動修改操作,但是也很容易忘記。那麼有沒有一種辦法實現自動化管理呢?答案當然是有的,使用 BuildConfig 類。

BuildConfig


類似 R 資原始檔,BuildConfig 也是在編譯階段,Gradle 外掛自動生成的一個 class 檔案。該檔案包含一些幫助開發人員辨別當前 build 型別的常量資訊。當然你也可以通過 Gradle 提供的定製功能向該檔案裡面新增其他輔助內容。這裡我們看一下預設情況下,BuildConfig 檔案都包含有哪些內容:

public final class BuildConfig {
    public static final boolean DEBUG = Boolean.parseBoolean("true");
    public static final String APPLICATION_ID = "com.yifeng.sample";
    public static final String BUILD_TYPE = "debug";
    public static final String FLAVOR = "";
    public static final int VERSION_CODE = 1;
    public static final String VERSION_NAME = "1.0";
}複製程式碼

能夠看出,都是一些大家很熟悉的資訊。其中包括一個 DEBUG 常量,其值便可用於判斷當前 build 型別。debug 模式下為 true,release 模式下為 false。所以,使用 BuildConfig.DEBUG 可以替代前面我們自定義的常量,實現自動管理 Log 日誌的列印:

public static void d(String message) {
    if (BuildConfig.DEBUG) {
        Log.d("TAG", message);
    }
}複製程式碼

看上去貌似已經很完美了,但其實還是有瑕疵的。BuildConfig 類檔案的生成依據於 Module,也就是說每一個 Module 編譯時都會產生自己的這個檔案。如果你的主 app module 使用其他依賴 module 中 BuildConfig 檔案裡面的 DEBUG 值,就需要多加註意。

預設情況下,Library 的構建永遠是以 Release 模式執行的,所以其 BuildConfig.DEBUG 值一定是 false!即使主 Module 使用 Debug 模式構建,也是如此。

那麼,有沒有辦法修改 Library Module 的預設構建方式呢?答案也是肯定的。開啟對應 Library 的 build.gradle 檔案,新增這樣一行配置程式碼:

android {
    // 這裡省略其他內容
    publishNonDefault true
}複製程式碼

即表示不使用預設構建方式,編譯時也會自動生成其他 build 型別的 BuildConfig 類檔案。你可以在相應 Library 路徑下檢視配置該命令前後 BuildConfig 檔案的生成情況,目錄地址為:

libraryName/build/generated/source/buildConfig/ + debug/release複製程式碼

然後在我們的主 Module 依賴的時候同時引入 debug 和 release 兩種配置,這裡以 extras/PullToRefresh 作為 Library 為例,看下依賴程式碼:

dependencies {
    releaseCompile project(path: ':extras:PullToRefresh', configuration: 'release')
    debugCompile project(path: ':extras:PullToRefresh', configuration: 'debug')
}複製程式碼

如此這般,便可以解決前面提到的依賴 Module 問題。當然,如果你的專案比較簡單,只是單一 Module,也就不存在這個問題。

但是如果專案中的依賴 Module 比較多的話,這種處理方式還是略顯麻煩。你需要在用到的地方針對每個 Module 逐一處理。其實還有一種更好的解決方案,那就是使用 Manifest 清單檔案中 application 標籤裡的 debuggable 屬性。

ApplicationInfo


application 標籤裡有個 android:debuggable 屬性,表示當前應用是否可以被除錯(一般不建議手動設定這個屬性)。這個屬性也會隨著 build 型別自動改變。所以,利用這個特性也能判定應用是否處於 Debug 模式,比如:

public static boolean isDebug(Context context) {
    return (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}複製程式碼

控制 Log 日誌列印的開關,除了上面講到的這些方式,其實還有別的方式。比如利用 Gradle 的靈活性在 build.gradle 檔案中自定義一個 Boolean 變數,根據 build 型別動態賦值,也能達到我們的目的。

更安全的 Log 用法


前面所有這些做法都只是使 release 包不去顯示 Log 日誌,從而提高安全性。但是,有沒有想過,如果 apk 被反編譯的話,這些 Log 相關的程式碼還是能夠別識別出來,別人只需要稍作修改,重新打包,依舊能夠使 Log 重現。

當然,使用常量作為 LogUtils 中的判斷條件的話,根據 proguard 的優化規則,在 Release 包中是不包含條件體中的 Log.d 等操作程式碼的。關於這一點,可以自己反編譯 apk 嘗試看下。

然而,在其他呼叫 LogUtils 工具類的地方依舊暴露了我們的意圖。所以,定義一個 LogUtils 類雖然提高了使用 Log 的效率,依舊解決不了 Log 安全的問題。相比而言,我們做了這麼多努力只是稍微提高了一些安全的門檻而已。

所以,最好的辦法就是,Release 包中不包含任何用於除錯的 Log 程式碼(如果使用 LogUtils 的話,也包括 該類的呼叫)。也就是說,不使用 LogUtils 工具類封裝,在任何需要的地方,不嫌麻煩的逐一新增判斷條件:(可以使用 Live Template 提高效率)

if (BuildConfig.DEBUG) {
    Log.d("TAG", message);       
}複製程式碼

這樣,打包時,開啟 proguard 後,Release 包會自動刪除上面的程式碼,徹底根絕 Log 引發的安全問題。關於這一部分的細節操作,可以參考這兩篇文章:

(END)

關於我:亦楓,部落格地址:yifeng.studio/,新浪微博:IT亦楓

微信掃描二維碼,歡迎關注我的個人公眾號:安卓筆記俠

不僅分享我的原創技術文章,還有程式設計師的職場遐想

相關文章