Android: 使用BuildConfig.DEBUG優(yōu)化你的Log輸出 & 開啟混淆(proguard)的優(yōu)化配置


一. 優(yōu)化思路

  1. 現(xiàn)代編譯器非常智能, 對于death blocks, 編譯器會(huì)直接刪除. 什么叫death blocks, 請看下面的代碼:
public void method() {
    if(false) {
        System.out.println("Read the fucking source code !");
    }
}

此方法中的if語句塊, 就是一個(gè)death blocks, 它是永遠(yuǎn)都不會(huì)執(zhí)行的. 因此編譯器會(huì)直接將其刪除, 刪除后代碼如下:

public void method() {
}

編譯器優(yōu)化也是有條件的, 即if括號(hào)中的值必須在編譯是就能確定為false且不可更改. 如下面的程序中, if括號(hào)中的值在運(yùn)行時(shí)可以隨時(shí)改變, flag的值可以為true也可以為false, 它是不確定的. 就算初始值為false, 但是因?yàn)樗强勺兊? 因此編譯器不能優(yōu)化掉if語句塊.

public class Demo {
  boolean flag = false;

  public void d(String tag, String msg) {
      if(flag) {
          Log.d(tag, msg);
      }
  }
}

再看下面一個(gè)程序:

public final class DevUtil {
  //Bad implementation, dependence app module (MyApplication)
  private static final boolean flag = debuggable(MyApplication.getInstance());

  private DevUtil() {
      //no instance
  }

  //什么事情都不做, 只是為了初始化flag的值
  public static void init() {
  }

  public static void d(String tag, String msg) {
      if(flag) {
          Log.d(tag, msg);
      }
  }

  private static boolean debuggable(Context appContext) {
      final int DEBUG_SIGNATURE_HASH = -545093291;
      final int ONLINE_SIGNATURE_HASH = -283702024;

      // 判斷是否為調(diào)試狀態(tài)
      // http://stackoverflow.com/questions/3029819/android-automatically-choose-debug-release-maps-api-key
      PackageManager manager = appContext.getPackageManager();
      try {
          PackageInfo info = manager.getPackageInfo(appContext.getPackageName(), PackageManager.GET_SIGNATURES);
          for (Signature sig : info.signatures) {
              int sigHashCode = sig.hashCode();
              switch(sigHashCode) {
                  case DEBUG_SIGNATURE_HASH:
                      return true;
                  case ONLINE_SIGNATURE_HASH:
                      return false;
              }
          }
      } catch (PackageManager.NameNotFoundException e) {
          e.printStackTrace();
      }

      return false;
  }
}

雖然DevUtil類中l(wèi)og輸出的開關(guān)邊是常量(不可變的), 但是由于賦值給flag的是一個(gè)方法調(diào)用, 編譯時(shí)并不能確定其值為false, 因此if語句塊也是不能優(yōu)化掉的.

要優(yōu)化掉一個(gè)if語句塊, 必須同時(shí)滿足兩個(gè)條件:

  1. if括號(hào)中的值必須編譯時(shí)就能確認(rèn)為false
  2. 且if語句中的值是不可變的

要滿足上面的條件, if語句只中的值只能是字面常量false, 或者是一個(gè)常量 --- 賦值給這個(gè)常量的必須是一個(gè)字面常量或者一個(gè)字面常量的引用(指向一個(gè)字面常量的常量, 如final boolean flag = false; flag就是字面常量的引用)

  1. Android Studio編譯module之后會(huì)在<module>/build/generated/source/buildConfig/{flavor}/{buildType} 目錄下生成一個(gè)BuildConfig.java文件. 如果沒有定義productFlavors生成的BuildConfig位于<module>/build/generated/source/buildConfig/{buildType}目錄下. 如圖:
BuildConfig.java文件所在目錄.png

BuildConfig.java文件(buildType為release)的內(nèi)容如下:

/**
 * Automatically generated file. DO NOT MODIFY
 */
package com.stone.mvp_demo;

public final class BuildConfig {
  public static final boolean DEBUG = false;
  public static final String APPLICATION_ID = "com.stone.mvp_demo";
  public static final String BUILD_TYPE = "release";
  public static final String FLAVOR = "flavor1";
  public static final int VERSION_CODE = 1;
  public static final String VERSION_NAME = "1.0";
  // Fields from build type: release
  public static final boolean LOG_DEBUG = false;
}

BuildConfig的DEBUG字段就是一個(gè)字面常量的引用. 這是滿足編譯器優(yōu)化條件的.

BuildConfig類的DEBUG字段會(huì)因buildType的不同而不同, 具體為debug版DEBUG的值為true, release版DEBUG的值為false. 因此可以利用BuildConfig類的DEBUG字段來作為log的開關(guān). 當(dāng)我們打release包時(shí), buildType肯定是release, 因此DEBUG的值是false. 此時(shí)log輸出代碼塊變成了Death Blocks, 形如:

if(false) {
    Log.e(TAG, message)
}

編譯器會(huì)刪除此段. 這樣log輸出被優(yōu)化成一個(gè)空方法, 減少了方法內(nèi)部語句的入棧出棧.

二. 具體代碼
推薦把DevUtil放在公共庫中(這里有個(gè)巨坑, 請參考《Android中使用BuildConfig.DEBUG必須知道的內(nèi)幕》), 方便代碼重用.

public final class DevUtil {
    private static final boolean isDebug = BuildConfig.DEBUG;

    private DevUtil() {
        //no instance
    }

    public static void d(String tag, String msg) {
        if (isDebug) {
            Log.d(tag, msg + " - tag:" + tag);
        }
    }

    public static void v(String tag, String msg) {
        if (isDebug) {
            Log.v(tag, msg + " - tag:" + tag);
        }
    }

    public static void w(String tag, String msg, Throwable e) {
        if (isDebug) {
            Log.w(tag, msg + " - tag:" + tag, e);
        }
    }

    // 更多l(xiāng)og輸出方法 ....

    public static boolean isDebug() {
        return isDebug;
    }
}

反編譯后的DevUtil類變成了下面的樣子:


編譯器優(yōu)化后的代碼.png

三. proguard優(yōu)化配置
推薦release版的混淆開啟優(yōu)化選項(xiàng), 要使用開啟優(yōu)化選項(xiàng)的規(guī)則文件, 默認(rèn)配置必須使用<ANDROID_SDK>/tools/proguard/proguard-android-optimize.txt, 而不是<ANDROID_SDK>/tools/proguard/proguard-android.txt文件. proguard-android.txt文件中已經(jīng)把優(yōu)化選項(xiàng)給關(guān)閉了, 并且告訴你僅僅包含優(yōu)化配置選項(xiàng)是沒有用的, 因?yàn)檫@個(gè)文件已經(jīng)將優(yōu)化的開關(guān)給關(guān)了, 你需要使用proguard-android-optimize.txt這個(gè)文件來開啟混淆的優(yōu)化功能, proguard-android.txt文件的部分截圖如下:

proguard-android.txt文件已經(jīng)關(guān)閉了優(yōu)化選項(xiàng)并且此文件中也告訴你怎么啟用優(yōu)化選項(xiàng).png

主項(xiàng)目module下的構(gòu)建腳本(build.gradle)的buildType配置(release版配置)如下:

release版的混淆配.png

proguard優(yōu)化開啟后, 沒有用到的類、方法、變量、無用語句塊 ... 等會(huì)被移除掉, 這樣程序會(huì)有一定的性能提升.

下面推薦幾款反編譯工具:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容