Android模組化分層化之: maven 庫中上傳的 aar 包 獲取 BuildConfig 相關常量值
最近在做專案分層化的一些工作,具體思路是將原有專案一些基礎服務模組和裝置服務模組抽離出來,上傳到本地的 Maven 伺服器,然後再通過在主專案中的 build.gradle 檔案中通過
compile
語句進行導包處理。但通過這種方法編譯成的 aar 包只能是 release 版本,無法使用到BuildConfig
動態設定的一些常量,比如常見的「Debug 日誌開關」,我們一般都是在日誌類通過BuildConfig.DEBUG
來獲取包狀態,從而設定是否要打印出日誌。
題外話
這裡特別提一下為什麼要用 Maven 這種方式,實際上單純的抽離模組可以通過子模組的方式,Git 也有 submodule 這樣的子模組管理方法
通過把子模組上傳至 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 中的域都可以用這種方式獲取到