android日志系统详解

  Android系统启动篇

1,《android系统启动流程简介》

2,《android init进程启动流程》

3,《android zygote进程启动流程》

4,《Android SystemServer进程启动流程》

5,《android launcher启动流程》

6,《Android Activity启动过程详解》

Android系统开发准备篇

1,《Android 源码下载和编译》

2,《android 11源码编译和pixel3 刷机》

3,《Android Framework代码IDE加载和调试》

Android系统开发实践篇

1,《android设置默认输入法》

2,《android framework预制APK应用》

3,《Android系统层面限制应用开机自启动详解》

4,《android单独编译framework模块并push》

5,《Android Framework开发系统问题分析》

Android系统开发核心知识储备篇

1,《Android编译系统-envsetup和lunch代码篇》

2,《Android编译系统-概念篇》

3,《android日志系统详解》

4,《Android系统Handler详解》

5,《Android系统Binder详解》

6,《Android中Activity、View和Window关系详解》

7,《android view绘制流程详解》

8,《Android读取系统属性详解》

9,《android 窗口管理机制详解》

10,《初识Android系统》

11,《android中AMS进程通知Zygote进程fork新进程的通信方式》

Android核心功能详解篇

1,《android应用市场点击下载APK安装详解》

2,《Android 手势导航(从下往上滑动进入多任务页面)》

3,《android手势分析(应用界面左往右边滑动退出应用)》

4,《android应用安装流程详解》

5,《android11安装应用触发桌面图标刷新流程》

6,《Android系统多任务Recents详解》

7,《android系统导航栏视图分析》

———————————————————————————————————————————

目录

一,背景介绍

二,基础概念

2.1 日志等级

2.2 日志缓冲区

2.3 调整Android log buffer 大小

三,实现原理

3.1 日志架构

3.2 调用流程

3.2.1 写流程

3.2.2 读流程

四,源码分析

4.1 logd守护进程启动

4.2 启动 logd-reinit

4.3 启动 logd-auditctl

4.4 logcat启动


一,背景介绍

        在framework开发过程中,日志是必不可少的一个调试手段。Android通过LOGD、SLOGD和EventLog等方式进行日志的打点。但是日志是如何写入和读取的?开启android日志系统之旅。

二,基础概念

2.1 日志等级

Android日志分为如下7个等级:

    V:详细(最低优先级)--(Verbose:2)

    D:调试 --(Debug:3)

    I :信息 -- (Info:4)

    W:警告 --(Warning:5)

    E:错误 --(Error:6)

    F:严重错误 --(Fatal:7)

    S:静默(最高优先级,绝不会输出任何内容)--(Silent)

 下面这是一条正常输出的Android11日志:
   

日志内容:2023-05-08 10:48:30.111 21802-21838 AutofillManagerService  system_process                       D  onBackKeyPressed()

日志格式:<日期> <时间> <PID>-<TID>  <日志标签TAG>  <进程名称> <日志等级> <日志内容>

2.2 日志缓冲区

        Android 日志系统为日志消息保留了多个缓冲区,可以通过 -b 参数指定特定的缓冲区日志,常用缓存区如下:

    radio:查看包含无线装置/电话相关消息的缓冲区。

    events:查看已经过解译的二进制系统事件缓冲区消息。

    main:查看主日志缓冲区(默认),不包含系统和崩溃日志消息。

    system:查看系统日志缓冲区(默认)。

    crash:查看崩溃日志缓冲区(默认)。

    all:查看所有缓冲区。

    default:报告 main、system 和 crash 缓冲区。

$ adb logcat -b events
05-08 10:48:30.199 29284 29284 I wm_on_start_called: [193592054,com.android.launcher3.uioverrides.QuickstepLauncher,handleStartActivity]
05-08 10:48:30.201 29284 29284 I wm_on_resume_called: [193592054,com.android.launcher3.uioverrides.QuickstepLauncher,RESUME_ACTIVITY]
05-08 10:48:30.201 29284 29284 I wm_on_top_resumed_gained_called: [193592054,com.android.launcher3.uioverrides.QuickstepLauncher,topWhenResuming]
05-08 10:48:30.886 21802 21840 I wm_destroy_activity: [0,185499341,49,com.android.settings/.Settings,finish-imm:idle]
05-08 10:48:30.918  6828  6828 I wm_on_stop_called: [185499341,com.android.settings.Settings,LIFECYCLER_STOP_ACTIVITY]
05-08 10:48:30.923  6828  6828 I wm_on_destroy_called: [185499341,com.android.settings.Settings,performDestroy]
05-08 10:48:30.937 21802 10690 I wm_task_removed: [49,removeChild: last r=ActivityRecord{b0e7ecd u0 com.android.settings/.Settings t-1 f}} in t=Task{ccf6a93 #49 visible=true type=standard mode=fullscreen translucent=false A=1000:com.android.settings.root U=0 StackId=49 sz=0}]
05-08 10:48:30.937 21802 10690 I wm_task_removed: [49,removeTask]
05-08 10:48:30.938 21802 10690 I wm_task_removed: [49,removeTask]
05-08 10:48:30.938 21802 10690 I wm_stack_removed: 49
05-08 10:49:23.205 21802 21948 I battery_level: [90,4346,256]
05-08 10:49:30.187 21802 21850 I am_uid_idle: 10136
05-08 10:49:30.189 21802 21850 I am_uid_idle: 10107
05-08 10:51:45.883 22174 22174 I service_manager_stats: [100,223,3074203]
05-08 10:51:49.401 21802 23866 I am_kill : [0,9667,android.process.acore,995,empty for 114809s]
05-08 10:51:49.444 21802 25008 I am_proc_died: [0,9667,android.process.acore,995,19]
05-08 10:51:49.452 21802 25008 I am_uid_stopped: 10072
05-08 10:51:49.455 21802 21948 I battery_level: [91,4345,259]

2.3 调整Android log buffer 大小

        日常调试时,会遇到日志丢失或者不全的情况,主要原因是日志量很大,但是日志缓冲区很小,此时只要把日志的缓冲区调大即可。

        原因就是Log Buffer太小导致部分Log被冲掉。有时候是客户打印了太多的Log导致。目前默认大小是256K.

   查询Android Log buffer的方法:

备注:  目前默认大小是256K,一般建议设置不超过16M。

方案1. 

    命令: logcat -G Size

    Example:  logcat -G 4M

方案2.

    "开发者选项"打开 -> "Developer options" -> "Logger buffer sizes" ->  按需调整

三,实现原理

3.1 日志架构

图片来自于CSDN-私房菜:

3.2 调用流程

3.2.1 写流程

        在应用层可以通过android.util.Log,android.util.SLog,android.util.EventLog接口,把日志写入到main,system,event的不同缓冲区中去。

应用层写日志方法如下:

        在Native C/C++中,进程通过加载liblog.so,调用ALOGD()、ALOGI()来进行日志的写入,最终也是通过logd写入到logdw的socket中。

    Native 层写日志方法如下:

3.2.2 读流程

    Android中主要通过logcat进程来读取日志,logcat属于native-C的进程,通过加载liblog,从而调用logd的read接口读取 logdr socket的日志内容。

四,源码分析

        Android系统日志主要有三个部分需要关注:

            logd守护进程:日志系统的大管家,管理三个日志的socket:logd、logdr、logdw。

            logcat进程:日志读取工具。

            liblog:提供日志读写、过滤等接口,供logcat、JAVA、Native等程序使用

4.1 logd守护进程启动

        见文件/system/core/logd/logd.rc

service logd /system/bin/logd
    socket logd stream 0666 logd logd
    socket logdr seqpacket 0666 logd logd
    socket logdw dgram+passcred 0222 logd logd
    file /proc/kmsg r
    file /dev/kmsg w
    user logd
    group logd system package_info readproc
    capabilities SYSLOG AUDIT_CONTROL
    priority 10
    writepid /dev/cpuset/system-background/tasks

service logd-reinit /system/bin/logd --reinit
    oneshot
    disabled
    user logd
    group logd
    writepid /dev/cpuset/system-background/tasks

# Limit SELinux denial generation to 5/second
service logd-auditctl /system/bin/auditctl -r 5
    oneshot
    disabled
    user logd
    group logd
    capabilities AUDIT_CONTROL

on fs
    write /dev/event-log-tags "# content owned by logd
"
    chown logd logd /dev/event-log-tags
    chmod 0644 /dev/event-log-tags

on property:sys.boot_completed=1
    start logd-auditctl

        从上面的service可以看出,启动了一个守护进程为logd,存放在手机的/system/bin中,同时创建并启动三个socket:

    logd 接收logcat 传递的指令然后处理 ,比如logcat -g, logcat -wrap等

    logdr logcat从此buffer中读取buffer

    logdw 日志写入的buffer

logd初始化调用栈如下:

logd的初始化流程:

    打开/dev/kmsg 来读取内核日志,通过LogKlog来进行存储

    如果属性"ro.logd.kernel" 配置了,打开/proc/kmsg来读取内核日志

    设置运行时优先级、权限

    启动 Reinit线程,当logd-reinit传入参数reinit时,进行调用,reinit开机只启动一次

    启动各个 log 监听器:LogBuffer、LogReader、LogListener、CommandListener、LogAudit和LogKlog

4.2 启动 logd-reinit

        启动logd-reinit的服务,主要工作是重新初始化logd的LogBuffer,在上面的启动脚本中,配置为oneshot,即开机只执行一次。

    通过上面logd的初始化,可以看到,logd启动后,创建了一个线程reinit_thread_start(),当logd-reinit 传入参数 reinit后,进行功能执行。

    logd-reinit两个步骤:

            如果reinit启动后,并且/deg/kmsg打开成功,把 logd.daemon: renit写入kmsg

            重新初始化各个log buffer的大小,以及其他参数的初始化,但不会重新生成LogBuffer对象

4.3 启动 logd-auditctl

logd-auditctl 的主体是 /system/bin/auditctl,在logd的android.bp中,通过编译 auditctl.cpp得来,并加载了liblogd的 库。

    logd-auditctl是Android 10.0中引入的新功能,目的是让selinux denia的日志打印限制为5秒一次。

logd-auditctl 初始化调用栈如下:

logd-auditctl的主要作用是 让selinux denia的日志打印限制为5秒一次

说明:在logd.rc中配置了logd-auditctl,传入参数为-r5,即限制selinux日志写入频率更新为5秒

4.4 logcat启动

        logcat编译时,会编译两个进程/system/bin/logcat 和/system/bin/logcatd。

        和logd一样,logcat进程启动,是init进程解析了logcatd.rc来进行加载。

————————————————

参考:

1,博主「IngresGe」,原文链接:https://blog.csdn.net/yiranfeng/article/details/104244900

  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Android系统提供了相应的API来获取设备的开关机数据,可以通过BroadcastReceiver接收设备开关机的广播,并在接收到广播时记录相应的数据。以下是一个示例代码: ```java public class BootReceiver extends BroadcastReceiver { private static final String TAG = "BootReceiver"; @Override public void onReceive(Context context, Intent intent) { if (intent == null) { return; } String action = intent.getAction(); if (action == null) { return; } switch (action) { case Intent.ACTION_BOOT_COMPLETED: // 设备开机 Log.i(TAG, "Boot completed"); saveBootData(context, true); break; case Intent.ACTION_SHUTDOWN: // 设备关机 Log.i(TAG, "Shutdown"); saveBootData(context, false); break; } } private void saveBootData(Context context, boolean isBoot) { SharedPreferences sp = context.getSharedPreferences("boot_data", Context.MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); String key = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); int bootCount = sp.getInt(key + "_boot_count", 0); int shutdownCount = sp.getInt(key + "_shutdown_count", 0); if (isBoot) { bootCount++; } else { shutdownCount++; } editor.putInt(key + "_boot_count", bootCount); editor.putInt(key + "_shutdown_count", shutdownCount); editor.apply(); } } ``` 可以在AndroidManifest.xml中注册该BroadcastReceiver: ```xml <receiver android:name=".BootReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.SHUTDOWN" /> </intent-filter> </receiver> ``` 可以使用JobScheduler或AlarmManager定时获取某段时间内的用户开机率,以下是一个示例代码: ```java public class BootJobService extends JobService { private static final String TAG = "BootJobService"; private static final int JOB_ID = 1001; private static final long INTERVAL = 24 * 60 * 60 * 1000; // 一天 @Override public boolean onStartJob(JobParameters params) { Log.i(TAG, "onStartJob"); getBootRate(getApplicationContext()); scheduleNextJob(); return false; } @Override public boolean onStopJob(JobParameters params) { Log.i(TAG, "onStopJob"); return false; } private void scheduleNextJob() { JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); if (jobScheduler == null) { return; } JobInfo jobInfo = new JobInfo.Builder(JOB_ID, new ComponentName(this, BootJobService.class)) .setMinimumLatency(INTERVAL) .setOverrideDeadline(INTERVAL + 60 * 1000) // 最多延迟一分钟 .setRequiresCharging(false) .setPersisted(true) .build(); jobScheduler.schedule(jobInfo); } private void getBootRate(Context context) { SharedPreferences sp = context.getSharedPreferences("boot_data", Context.MODE_PRIVATE); String key = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()); int bootCount = sp.getInt(key + "_boot_count", 0); int shutdownCount = sp.getInt(key + "_shutdown_count", 0); double rate = bootCount * 1.0 / (bootCount + shutdownCount); Log.i(TAG, "Boot rate: " + rate); // TODO: 将数据保存到日志文件中 } } ``` 可以在应用启动启动定时任务: ```java public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); scheduleJob(); } private void scheduleJob() { JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); if (jobScheduler == null) { return; } JobInfo jobInfo = new JobInfo.Builder(BootJobService.JOB_ID, new ComponentName(this, BootJobService.class)) .setMinimumLatency(BootJobService.INTERVAL) .setOverrideDeadline(BootJobService.INTERVAL + 60 * 1000) // 最多延迟一分钟 .setRequiresCharging(false) .setPersisted(true) .build(); jobScheduler.schedule(jobInfo); Log.i(TAG, "Job scheduled"); } } ``` 最后,可以使用File类将数据保存到日志文件中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

佳哥的技术分享

创作不易,谢谢鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值