Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/android/181.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Android 在ProGuard优化过程中删除未使用的字符串_Android_Proguard - Fatal编程技术网

Android 在ProGuard优化过程中删除未使用的字符串

Android 在ProGuard优化过程中删除未使用的字符串,android,proguard,Android,Proguard,在发布Android应用程序时,我包括删除调试日志语句: -assumenosideeffects class android.util.Log { public static *** d(...); public static *** v(...); } 这和预期的一样——我可以从ProGuard日志和Android日志输出中看到,这些日志输出调用了log.d(“这是一条调试语句”)已删除 但是,如果我在这个阶段反编译应用程序,我仍然可以看到所有使用的字符串文本-即这是本例中

在发布Android应用程序时,我包括删除调试日志语句:

-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** v(...);
}
这和预期的一样——我可以从ProGuard日志和Android日志输出中看到,这些日志输出调用了
log.d(“这是一条调试语句”)已删除

但是,如果我在这个阶段反编译应用程序,我仍然可以看到所有使用的
字符串
文本-即
这是本例中的调试语句


是否有一种方法也可以从字节码中删除不再需要的每个
字符串?

下面是我们的方法-使用ant任务

<target name="base.removelogs">
    <replaceregexp byline="true">
        <regexp pattern="Log.d\s*\(\s*\)\s*;"/>
        <substitution expression="{};"/>
        <fileset dir="src/"><include name="**/*.java"/></fileset>
    </replaceregexp>
</target>

ProGuard可以删除简单的常量参数(字符串、整数等)。因此,在这种情况下,代码和字符串常量应该完全消失:

Log.d("This is a debug statement");
但是,您可能已经注意到某些代码存在以下问题:

Log.d("The answer is "+answer);
<replaceregexp>
    <regexp pattern="(android\.util\.)*Log\.([ewidv]|wtf)\s*\([\S\s]*?\)\s*;"/>
    <substitution expression=""/>
    <fileset dir="src/">
        <include name="**/*.java"/>
    </fileset>
</replaceregexp>
编译后,这实际上对应于:

Log.d(new StringBuilder().append("The answer is ").append(answer).toString());
ProGuard 4.6版可将其简化为:

new StringBuilder().append("The answer is ").append(answer).toString();
因此,日志记录已经消失,但优化步骤仍有一些不足之处。在不深入了解StringBuilder类的情况下简化这个过程是非常困难的。就ProGuard而言,它可以说:

new DatabaseBuilder().setup("MyDatabase").initialize(table).close();
对于人类来说,StringBuilder代码显然可以删除,但DatabaseBuilder代码可能无法删除。ProGuard需要逃逸分析和其他一些技术,这些技术目前还不在本版本中

至于解决方案:您可以创建附加的调试方法,这些方法采用简单的参数,并让ProGuard删除这些参数:

MyLog.d("The answer is ", answer);

或者,您可以尝试在每个调试语句前面加上一个条件,ProGuard稍后会将该条件计算为false。这个选项可能有点复杂,在调试标志的初始化方法上需要一些额外的-assumenosideeffects选项。

因为我没有足够的代表直接评论ant任务答案,下面是对它的一些更正,因为事实证明,它与Jenkins这样的CI服务器结合使用非常有用,Jenkins可以为发布版本执行它:

<target name="removelogs">
    <replaceregexp byline="true">
        <regexp pattern="\s*Log\.d\s*\(.*\)\s*;"/>
        <substitution expression="{};"/>
        <fileset dir="src">
            <include name="**/*.java"/>
        </fileset>
    </replaceregexp>
</target>

日志后的“.”必须转义,括号内的“.”指向任何日志语句,而不仅仅像“\s*”那样指向空格


由于我对正则表达式没有太多的经验,我希望这将有助于处于相同情况的一些人使ant任务正常工作(例如在Jenkins上)。

如果您想支持多行日志调用,可以使用此正则表达式:

(android\.util\.)*Log\.@([ewidv]|wtf)\s*\([\S\s]*?\)\s*;
您应该能够在ant
replaceregexp
任务中使用它,如下所示:

<replaceregexp>
    <regexp pattern="((android\.util\.)*Log\.([ewidv]|wtf)\s*\([\S\s]*?\)\s*;)"/>
    <substitution expression="if(false){\1}"/>
    <fileset dir="src/">
        <include name="**/*.java"/>
    </fileset>
</replaceregexp>
<copy ...>
    <fileset ... />
    <filterchain>
        <tokenfilter if:true="${strip.log.calls}">
            <stringtokenizer delims=";" includeDelims="true"/>
            <replaceregex pattern="((android\.util\.)*Log\.([ewidv]|wtf)\s*\([\S\s]*?\)\s*;)" replace="if(false){\1}"/>
        </tokenfilter>
    </filterchain>
    <!-- other-filters-etc -->
</copy>
您还可以在
任务中将正则表达式作为筛选器应用,如下所示:

<replaceregexp>
    <regexp pattern="((android\.util\.)*Log\.([ewidv]|wtf)\s*\([\S\s]*?\)\s*;)"/>
    <substitution expression="if(false){\1}"/>
    <fileset dir="src/">
        <include name="**/*.java"/>
    </fileset>
</replaceregexp>
<copy ...>
    <fileset ... />
    <filterchain>
        <tokenfilter if:true="${strip.log.calls}">
            <stringtokenizer delims=";" includeDelims="true"/>
            <replaceregex pattern="((android\.util\.)*Log\.([ewidv]|wtf)\s*\([\S\s]*?\)\s*;)" replace="if(false){\1}"/>
        </tokenfilter>
    </filterchain>
    <!-- other-filters-etc -->
</copy>


很有趣,但我想问一个更一般的ProGuard问题:)好方法。但是,如果Log.d方法调用中有中断行呢?@qiuping345,尝试添加@nir,
\(\s*\)
如何匹配任何日志记录?它的意思是“括号之间的空格”,谢谢你的详细回答。添加
-assumenosideeffects StringBuilder{append(String);append(…);toString();}
似乎可以有效地删除现在未引用的StringBuilder。我是不是错过了一些让事情变得糟糕的原因?:)@Christopher append方法确实有副作用:它们扩展了StringBuilder实例,即使没有使用返回值。因此,这样的配置可以删除所需的事件。这可能有用,但风险很大。啊,好吧。。例如,我可以调用
sb.append(“x”)
,不使用返回的
StringBuilder
,然后调用
foo(sb)
——但是
“x”
不会被追加,因为该方法调用已被删除?这确实是一个很好的方法,我不明白为什么框架不提供带有字符串格式的日志方法。实际上,构建一个字符串(有时与String.format复杂)来最终删除它是没有意义的,因为它没有达到最小日志level@rds使用它有非常灵活的API,比如:
LOG.warn(“{}did{}”,这个“something”,ex)
,注意
ex
没有相应的
{}
因此将打印stacktrace。您也不必担心使用正确的%模式或StringBuilder。这是否与
-dontoptimize
一起起作用,因为Android文档中说“添加优化会带来一定的风险,例如,ProGuard执行的所有优化并非都适用于所有版本的Dalvik。”@codingcrow可能不是因为,据我所知,这是一种优化。但我相信默认的Android ProGuard配置一般都启用了优化,但禁用了一些他们认为不可靠的特定优化。因此,您应该能够毫无问题地将其添加到默认配置中。如果查看project.properties,您将发现行
proguard.config=${sdk.dir}/tools/proguard/proguard android.txt:proguard project.txt
,并且在progaurd-android.txt中,您将发现优化默认关闭。Dex不喜欢通过ProGuard优化和预验证步骤运行代码。这是第22条军规。好吧,这不是默认情况。但是您可以使用捆绑的
proguard android optimize.txt
config来代替。同样,与实际问题无关。如果您的日志语句跨越多行,这可能不起作用?@Christopher,您是对的,但如果这是一个真正的Java模式,这是可能的:。