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

Posted

技术标签:

【中文标题】如何在构建 Android 应用程序的发布版本之前删除所有调试日志记录调用?【英文标题】:How to remove all debug logging calls before building the release version of an Android app? 【发布时间】:2011-01-27 14:57:53 【问题描述】:

根据 Google 的说法,在将我的 android 应用发布到 Google Play 之前,我必须“停用对源代码中 Log 方法的任何调用”。摘自publication checklist的第3节:

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

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

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

如果我注释 Log 行,则条件适用于下一行,并且可能不会调用 load()。这种情况是否足够少,以至于我可以决定它不应该存在?

那么,有没有更好的源代码级别的方法来做到这一点?或者也许一些巧妙的 ProGuard 语法可以有效但安全地删除所有日志行?

【问题讨论】:

+1 因为我不记得这在出版清单中。 注释掉非阻塞行,我使用“;//”而不是“//”。 如果您需要能够撤消此操作,您可能需要改用sed 's_^\(\s*Log\.\)_;//'`date|tr -s \ -`'\1_g' Dimitar 添加的链接失效了。我找到了这个 source.android.com/source/code-style.html#log-sparingly. @mboy:现在可能主要是为了性能,但在旧的 Android 版本上它也有安全优势。 【参考方案1】:

Christopher 的 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 命令)

【讨论】:

如果在 Mac 上运行,在 Sed 中的 -i 后面需要一个 ""(根据 this )谢谢。 我觉得这可能是我最终使用的东西,因为我在使用 Proguard 时完全没有运气 如您在第一篇文章中建议的那样,如果您在非括号 while 分支之后有一个日志怎么办? @type-a1pha:如果您采用此解决方案,那么您必须将括号块视为强制性的。 @NicolasRaoul 分号修复了这个问题(// vs. ;//【参考方案2】:

我建议在某处使用静态布尔值来指示是否记录:

类我的调试 静态最终布尔 LOG = true;

然后,无论您想在哪里登录代码,都可以这样做:

如果(MyDebug.LOG) if (条件) 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 + ")");

【讨论】:

我也会投票支持这种方法。它还在 Google 的官方应用内计费示例中使用。 将条件作为第一个参数传递不是更简洁吗? 这似乎是最好的解决方案,尽管它需要在每个日志语句上附加代码:保留行号(ProGuard 方法的弱点),不执行创建日志消息的代码(weakness of wrapper class approach 显然的日志库方法也)。根据@LA_,在谷歌应用计费示例中使用这种方法也支持我的想法。 @Snicolas 如何在不实现包装器的情况下将条件作为第一个参数传递?此外,如果将其添加为参数,则在进入方法之前,需要评估所有参数,即消息字符串。在构建参数之前需要测试条件。在没有外部工具的情况下,所提出的解决方案可能是最好的解决方案。 二进制代码,这是最好的。但是像这样的编码只是为了简单的调试日志输出而付出了很多努力。代码可读性显着下降。赢了,输了,我猜……【参考方案3】:

我发现一个更简单的解决方案是在我们调用 Ant release 目标时忘记所有的 if 检查并使用 ProGuard 去除任何 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 手册中的the examples。


更新(4.5 年后):现在我使用Timber 进行 Android 日志记录。

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

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

木材在我的ApplicationonCreate()方法中设置:

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

然后我可以在我的代码中的任何其他地方轻松记录:

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

有关更高级的示例,请参阅 Timber sample app,其中所有日志语句都在开发期间发送到 logcat,而在生产中,不记录任何调试语句,但错误会以静默方式报告给 Crashlytics。

【讨论】:

为什么不在默认的 proguard 文件中? +rds 因为它会使生产堆栈跟踪行号与代码中的行号不同,因为行被删除了。 我可以确认剥离 Log 调用将改变堆栈跟踪中的行号。它不会总是不同步(我做了几个快速测试,但无法准确找出原因,可能是在 Log 调用中连接一个字符串),但有时它会偏离几行。值得 IMO 麻烦,因为它能够轻松删除 Log 调用。 @Fraggle 来自 ADT 工具中的 proguard-android.txt:“请注意,如果要启用优化,您不能只在自己的项目配置文件中包含优化标志;相反,您需要指向“proguard-android-optimize.txt”文件,而不是你的“#project.properties”文件中的这个文件。 正如 espinchi 在下面的答案中所说。 “这种方法的唯一问题是,如果您执行 Log.d("tag", "Processed: " + new ItemCounter(blabla) + " items "),即使此日志消息没有出现在您发布的版本中, StringBuilder 用于创建消息,创建起来可能很昂贵。“在 Timber 案例中也是如此吗?【参考方案4】:

所有答案都很好,但是当我完成开发后,我既不想在所有 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 的导入。

【讨论】:

这种方法的唯一问题是,如果你使用 Log.d("tag", "Processed: " + new ItemCounter(blabla) + " items "),即使这条日志消息是没有出现在您发布的版本中,StringBuilder 用于创建消息,这可能会很昂贵。 这个方案有一个大问题。 espinchi 提到的只是冰山一角。问题是,当您调用 Log.d("tag", someValue.toString()); 时,很容易忘记检查 someValue 是否为空,这意味着它可能会在生产中抛出 NullPointerException。它建议了一个安全的解决方案,但它会欺骗你。我们是private static boolean DEBUG,然后是if(DEBUG)Log.d(TAG, msg); @espinchi 您的担忧似乎适用于所有日志库,如此答案***.com/a/15452492/433718 (Slf4j,积压,...)中讨论的。不建议使用吗? @espinchi 在第一条评论中提到的最小化开销的唯一方法是更改​​日志记录方法以接受可变参数而不是 String。完整的解决方案描述为here。这显然还有另一个缺点:每个调用都应该被编辑(不仅仅是一个导入行)。 仅供参考,如果您使用的是 Android Studio 和 gradle 构建系统,您可以使用 static final boolean LOG = BuildConfig.DEBUG 而无需修改此文件。【参考方案5】:

我会考虑使用 roboguice 的 logging facility 而不是内置的 android.util.Log

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

使用 proguard 可能会很麻烦,除非您有充分的理由(禁用日志不是好一个)

【讨论】:

当你不能使用混淆时的一个非常好的方法....特别是因为 proguard LOL 破坏了roboguice 更新了 robojuice 日志工具的链接:github.com/roboguice/roboguice/wiki/Logging-via-Ln【参考方案6】:

我通过提供对不同日志级别的支持以及根据代码是在实时设备上还是在模拟器上运行自动更改日志级别来改进上述解决方案。

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);
    




【讨论】:

与上一个解决方案相同的问题。如果字符串参数是使用昂贵的调用构建的,它仍然会浪费资源。调用的检查需要在构建传递的参数之前完成。【参考方案7】:

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

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

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

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

本文档描述了如何启用和配置 ProGuard 以及使用回溯工具来解码混淆的堆栈跟踪

【讨论】:

默认情况下它似乎不会删除调试日志记录。所以克里斯托弗的回答听起来更好。【参考方案8】:

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

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");
  

【讨论】:

+1 用于报告我曾经使用过的Build.DEBUG。我也放弃了各种“正确”的解决方法,并使用了与您类似的风格解决方案。【参考方案9】:

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

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

默认方法 isLoggable(...) 返回 false,只有在您在设备中设置属性后才会这样:

adb shell setprop log.tag.MyAppTag DEBUG

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

检查指定标签的日志是否可在指定级别记录。设置任何标签的默认级别 到信息。这意味着任何高于并包括 INFO 的级别都将是 记录。在您对记录方法进行任何调用之前,您应该检查 查看是否应记录您的标签。您可以更改默认级别 通过设置系统属性:'setprop log.tag。 ' 其中 level 是 VERBOSE、DEBUG、INFO、WARN、ERROR、ASSERT 或 压制。 SUPPRESS 将关闭您的标签的所有日志记录。你可以 还创建一个 local.prop 文件,其中包含以下内容: 'log.tag.=' 并将其放在 /data/local.prop 中。

所以我们可以使用自定义日志工具:

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);
    

【讨论】:

【参考方案10】:

我强烈建议使用 Jake Wharton 的 Timber

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");

打印

“你的班级/味精”而不指定标签

【讨论】:

Timber 非常好,但是如果你已经有一个现有的项目 - 你可以试试 github.com/zserge/log 。它是 android.util.Log 的直接替代品,具有 Timber 的大部分功能,甚至更多。 zserge,您的日志解决方案看起来不错。很多功能。您是否考虑过像 Timber 那样添加 Lint 规则?【参考方案11】:

我有一个非常简单的解决方案。我使用 IntelliJ 进行开发,所以细节会有所不同,但这个想法应该适用于所有 IDE。

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

非常适合我。只要记住将替换设置为区分大小写,以避免出现“对话框”等意外。为了增加保证,您还可以使用“日志”执行第一步。作为要搜索的字符串。

太棒了。

【讨论】:

请阅读我的问题中的“如果我评论日志行” 段落。 好的,是的,我应该在浏览答案后更频繁地重新阅读:)。如果您有这种情况,您可能需要不同的解决方案,例如之前建议的,例如将所有日志放在另一个界面后面。我的建议可能更适合较小的团队和项目,因为人们希望避免额外的日志库开销,你了解人员和代码等等。 用 ;//Log.d 替换 Log.d 也可以处理“如果”场景。【参考方案12】:

我喜欢用 Log.d(TAG, some string, 通常是 String.format())。

TAG 始终是类名

在你的课文中转换 Log.d(TAG, --> Logd(

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

这样当你准备好发布版本时,将MainClass.debug设置为false!

【讨论】:

这个问题和其他解决方案除了 proguard 或注释之外的问题是你离开了代码,可能会导致大量的字符串构建。在普通应用中不是问题,但如果您尝试优化它就会成为问题。【参考方案13】:

最简单的方法;

使用DebugLog

在应用发布时,DebugLog 会禁用所有日志。

https://github.com/MustafaFerhan/DebugLog

【讨论】:

这绝对是错误的。这只会导致日志不被记录,它不会从代码中删除它们,因此它们仍然在那里帮助人们对您的代码进行逆向工程,并且仍然需要格式化所有这些日志的字符串。 【参考方案14】:

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

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

在您的 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(...);

【讨论】:

这不适用于新的 Jack 编译器 -- ***.com/questions/37932114/… 这对我有帮助;需要 proguard-android-optimize.txt 作为默认 Proguard 文件和 -assumenosideeffects 在自定义 Proguard 文件中!我正在使用 R8 shinker(现在的默认设置)和默认的 Android 日志记录。【参考方案15】:

这是我过去在我的 android 项目中所做的......

在 Android Studio 中我们可以通过 Ctrl+Shift+F 从整个项目中查找(MacOs 中为 Command+Shift+F)和 Ctrl+Shift+R 替换((MacOs 中为Command+Shift+R)进行类似的操作)

【讨论】:

这似乎开启了与 Eclipse 项目的合作。搜索选项甚至在 android studios 上都不可用。 在 Android Studio 中你可以使用 Ctrl+Shift+F 快捷键进行类似的搜索 问题中的示例代码解释了为什么这不可靠。 这可能会导致删除日志中包含的任何命令时出现问题。例如 ChocolateLog.recipie(); 无法为 Android Studio 2.1 找到此选项。另外,我可以通过正常的搜索/替换一次在 1 个文件上使用这个技巧。【参考方案16】:

我发布了这个专门针对 Android Studio 用户的解决方案。我最近还发现了 Timber,并通过执行以下操作将其成功导入我的应用程序:

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

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

然后在 Android Studios 中,转到 Edit -> Find -> Replace in Path...

输入Log.e(TAG, 或者您已经在"Text to find" 文本框中定义了您的日志消息。然后你只需将其替换为Timber.e(

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

Android Studios 现在将检查您项目中的所有文件,并将所有日志替换为 Timbers。

我使用此方法的唯一问题是 gradle 之后确实会出现一百万条错误消息,因为它无法在每个 java 文件的导入中找到“Timber”。只需单击错误,Android Studios 就会自动将“Timber”导入到您的 java.util 文件中。完成所有错误文件后,gradle 将再次编译。

您还需要将这段代码放入您的 Application 类的 onCreate 方法中:

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

这将导致应用程序仅在您处于开发模式而非生产模式时进行日志记录。您也可以使用BuildConfig.RELEASE 登录发布模式。

【讨论】:

尝试对您的导入做同样的事情,并确保选中正则表达式框 要查找的文本:import android\.util\.Log\; 替换为:import android\.util\.Log\;\nimport timber\.log\.Timber\; 或者您可以使用 结构搜索 并像 Chike Mgbemena 在他的post 中显示的那样替换 @MaksimTuraev 您的链接不再相关。现在是一个关于发型的博客。 帖子好像被删除了=(在任何地方都找不到。 @MaksimTuraev 这是来自 Wayback 机器的副本,但图像已损坏 - web.archive.org/web/20161004161318/http://chikemgbemena.com/…【参考方案17】:

正如zserge's comment 建议的那样,

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

his log library 提供简单的启用/禁用日志打印开关,如下所示。

另外,它需要更改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

【讨论】:

你必须把那行代码放在每个 Activity/Fragment 中,还是只放在一个地方? @NoahTernullo // 在派生的应用程序文件中。 DefaultApplication.java【参考方案18】:

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

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

适用于多行日志。在此解决方案中,您可以确定生产代码中不存在日志。

【讨论】:

【参考方案19】:

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

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

【讨论】:

【参考方案20】:

如果您可以运行全局替换(一次),然后保留一些编码约定,您可以遵循 Android framework 中常用的模式。

而不是写

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.<strong><em>DEBUG</em></strong> 相当可靠,因此您不需要额外的常量来控制剥离。

【讨论】:

【参考方案21】:

我知道这是一个老问题,但你为什么不将所有日志调用替换为类似 布尔 logCallWasHere=true; //---你的日志的其余部分在这里

这就是为什么您会知道何时要将它们放回去,并且它们不会影响您的 if 语句调用:)

【讨论】:

有趣,希望编译器/优化器会忽略这些行。但是,变量名必须是唯一的,因为某些方法有多个日志调用,并且您不能两次声明同一个变量。 您可以在活动的顶部声明变量并从此行中删除布尔声明;)【参考方案22】:

为什么不干呢

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

?不需要额外的库,没有倾向于搞砸项目的 proguard 规则,当您进行发布构建时,java 编译器只会为这个调用省略字节码。

【讨论】:

一个不方便的是,它比写Log.d("tag","msg");更冗长,而且很容易忘记写if(BuildConfig.DEBUG)部分。 另一个问题是字符串保留在打包版本中。【参考方案23】:

如果您不想弄乱其他库或手动编辑代码,这是我的解决方案。我创建了this Jupyter notebook 来检查所有java 文件并注释掉所有日志消息。不完美,但它为我完成了工作。

【讨论】:

【参考方案24】:

我的方式:

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

2) 选择一个 Log.d(TAG, "text"); “日志”部分。

3) 然后执行 shift + ctrl + alt + j

4) 点击左箭头

5) 换档+结束

6) 点击删除。

这会立即删除 java 文件中的所有 LOG 调用。

【讨论】:

【参考方案25】:

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

Ctrl+Shift+R

替换

Log.e(

// Log.e(

【讨论】:

这不适用于问题中给出的示例代码。【参考方案26】:

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)

【讨论】:

【参考方案27】:

这是我在投入生产之前在我的 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(...);

【讨论】:

【参考方案28】:

如果您想使用编程方法而不是使用 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")
    

== 更新 ==

如果我们想避免字符串连接以获得更好的性能,我们可以添加一个带有 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")
 

由于我们使用的是内联函数,因此没有额外的对象分配和额外的虚拟方法调用。

【讨论】:

如果您使用Log.d("tag", "Processed: " + new ItemCounter(blabla) + " items "),即使您发布的版本中没有出现此日志消息,也会使用 StringBuilder 来创建消息,这可能会很昂贵。 在性能关键的代码情况下,您是对的,创建字符串连接的成本可能很高,尤其是在循环内部。在这些情况下,我会使用 PorGuard 或其他方法完全删除日志记录代码。否则,如果我们仍然想避免字符串连接,但我们确实想以编程方式解决问题,我们可以使用只有在调试配置中才会调用的内联函数块。【参考方案29】:

    转到Application-&gt;app-&gt;proguard-rules.pro

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

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

# 如果您想在日志中显示该调试类型的错误,您可以删除特定的调试类

    build.gradle(app) -&gt;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
      
    

【讨论】:

【参考方案30】:

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

创建自定义记录器类:

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);
            
        
    

希望对你也有帮助。

【讨论】:

以上是关于如何在构建 Android 应用程序的发布版本之前删除所有调试日志记录调用?的主要内容,如果未能解决你的问题,请参考以下文章

如何获取 Android 应用程序的构建/版本号?

如何在构建服务器和工作站之间同步 Gradle/Android 版本代码

如何通过 Jenkins 中的构建数量设置 android 应用程序版本?

Flutter 如何在 Android Studio 中制作发布版本?

如何使用 Kotlin 为 Android 版本构建配置 ProGuard

如何从 android app bundle 分发支持版本?