1. 程式人生 > >Android模組化分層化之: maven 庫中上傳的 aar 包 獲取 BuildConfig 相關常量值

Android模組化分層化之: maven 庫中上傳的 aar 包 獲取 BuildConfig 相關常量值

最近在做專案分層化的一些工作,具體思路是將原有專案一些基礎服務模組和裝置服務模組抽離出來,上傳到本地的 Maven 伺服器,然後再通過在主專案中的 build.gradle 檔案中通過 compile 語句進行導包處理。但通過這種方法編譯成的 aar 包只能是 release 版本,無法使用到 BuildConfig 動態設定的一些常量,比如常見的「Debug 日誌開關」,我們一般都是在日誌類通過 BuildConfig.DEBUG 來獲取包狀態,從而設定是否要打印出日誌。

題外話

這裡特別提一下為什麼要用 Maven 這種方式,實際上單純的抽離模組可以通過子模組的方式,Git 也有 submodule 這樣的子模組管理方法

,但用 Git 管理子模組會有很多問題,比如專案引用子模組的指標問題,在一個並行開發團隊中,子模組指標往往會導致很多出人意料的問題,以後我會專門的在這方面寫一篇部落格做一個解析。

通過把子模組上傳至 Maven 庫有很多好處,比如匯入起來很方便,只需要在配置檔案中 compile 即可,而且開發該模組的時候只需要單獨開啟該子模組的程式碼,不像通過 Git 管理的依賴子模組,即使你只是為了修改子模組程式碼,也需要開啟原來的完整專案,然後在專案下的子 module 進行開發,最後子模組程式碼和主專案程式碼得同時提交。

compile 'com.github.bumptech.glide:okhttp-integration:1.4.0'


另外它還有一些安全性的好處。通過這種方式,實際上也是一種對程式碼的保護,客戶端開發人員不會輕易的修改這部分程式碼。

具體問題

我把專案中的一些基礎服務專門抽離了出來,其中就包括了一些跟 Log 日誌列印相關的程式碼,原來專案中在build.gradle 中的 buildTypes 中設定了 DEVELOP_MODE 常量,來控制在不同渠道打包下的 Log 開關。

buildTypes {
        debug {
            // log日誌開關
            buildConfigField("boolean", "DEVELOP_MODE", "true"
)
} release{ buildConfigField("boolean", "DEVELOP_MODE", "false") }

然後,在具體的日誌類中呼叫,動態獲取 DEVELOP_MODE

public class DEBUG {
    public final static boolean DEVELOP_MODE = BuildConfig.DEVELOP_MODE;
}

現在,當我把這部分程式碼單獨抽離出來到名叫「middleWare」的子 library 後,即使我仍然可以在該 library 中的 build.gradle 檔案設定上述的 buildTypes 程式碼,但通過 maven 編譯出來的 aar 包只能是 release 版本,自然也無法獲取到那些動態配置的常量值。

所以,只能通過獲取引入專案的 BuildConfig 類來獲取了,所以我們自然的想到了用反射。下面是具體程式碼,註釋也比較詳細:

public class BuildConfigProvider {
    private static Context sContext;

    private static String packageName;

    /**
     * 通過反射獲取ApplicationContext
     *
     * @return
     */
    private static Context getContext() {
        if (sContext == null) {
            try {
                final Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
                final Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread");
                final Object activityThread = currentActivityThread.invoke(null);
                final Method getApplication = activityThreadClass.getDeclaredMethod("getApplication");
                final Application application = (Application) getApplication.invoke(activityThread);
                sContext = application.getApplicationContext();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return sContext;
    }

    /**
     * 通過反射獲取包名
     *
     * @return
     */
    private static String getPackageName() {
        if (packageName == null) {
            try {
                final Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
                final Method currentPackageName = activityThreadClass.getDeclaredMethod("currentPackageName");
                packageName = (String) currentPackageName.invoke(null);
            } catch (Exception e) {
                packageName = getContext().getPackageName();
            }
        }

        return packageName;
    }
    /**
     * 獲取具體的域值
     *
     * @return
     */
    public static Object getBuildConfigValue(String fieldName) {
        try {
            Class<?> clazz = Class.forName(getPackageName() + ".BuildConfig");
            Field field = clazz.getField(fieldName);
            return field.get(null);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        return "";
    }
}

這裡實際上可以用一個方法獲取 context 然後就直接可以拿到 packageName,為了看的清晰,所以分成兩個方法了。

然後把之前那個 DEBUG 類適當改動下,將 DEBUG 類放在該子模組程式碼中:

    public final static boolean DEVELOP_MODE = isDevelopMode();
    static boolean isDevelopMode(){
        return (boolean)BuildConfigProvider.getBuildConfigValue("DEVELOP_MODE");
    }
}

這樣只需要在引入專案中的 buildTypes 中設定 DEVELOP_MODE 的各種配置就能在專案中動態的呼叫日誌開關了。

比如我們專案中的日誌列印類 Glog.class,每個日誌等級的列印前都會有 DEBUG.DEVELOP_MODE 的判斷:

public class GLog {

    public static void d(String message) {
        if(TextUtils.isEmpty(message)){
            return;
        }
        //判斷日誌開關
        if (DEBUG.DEVELOP_MODE) {
            final StackTraceElement[] stack = new Throwable().getStackTrace();
            final int i = 1;
            final StackTraceElement ste = stack[i];
            Log.println(Log.DEBUG, LOG_TAG, String.format("[%s][%s][%s]%s", ste.getFileName(), ste.getMethodName(), ste.getLineNumber(), message));
        }
    }
}

這裡只是通過列印日誌的例子來展示如何在 aar 中呼叫主專案的 BuildConfig 類獲取編譯型別,其他各種 BuildConfig 中的域都可以用這種方式獲取到