根据谷歌,我必须在发布我的Android应用程序谷歌播放之前“停用源代码中的Log方法的任何调用”。出版清单第3节节选:

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

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

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

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

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


当前回答

正如zserge的评论所言,

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

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

此外,它只需要更改导入行,对于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

其他回答

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

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

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

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文档:

Checks to see whether or not a log for the specified tag is loggable at the specified level. The default level of any tag is set to INFO. This means that any level above and including INFO will be logged. Before you make any calls to a logging method you should check to see if your tag should be logged. You can change the default level by setting a system property: 'setprop log.tag. ' Where level is either VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT, or SUPPRESS. SUPPRESS will turn off all logging for your tag. You can also create a local.prop file that with the following in it: 'log.tag.=' and place that in /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);
    }
}

我发现一个更简单的解决方案是忘记所有的if检查,只是使用ProGuard剥离任何Log.d()或Log.v()方法调用时,我们调用我们的Ant发布目标。

通过这种方式,我们总是可以输出常规构建的调试信息,而不必对发布构建进行任何代码更改。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手册中的示例。


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

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

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

木材是在我的Application onCreate()方法中设置的:

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示例应用程序以获得更高级的示例,其中所有日志语句都在开发期间发送到logcat,在生产中,没有调试语句被记录,但错误会无声地报告给Crashlytics。

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

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