如何在构建Android应用程序的发布版本之前删除所有调试日志调用?

根据谷歌,我必须“禁用源代码中对Log方法的任何调用”之前发布我的Android应用到谷歌播放。出版物清单的第3节摘录:

在构建应用程序发布之前,请确保禁用了日志记录并禁用了调试选项。您可以通过删除源文件中对Log方法的调用来禁用日志记录。

我的开源项目很大,每次发布都要手动完成,这很痛苦。此外,删除Log行可能很棘手,例如:

if(condition)
Log.d(LOG_TAG, "Something");
data.load();
data.show();

如果注释Log行,则该条件将应用于下一行,并且可能不会调用load()。这样的情况是否罕见到我可以决定它不应该存在?

那么,是否有更好的源代码级方法来做到这一点呢?或者是一些聪明的ProGuard语法,有效但安全地删除所有Log行?

205235 次浏览

克里斯托弗的Proguard解决方案是最好的,但如果因为任何原因你不喜欢Proguard,这里有一个非常低技术含量的解决方案:

评论日志:

find . -name "*\.java" | xargs grep -l 'Log\.' | xargs sed -i 's/Log\./;\/\/ Log\./g'

取消日志:

find . -name "*\.java" | xargs grep -l 'Log\.' | xargs sed -i 's/;\/\/ Log\./Log\./g'

一个限制是您的日志记录指令不能跨越多行。

(在项目根的UNIX shell中执行这些行。如果使用Windows,请使用UNIX层或使用等效的Windows命令)

我建议在某个地方使用一个静态布尔值来指示是否记录日志:

class MyDebug {
static final boolean LOG = true;
}

然后无论你想在哪里登录你的代码,只需要这样做:

if (MyDebug.LOG) {
if (condition) Log.i(...);
}

现在,当你将MyDebug.LOG设置为false时,编译器将剔除这些检查中的所有代码(因为它是一个静态final,它在编译时知道代码没有被使用)。

对于较大的项目,您可能希望开始在单个文件中使用布尔值,以便能够根据需要轻松启用或禁用日志记录。例如,这些是我们在窗口管理器中拥有的各种日志常量:

static final String TAG = "WindowManager";
static final boolean DEBUG = false;
static final boolean DEBUG_FOCUS = false;
static final boolean DEBUG_ANIM = false;
static final boolean DEBUG_LAYOUT = false;
static final boolean DEBUG_RESIZE = false;
static final boolean DEBUG_LAYERS = false;
static final boolean DEBUG_INPUT = false;
static final boolean DEBUG_INPUT_METHOD = false;
static final boolean DEBUG_VISIBILITY = false;
static final boolean DEBUG_WINDOW_MOVEMENT = false;
static final boolean DEBUG_ORIENTATION = false;
static final boolean DEBUG_APP_TRANSITIONS = false;
static final boolean DEBUG_STARTING_WINDOW = false;
static final boolean DEBUG_REORDER = false;
static final boolean DEBUG_WALLPAPER = false;
static final boolean SHOW_TRANSACTIONS = false;
static final boolean HIDE_STACK_CRAWLS = true;
static final boolean MEASURE_LATENCY = false;

对应代码如下:

    if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT) Log.v(
TAG, "Adding window " + window + " at "
+ (i+1) + " of " + mWindows.size() + " (after " + pos + ")");

我发现一个更简单的解决方案是,在调用Ant release目标时,忘记所有的if检查,只使用混淆器去除任何Log.d()Log.v()方法调用。

通过这种方式,我们总是可以输出常规构建的调试信息,而不必对发布构建进行任何代码更改。ProGuard还可以对字节码进行多次传递,以删除其他不需要的语句和空块,并可以在适当的地方自动内联短方法。

例如,这是一个非常基本的Android ProGuard配置:

-dontskipnonpubliclibraryclasses
-dontobfuscate
-forceprocessing
-optimizationpasses 5


-keep class * extends android.app.Activity
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
}

因此,您可以将其保存到一个文件中,然后从Ant调用ProGuard,传入刚刚编译的JAR和正在使用的Android平台JAR。

也可参见ProGuard手册中的的例子


现在我使用木材进行Android日志记录。

它不仅比默认的Log实现更好一点——日志标记是自动设置的,很容易记录格式化的字符串和异常——而且你还可以在运行时指定不同的日志行为。

在这个例子中,日志语句只会在我的应用程序的调试版本中写入logcat:

在我的Application onCreate()方法中设置了Timber:

if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
}

然后在我的代码中的其他地方,我可以很容易地记录:

Timber.d("Downloading URL: %s", url);
try {
// ...
} catch (IOException ioe) {
Timber.e(ioe, "Bad things happened!");
}

木材样本应用程序中有一个更高级的例子,在开发过程中,所有日志语句都被发送到logcat,在生产中,没有调试语句被记录,但是错误会被无声地报告给Crashlytics。

这些都是很好的答案,但是当我完成我的开发时,我既不想在所有的Log调用周围使用if语句,也不想使用外部工具。

所以我使用的解决方案是用我自己的Log类替换android.util.Log类:

public class Log {
static final boolean LOG = BuildConfig.DEBUG;


public static void i(String tag, String string) {
if (LOG) android.util.Log.i(tag, string);
}
public static void e(String tag, String string) {
if (LOG) android.util.Log.e(tag, string);
}
public static void d(String tag, String string) {
if (LOG) android.util.Log.d(tag, string);
}
public static void v(String tag, String string) {
if (LOG) android.util.Log.v(tag, string);
}
public static void w(String tag, String string) {
if (LOG) android.util.Log.w(tag, string);
}
}

在所有的源文件中,我唯一要做的就是用我自己的类替换android.util.Log的导入。

我会考虑使用roboguice的日志记录工具而不是内置的android.util.Log

他们的功能会自动禁用发布版本的调试和详细日志。 此外,您还可以免费获得一些漂亮的功能(例如,可定制的日志记录行为,每个日志的额外数据等等)

使用proguard可能是相当麻烦的,我不会通过配置的麻烦,并使它工作与你的应用程序,除非你有一个很好的理由(禁用日志不是一个很好的理由)

我对上述解决方案进行了改进,提供了对不同日志级别的支持,并根据代码是在活动设备上运行还是在模拟器上运行而自动更改日志级别。

public class Log {


final static int WARN = 1;
final static int INFO = 2;
final static int DEBUG = 3;
final static int VERB = 4;


static int LOG_LEVEL;


static
{
if ("google_sdk".equals(Build.PRODUCT) || "sdk".equals(Build.PRODUCT)) {
LOG_LEVEL = VERB;
} else {
LOG_LEVEL = INFO;
}


}




/**
*Error
*/
public static void e(String tag, String string)
{
android.util.Log.e(tag, string);
}


/**
* Warn
*/
public static void w(String tag, String string)
{
android.util.Log.w(tag, string);
}


/**
* Info
*/
public static void i(String tag, String string)
{
if(LOG_LEVEL >= INFO)
{
android.util.Log.i(tag, string);
}
}


/**
* Debug
*/
public static void d(String tag, String string)
{
if(LOG_LEVEL >= DEBUG)
{
android.util.Log.d(tag, string);
}
}


/**
* Verbose
*/
public static void v(String tag, String string)
{
if(LOG_LEVEL >= VERB)
{
android.util.Log.v(tag, string);
}
}




}

ProGuard将在你的发布版本中为你做这件事,现在来自android.com的好消息是:

http://developer.android.com/tools/help/proguard.html

ProGuard工具通过删除未使用的代码和用语义模糊的名称重命名类、字段和方法来缩小、优化和混淆代码。结果是一个更小的.apk文件,更难以进行反向工程。由于ProGuard使应用程序更难进行反向工程,因此当应用程序使用对安全性敏感的特性时,例如在应用程序授权时,使用它是很重要的。

ProGuard集成到Android构建系统中,所以你不需要手动调用它。ProGuard仅在以发布模式构建应用程序时运行,因此在以调试模式构建应用程序时不必处理混淆的代码。运行ProGuard是完全可选的,但强烈推荐。

本文档介绍如何启用和配置ProGuard,以及如何使用retrace工具解码混淆的堆栈跟踪

我在谷歌IO示例应用程序中使用了LogUtils类。我将其修改为使用特定于应用程序的DEBUG常量而不是BuildConfig。因为BuildConfig。调试不可靠. DEBUG。然后在我的课程中,我有以下内容。

import static my.app.util.LogUtils.makeLogTag;
import static my.app.util.LogUtils.LOGV;


public class MyActivity extends FragmentActivity {
private static final String TAG = makeLogTag(MyActivity.class);


protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);


LOGV(TAG, "my message");
}
}

Per android.util.Log提供了一种启用/禁用日志的方法:

public static native boolean isLoggable(String tag, int level);

默认情况下,isLoggable(…)方法返回false,只有在你在设备中设置prop后,像这样:

adb shell setprop log.tag.MyAppTag DEBUG

这意味着DEBUG级别以上的任何日志都可以打印出来。参考android文档:

检查指定标记的日志在指定级别是否可记录。任何标记的默认级别都已设置 信息。这意味着任何高于并包括INFO的级别都将被删除 记录。在对日志方法进行任何调用之前,您应该进行检查 查看您的标记是否应该被记录。您可以更改默认级别 通过设置一个系统属性:'setprop log.tag。' 其中级别是VERBOSE,调试,信息,警告,错误,断言,或 抑制。SUPPRESS将关闭标签的所有日志记录。你可以 还可以创建一个本地。支持文件,其中包括以下内容: “log.tag。='并将其放在/data/local.prop.

所以我们可以使用自定义log util:

public final class Dlog
{
public static void v(String tag, String msg)
{
if (Log.isLoggable(tag, Log.VERBOSE))
Log.v(tag, msg);
}


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


public static void i(String tag, String msg)
{
if (Log.isLoggable(tag, Log.INFO))
Log.i(tag, msg);
}


public static void w(String tag, String msg)
{
if (Log.isLoggable(tag, Log.WARN))
Log.w(tag, msg);
}


public static void e(String tag, String msg)
{
if (Log.isLoggable(tag, Log.ERROR))
Log.e(tag, msg);
}
}

我强烈建议使用Jake Wharton的Timber

https://github.com/JakeWharton/timber < a href = " https://github.com/JakeWharton/timber " > < / >

它解决了您的问题,启用/禁用加添加标签类自动魔术

只是

public class MyApp extends Application {


public void onCreate() {
super.onCreate();
//Timber
if (BuildConfig.DEBUG) {
Timber.plant(new DebugTree());
}
...

日志将仅在调试版本中使用,然后使用

Timber.d("lol");

Timber.i("lol says %s","lol");

打印

“Your class / msg”,而不指定标签

我有一个很简单的解决办法。我使用IntelliJ进行开发,因此细节有所不同,但这个想法应该适用于所有IDE。

我选择我的源树的根,右键单击并选择“替换”。然后我选择将所有“Log.”替换为“//Log.”。这将删除所有日志语句。为了稍后将它们放回去,我重复相同的替换,但这次将所有“//Log.”替换为“Log.”。

对我来说太好了。只需记住将replace设置为区分大小写,以避免出现“Dialog.”之类的意外。为了获得额外的保证,您还可以使用“Log.”作为要搜索的字符串执行第一步。

辉煌。

我喜欢使用Log。d(TAG,一些字符串,通常是字符串。格式())。

TAG总是类名

改变日志。d(TAG,——> Logd(在类的文本中

private void Logd(String str){
if (MainClass.debug) Log.d(className, str);
}

通过这种方式,当您准备发布版本时,将MainClass.debug设置为false!

最简单的方法;

使用DebugLog

当应用程序发布时,DebugLog会禁用所有日志。

https://github.com/MustafaFerhan/DebugLog

我想添加一些关于使用Proguard与Android Studio和gradle的精度,因为我有很多问题,从最终的二进制文件中删除日志行。

为了使Proguard中的assumenosideeffects工作,有一个先决条件。

在你的gradle文件中,你必须指定proguard-android-optimize.txt的用法作为默认文件。

buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'


// With the file below, it does not work!
//proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

实际上,在默认的proguard-android.txt文件中,通过以下两个标志禁用了优化:

-dontoptimize
-dontpreverify

proguard-android-optimize.txt文件没有添加这些行,所以现在assumenosideeffects可以工作了。

然后,就我个人而言,我使用SLF4J,尤其是当我开发一些分发给其他人的库时。其优点是默认情况下没有输出。如果积分器想要一些日志输出,他可以使用Android的Logback并激活日志,这样日志就可以重定向到文件或LogCat。

如果我真的需要从最终库中剥离日志,我然后在我的Proguard文件中添加(当然是在启用proguard-android-optimize.txt文件之后):

-assumenosideeffects class * implements org.slf4j.Logger {
public *** trace(...);
public *** debug(...);
public *** info(...);
public *** warn(...);
public *** error(...);
}

enter image description here

这就是我过去在我的android项目上所做的。

在Android Studio我们可以做类似的操作,Ctrl+Shift+F从整个项目(命令+Shift+F在MacOs)和Ctrl+Shift+R替换((命令+Shift+R在MacOs))

我发布的这个解决方案特别适用于Android Studio用户。我最近也发现了Timber,并通过以下方法成功地将其导入到我的应用程序中:

将最新版本的库放入build.gradle:

compile 'com.jakewharton.timber:timber:4.1.1'

然后在Android工作室,点击编辑->查找->替换路径…

Log.e(TAG,或其他你定义的日志消息中输入"Text to find"文本框。然后用Timber.e(替换它

enter image description here

单击“查找”,然后替换全部。

Android Studios现在将遍历项目中的所有文件,并将所有日志替换为木材。

我用这个方法遇到的唯一问题是,gradle之后会出现一百万个错误消息,因为它无法在每个java文件的导入中找到“Timber”。只需点击错误和Android工作室将自动导入“木材”到你的java。一旦你为所有的错误文件做了这些,gradle就会再次编译。

你还需要把这段代码放在你的Application类的onCreate方法中:

    if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
}

这将导致只有当你处于开发模式而不是生产模式时才会出现应用日志记录。你也可以用BuildConfig.RELEASE来登录发布模式。

正如zserge的评论所示,

Timber非常好,但如果你已经有一个现有的项目-你可以尝试github.com/zserge/log。它是android.util.Log的替代品,具有Timber的大部分功能,甚至更多。

他的日志库提供了简单的启用/禁用日志打印开关,如下所示。

此外,它只有需要更改import行,而没有什么需要更改Log.d(...);语句。

if (!BuildConfig.DEBUG)
Log.usePrinter(Log.ANDROID, false); // from now on Log.d etc do nothing and is likely to be optimized with JIT

linux下可以使用bash和sed删除日志:

find . -name "*\.java" | xargs sed -ri ':a; s%Log\.[ivdwe].*\);%;%; ta; /Log\.[ivdwe]/ !b; N; ba'

适用于多行日志。在这个解决方案中,您可以确定,日志不会出现在生产代码中。

将以下内容添加到proguard-rules.txt文件中

-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** w(...);
public static *** v(...);
public static *** i(...);
}

如果你可以运行一个全局替换(一次),然后保留一些编码惯例,你可以遵循Android 框架中经常使用的模式。

而不是写作

Log.d(TAG, string1 + string2 + arg3.toString());

把它当作

if (BuildConfig.DEBUG) Log.d(TAG, string1 + String.format("%.2f", arg2) + arg3.toString());

现在proguard可以从优化版的DEX中删除StringBuilder和它使用的所有字符串和方法。使用proguard-android-optimize.txt,你不需要担心proguard-rules.pro中的android.util.Log:

android {
…
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}

对于Android Studio gradle插件,BuildConfig.DEBUG是相当可靠的,所以你不需要额外的常量来控制剥离。

我知道这是一个老问题,但是为什么你不把所有的日志调用替换成类似于 布尔logCallWasHere = true;//——剩下的日志在这里

这就是为什么你会知道什么时候你想把它们放回去,他们不会影响你的if语句调用:)

为什么不直接做

if(BuildConfig.DEBUG)
Log.d("tag","msg");

? 不需要额外的库,没有保护规则,往往会搞砸项目,java编译器只会为这个调用留下字节码,当你发布构建。

如果您不想弄乱额外的库或手动编辑代码,以下是我的解决方案。我创建了这个Jupyter笔记本来检查所有的java文件,并注释掉所有的日志消息。虽然不完美,但它帮我完成了任务。

我的方法:

1)启用列选择模式(alt+shift+insert)

2)选择一个日志。d(标签,“文本”);“日志”部分。

3)然后按shift + CTRL + Alt + j

4)点击左箭头

5)做shift+end

6)点击删除。

这将一次性删除java文件中的所有LOG调用。

你可以试试这个简单的常规方法:

Ctrl + 转变 + R

取代

Log.e(

// Log.e(

使用kotlin很简单,只需声明几个顶级函数

val isDebug: Boolean
get() = BuildConfig.DEBUG


fun logE(tag: String, message: String) {
if (isDebug) Log.e(tag, message)
}


fun logD(tag: String, message: String) {
if (isDebug) Log.d(tag, message)
}

这是我如何解决它在我的Kotlin项目前进入生产:

buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}


-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int d(...);
public static int w(...);
public static int v(...);
public static int i(...);
public static int e(...);
}

如果您希望使用编程方法而不是使用ProGuard,那么通过创建自己的类,其中包含两个实例(一个用于调试,一个用于发布),您可以选择在任何一种情况下登录什么。

所以,如果你不想在发布时记录任何东西,只需实现一个什么都不做的Logger,如下例所示:

import android.util.Log


sealed class Logger(defaultTag: String? = null) {
protected val defaultTag: String = defaultTag ?: "[APP-DEBUG]"


abstract fun log(string: String, tag: String = defaultTag)


object LoggerDebug : Logger() {
override fun log(string: String, tag: String) {
Log.d(tag, string)
}
}


object LoggerRelease : Logger() {
override fun log(string: String, tag: String) {}
}


companion object {
private val isDebugConfig = BuildConfig.DEBUG


val instance: Logger by lazy {
if(isDebugConfig)
LoggerDebug
else
LoggerRelease
}


}
}

然后使用记录器类:

class MainActivity : AppCompatActivity() {


private val logger = Logger.instance


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
logger.log("Activity launched...")
...
myView.setOnClickListener {
...


logger.log("My View clicked!", "View-click")
}
}

== update ==

如果我们想避免字符串连接以获得更好的性能,我们可以添加一个带有lambda的内联函数,该函数只在调试配置中调用:

// Add this function to the Logger class.
inline fun commit(block: Logger.() -> Unit) {
if(this is LoggerDebug)
block.invoke(this)
}

然后:

 logger.commit {
log("Logging without $myVar waste of resources"+ "My fancy concat")
}

因为我们使用的是内联函数,所以没有额外的对象分配,也没有额外的虚方法调用。

  1. 执行Application->app->proguard-rules.pro enter image description here < / p >

  2. 在proguard-rules.pro内输入以下代码

    -assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
    public static *** w(...);
    public static *** i(...);
    public static *** e(...);
    }
    

如果您想要log中的调试类型错误,可以删除特定的调试类

  1. build.gradle(app) ->android中做这个事情

    buildTypes {
    debug{
    debuggable false
    minifyEnabled true
    shrinkResources true
    proguardFiles getDefaultProguardFile('proguard-android-
    optimize.txt'), 'proguard-rules.pro'
    }
    release {
    debuggable false
    minifyEnabled true
    shrinkResources true
    proguardFiles getDefaultProguardFile('proguard-android-
    optimize.txt'), 'proguard-rules.pro'
    }
    }
    
    
    lintOptions {
    checkReleaseBuilds false
    // Or, if you prefer, you can continue to check for errors in release builds,
    // but continue the build even when errors are found:
    abortOnError false
    }
    

我在我的项目中使用了以下方法

创建自定义记录器类:

public class LoggerData
{
   

public static void showLog(String type, Object object) {
try {
Log.d("loggerData:" + type + "-", "showLog: " + new Gson().toJson(object));
} catch (Exception e) {
Log.d("TAG", "showLog: " + e.getLocalizedMessage());
Log.d("loggerData:" + type + "-", "showLog: " + object);
}


}


public static void showLog(Object object) {
        

try {
Log.d("loggerData:" + "-", "showLog: +" + new Gson().toJson(object));
} catch (Exception e) {
Log.d("TAG", "showLog: " + e.getLocalizedMessage());
Log.d("loggerData:" + "-", "showLog: " + object);
}
        

}
}

然后每当需要登录代码时,就像这样使用

  LoggerData.showLog("Refreshed token: ", token);

在构建发布APK之前,在LoggerData类中只禁用一个地方的日志

例子

public class LoggerData {
    



public static void showLog(String type, Object object) {
try {
//Log.d("loggerData:" + type + "-", "showLog: " + new Gson().toJson(object));
} catch (Exception e) {
//Log.d("TAG", "showLog: " + e.getLocalizedMessage());
//Log.d("loggerData:" + type + "-", "showLog: " + object);
}


}


public static void showLog(Object object) {
       

try {
//  Log.d("loggerData:" + "-", "showLog: +" + new Gson().toJson(object));
} catch (Exception e) {
//Log.d("TAG", "showLog: " + e.getLocalizedMessage());
//Log.d("loggerData:" + "-", "showLog: " + object);
}
}
}

希望它也能帮助到你。