设置
  • 日夜间
    随系统
    浅色
    深色
  • 主题色

浅谈Android的日志机制:Log和logcat

2017/7/31 15:53:56 来源:IT之家 作者:東京特許許可局局長 责编:弥尘

感谢IT之家网友 東京特許許可局局長 的原创投稿

注:测试设备Android版本为7.1。

一、日志的概况,Android自带终端的使用和初次使用logcat

要对应用程序进行调试,记录日志是最常见快捷的方式。日志可以清楚地展现出应用的运行状态。从而给程序员提供修正和改进的建议。

(Windows事件管理器中的Windows系统日志)

和Windows一样,在Android系统中也提供了类似的日志记录机制。只不过与Windows不同,在Android中并没有明显的可以查看日志的入口。其实Android中已经内置了查看日志的工具,只不过我们需要比较特殊的手段召唤它出来。

(打开开发者选项,打开本地终端的开关)

(打开启动器界面中新增的“终端”图标)

当然,除了以上方法。对于其他基于Android深度优化的ROM,本选项被阉割时,可以下载第三方终端模拟器类应用。

这是一个第三方Terminal应用

(在黑色界面中输入logcat,回车执行)

1. logcat  


二、logcat概况、日志格式、logcat详细用法、还有MatLog

logcat是Android中用来查看日志的命令行工具(Log Catcher),由图可知,logcat以

1. --------- beginning of xxx 

为起点,开始捕捉Android日志。xxx对应这存储着Android日志记录器的环形缓冲区。Android系统在运行时会时刻在几个设备文件中的一个中写入字符串。而这几个设备文件指向几个环形缓冲区。这是Android日志的记录原理。而这几个缓冲区合称日志记录器缓冲区。

(打开开发者选项,可以看到有个选项叫做“日志记录器缓冲区大小”,默认是256K。由上文所述,日志是循环写入环形缓冲区的。在通常情况下,写满时最旧的日志会被删除以给新输出的日志留内存空间。设置的越大就可以保存离当前时间越久远的日志。但相对地,设置的越大对内存的占用也越大,内存的可用空间也越少,请酌情修改)

默认输出的日志格式如下文所示:(不带任何参数的logcat命令)

1. 07-22 20:31:21.565   993  1032 W BroadcastQueue: Background execution not allowed: receiving Intent { act=com.tencent.mm.plugin.openapi.Intent.ACTION_HANDLE_APP_REGISTER flg=0x10 (has extras) } to com.tencent.mm/.plugin.base.stub.WXEntryActivity$EntryReceiver  

由六部分组成:

1. 写下日志时的时间,如上中07-22 20:31:21.565。

2. PID(进程ID),如上中993。

3. TID(线程ID),如上中1032。

4. 优先级,在Android中,日志的优先级从低到高分以下几种:

  • V—Verbose(啰嗦,最低级别,开发调试中的一些详细信息,仅在开发中使用,不可在发布产品中输出,不是很常见,包含诸如方法名,变量值之类的信息)

  • D—Debug(调试,用于调试的信息,可以在发布产品中关闭,比较常见,开发中经常选择输出此种级别的日志,有时在beta版应用中出现)

  • I—Info(信息,该等级日志显示运行状态信息,可在产品出现问题时提供帮助,从该级别开始的日志通常包含完整意义的英语语句和调试信息,是最常见的日志级别)

  • W—Warning(警告,运行出现异常即将发生错误或表明已发生非致命性错误,该级别日志通常显示出执行过程中的意外情况,例如将try-catch语句块中的异常打印堆栈轨迹之后可输出此种级别日志)

  • E—Error(错误,已经出现可影响运行的错误,比如应用crash时输出的日志)

  • F—Fatal(严重错误,比error级别更高,目前我只在android系统内核发出的日志中看到此种级别。在Android6.0以前表明开发者认为绝对不应该出现的错误,在此以后一般开发场景下绝不应该输出此种级别的日志)

  • S—Silent(寂静,最高级别,没有一条日志会属于这个级别,仅仅作为关闭logcat输出的过滤器参数)

示例中日志等级显示为W,表示警告级别。

5. 标签,标明日志发起者和方便日志的过滤筛选,如上中BroadcastQueue,表示该日志关于广播队列。

6. 正文,本日志的主体内容。示例日志中表示一个后台执行被阻止,并显示出了接收到的意图的详细信息。

在之前的操作中,日志只是输出了几段就暂停了输出。其实在非root的系统权限下,logcat只能够输出当前应用动作引起的日志(这里是指终端应用)。如果想获取全局日志,必须在root权限下获取,所以。。。

1. kenzo:/ $ su  

2. kenzo:/ # logcat  

输入su回车之后再输入logcat回车,输出的全局日志看起来像流水一样,只不过由于新日志的不断输出,方向是向上流的。

logcat的使用姿势很丰富。用法如下:

logcat [选项] [过滤器]?

选项:

-s <过滤器>设置过滤器,后加过滤器,多个过滤器用逗号分隔。若其后不加过滤器。等效于"-s *:S",不输出日志。

过滤器格式:

<标签>[:优先级]

代表匹配标签和优先级这两方面的条件。

标签部分中*(星号)代表"任何标签的日志"。单独一个*代表'*:D',单独的标签代表'<标签>:V'。如果没有'*',过滤器或者-s参数,默认为'*:V"。

'*:S <标签>'仅打印标签指定的日志,'<标签>:S',不打印指定标签的日志,其他日志打印出来。?

示例:

输出标签为AndroidRuntime,等级为E或F的日志内容。(由上文可知,优先级越高的日志内容会越重要,所以过滤器中的等级意味着指定等级或以上。)

1. kenzo:/ # logcat -s AndroidRuntime:E  

除以上内容外,还输出标签为DEBUG,等级不限的日志。

1. kenzo:/ # logcat -s AndroidRuntime:E,DEBUG  

仅仅打印标签为DEBUG的日志。(三者之一均可实现)

1. kenzo:/ # logcat *:S DEBUG  

2. kenzo:/ # logcat DEBUG  

3. kenzo:/ # logcat DEBUG:V  

除了DEBUG标签的日志,其他的都打印。

1. kenzo:/ # logcat DEBUG:S  

-f <文件路径>将日志导出至指定路径的文件。会创建一个新文件并连续不断地往其中写日志,并不会输出到屏幕上。logcat进入阻塞状态。(阻塞是指调用结果返回之前,当前线程会被挂起,暂停运行。只有返回结果才会继续运行)?

示例:

往内置存储的log.txt写日志。(用文件浏览器可以看到内置存储中新建了一个叫log.txt的文件。而且随着时间推移,该文件会越来越大)

1. kenzo:/ # logcat -f /sdcard/log.txt  

-r <数字>输出指定体积的日志文件,以KB为单位。本参数必须与-f同用。同上也会创建一个新文件并连续不断地往其中写日志。只不过到达指定的体积之后,原文件名重命名为"文件名+.数字编号",然后再创建一个原文件继续写入,体积超限时,分文件重命名为"文件名+.(数字编号+1)",原文件加上个分文件的数字编号,重复以上步骤。数字编号越来越大,创建的文件越来越多。原命名文件体积不大于指定参数。分文件不大于指定参数+1KB。

示例:

在内置存储的log.txt中写日志,文件不大于10KB。(用文件浏览器可以看到内置存储中新建了一个叫log.txt的文件,文件越来越大,体积到达10KB时,刷新出现log.txt.1,原文件体积又从零开始增大,体积再次超限时出现log.txt.2,以此类推。这些log.txt.xxx的文件体积在10~11KB之间。数字编号最大的分文件修改时间最早)

1. kenzo:/ # logcat -f /sdcard/log.txt -r 10  

-n <数字>设置日志文件分割输出的最大数目。(这个参数与-r连用才生效)

示例:

作用基本同上,只不过创建到log.txt.5就不再创建,每个文件也会按顺序更新日志内容,过旧的日志文件被删除。

1. kenzo:/ # logcat -f /sdcard/log.txt -r 10 -n 5  

-v <格式>设置日志输出格式。格式属于以下字串之一: brief color epoch long monotonic printable process raw tag thread threadtime time uid usec UTC year zone。

"格式"注释:

brief:概要。在Android6.0以前是默认的输出格式。“优先级/标签(PID):正文”

1. logcat v brief (命令示例)

2. E/cnss-daemon(19173): Cap bouncing fail EXIT!!: Operation not permitted(1)  

color:色彩,等同于-C参数。

epoch:纪元,Unix时间戳。“Unix时间戳PID TID优先级标签:正文”

1. 1501053044.433   886  5295 W ActivityManager: Background start not allowed: service Intent { act=com.xiaomi.push.timer cmp=tv.danmaku.bili/com.xiaomi.push.service.XMPushService (has extras) } to tv.danmaku.bili/com.xiaomi.push.service.XMPushService from pid=18301 uid=10090 pkg=tv.danmaku.bili  

long:长。“[时间PID:TID优先级/标签]”,下一行正文

1. [ 07-26 15:13:44.891   886:  896 I/art      ]  

2. Background sticky concurrent mark sweep GC freed 67174(4MB) AllocSpace objects, 22(504KB) LOS objects, 14% free, 31MB/37MB, paused 6.243ms total 159.174ms  

monotonic:单调的。开机时间。“【开机状态持续时间(秒.毫秒)】PID TID优先级标签:正文”

1. 20758.245  5167  5533 E Backup  : Caused by: java.net.ConnectException: Connection timed out  

printable:可打印的。能够被输出的日志。绝大多数情况下用此格式跟默认无异。在极其特殊的情况下缓冲区的数据被破坏之后,使用此格式可以避免数据碎片构成的乱码输出。

process:进程。“优先级(PID)日志信息(标签)”

1. E(  886) no controller energy info supplied  (BatteryStatsService)  

raw:原料。仅输出日志正文

tag:标签。“优先级/标签:正文”

1. I/TetherStatsReporting:   [26] iface=tun0 uid=10233 set=DEFAULT tag=0x0 roaming=NO rxBytes=0 rxPackets=0 txBytes=8054 txPackets=144 operations=0  

thread:进程。“优先级(PID:TID)正文”

1. I(20065:20065) Found internal modem: modem  

threadtime:进程时间。在Android6.0和以上为默认格式。如示例所示。

time:时间。“时间优先级/标签(PID):正文”

1. 07-26 15:43:27.790 I/TetherStatsReporting(  886): binding to service  

uid:用户ID。“时间UID PID TID优先级:正文”

1. 07-26 15:41:54.909 10035  5167  7428 I NearbyDirect: MagicPairScanner: BluetoothLeScanner is null, Bluetooth might be off. Enabled=false  

usec:微秒。精确到微秒的时间输出。“时间PID TID优先级标签:正文”

1. 07-26 15:49:12.090409 18287 18296 E System  :   at java.lang.Thread.run(Thread.java:761)  

UTC:世界标准时间。看看这一条的时间和上一条有什么不同。“时间时区PID TID优先级标签:正文”

1. 07-26 07:49:12.349 +0000   886 20301 D WifiNetworkHistory: saving network history: "PHICOMM_146585"WPA_PSK gw: null Network Selection-status: NETWORK_SELECTION_ENABLED ephemeral=false choice:"dakara"WPA_PSK link:0 status:1 nid:3 hasEverConnected: true  

year:年。时间上加上年份。“时间PID TID优先级标签:正文”

1. 2017-07-26 15:18:19.924   886  1039 I DisplayManagerService: Display device changed state: "鍐呯疆灞忓箷", OFF  

zone:时区。东八区。“时间时区PID TID优先级标签:正文”

1.   07-26 15:55:45.406 +0800   886 20445 D ConnectivityService: Returning blocked NetworkInfo to uid=10238  

-D 在每个日志缓冲区之间打印分隔符。(当不同缓冲区的日志出现时,以"--------- switch to xxx"的标记分隔)

1.   07-26 16:03:15.422  3660  3660 D PhoneStatusBar: disable: < expand icons alerts system_info back home* recent* clock search* quick_settings >  

2. --------- switch to system  

3. 07-26 16:03:15.425   886  1042 I DisplayPowerController: Unblocked screen on after 603 ms  

4. --------- switch to main  

5. 07-26 16:03:15.427   886  1042 V KeyguardServiceDelegate: onScreenTurnedOn()  

6. --------- switch to system  

7. 07-26 16:03:15.427   886  1042 I DreamManagerService: Gently waking up from dream.  

如上,当写入不同的缓冲区时会进行分隔。

-c清除缓存日志。(先执行logcat -c再执行logcat,不会像之前那样先输出一大堆调用之前就存在的缓存日志再打印新的日志,而是以logcat -c执行完成为开始时间打印从此之后的日志)

-d将当前现存缓冲区的日志全部打印出来然后退出,不阻塞。

-e <正则表达式>输出匹配指定正则表达式的日志。

示例:

输出包含android一次或多次的日志。

1. kenzo:/ # logcat -e android+  

以下是被匹配到的日志之一。

1. 07-26 16:24:11.935 23097 23097 I Radio-JNI: register_android_hardware_Radio DONE  

-m <数字>打印指定行数日志后退出,此参数本是为了配合-e参数。但也可以自己工作。(单独用与-t<数字>无异,只不过如果使用-e参数后又想指定打印条数的话必须要使用-m指定,此时用-t无效)?

示例:

输出以上描述的日志三条:

1. kenzo:/ # logcat -e android+ -m 3  

以下是被匹配到的日志。

1. 07-26 16:32:42.509   886   911 W art     : Long monitor contention with owner ActivityManager (1015) at void com.android.internal.os.BatteryStatsImpl$MyHandler.handleMessage(android.os.Message)(BatteryStatsImpl.java:170) waiters=0 in boolean com.android.server.am.ActiveServices.stopServiceTokenLocked(android.content.ComponentName, android.os.IBinder, int) for 276ms  

2. 07-26 16:32:46.178   886   886 V FingerprintService: onAuthenticated(owner=com.android.systemui, id=-870104779, gp=0)  

3. 07-26 16:32:46.246   886  1042 V KeyguardServiceDelegate: onScreenTurnedOn(showListener = com.android.server.policy.PhoneWindowManager$2@96c4e5e)  

注意:-m参数必须与-c执行之后马上用!要是缓冲区有适合匹配的日志那这样输出的日志就不止三条了!

-t <数字>打印出最近几条指定条数的日志。同时暗示-d参数。

示例:

打印最近一条日志。

1. kenzo:/ # logcat -t 1  

2. --------- beginning of main  

3. 07-26 16:39:10.473 23667 23667 E cnss-daemon: Cap bouncing fail EXIT!!: Operation not permitted(1)  

4. kenzo:/ #  

-t '<时间>'打印从指定时间至最近发出的日志。同时暗示-d参数。(时间两侧要加单引号)。

示例:

打印在今天下午4点42分40秒012毫秒之后的日志。

1. kenzo:/ $ logcat -t '2017-07-26 16:42:40.012' -d  

2. --------- beginning of main  

3. 07-26 16:42:47.747 23973 23973 I libmdmdetect: ESOC framework not supported  

4. 07-26 16:42:47.748 23973 23973 I libmdmdetect: Found internal modem: modem 

5. kenzo:/ $ 

-T <数字>与-t<数字>参数同义,但不暗示-d参数。(意思就是输出x条日志后不退出而进入阻塞状态,等到最新日志条数累积到x条在一并输出,一次打印x条日志)

示例:

打印3*X条日志。

1. kenzo:/ # logcat -T 3  

-T '<时间>'与-t '<时间>'同义,但不暗示-d参数。(与无参数logcat的区别就是从指定时间开始输出日志。)

示例:

打印在今天下午4点42分40秒012毫秒之后的日志。

1. kenzo:/ $ logcat -T '2017-07-26 16:42:40.012' -d  

2. --------- beginning of main  

3. 07-26 16:42:47.747 23973 23973 I libmdmdetect: ESOC framework not supported  

4. 07-26 16:42:47.748 23973 23973 I libmdmdetect: Found internal modem: modem 

5. (在这里将会持续打印出更多日志)

(以上的"时间"如果在当前时间之后,参数无效。时间格式属于'MM-DD hh:mm:ss.mmm...','YYYY-MM-DD hh:mm:ss.mmm...'或'sssss.mmm...'格式)?

-g获取日志记录器环形缓冲区可容纳体积信息。

示例:

1. kenzo:/ # logcat -g  

2. main: ring buffer is 256Kb (56Kb consumed), max entry is 5120b, max payload is 4068b  

3. system: ring buffer is 256Kb (11Kb consumed), max entry is 5120b, max payload is 4068b  

4. crash: ring buffer is 256Kb (0b consumed), max entry is 5120b, max payload is 4068b  

5. kenzo:/ #  

比如:main缓冲区的大小是256KB,已经使用了56KB。每条日志当超过4068B时自动截断,超过5120B时启用”chatty”。

-G <体积>设置日志记录器环形缓冲区的大小,体积数字后需要加K或M。(需要root权限)

示例:

1. kenzo:/ # logcat -G 48M  

2. kenzo:/ # logcat -g  

3. main: ring buffer is 48Mb (198Kb consumed), max entry is 5120b, max payload is 4068b  

4. system: ring buffer is 48Mb (14Kb consumed), max entry is 5120b, max payload is 4068b  

5. crash: ring buffer is 48Mb (0b consumed), max entry is 5120b, max payload is 4068b  

6. kenzo:/ #   

-L打印从从上次重启记录下来的转储日志。

-b <缓冲区名称>输出指定环形缓冲区的日志。缓冲区名称属于'main','system', 'radio', 'events', 'crash', 'default'和'all'。可以指定用逗号分隔的多个缓冲区。

缓冲区名称注释:

main:主要,大多数日志写入区域。

以下是部分在main区的日志。

1. 07-26 17:00:17.613   886   886 E QCOM PowerHAL: Failed to acquire lock.  

2. 07-26 17:00:18.230   886   911 I TetherStatsReporting:   [9] iface=tun0 uid=10035 set=FOREGROUND tag=0x0 roaming=NO rxBytes=48207 rxPackets=100 txBytes=26676 txPackets=118 operations=0  

3. 07-26 17:00:18.395   886 17178 W art     : Long monitor contention with owner Binder:886_19 (22377) at void com.android.server.am.ActivityManagerService.keyguardGoingAway(int)(ActivityManagerService.java:6932) waiters=0 in boolean com.android.server.am.ActivityManagerService.refContentProvider(android.os.IBinder, intintfor 229ms  

4. 07-26 17:00:19.088 18287 18296 E System  : Uncaught exception thrown by finalizer  

5. 07-26 17:29:15.113 25801 25890 D OkHttp  : User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1.1; Redmi Note 3 Build/NMF26V) (#Build; Xiaomi; Redmi Note 3; lineage_kenzo-userdebug 7.1.1 NMF26V e6195e47a5; 7.1.1) +CoolMarket/7.9.5  

6. 07-26 17:26:04.004 25801 25914 V xiaomi  : [Thread:927] receive an intent from server, action=com.xiaomi.mipush.RECEIVE_MESSAGE  

可以看出这里第三方应用和部分系统组件的地盘。

system:系统,系统组件或系统应用日志写入区域。

以下是部分在system区的日志。

1. 07-26 17:26:43.294   886 22380 I ActivityManager: START u0 {cmp=com.coolapk.market/.view.user.UserSpaceActivity (has extras)} from uid 10081 on display 0  

2. 07-26 17:26:54.155   886  1020 E BatteryStatsService: modem info is invalid: ModemActivityInfo{ mTimestamp=69669705 mSleepTimeMs=56612626 mIdleTimeMs=-56071485 mTxTimeMs[]=[0, 0, 0, 0, 0] mRxTimeMs=12821 mEnergyUsed=0}  

3. 07-26 17:27:13.631   886  1038 D BluetoothManagerService: Added callback: android.bluetooth.IBluetoothManagerCallback$Stub$Proxy@3fca409:true  

4. 07-26 17:28:17.378   886   886 V FingerprintService: starting client AuthenticationClient(com.android.systemui), initiatedByClient = true)  

5. 07-26 17:29:13.091   886  1037 I DreamController: Stopping dream: name=ComponentInfo{com.android.systemui/com.android.systemui.doze.DozeService}, isTest=false, canDoze=true, userId=0  

6. 07-26 17:29:26.127   886  3332 E ConnectivityService: RemoteException caught trying to send a callback msg for NetworkRequest [ LISTEN id=94, [ Capabilities: INTERNET&NOT_RESTRICTED&TRUSTED&FOREGROUND] ]  

看标签就知道这些日志是和系统紧密相关的。

radio:无线电,无线网络设备/电话通讯相关日志存放区。

1. 07-26 16:03:15.032  4387  4387 D SST     : Poll ServiceState done:  oldSS=[0 0 voice home data home CHN-UNICOM UNICOM 46001 CHN-UNICOM UNICOM 46001  LTE LTE CSS not supported -1 -1 RoamInd=-1 DefRoamInd=-1 EmergOnly=false IsDataRoamingFromRegistration=false IsUsingCarrierAggregation=false] newSS=[0 0 voice home data home CHN-UNICOM UNICOM 46001 CHN-UNICOM UNICOM 46001  LTE LTE CSS not supported -1 -1 RoamInd=-1 DefRoamInd=-1 EmergOnly=false IsDataRoamingFromRegistration=false IsUsingCarrierAggregation=false] oldMaxDataCalls=20 mNewMaxDataCalls=20 oldReasonDataDenied=-1 mNewReasonDataDenied=-1  

2. 07-26 16:06:44.919   497 20597 E RILQ    : (0/497): RIL[0][cmd-22(296)] qcril_cm_ss_convert_gsm8bit_alpha_string_to_utf8: Invalid parameters for GSM alphabet to UTF8 conversion, input len = 0  

events:事件,事件/监听相关日志存放区。

1. 07-26 17:45:52.539   886  1039 I sysui_action: [319,253]  

2. 07-26 17:41:49.162   886 20445 I netstats_wifi_sample: [177386760,138674,18171096,94318,177316560,137010,16902671,93826,177316528,137009,16902671,93826,1501062109170]  

3. 07-26 17:44:01.445   886  1014 I sync    : [com.google.android.gms.fitness,1,4,-1318889753]  

4. 07-26 17:46:07.792   886  1042 I am_pause_activity: [0,135372798,com.ruanmei.ithome/.ui.MainActivity]  

看出来当前系统正在执行的事件记录。

crash:崩溃,应用崩溃时在此写入崩溃信息日志。

注意:在应用发生FC“停止运行”的错误时,用此命令可将错误日志发送给开发者!MIUI自带错误日志记录功能。当然,如果不是MIUI,又不想这么麻烦,也可以下载这个)

1. logcat -b crash  

1. 07-26 17:56:00.226 27976 27976 E AndroidRuntime: FATAL EXCEPTION: main  

2. 07-26 17:56:00.226 27976 27976 E AndroidRuntime: Process: com.android.development, PID: 27976  

3. 07-26 17:56:00.226 27976 27976 E AndroidRuntime: com.android.development.BadBehaviorActivity$BadBehaviorException: Whatcha gonna do, whatcha gonna do  

4. 07-26 17:56:05.493 28027 28027 F DEBUG   : pid: 28005, tid: 28005, name: oid.development  >>> com.android.development <<<  

5. 07-26 17:56:05.494 28027 28027 F DEBUG   : signal 11 (SIGSEGV), code 0 (SI_USER), fault addr 0x3e800006d65  

6. 0  

7. 07-26 17:56:05.499 28027 28027 F DEBUG   :     #01 pc 000000007482b61c  /data/dalvik-cache/arm64/system@framework@boot-framework.oat (offset 0x16c9000)  

(开发者最需要的日志)

default:默认,未特殊指定时指main,system,crash这三个缓冲区。

all:全部,所有日志输出。

-B以二进制方式打印日志。(在终端上不做特殊处理的话输出的就是乱码)

-S打印统计信息。?

输出就像是这样:总数统计和从最近一次开机时的统计三个缓存日志的总体积和总条数。再加上各缓存的统计结果(这里只截取了main缓冲区),其中从写入日志最多的进程开始排序,PID/UID以及所属的二进制或者包名。

1. size/num main               system             crash    

2. Total    25227193/197668    2272774/15584      25491/226    

3. Now      1186556/5480       94321/793          2481/21    

4.     

5. Chattiest UIDs in main log buffer:                           Size   +/-  Pruned    

6. UID   PACKAGE                                               BYTES           NUM    

7. 1000  system                                               892079    

8.   PID/UID   COMMAND LINE                                       "    

9.   886/1000  system_server                                  807384    

10.   505/1000  /system/bin/fingerprintd                        18589    

11. 10081 com.coolapk.market                                   133213    

12. 10086 com.andcreate.app.trafficmonitor                      32354    

13. 10104 com.ruanmei.ithome                                    28214    

14. 10028 com.android.systemui                                  26367    

15. 10035 com.google.android.gms                                13347    

16. 10153 com.aide.ui                                           11848    

17. 1041  audioserver                                            8980  


-p从Android 5.0开始,当某一个进程连续输出大量日志超出缓冲区容量时,该部分日志将被删除不予输出,仅留下一条"chatty"日志表明本部分日志已被删除。此选项可以设置黑白名单。当超限时,折叠日志的次序依次为黑名单进程,正常进程,白名单进程。本选项可输出已被设置的黑白名单信息。

注意:所有冗余日志都被折叠成如下形式:

1. 07-26 20:27:37.944 I/chatty  (1683): uid=1000(system) com.android.systemui expire 4 lines  

这里可以看出被折叠的是系统UI界面的日志。这里可能大家有疑问,为什么Android会输出这么多的日志呢?其实,这条是从MIUI上截下来的。我之前开发过一个项目,其中有段代码连续不断地注册一个系统组件对象。结果这个项目在原生4.4和7.0都能正常运行,在Android6.0的MIUI上正常运行,在Android4.4的MIUI就极为卡顿。经排查,在原生系统中,这段代码的操作是不会任何输出日志的,而在MIUI上这中操作一次会输出两条日志。在Android4.4上没有这种”chatty”机制,因而会大量占用I/O通道。而由于”chatty”对系统日志的选择性输出,这种操作在Android6.0的MIUI安然无恙。其实MIUI输出日志过多这毛病一直被开发者所诟病。虽然它在看得见的地方很人性化,但在看不见的地方或许不是这样。

(下图中这个查看日志的应用叫MatLog,看日志很方便的。用logcat看毕竟不是很方便)

?-C这个参数会将日志以高亮形式显示出来。

以上为各个优先级日志所显示的颜色:V白,D蓝,I绿,W橙,E红。

-P '<名单>'设置黑白名单。多条名单空格分隔,外加单引号。

名单格式:白名单:数字。黑名单:~数字。UID:数字。UID和PID:数字(UID)/数字(PID)。PID:/数字。从统计数据中选取最"吵闹"的UID中选取统计数据中最高的进程日志进行删除:~!。(需要root权限)

示例:

默认配置是~!~1000/!。表示按照从最吵闹的UID中和从UID为1000中的(从系统应用或从已获取root权限的)进程们按照统计数据进行日志折叠。然后将UID为1000的进程写入白名单,PID为2233的和P同时符合PID:2333和UID:1000的进程写入黑名单。再次输出logcat-p既可以发现名单被修改了。不过重启可以恢复成默认。

1. kenzo:/ # logcat -p  

2. logcat -p  

3. ~! ~1000/!  

4. kenzo:/ # logcat -P '1000 ~2233'  

5. logcat -P '1000 ~/2233 ~1000/2333'  

6. kenzo:/ # logcat -p  

7. logcat -p  

8. 1000 ~2233  

--pid=只输出特定PID的日志。

--wrap这个参数用于提高日志输出轮询效率。加上之后,输出日志时确实更快一点。

logcat只是用来查看日志的命令行工具,那有没有发出日志的命令行工具呢?有的。

1. kenzo:/ # log  

2. log  

3. USAGE: log [-p priorityChar] [-t tag] message  

4.         priorityChar should be one of:  

5.                 v,d,i,w,e  

如上所示,log就是发出日志的命令行工具。-p指定优先级,-t指定标签,然后指定信息。-p–t不设定时默认标签为log,等级为Info。

示例:

1. kenzo:/ $ log -p w -t log_test I am WARNING  

这是-C参数截图时所用的W级别日志输出方法,可惜的是,在开发时这种方法执行的速度不够快。在写这篇文章的时候,按照优先级顺序,W应该在E之上的,结果却相反。其实在Android开发中,Google对日志输出提供了原生支持。

三、书写日志和从源头过滤日志的方法,和LiveBoot

1. //省略部分代码  

2. //导入Android工具类:Log  

3. import android.util.Log;  

4. //...  

5. //设置一个用作标签的常量,方便查找日志  

6. static final String TAG="log_test";  

7. //Log输出方法一:println,传入参数log等级,标签和内容。  

8. Log.println(Log.VERBOSE, TAG, "I am VERBOSE");  

9. //Log输出方法二:(代表着等级的字母),传入参数log标签和内容。有5种这样的方法。这种输出方法最常用。  

10. Log.d(TAG, "I am DEBUG");  

11. Log.i(TAG, "I am INFO");  

12. //Log输出方法三:shell使用log命令,不常用。cmd()shell执行方法。  

13. cmd("log -p w -t " + TAG + " I am WARNING");  

14. try  

15. {  

16.     int i=0 / 0;    //除以零,在这里会抛出异常。  

17. }  

18. catch (Exception e)  

19. {  

20.     Log.e(TAG, "I am ERROR", e);    //将错误堆栈日志以特定的标签和优先级记录下来。  

21.     Log.e(TAG, "getStackTraceString():" + Log.getStackTraceString(e));    //getStackTraceString方法可以将错误堆栈信息转为文本  

22. }  

23. //isLoggable这个方法可以查看日志是否可被输出。Android中可以配置指定标签可输出日志最低等级。在标签长度超过23时在Android7.0以下会抛出IllegalArgumentException  

24. Log.e(TAG, "isLoggable()=" + String.valueOf(Log.isLoggable(TAG, Log.VERBOSE)));  

25. //以下两条为输出Fatal级别日志的方法,然而在Android6.0和以上,不再允许第三方输出Fatal级别日志。这两种方法在这种环境只能输出Error级别日志。  

26. Log.wtf(TAG, "I am FATAL if I can");  

27. Log.println(Log.ASSERT, TAG, "I am FATAL if I can");  

这是Java层的日志的输出方法,在C/C++层中使用的方法虽然有点迂回,但是也不难。

1. //省略部分代码  

2. //1.build.gradle配置log系统库文件  

3. ndk  

4. {  

5.     ldLibs "log",...  

6. }  

7. //2.在要打印log的代码文件中装载头文件  

8. #include <android/log.h>  

9. //3.接着定义标签和简化的函数(如果这么一长串就用下去也太麻烦了)  

10. #define TAG  "log_test"  

11. #define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  

12. //4.使用  

13. LOGD("sometimes naive");    

在Java层的代码中,发现“Android中可以配置指定标签可输出日志最低等级”。接下来讲一下配置方法。同样也是用命令行的方式,只不过我们要使用新的工具——setprop。

用法:第一个参数是键名,第二个参数是键值。这个工具的用途是设置Android系统属性(prop配置),要root权限。

1. setprop  

2. usage: setprop NAME VALUE  

3.   

4. Sets an Android system property.  

我们要设置的键名为log.tag.<标签>,键值为日志优先级。VERBOSE, DEBUG, INFO, WARN, ERROR, ASSERT(表示fatal等级),SUPPRESS(表示slient等级)。就像这样…

1. kenzo:/ # setprop log.tag.log_test INFO  

然后运行测试应用,输出的日志结果如下。

1. kenzo:/ # logcat -s log_test -d  

2. --------- beginning of system  

3. --------- beginning of main  

4. 07-27 19:09:38.886 25456 25456 I log_test: I am INFO  

5. ...省略部分输出日志...  

6. 07-27 19:09:38.911 25456 25456 I log_test: isLoggable()=false  

7. ...省略部分输出日志...  

8. kenzo:/ #  

可以看出I am VERBOSE的日志没有输出。isLoggable返回了false表示标签为log_test,等级为VERBOSE的日志无法输出。

1. kenzo:/ # echo "log.tag.log_test=INFO" > /data/local.prop  

除了setprop,还可以将“log.tag.<标签>=<优先级>”这一内容写到/data目录的local.prop下,如上的echo命令就是发挥这样的用途。写入到文件的好处是与setprop不同,重启后配置不会丢失,log输出过滤依然有效。

除了方便查看日志的MatLog之外,这里再介绍一个应用叫LiveBoot。这个应用可以将开机时正式进入系统前,还在播放开机动画时,就将日志显示在屏幕上。这个功能最大的好处就是当系统长期播放开机动画时,通过显示出的日志可以判断系统是在建立虚拟机缓存(比如在Recovery中清除Cache和Dalvik/ART Cache了之后的重启)还是变成了bootloop软砖。(永久开机动画循环)

(应用界面,可以看到这个应用的作者是大名鼎鼎的谁)

(开机效果,看起来很装逼不是吗)

这个工具需要root权限支持。而且并不是都支持所有非类原生ROM,还会稍微拖慢开机时间。不过想到在疑似系统故障的时候不知道系统到底是好是坏的忐忑心情,这点小瑕疵算得了什么?

四、免root获取全局日志,Flyme6获取真正全局日志的方法

之前就说过,要想获取全局日志,必须要在root权限下执行logcat才能达到目的。这样做的目的是为了保护用户隐私信息。(大家如果闲着没事的话,打开MatLog,如果懂点英语的话看看正在输出的日志会透露出多少用户隐私。或者正常使用设备一段时间后再用logcat导出日志到文件查看,这样显示出的用户隐私更多)不过,Google并没有完全禁止免root查看日志。Android中有一个权限叫"android.permission.READ_LOGS"。从字面意思上看就是读取日志时使用的权限。这个权限默认不授予给第三方应用,如果想要将这个权限授予给第三方应用。可以使用在电脑上安装ADB和相关驱动。在Android设备上打开USB调试权限。(adb大家都很熟悉吧)。然后输入以下命令。

1. adb shell pm grant <要授权的应用包名> android.permission.READ_LOGS  

正常的情况下。执行完即退出,不会输出任何字符。第三方应用在授权之后重新启动,通过使用shell召唤logcat,即使在非root权限下也能获取日志。如果出现以下错误:

1. Operation not allowed: java.lang.SecurityException: Package <应用包名> has not requested permission android.permission.READ_LOGS  

则表明应用的清单文件(AndroidManifest.xml)中需要添加以下权限:

1. <uses-permission android:name="android.permission.READ_LOGS" />  

而且目标SDK(targetSdkVersion)必须不小于21。

在MIUI上,可能还会出现这个错误。

1. Operation not allowed: java.lang.SecurityException: grantRuntimePermission: Neither user 2000 nor current process has android.permission.GRANT_RUNTIME_PERMISSIONS.  

其实MIUI默认不允许adb权限更改。如果要想允许权限更改。需要打开USB调试的安全设置开关。

注意:需要登录MIUI账号。

由于日志本身的隐私属性。有一些非原生ROM,就算是在root权限的logcat也无法全部获取日志。比如说Flyme。这时也需要找深藏的设置开关来解决此问题。

(同样是点击系统版本打开开发者模式)

(设置--辅助功能--开发者选项--性能优化--高级日志输出--全部允许)

不同的xxOS,XXUI有不同的方法,请大家自行查找。

五、总结

总的来说,这篇文章,也就是围绕着“Android日志”这个主题讲了一些东西。不过个人感觉最好的部分还是在logcat的详细介绍中。(毕竟这部分我是做得最辛苦的,网上说的太简单了,每一条命令都是我测试过的。当然,这文章的所有做法我都严格测试过以证明其有效性)当然,这篇文章对于普罗大众来说最大的用处就是知道Android中有日志这个东西,并且能够查看,导出并使用它。日志中,我认为,尽管只是对于爱好玩机的普罗大众,也蕴含着丰富的财富。通过查看日志,可以了解到Android系统中的许多奥秘,这种好奇心被满足的兴奋感绝不亚于小时候拆开机械手表查看其中的齿轮运转所感受到的心情。总之,お楽しみ下さい(请尽情享【shi】受【jian】吧)!谢谢大家!

广告声明:文内含有的对外跳转链接(包括不限于超链接、二维码、口令等形式),用于传递更多信息,节省甄选时间,结果仅供参考,IT之家所有文章均包含本声明。

【广告】

软媒旗下网站: IT之家 最会买 - 返利返现优惠券 iPhone之家 Win7之家 Win10之家 Win11之家

软媒旗下软件: 软媒手机APP应用 魔方 最会买 要知