debug log 工具类增强
BuildConfig.DEBUG v.s. android:debuggable
BuildConfig是由IDE自动生成的,其值取决于 build variants。android:debuggable 也是IDE自动生成的,通常不需要人工去设置。不同之处在于,android:debuggable是在编译过程中在AndroidManifest.xml中插入的,而BuildConfig.java是编译过程中generate的。
<application |
使用方法都很简单:
if(BuildConfig.DEBUG){ |
|
然而,当这两个都被设置时,哪个会生效呢?见下表:
application element | Released APK | In IDE |
---|---|---|
No android:debuggable | FLAG_DEBUGGABLE bit is 0 and BuildConfig.DEBUG is false | FLAG_DEBUGGABLE bit is 1 and BuildConfig.DEBUG is true |
android:debuggable=”true” | FLAG_DEBUGGABLE bit is 1 and BuildConfig.DEBUG is true | FLAG_DEBUGGABLE bit is 1 and BuildConfig.DEBUG is true |
android:debuggable=”false” | FLAG_DEBUGGABLE bit is 0 and BuildConfig.DEBUG is false | FLAG_DEBUGGABLE bit is 0 and BuildConfig.DEBUG is true |
一句话总结上表:只有当android:debuggable=”false”
时,BuildConfig.DEBUG
才会按照预期的去指示debug,否则一切以android:debuggable
为准。
不足:
在library project中,当发布aar包到maven库时,BuildConfig.DEBUG总是被设置为release,而无视了宿主 App project的IDE编译坏境
改进:
public class DebugUtils { |
其中ApplicationContextGetter
如下:(或者传入Context)
import android.app.Application; | |
import android.os.Handler; | |
import android.os.Looper; | |
import java.lang.ref.WeakReference; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.util.concurrent.Semaphore; | |
/** | |
* 反射获取Application变量 | |
*/ | |
public class ApplicationContextProvider { | |
private static volatile ApplicationContextProvider sInstance; | |
private WeakReference<Application> mApplication; | |
private ApplicationContextProvider() { | |
try { | |
if (Thread.currentThread() == Looper.getMainLooper().getThread()) { | |
initApplicationContext(); | |
} else { | |
Class<?> clazz = Class.forName("android.app.ActivityThread"); | |
Field mainHandlerField = clazz.getDeclaredField("sMainThreadHandler"); | |
mainHandlerField.setAccessible(true); | |
Handler mainHandler = (Handler) mainHandlerField.get(null); | |
final Semaphore semaphore = new Semaphore(0); | |
mainHandler.post(new Runnable() { | |
@Override | |
public void run() { | |
try { | |
initApplicationContext(); | |
} catch (ClassNotFoundException e) { | |
e.printStackTrace(); | |
} | |
semaphore.release(); | |
} | |
}); | |
semaphore.acquire(); | |
} | |
} catch (ClassNotFoundException e) { | |
e.printStackTrace(); | |
} catch (InterruptedException e) { | |
e.printStackTrace(); | |
} catch (IllegalAccessException e) { | |
e.printStackTrace(); | |
} catch (NoSuchFieldException e) { | |
e.printStackTrace(); | |
} | |
} | |
public static synchronized ApplicationContextProvider instance() { | |
if (sInstance == null) { | |
sInstance = new ApplicationContextProvider(); | |
} | |
return sInstance; | |
} | |
private void initApplicationContext() throws ClassNotFoundException { | |
Application application = null; | |
try { | |
Class<?> clazz = Class.forName("android.app.ActivityThread"); | |
Method e = clazz.getDeclaredMethod("currentActivityThread"); | |
Object activityThreadInstance = e.invoke(null); | |
Field applicationField = clazz.getDeclaredField("mInitialApplication"); | |
application = (Application) applicationField.get(activityThreadInstance); | |
} catch (NoSuchMethodException e1) { | |
e1.printStackTrace(); | |
} catch (IllegalAccessException e1) { | |
e1.printStackTrace(); | |
} catch (InvocationTargetException e1) { | |
e1.printStackTrace(); | |
} catch (NoSuchFieldException e1) { | |
e1.printStackTrace(); | |
} | |
mApplication = new WeakReference<>(application); | |
} | |
public Application get() { | |
return mApplication == null ? null : mApplication.get(); | |
} | |
} | |
可能的缺陷:
原来的字段是个static final 常亮,在编译时可以被inline优化,从而屏蔽了debug 的相关代码语句。现在由于需要在运行期动态的读取BuildConfig.DEBUG 的值,编译器就不会优化了。
改进方案 2 :
build.gradle
自己玩自己的,不用IDE生成的那一套
buildTypes { |
编译后会生成 如下字段BuildConfig.java
public final class BuildConfig { |
使用方法如下
if (BuildConfig.DEBUG_MODE) { |
当然不一定非得是 boolean ,如果开发环境比较复杂,还可以用 String来判断。比如 “PreRelease” ,“Daily” , “Release”,这种方式比较灵活,但是 aar 在上传时会自动变为 release 的问题还是没法解决。
Log 的小改造(对性能有影响)
public class LogUtils { |
new Throwable().getStackTrace()[2])
这行语句中取的是第二层函数调用栈,这个根据实际情况可以自行调整
优化:
proguard-project.txt
# Remove debug logs |
上述配置会自动删掉log的相关代码
避免了上面的log代码在 release 中带来的额外开销。
Author: deskid
Link: https://deskid.github.io/2016/10/08/debug_工具改造/
License: 知识共享署名-非商业性使用 4.0 国际许可协议