【Android11】开机启动日志捕捉服务

一、前言

制作这个功能的原因是客户想要自动的记录日志中的报错和警告到设备的内存卡里面。虽然开发者模式中有一个“bug report” 会在/data/user_de/0/com.android.shell/files/bugreports/目录下生成一个zip包记录了日志。但是客户觉得这个日志很难获取到他们需要的信息,他们想要的是logcat这种。于是我只能在网上寻找相关的解决办法。
终于被我找到一个:
android日志服务,将日志记录在log文件中并每天生成一个日志文件
感谢大佬!!!
他使用了一个服务来过滤logcat日志并且记录下来,我这里将他转换成kotlin然后设置成开机启动。

二、代码

这里我将它放在了我自己编写的OTA升级APP里面,当然你们也可以放在其他位置。需要使用的时候可以通过服务调用就可以了

import android.app.AlarmManager
import android.app.PendingIntent
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Environment
import android.os.IBinder
import android.os.PowerManager
import android.util.Log
import java.io.*
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*/*** 日志服务,日志默认会存储在SDcar里如果没有SDcard会存储在内存中的安装目录下面。* 1.本服务默认在SDcard中每天生成一个日志文件;* 2.如果有SDCard的话会将之前内存中的文件拷贝到SDCard中;* 3.如果没有SDCard,在安装目录下只保存当前在写日志;* 4.SDcard的装载卸载动作会在步骤2,3中切换 ;* 5.SDcard中的日志文件只保存7天。* @author Administrator*/
class LogService() : Service() {private var LOG_PATH_MEMORY_DIR: String? = null // 日志文件在内存中的路径(日志文件在安装目录中的路径)private var LOG_PATH_SDCARD_DIR: String? = null // 日志文件在sdcard中的路径@Suppress("unused")private var LOG_SERVICE_LOG_PATH: String? = null // 本服务产生的日志,记录日志服务开启失败信息private val SDCARD_TYPE = 0 // 当前的日志记录类型为存储在SD卡下面private val MEMORY_TYPE = 1 // 当前的日志记录类型为存储在内存中private var CURR_LOG_TYPE = SDCARD_TYPE // 当前的日志记录类型private var CURR_INSTALL_LOG_NAME: String? = null // 如果当前的日志写在内存中,记录当前的日志文件名称private val logServiceLogName = "Log.log" // 本服务输出的日志文件名称private val myLogSdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")private val writer: OutputStreamWriter? = nullprivate val sdf = SimpleDateFormat("yyyy-MM-dd HHmmss") // 日志名称格式private var process: Process? = nullprivate var wakeLock: PowerManager.WakeLock? = nullprivate var sdStateReceiver: SDStateMonitorReceiver? = null // SDcard状态监测private var logTaskReceiver: LogTaskReceiver? = null/** 是否正在监测日志文件大小; 如果当前日志记录在SDcard中则为false 如果当前日志记录在内存中则为true*/private var logSizeMoniting = falseoverride fun onBind(intent: Intent): IBinder? {return null}override fun onCreate() {super.onCreate()init()register()deploySwitchLogFileTask()LogCollectorThread().start()}private fun init() {LOG_PATH_MEMORY_DIR = (filesDir.absolutePath + File.separator+ "log")LOG_SERVICE_LOG_PATH = (LOG_PATH_MEMORY_DIR + File.separator+ logServiceLogName)LOG_PATH_SDCARD_DIR = (Environment.getExternalStorageDirectory().absolutePath+ File.separator+ "walktour"+ File.separator + "log")createLogDir()/* ******************************************************* try { writer = new OutputStreamWriter(new FileOutputStream(* LOG_SERVICE_LOG_PATH, true)); } catch (FileNotFoundException e) {* Log.e(TAG, e.getMessage(), e); }* ******************************************************/val pm = applicationContext.getSystemService(POWER_SERVICE) as PowerManagerwakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "YourAppName:LogService")CURR_LOG_TYPE = currLogTypeLog.i(TAG, "LogService onCreate")}private fun register() {val sdCarMonitorFilter = IntentFilter()sdCarMonitorFilter.addAction(Intent.ACTION_MEDIA_MOUNTED)sdCarMonitorFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED)sdCarMonitorFilter.addDataScheme("file")sdStateReceiver = SDStateMonitorReceiver()registerReceiver(sdStateReceiver, sdCarMonitorFilter)val logTaskFilter = IntentFilter()logTaskFilter.addAction(MONITOR_LOG_SIZE_ACTION)logTaskFilter.addAction(SWITCH_LOG_FILE_ACTION)logTaskReceiver = LogTaskReceiver()registerReceiver(logTaskReceiver, logTaskFilter)}val currLogType: Int/*** 获取当前应存储在内存中还是存储在SDCard中** @return*/get() {if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {return MEMORY_TYPE} else {return SDCARD_TYPE}}/*** 部署日志切换任务,每天凌晨切换日志文件*/private fun deploySwitchLogFileTask() {val intent = Intent(SWITCH_LOG_FILE_ACTION)val sender = PendingIntent.getBroadcast(this, 0, intent, 0)val calendar = Calendar.getInstance()calendar.add(Calendar.DAY_OF_MONTH, 1)calendar[Calendar.HOUR_OF_DAY] = 0calendar[Calendar.MINUTE] = 0calendar[Calendar.SECOND] = 0// 部署任务val am = getSystemService(ALARM_SERVICE) as AlarmManageram.setRepeating(AlarmManager.RTC_WAKEUP, calendar.timeInMillis,AlarmManager.INTERVAL_DAY, sender)recordLogServiceLog("deployNextTask succ,next task time is:"+ myLogSdf.format(calendar.time))}/*** 日志收集 1.清除日志缓存 2.杀死应用程序已开启的Logcat进程防止多个进程写入一个日志文件 3.开启日志收集进程 4.处理日志文件 移动* OR 删除*/internal inner class LogCollectorThread() : Thread("LogCollectorThread") {init {Log.d(TAG, "LogCollectorThread is create")}override fun run() {try {wakeLock!!.acquire() // 唤醒手机clearLogCache()val orgProcessList: MutableList<String?> = this@LogService.allProcess;val processInfoList = getProcessInfoList(orgProcessList)killLogcatProc(processInfoList)createLogCollector()sleep(1000) // 休眠,创建文件,然后处理文件,不然该文件还没创建,会影响文件删除handleLog()wakeLock!!.release() // 释放} catch (e: Exception) {e.printStackTrace()recordLogServiceLog(Log.getStackTraceString(e))}}}/*** 每次记录日志之前先清除日志的缓存, 不然会在两个日志文件中记录重复的日志*/private fun clearLogCache() {var proc: Process? = nullval commandList: MutableList<String> = ArrayList()commandList.add("logcat")commandList.add("-c")try {proc = Runtime.getRuntime().exec(commandList.toTypedArray<String>())val errorGobbler: StreamConsumer = StreamConsumer(proc.errorStream)val outputGobbler: StreamConsumer = StreamConsumer(proc.getInputStream())errorGobbler.start()outputGobbler.start()if (proc.waitFor() != 0) {Log.e(TAG, " clearLogCache proc.waitFor() != 0")recordLogServiceLog("clearLogCache clearLogCache proc.waitFor() != 0")}} catch (e: Exception) {Log.e(TAG, "clearLogCache failed", e)recordLogServiceLog("clearLogCache failed")} finally {try {proc!!.destroy()} catch (e: Exception) {Log.e(TAG, "clearLogCache failed", e)recordLogServiceLog("clearLogCache failed")}}}/*** 关闭由本程序开启的logcat进程: 根据用户名称杀死进程(如果是本程序进程开启的Logcat收集进程那么两者的USER一致)* 如果不关闭会有多个进程读取logcat日志缓存信息写入日志文件** @param allProcList* @return*/private fun killLogcatProc(allProcList: List<ProcessInfo>) {if (process != null) {process!!.destroy()}val packName = this.packageNameval myUser = getAppUser(packName, allProcList)/** recordLogServiceLog("app user is:"+myUser);* recordLogServiceLog("========================"); for (ProcessInfo* processInfo : allProcList) {* recordLogServiceLog(processInfo.toString()); }* recordLogServiceLog("========================");*/for (processInfo: ProcessInfo in allProcList) {if (((processInfo.name!!.lowercase(Locale.getDefault()) == "logcat") && (processInfo.user == myUser))) {android.os.Process.killProcess(processInfo.pid!!.toInt())// recordLogServiceLog("kill another logcat process success,the process info is:"// + processInfo);}}}/*** 获取本程序的用户名称** @param packName* @param allProcList* @return*/private fun getAppUser(packName: String, allProcList: List<ProcessInfo>): String? {for (processInfo: ProcessInfo in allProcList) {if ((processInfo.name == packName)) {return processInfo.user}}return null}/*** 根据ps命令得到的内容获取PID,User,name等信息** @param orgProcessList* @return*/private fun getProcessInfoList(orgProcessList: MutableList<String?>): List<ProcessInfo> {val procInfoList: MutableList<ProcessInfo> = ArrayList()for (i in 1 until orgProcessList.size) {val processInfo = orgProcessList[i]val proStr = processInfo?.split(" ".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray()// USER PID PPID VSIZE RSS WCHAN PC NAME// root 1 0 416 300 c00d4b28 0000cd5c S /initval orgInfo: MutableList<String> = ArrayList()if (proStr != null) {for (str: String in proStr) {if ("" != str) {orgInfo.add(str)}}}if (orgInfo.size == 9) {val pInfo: ProcessInfo = ProcessInfo()pInfo.user = orgInfo[0]pInfo.pid = orgInfo[1]pInfo.ppid = orgInfo[2]pInfo.name = orgInfo[8]procInfoList.add(pInfo)}}return procInfoList}private val allProcess: MutableList<String?>/*** 运行PS命令得到进程信息** @return USER PID PPID VSIZE RSS WCHAN PC NAME root 1 0 416 300 c00d4b28* 0000cd5c S /init*/get() {val orgProcList: MutableList<String?> = ArrayList()var proc: Process? = nulltry {proc = Runtime.getRuntime().exec("ps")val errorConsumer: StreamConsumer = StreamConsumer(proc.errorStream)val outputConsumer: StreamConsumer = StreamConsumer(proc.getInputStream(), orgProcList)errorConsumer.start()outputConsumer.start()if (proc.waitFor() != 0) {Log.e(TAG, "getAllProcess proc.waitFor() != 0")recordLogServiceLog("getAllProcess proc.waitFor() != 0")}} catch (e: Exception) {Log.e(TAG, "getAllProcess failed", e)recordLogServiceLog("getAllProcess failed")} finally {try {proc!!.destroy()} catch (e: Exception) {Log.e(TAG, "getAllProcess failed", e)recordLogServiceLog("getAllProcess failed")}}return orgProcList}/*** 开始收集日志信息*/fun createLogCollector() {val logFileName = sdf.format(Date()) + ".log" // 日志文件名称val commandList: MutableList<String> = ArrayList()commandList.add("logcat")commandList.add("-f")// commandList.add(LOG_PATH_INSTALL_DIR + File.separator + logFileName);commandList.add(logPath)commandList.add("-v")commandList.add("time")commandList.add("*:I")// commandList.add("*:E");// 过滤所有的错误信息// 过滤指定TAG的信息// commandList.add("MyAPP:V");// commandList.add("*:S");try {process = Runtime.getRuntime().exec(commandList.toTypedArray<String>())recordLogServiceLog(("start collecting the log,and log name is:"+ logFileName))// process.waitFor();} catch (e: Exception) {Log.e(TAG, "CollectorThread == >" + e.message, e)recordLogServiceLog("CollectorThread == >" + e.message)}}val logPath: String/*** 根据当前的存储位置得到日志的绝对存储路径** @return*/get() {createLogDir()val logFileName = sdf.format(Date()) + ".log" // 日志文件名称if (CURR_LOG_TYPE == MEMORY_TYPE) {CURR_INSTALL_LOG_NAME = logFileNameLog.d(TAG, (("Log stored in memory, the path is:"+ LOG_PATH_MEMORY_DIR + File.separator + logFileName)))return LOG_PATH_MEMORY_DIR + File.separator + logFileName} else {CURR_INSTALL_LOG_NAME = nullLog.d(TAG, (("Log stored in SDcard, the path is:"+ LOG_PATH_SDCARD_DIR + File.separator + logFileName)))return LOG_PATH_SDCARD_DIR + File.separator + logFileName}}/*** 处理日志文件 1.如果日志文件存储位置切换到内存中,删除除了正在写的日志文件 并且部署日志大小监控任务,控制日志大小不超过规定值* 2.如果日志文件存储位置切换到SDCard中,删除7天之前的日志,移 动所有存储在内存中的日志到SDCard中,并将之前部署的日志大小 监控取消*/fun handleLog() {if (CURR_LOG_TYPE == MEMORY_TYPE) {deployLogSizeMonitorTask()deleteMemoryExpiredLog()} else {moveLogfile()cancelLogSizeMonitorTask()deleteSDcardExpiredLog()}}/*** 部署日志大小监控任务*/private fun deployLogSizeMonitorTask() {if (logSizeMoniting) { // 如果当前正在监控着,则不需要继续部署return}logSizeMoniting = trueval intent = Intent(MONITOR_LOG_SIZE_ACTION)val sender = PendingIntent.getBroadcast(this, 0, intent, 0)val am = getSystemService(ALARM_SERVICE) as AlarmManageram.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),MEMORY_LOG_FILE_MONITOR_INTERVAL.toLong(), sender)Log.d(TAG, "deployLogSizeMonitorTask() succ !")// recordLogServiceLog("deployLogSizeMonitorTask() succ ,start time is "// + calendar.getTime().toLocaleString());}/*** 取消部署日志大小监控任务*/private fun cancelLogSizeMonitorTask() {logSizeMoniting = falseval am = getSystemService(ALARM_SERVICE) as AlarmManagerval intent = Intent(MONITOR_LOG_SIZE_ACTION)val sender = PendingIntent.getBroadcast(this, 0, intent, 0)am.cancel(sender)Log.d(TAG, "canelLogSizeMonitorTask() succ")}/*** 检查日志文件大小是否超过了规定大小 如果超过了重新开启一个日志收集进程*/private fun checkLogSize() {if (CURR_INSTALL_LOG_NAME != null && "" != CURR_INSTALL_LOG_NAME) {val path = (LOG_PATH_MEMORY_DIR + File.separator+ CURR_INSTALL_LOG_NAME)val file = File(path)if (!file.exists()) {return}Log.d(TAG, "checkLog() ==> The size of the log is too big?")if (file.length() >= MEMORY_LOG_FILE_MAX_SIZE) {Log.d(TAG, "The log's size is too big!")LogCollectorThread().start()}}}/*** 创建日志目录*/private fun createLogDir() {var file = File(LOG_PATH_MEMORY_DIR)var mkOk: Booleanif (!file.isDirectory) {mkOk = file.mkdirs()if (!mkOk) {mkOk = file.mkdirs()}}/* ************************************* file = new File(LOG_SERVICE_LOG_PATH); if (!file.exists()) { try {* mkOk = file.createNewFile(); if (!mkOk) { file.createNewFile(); } }* catch (IOException e) { Log.e(TAG, e.getMessage(), e); } }* ************************************/if ((Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED)) {file = File(LOG_PATH_SDCARD_DIR)if (!file.isDirectory) {mkOk = file.mkdirs()if (!mkOk) {recordLogServiceLog("move file failed,dir is not created succ")return}}}}/*** 将日志文件转移到SD卡下面*/private fun moveLogfile() {if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {// recordLogServiceLog("move file failed, sd card does not mount");return}var file = File(LOG_PATH_SDCARD_DIR)if (!file.isDirectory) {val mkOk = file.mkdirs()if (!mkOk) {// recordLogServiceLog("move file failed,dir is not created succ");return}}file = File(LOG_PATH_MEMORY_DIR)if (file.isDirectory) {val allFiles = file.listFiles()for (logFile: File in allFiles) {val fileName = logFile.nameif ((logServiceLogName == fileName)) {continue}// String createDateInfo =// getFileNameWithoutExtension(fileName);val isSucc = copy(logFile, File(((LOG_PATH_SDCARD_DIR+ File.separator + fileName))))if (isSucc) {logFile.delete()// recordLogServiceLog("move file success,log name is:"+fileName);}}}}/*** 删除内存下过期的日志*/private fun deleteSDcardExpiredLog() {val file = File(LOG_PATH_SDCARD_DIR)if (file.isDirectory) {val allFiles = file.listFiles()for (logFile: File in allFiles) {val fileName = logFile.nameif ((logServiceLogName == fileName)) {continue}val createDateInfo = getFileNameWithoutExtension(fileName)if (canDeleteSDLog(createDateInfo)) {logFile.delete()Log.d(TAG, ("delete expired log success,the log path is:"+ logFile.absolutePath))}}}}/*** 判断sdcard上的日志文件是否可以删除** @param createDateStr* @return*/fun canDeleteSDLog(createDateStr: String?): Boolean {var canDel = falseval calendar = Calendar.getInstance()calendar.add(Calendar.DAY_OF_MONTH, -1 * SDCARD_LOG_FILE_SAVE_DAYS) // 删除7天之前日志val expiredDate = calendar.timetry {val createDate = sdf.parse(createDateStr)canDel = createDate.before(expiredDate)} catch (e: ParseException) {Log.e(TAG, e.message, e)canDel = false}return canDel}/*** 删除内存中的过期日志,删除规则: 除了当前的日志和离当前时间最近的日志保存其他的都删除*/private fun deleteMemoryExpiredLog() {val file = File(LOG_PATH_MEMORY_DIR)if (file.isDirectory) {val allFiles = file.listFiles()Arrays.sort(allFiles, FileComparator())for (i in 0 until (allFiles.size - 2)) { // "-2"保存最近的两个日志文件val _file = allFiles[i]if (((logServiceLogName == _file.name) || (_file.name == CURR_INSTALL_LOG_NAME))) {continue}_file.delete()Log.d(TAG, ("delete expired log success,the log path is:"+ _file.absolutePath))}}}/*** 拷贝文件** @param source* @param target* @return*/private fun copy(source: File, target: File): Boolean {var `in`: FileInputStream? = nullvar out: FileOutputStream? = nulltry {if (!target.exists()) {val createSucc = target.createNewFile()if (!createSucc) {return false}}`in` = FileInputStream(source)out = FileOutputStream(target)val buffer = ByteArray(8 * 1024)var count: Intwhile ((`in`.read(buffer).also { count = it }) != -1) {out.write(buffer, 0, count)}return true} catch (e: Exception) {e.printStackTrace()Log.e(TAG, e.message, e)recordLogServiceLog("copy file fail")return false} finally {try {`in`?.close()out?.close()} catch (e: IOException) {e.printStackTrace()Log.e(TAG, e.message, e)recordLogServiceLog("copy file fail")return false}}}/*** 记录日志服务的基本信息 防止日志服务有错,在LogCat日志中无法查找 此日志名称为Log.log** @param msg*/private fun recordLogServiceLog(msg: String) {if (writer != null) {try {val time = Date()writer.write(myLogSdf.format(time) + " : " + msg)writer.write("\n")writer.flush()} catch (e: IOException) {e.printStackTrace()Log.e(TAG, e.message, e)}}}/*** 去除文件的扩展类型(.log)** @param fileName* @return*/private fun getFileNameWithoutExtension(fileName: String): String {return fileName.substring(0, fileName.indexOf("."))}internal inner class ProcessInfo() {var user: String? = nullvar pid: String? = nullvar ppid: String? = nullvar name: String? = nulloverride fun toString(): String {val str = ("user=" + user + " pid=" + pid + " ppid=" + ppid+ " name=" + name)return str}}internal inner class StreamConsumer : Thread {var `is`: InputStreamvar list: MutableList<String?>? = nullconstructor(`is`: InputStream) {this.`is` = `is`}constructor(`is`: InputStream, list: MutableList<String?>?) {this.`is` = `is`this.list = list}override fun run() {try {val isr = InputStreamReader(`is`)val br = BufferedReader(isr)var line: String? = nullwhile ((br.readLine().also { line = it }) != null) {if (list != null) {list!!.add(line)}}} catch (ioe: IOException) {ioe.printStackTrace()}}}/*** 监控SD卡状态** @author Administrator*/internal inner class SDStateMonitorReceiver() : BroadcastReceiver() {override fun onReceive(context: Context, intent: Intent) {if ((Intent.ACTION_MEDIA_UNMOUNTED == intent.action)) { // 存储卡被卸载if (CURR_LOG_TYPE == SDCARD_TYPE) {Log.d(TAG, "SDcar is UNMOUNTED")CURR_LOG_TYPE = MEMORY_TYPELogCollectorThread().start()}} else { // 存储卡被挂载if (CURR_LOG_TYPE == MEMORY_TYPE) {Log.d(TAG, "SDcar is MOUNTED")CURR_LOG_TYPE = SDCARD_TYPELogCollectorThread().start()}}}}/*** 日志任务接收 切换日志,监控日志大小** @author Administrator*/internal inner class LogTaskReceiver() : BroadcastReceiver() {override fun onReceive(context: Context, intent: Intent) {val action = intent.actionif ((SWITCH_LOG_FILE_ACTION == action)) {LogCollectorThread().start()} else if ((MONITOR_LOG_SIZE_ACTION == action)) {checkLogSize()}}}internal inner class FileComparator() : Comparator<File> {override fun compare(file1: File, file2: File): Int {if ((logServiceLogName == file1.name)) {return -1} else if ((logServiceLogName == file2.name)) {return 1}val createInfo1 = getFileNameWithoutExtension(file1.name)val createInfo2 = getFileNameWithoutExtension(file2.name)try {val create1 = sdf.parse(createInfo1)val create2 = sdf.parse(createInfo2)if (create1.before(create2)) {return -1} else {return 1}} catch (e: ParseException) {return 0}}}override fun onDestroy() {super.onDestroy()recordLogServiceLog("LogService onDestroy")if (writer != null) {try {writer.close()} catch (e: IOException) {e.printStackTrace()}}if (process != null) {process!!.destroy()}unregisterReceiver(sdStateReceiver)unregisterReceiver(logTaskReceiver)}companion object {private val TAG = "LogService"private val MEMORY_LOG_FILE_MAX_SIZE = 10 * 1024 * 1024 // 内存中日志文件最大值,10Mprivate val MEMORY_LOG_FILE_MONITOR_INTERVAL = 10 * 60 * 1000 // 内存中的日志文件大小监控时间间隔,10分钟private val SDCARD_LOG_FILE_SAVE_DAYS = 7 // sd卡中日志文件的最多保存天数private val MONITOR_LOG_SIZE_ACTION = "com.walktour.gui.MONITOR_LOG_SIZE" // 日志文件监测actionprivate val SWITCH_LOG_FILE_ACTION = "com.walktour.gui.SWITCH_LOG_FILE_ACTION" // 切换日志文件action}
}

第二步,在AndroidManifest.xml里面注册服务

        <serviceandroid:name=".service.LogService"android:enabled="true"android:exported="true" />

当然别忘了权限

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

这样就配置好了,我们可以使用如下命令启动这个服务:
am startservice -n "软件包名/.service.LogService"

三、开机自启动

  1. 写一个shell 脚本
    vendor/xxxx/sh/logservice.sh
#!/system/bin/sh# Wait for start up
sleep 60am startservice -n "com.giec.ota_ab/com.giec.otaforab.service.LogService"

其实就是用广播启动服务而已,然后我们把这个脚本在init.rc里面设置成脚本开机启动就行了。
其实也可以使用Android中的闹钟功能AlarmManager,但是我写shell脚本的方式比较熟练了,加上第一次尝试使用AlarmManager的时候失败了,所以我就没有使用这种方式了。

  1. 编译到MK里面
+PRODUCT_COPY_FILES += \
+  $(CUR_PATH)/sh/logservice.sh:vendor/bin/logservice.sh
  1. 给执行权限
    system/core/libcutils/fs_config.cpp
{ 00550, AID_ROOT,      AID_SHELL,     0, "vendor/bin/logservice.sh" },
  1. 写成服务
    init.rc 找到自己的init.rc,有的厂商会定制 init.xxxxxx.rc
+service logservice /vendor/bin/logservice.sh
+    class main
+    user root
+    group root
+    oneshot
+    seclabel u:r:init:s0

这里我要提一个知识点,我之前在写一个需要不断循环的服务的时候,这里写的是oneshot,然后通过shell脚本里面的sleep while true 来实现循环。但是这样其实是非常耗费资源的。我找到了一个新的方式,那就是oneshot改成restart_period 86400 。oneshot表示执行一次,服务销毁之后不再开启,而restart_period 86400 后面那个参数是秒,表示每隔多久时间重新启动一次服务。这种方式更好。

四、说明

这个程序中
日志文件名和路径:
日志文件名格式为:yyyy-MM-dd HHmmss.log
内存中的日志路径 (LOG_PATH_MEMORY_DIR):/data/data/[package_name]/files/log/。
SD卡中的日志路径 (LOG_PATH_SDCARD_DIR):/mnt/sdcard/walktour/log/。

日志切换时间:
日志文件每天切换一次,切换时间是每天的凌晨0点。

日志文件大小和保存时间:
内存中的日志文件最大值 (MEMORY_LOG_FILE_MAX_SIZE):10 MB。
SD卡中的日志文件最多保存天数 (SDCARD_LOG_FILE_SAVE_DAYS):7天。

日志抓取间隔和监控:
内存中的日志文件大小监控时间间隔 (MEMORY_LOG_FILE_MONITOR_INTERVAL):10分钟。
切换日志文件的定时任务是每天凌晨0点触发,部署在 deploySwitchLogFileTask() 方法中。

日志记录类型:
日志记录类型分为两种:内存类型 (MEMORY_TYPE) 和 SD卡类型 (SDCARD_TYPE)。默认类型是 SD卡类型 (CURR_LOG_TYPE = SDCARD_TYPE)。
日志文件存储在内存中还是 SD卡中取决于当前 SD卡的挂载状态。

日志文件管理:
过期日志删除:内存中的日志文件最多保留两个最近的文件,其他过期文件会被删除;SD卡中的日志文件超过7天的会被删除。
日志文件大小监控:如果当前日志文件大小超过10 MB,会启动新的日志收集线程。

日志服务日志文件:
服务自身的日志文件 (logServiceLogName):Log.log,存储路径在内存中 (LOG_SERVICE_LOG_PATH)。

其他:
电源管理:使用 PowerManager.WakeLock 保证日志收集时设备不会进入休眠。
SD卡状态监控:通过 SDStateMonitorReceiver 接收 SD卡挂载和卸载事件,调整日志存储位置。
日志收集线程:LogCollectorThread 负责收集日志,启动时会清理日志缓存并处理日志文件。

注意:
log文件的文件名配置中有空格,可以在下面这个地方修改掉

    private val myLogSdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")

五、检查

1. 服务是否启动

如果想检查日志服务是否正在运行,请使用:
dumpsys activity services | grep LogService

console:/ # dumpsys activity services | grep LogService* ServiceRecord{886020d u0 com.giec.ota_ab/com.giec.otaforab.service.LogService}intent={cmp=com.giec.ota_ab/com.giec.otaforab.service.LogService}

这就表示服务正在运行

2. log保存地址

如果想要查看log保存的地址:
logcat -s LogService

06-27 16:39:22.463  1612  1909 D LogService: Log stored in SDcard, the path is:/storage/emulated/0/walktour/log/2024-06-27 163922.log
..........................
06-27 16:39:23.510  1612  1909 D LogService: canelLogSizeMonitorTask() succ

log中会写明日志地址

3. 查看和拉取log文件到本机

使用adb shell cat log文件地址查看
使用adb pull log文件地址 / 拉取文件到当前目录
在这里插入图片描述
可以查看到log日志已经保存下来了,但是指的注意的是由于log日志的文件名有空格,因此使用adb pull "log文件地址" / 的时候一定要在地址外面加双引号。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/35406.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于盲信号处理的声音分离——基于自然梯度的ICA算法

基于自然梯度的ICA算法主要利用相互独立的随机信号的联合概率密度是各自概率密度的乘积这一特性&#xff0c;建立了等独立性度量原则&#xff0c;具体实现如下。 首先&#xff0c;输出信号 相互独立&#xff0c;则其概率密度满足 上式中 表示 的概率密度函数&#xff0c;可以…

怎么扫描图片变成pdf格式?办公人士值得收藏的宝藏工具

将图片扫描并转换为PDF格式可以通过多种途径实现&#xff0c;无论是使用专业的扫描仪还是智能手机&#xff0c;都有相应的方法。 PDF 是什么&#xff1f; PDF&#xff0c;全称为 Portable Document Format&#xff08;便携式文档格式&#xff09;&#xff0c;是由Adobe System…

12,SPI

Flash芯片&#xff1a;W25Q64&#xff0c;可以看成一个储存器 W25Q64芯片和单片机之间的通信方式是SPI SPI:串行同步全双工&#xff0c;主从通信 判断一个设备是不是SPI通信&#xff0c;看是否有这几个线&#xff1a;SCK&#xff0c;CS&#xff0c;MISO&#xff0c;MOSI SCK…

Altium Designer软件下载安装「PCB设计软件」安装包资源获取

Altium Designer作为一款集成化的电子产品开发系统&#xff0c;它主要适用于Windows操作系统&#xff0c;为电子设计工程师们提供了一个高效、便捷的工作平台。 在Altium Designer中&#xff0c;原理图设计、电路仿真、PCB绘制编辑、拓扑逻辑自动布线、信号完整性分析和设计输出…

vuex的学习

vuex vuex是个插件&#xff0c;用于多个组件操作共享变量 引入&#xff1a;数字操作案例 基于组件自定义事件而实现的操作数字案例如下&#xff1a; App.vue <template><div id"app"><input class"num_input" type"text" v…

LeeCode 994. 腐烂的橘子

原题链接994. 腐烂的橘子 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;采用bfs遍历图&#xff0c;将烂橘子加入队列&#xff0c;然后将被烂橘子感染的橘子也加入队列&#xff0c;bfs的具体细节就不多说了&#xff0c;可以自己去搜&#xff0c;很简单&#xff0c;…

560.滑动窗口最大值

滑动窗口最大值 239. 滑动窗口最大值 - 力扣&#xff08;LeetCode&#xff09; 题目大意&#xff0c;返回每个窗口内的最大值。 思路-优先队列 优先队列&#xff08;堆&#xff09;&#xff0c;其中的大根堆可以实时维护一系列元素中的最大值。 每当我们向右移动窗口时&#…

地形沉降测量仪器静力水准仪应用全攻略

在地形监测和地质勘探的领域中&#xff0c;地形沉降测量是一项至关重要的工作。它不仅能够及时揭示地质结构的变化&#xff0c;还能为城市规划和基础工程建设提供宝贵的数据支持。在众多测量工具中&#xff0c;静力水准仪以其高精度、高效率的特点&#xff0c;成为地形沉降测量…

AI绘画生成人物的关键词怎么写?手把手教你学会

AI绘画生成人物的关键词怎么写&#xff1f;随着人工智能技术的不断发展&#xff0c;AI绘画已成为艺术领域的一股新势力。特别是在生成人物绘画方面&#xff0c;AI技术以其独特的优势和无限的可能性&#xff0c;为我们带来了全新的创作体验。下面&#xff0c;本文将分享AI绘画生…

svn怎么新建分支,切换分支

在当前分支下&#xff0c;点svn右键&#xff0c;选择分支/标记 在选择远端地址时&#xff0c;点右边更多选项&#xff0c;打开远端版本库。找到对应的分支上级位置&#xff0c;点击确定 填写新分支名称&#xff0c;我这儿是将分支建在了branches下&#xff0c;分支名称为V1.1 填…

【MLP-BEV(7)】深度的计算。针孔相机和鱼眼相机对于深度depth的采样一个是均匀采样,一个是最大深度均匀采样

文章目录 1.1 问题提出1.1 看看DD3D 的深度是怎么处理的给出代码示例 1.2 我们看看BEVDepth的代码 1.1 问题提出 针孔相机和鱼眼相机的投影模型和畸变模型不一样&#xff0c;如果对鱼眼的模型不太了解可以到我的这篇博客【鱼眼镜头11】Kannala-Brandt模型和Scaramuzza多项式模…

vscode刷LeetCode算法题环境配置

首先&#xff0c;下载nodejs 在vscode中安装LeetCode插件 安装好进行配置 选择leetcode-cn 填上刚才下载node.exe的路径 完成之后重启一下vscode 重启之后登陆LeetCode 完成之后就可以看到题目了 点击 code now 就可以开始刷题了

【Linux】进程优先级 | 环境变量

目录 Ⅰ. 进程优先级&#xff08;Process Priority&#xff09; 1. 什么是进程优先级&#xff1f; 2. 查看系统进程 3. 修改进程优先级 4.优先级调度原理 Ⅱ. 进程的切换&#xff08;Process Switch&#xff09; 1. 竞争与独立 2. 并行与并发 3. 进程抢占 4.实现切换…

鸿蒙期末项目(完结)

两天仅睡3个小时的努力奋斗之下&#xff0c;终于写完了这个无比拉跨的项目&#xff0c;最后一篇博客总体展示一下本项目运行效果兼测试&#xff0c;随后就是答辩被同学乱沙&#xff08;悲 刚打开软件&#xff0c;会看到如下欢迎界面&#xff0c;介绍本app的功能和优点 随后我们…

在线开发、实时交互 | 三维天地低代码开发平台助力提高项目交付速度

1.什么是低代码开发平台? 低代码开发平台基于北京三维天地科技股份有限公司自研原生技术架构研发。三维天地作为国内知名的检验检测信息化领域软件开发服务商,拥有多项自主知识产权及自主研发核心技术,致力于为客户提供信息化整体解决方案及相关软件产品与服务。 三维天地低…

【包邮送书】深度学习与信号处理

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

视觉震撼背后:带宽对渲染农场的重要性

在这个注重视觉体验的时代&#xff0c;无论是电影、电子游戏还是虚拟现实&#xff08;VR&#xff09;&#xff0c;令人印象深刻的视觉效果都依赖于渲染农场的强大能力。而带宽&#xff0c;则是确保这些画面能够迅速且以高清晰度传递给我们的核心要素。 一、核心概念&#xff1a…

1.2章节input输入函数语法使用和示例

在Python中&#xff0c;input() 函数用于从用户获取输入。这个函数会等待用户输入一行文本&#xff0c;然后按回车键&#xff0c;最后将输入的文本作为字符串返回。 一、基本语法 当你运行上面的代码时&#xff0c;它会打印出 "请输入一些文字: "&#xff0c;然后等…

工商银行:低息差下的挣扎

时隔四年&#xff0c;市值再度超越贵州茅台成为A股“股王”。 今天要说的就是“宇宙行”——中国工商银行 虽然茅台的信仰开始崩塌&#xff0c;但各大银行股巨头们今年也不好过。2024年一季度六大行业绩集体受挫&#xff0c;息差普遍收窄超过20个基点。其中&#xff0c;包括工…

航天航空零部件装配制造MES系统解决方案详解

航天航空零部件制造行业是一个技术密集、工艺复杂且对精度和可靠性要求极高的行业。为了提升生产效率、保证产品质量并满足严格的行业标准&#xff0c;越来越多的航天航空零部件制造企业引入了MES系统。本文将详细介绍MES系统在航天航空零部件制造行业的应用方法及其价值。 一…