`
tomcat_oracle
  • 浏览: 316815 次
社区版块
存档分类
最新评论

关于Android Log的一些思考

阅读更多

在日常的Android开发中,日志打印是一项必不可少的操作,我们通过分析打印的日志可以分析程序的运行数据和情况。然而使用日志打印的正确姿势又是怎样呢,如何屏蔽日志信息输出呢,本文将逐一进行回答。

哪些形式

System.out.println

这是标准的Java输出方法,相信很多公司都不提倡使用,这里进行列举,目的是为了提醒大家不用。

Android Log

Android自身提供了一个日志工具类,那就是android.util.Log。使用很简单,如下

1
Log.i(LOGTAG, "onCreate");

TAG选取

选用人名

关于TAG的选取,很多人都曾采用人名的形式,比如

1
Log.i("andy", "onCreate");

这样做的目标一是为了过滤方便,当一个人在写一个模块多个文件时,使用这个形式,过滤起来很容易帮助理解程序的执行情况。另外的目的就是为了表明日志周围代码的作者姓甚名谁。

然而,我却不推荐这种人名作为TAG的形式。原因如下

  • 以人名作为关键字过滤,不易确定产生日志的类文件
  • 随着某个人模块实现的增加,过滤人名易产生来自其他模块的干扰信息。

动态选取

还有一种选取LOGTAG的方式,就是

1
private static final String LOGTAG = DroidSettings.class.getSimpleName();

这样使用,得到的LOGTAG的值就是DroidSettings,然而并非如此,当DroidSettings这个类进行了混淆之后,类名变成了类似a,b,c这样的名称,LOGTAG则不再是DroidSettings这个值了。这样可能造成的问题就是,内部混淆有日志的包,我们去过滤DroidSettings 却永远得不到任何信息。

推荐的记录形式

推荐的形式就是以字符串字面量形式去设置LOGTAG。如下,在DroidSettings类中

1
private static final String LOGTAG = "DroidSettings";

优雅打印日志的姿势

什么才是打印日志的优雅姿势是,我认为一条好的日志需要包含以下三点

  • 这条日志所在的类,我们通过LOGTAG可以表示
  • 这条日志所在的方法,需要加入方法名的字符串
  • 必要的其他信息,比如参数或者局部变量。

结合三点,下面是一个符合规则的简单示例

1
2
3
4
5
private String  getBookName(int bookId) {
    String bookName = mBooks.get(bookId);
    DroidLog.i(LOGTAG, "getBookName bookId=" + bookId + ";bookName=" + bookName);
    return bookName;
}

上面的代码,包含了所在类(LOGTAG),方法名(getBookName), 参数(bookId),局部变量(bookName)。必要的信息都展示了出来,对于了解程序运行很有帮助。

屏蔽日志输出

在Android中进行屏蔽日志,有两种实现形式,一种是在编译期屏蔽,另一种则是从运行时进行屏蔽,后者相对比较常见,从后向前介绍。

运行时屏蔽

在运行时屏蔽日志,通常的做法是创建一个自定义的类,比如叫做DroidLog

1
2
3
4
5
6
7
8
9
10
public class DroidLog {
    private static final boolean ENABLE_LOG = true;
    public static void i(String tag, String message) {
        if (ENABLE_LOG) {
            android.util.Log.i(tag, message);
        }
    }
}

在编码时,我们调用DroidLog.i方法来记录日志,然后在打包时,修改ENABLE_LOG的值为false,这样就能屏蔽了日志输出。

然后运行时屏蔽的方案实际上有一点小问题,比如

1
2
3
private void dumpDebugInfo() {
    DroidLog.i(LOGTAG, "sdkVersion=" + Build.VERSION.SDK_INT + "; Locale=" + Locale.getDefault());
}

虽然上面的日志不会打印,但是"sdkVersion=" + Build.VERSION.SDK_INT + "; Locale=" + Locale.getDefault()这段字符串拼接语句却实实在在执行了。总的来说,还是会产生一些影响。

关于字符串拼接的细节,可以阅读Java细节:字符串的拼接

编译期屏蔽

既然运行时屏蔽存在问题,那么是否可以提前到编译期进行屏蔽呢,答案是肯定的。这里我们就使用了Proguard的一个小功能。

assumenosideeffects从英文单词上去理解,意思为 假设没有副作用。该功能属于优化的一种方式,该功能常常用来处理日志打印,比如我们想要屏蔽掉来自DroidLog的日志打印。 在混淆的配置文件中,加入下列代码

1
2
3
-assumenosideeffects class com.droidyue.logdemo.DroidLog {
        public static *** i(...);
}

然而仅仅处理DroidLog是不够的,因为我们无法保证团队其他成员是否使用了原生的android.utils.Log来进行日志打印(尽管有编码约束)

1
2
3
4
5
6
7
8
9
-assumenosideeffects class android.util.Log {
        public static *** d(...);
        public static *** e(...);
        public static *** i(...);
        public static *** v(...);
        public static *** println(...);
        public static *** w(...);
        public static *** wtf(...);
}

一般写到这里,基本可以结束,但是我们还需要探究一下,编译期屏蔽是否和运行时屏蔽一样有着同样的问题呢? 我们接下来证明
首先,我们选用这段代码作为例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MainActivity extends Activity {
    private static final String LOGTAG = "MainActivity" ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dumpDebugInfo();
    }
    private void dumpDebugInfo() {
        Locale defaultLocale = Locale.getDefault();
        DroidLog.i(LOGTAG, "sdkVersion=" + Build.VERSION.SDK_INT + "; Locale=" + defaultLocale);
    }
}

然后修改混淆文件proguard-project.txt,启用混淆处理。

1
2
3
4
5
6
7
8
9
-assumenosideeffects class android.util.Log {
    public static *** d(...);
    public static *** i(...);
    public static *** v(...);
}
-assumenosideeffects class com.droidyue.logdemo.DroidLog {
        public static *** i(...);
}

然后使用Eclipse的导出功能,生成指定签名的APK包,运行必然没有日志输出。

接下来对生成的APK包进行反编译,得到的smali文件。查看MainActivity.smali。

注意:Proguard进行优化,发生了内联操作,讲dumpDebugInfo的方法体实现提取到onCreate方法中。

onCreate方法体中没有任何关于DroidLog.i方法的调用,但是"sdkVersion=" + Build.VERSION.SDK_INT + "; Locale=" + defaultLocale对应的字符串拼接操作依然存在。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# virtual methods
.method protected onCreate(Landroid/os/Bundle;)V
    .locals 3
    invoke-super {p0, p1}, Landroid/app/Activity;->onCreate(Landroid/os/Bundle;)V
    const v0, 0x7f030017
    invoke-virtual {p0, v0}, Lcom/droidyue/logdemo/MainActivity;->setContentView(I)V
    invoke-static {}, Ljava/util/Locale;->getDefault()Ljava/util/Locale;
    move-result-object v0
    new-instance v1, Ljava/lang/StringBuilder;
    const-string v2, "sdkVersion="
    invoke-direct {v1, v2}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V
    sget v2, Landroid/os/Build$VERSION;->SDK_INT:I
    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;
    move-result-object v1
    const-string v2, "; Locale="
    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
    move-result-object v1
    invoke-virtual {v1, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;
    move-result-object v0
    invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
    return-void
.end method

因此,无论是运行时日志屏蔽还是编译期,message参数上发生的字符串拼接都依然存在。但是编译期屏蔽减少了方法调用(即方法进出栈操作),理论上编译期屏蔽日志更优。

2
2
分享到:
评论

相关推荐

    Android应用程序设计实践指导典型案例

    ### Android应用程序设计实践指导典型案例 #### 一、Android开发环境实践 ##### 1.1 第一个Android程序 **实训目的:** 本实训旨在通过实践操作让读者熟悉Android开发环境的搭建,并通过编写简单的Hello World...

    完全理解android事件分发机制

    但是源码的复杂往往会让新手产生畏惧难以理解,于是笔者最终还是打算使用实例log来让读者理解android事件分发。 #重要函数 笔者此次主要提及最常用的几个函数: (其间区别看源码很容易理解,此处直接给上结果) **...

    Android之在linux终端执行shell脚本直接打印当前运行app的日志的实现方法

    1、问题 我们一般很多时候会需要在...3)、思考,为什么每次都需要这样重复的操作呢?一说到重复,我们应该立马想到是否可以用脚本解决重复操作 2、解决办法 通过执行脚本文件获取包名,然后再执行pidcat.py packageNam

    android远程文件下载(任何格式)

    #### 三、扩展思考 1. **安全性**:直接使用`URL`可能会导致安全问题,建议使用HTTPS协议。 2. **性能优化**:可以考虑使用多线程技术提高下载速度。 3. **错误处理**:添加更多的错误处理逻辑,例如网络连接...

    详解Android数据存储之Android 6.0运行时权限下文件存储的思考

    在我们做App开发的过程中基本上都会用到文件存储,所以文件存储对于我们来说是相当熟悉了,不过自从Android 6.0发布之后,基于运行时权限机制访问外置sdcard是需要动态申请权限,所以以往直接sdcard根目录上直接新建...

    Java开发与技术挑战——关于技术的技术思考.docx

    - 随后,开发者会接触到诸如dom4j、jdom、log4j、Hibernate、Spring、iBatis、Struts等开源框架。 - 数据交换中,JSON和XML的封装和解析是必备技能,正则表达式也在开发中扮演重要角色。 - 开发环境如Tomcat、...

    Android实现一对一蓝牙聊天APP

    学习了,三天的Android 蓝牙开发,开始是一头雾水,看着别人讲的Google官方的demo感觉很容易,所有自己也尝试写一个很简单的聊天demo.可是想的很简单,自己做起来也花了,将近一天的时间才搞定这个基本的流程设计.下面是几...

    Android中AOP(面向切向编程)的深入讲解

    AOP则提供了一种新的思考方式,将这类关注点定义为“切面”,并在运行时自动插入到目标代码的特定位置,称为“连接点”(Join Point)。 在Android中实现AOP的一种常见方式是使用注解(Annotation)和编译时织入...

    multi-platform-AUTH:一套基于Djinni、GRPC框架,使用C++实现的跨平台单终端登录系统

    Log系统:合理的错误码定义,封装统一Log打印接口,便于后续对log统一上报/监控。 二、涉及技术 基于 框架实现多端(PC、Android、IOS)跨平台,本系统选用Android作为演示端。 后台采用 实现,用户信息存储采用实现...

    【移动开发】1.刘强-基于Flutter跨平台框架的Now直播应用实践.pdf

    刘强-基于Flutter跨平台框架的Now直播应用实践.pdf这份文档主要介绍了如何使用Flutter框架开发Now直播应用,分享了在实际项目中的开发模式、版本实践和技术思考。以下是详细的知识点解析: 1. **Flutter框架介绍**...

    IOS设备上给body绑定click事件不生效的原因及解决办法

    思考:  暂借助jquery展示下事件绑定代码,将所有标签含有data-tip属性的元素通过事件代理至body $('body').on('click','[data-tip]',function(e){ console.log($(this.).attr('data-tip')) })  这样做在...

    Git权威指南PDF完整版

    11.4.3 浏览日志:git log/ 146 11.4.4 差异比较:git diff/ 150 11.4.5 文件追溯:git blame/ 151 11.4.6 二分查找:git bisect/ 152 11.4.7 获取历史版本/ 156 第12章 改变历史/ 157 12.1 悔棋/ 157 12.2 多步悔棋...

    Diversity

    开源项目如Apache Commons、Log4j和Lucene等,以及全球各地的Java User Groups (JUGs) 和年度JavaOne大会,都展示了Java社区的活力和多样性。 综上所述,"Diversity"在Java领域中不仅意味着语言本身的广泛应用和...

Global site tag (gtag.js) - Google Analytics