【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? = null

    private val sdf = SimpleDateFormat("yyyy-MM-dd HHmmss") // 日志名称格式

    private var process: Process? = null

    private var wakeLock: PowerManager.WakeLock? = null

    private var sdStateReceiver: SDStateMonitorReceiver? = null // SDcard状态监测
    private var logTaskReceiver: LogTaskReceiver? = null

    /*
	 * 是否正在监测日志文件大小; 如果当前日志记录在SDcard中则为false 如果当前日志记录在内存中则为true
	 */
    private var logSizeMoniting = false

    override 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 PowerManager
        wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "YourAppName:LogService")


        CURR_LOG_TYPE = currLogType
        Log.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] = 0
        calendar[Calendar.MINUTE] = 0
        calendar[Calendar.SECOND] = 0


        // 部署任务
        val am = getSystemService(ALARM_SERVICE) as AlarmManager
        am.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? = null
        val 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.packageName
        val 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 /init
            val 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? = null
            try {
                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 = logFileName
                Log.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 = null
                Log.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 = true
        val intent = Intent(MONITOR_LOG_SIZE_ACTION)
        val sender = PendingIntent.getBroadcast(this, 0, intent, 0)
        val am = getSystemService(ALARM_SERVICE) as AlarmManager
        am.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 = false
        val am = getSystemService(ALARM_SERVICE) as AlarmManager
        val 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: Boolean
        if (!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.name
                if ((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.name
                if ((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 = false
        val calendar = Calendar.getInstance()
        calendar.add(Calendar.DAY_OF_MONTH, -1 * SDCARD_LOG_FILE_SAVE_DAYS) // 删除7天之前日志
        val expiredDate = calendar.time
        try {
            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? = null
        var out: FileOutputStream? = null
        try {
            if (!target.exists()) {
                val createSucc = target.createNewFile()
                if (!createSucc) {
                    return false
                }
            }
            `in` = FileInputStream(source)
            out = FileOutputStream(target)
            val buffer = ByteArray(8 * 1024)
            var count: Int
            while ((`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? = null
        var pid: String? = null
        var ppid: String? = null
        var name: String? = null

        override fun toString(): String {
            val str = ("user=" + user + " pid=" + pid + " ppid=" + ppid
                    + " name=" + name)
            return str
        }
    }

    internal inner class StreamConsumer : Thread {
        var `is`: InputStream
        var list: MutableList<String?>? = null

        constructor(`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? = null
                while ((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_TYPE
                    LogCollectorThread().start()
                }
            } else { // 存储卡被挂载
                if (CURR_LOG_TYPE == MEMORY_TYPE) {
                    Log.d(TAG, "SDcar is MOUNTED")
                    CURR_LOG_TYPE = SDCARD_TYPE
                    LogCollectorThread().start()
                }
            }
        }
    }

    /**
     * 日志任务接收 切换日志,监控日志大小
     *
     * @author Administrator
     */
    internal inner class LogTaskReceiver() : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val action = intent.action
            if ((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 // 内存中日志文件最大值,10M
        private 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" // 日志文件监测action
        private val SWITCH_LOG_FILE_ACTION = "com.walktour.gui.SWITCH_LOG_FILE_ACTION" // 切换日志文件action
    }
}

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

        <service
                android: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 60

am 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.mfbz.cn/a/750533.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Transformer教程之神经网络和深度学习基础

在当今的人工智能领域&#xff0c;Transformer已经成为了一个热门的词汇。它不仅在自然语言处理&#xff08;NLP&#xff09;领域取得了巨大的成功&#xff0c;还在计算机视觉等其他领域展现出了强大的潜力。然而&#xff0c;要真正理解Transformer&#xff0c;我们首先需要扎实…

希喂生骨肉冻干值得入手吗?拯救瘦弱、增强抵抗力最强主食测评!

希喂生骨肉冻干值得入手吗&#xff1f;很多小姐妹觉着自家猫咪太瘦了、体质不咋好&#xff0c;换季还敏感、掉毛、不吃东西&#xff0c;听说生骨肉冻干好吸收、营养好&#xff0c;可以改善体质、拯救瘦弱、增强抵抗力&#xff0c;为了图省事&#xff0c;开始盲入生骨肉冻干&…

Linux—进程与计划管理

目录 一、程序 二、进程 1、什么是进程 2、进程的特点 3、进程、线程、携程 3.1、进程 3.2、线程 3.3、携程 三、查看进程信息 1、ps -aux 2、ps -elf 3、top ​3.2、输出内容详解 3.2.1、输出第一部分解释 3.2.2、输出第二部分解释 4、pgrep 5、pstree 四、进…

The ‘textprediction‘ attribute will be removed in the future

页面标签不展示&#xff0c;明明是复制的&#xff0c;反复检查&#xff0c;眼睛都看瞎了&#xff0c;也没找到&#xff0c;最后还是看后台报错&#xff0c;The textprediction attribute will be removed in the future说什么要被废弃&#xff0c;但是好好的标签怎么会无缘无辜…

C语言 | Leetcode C语言题解之第191题位1的个数

题目&#xff1a; 题解&#xff1a; int hammingWeight(uint32_t n) {int ret 0;while (n) {n & n - 1;ret;}return ret; }

2024最新特种设备(锅炉作业)题库分享。

1.锅炉蒸发量大小是由(  )决定的。 A.压力的高低 B.受压元件多少 C.受热面积大小 答案:C 2.哪项不是自然循环的故障?&#xff08; &#xff09; A.停滞 B.倒流 C.下降管带汽 D.上升管带汽 答案:D 3.水冷壁被现代大型锅炉广泛采用的是(  )。 A.光管水冷壁 B.膜…

龙迅LT8711V TYPE-CDP 1.2转VGA芯片,内置MCU,成熟批量产品

龙迅LT8711V描述&#xff1a; LT8711V是一种高性能的Type-C/DP1.2到VGA转换器&#xff0c;设计用于连接USB Type-C源或DP1.2源到VGA接收器。LT8711V集成了一个DP1.2兼容的接收器&#xff0c;和一个高速三通道视频DAC。此外&#xff0c;还包括两个CC控制器&#xff0c;用于CC通…

SherlockChain:基于高级AI实现的智能合约安全分析框架

关于SherlockChain SherlockChain是一款功能强大的智能合约安全分析框架&#xff0c;该工具整合了Slither工具&#xff08;一款针对智能合约的安全工具&#xff09;的功能&#xff0c;并引入了高级人工智能模型&#xff0c;旨在辅助广大研究人员针对Solidity、Vyper和Plutus智…

个人支付系统实现

基础首页&#xff1a; 订单&#xff1a; 智能售卡系统 基于webmanworkerman开发 禁用函数检查 使用这个脚本检查是否有禁用函数。命令行运行curl -Ss https://www.workerman.net/check | php 如果有提示Function 函数名 may be disabled. Please check disable_functions in …

显卡GTX与RTX有什么区别?哪一个更适合玩游戏?

游戏发烧友们可能对游戏显卡并不陌生&#xff0c;它直接关系到游戏画面的流畅度、细腻程度和真实感。在众多显卡品牌中&#xff0c;英伟达的GTX和RTX系列显卡因其出色的性能而备受关注。 一、GTX与RTX的区别 架构差异 GTX系列显卡采用的是Pascal架构&#xff0c;这是英伟达在…

Redis 7.x 系列【7】数据类型之列表(List)

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Redis 版本 7.2.5 源码地址&#xff1a;https://gitee.com/pearl-organization/study-redis-demo 文章目录 1. 概述2. 常用命令2.1 RPUSH2.2 LPUSH2.3 LRANGE2.4 LINDEX2.6 LREM2.7 LLEN2.8 LPOP…

AGI 远不止 ChatGPT!一文入门 AGI 通识及应用开发_通向agi之路网站使用什么开发的网站

AI 大语言模型进入爆发阶段 2022 年 12 月 ChatGPT 突然爆火&#xff0c;原因是其表现出来的智能化已经远远突破了我们的常规认知。虽然其呈现在使用者面前仅仅只是一个简单的对话问答形式&#xff0c;但是它的内容化水平非常强大&#xff0c;甚至在某些方面已经超过人类了&am…

WordPress Dokan Pro插件 SQL注入漏洞复现(CVE-2024-3922)

0x01 产品简介 WordPress Dokan Pro插件是一款功能强大的多供应商电子商务市场解决方案,功能全面、易于使用的多供应商电子商务平台解决方案,适合各种规模的电商项目。允许管理员创建一个多卖家平台,卖家可以注册账户并在平台上创建自己的店铺,展示和销售自己的产品。提供…

python API自动化(基于Flask搭建MockServer)

接口Mock的理念与实战场景: 什么是Mock: 在接口中&#xff0c;"mock"通常是指创建一个模拟对象来代替实际的依赖项&#xff0c;以便进行单元测试。当一个类或方法依赖于其他类或组件时&#xff0c;为了测试这个类或方法的功能&#xff0c;我们可以使用模拟对象来替代…

k-NN 剪辑近邻法

本篇文章是博主在人工智能等领域学习时&#xff0c;用于个人学习、研究或者欣赏使用&#xff0c;并基于博主对人工智能等领域的一些理解而记录的学习摘录和笔记&#xff0c;若有不当和侵权之处&#xff0c;指出后将会立即改正&#xff0c;还望谅解。文章分类在AI学习笔记&#…

【分享】30秒在线自助制作电子证件照

近期由于自己需要制作电子证件照&#xff0c;所以在网上找在线制作电子证件照的网站&#xff0c;找了很多网站都是收费的&#xff0c;也下载了很多app制作&#xff0c;都是要收费的。最后&#xff0c;所以索性自己开发一个网站制作电子证件照。这里分享给需要的朋友。&#xff…

Jenkins教程-9-发送企业微信测试报告通知

上一小节我们学习了Jenkins上下游关联自动化测试任务的构建的方法&#xff0c;本小节我们讲解一下发送企业微信测试报告通知的方法。 1、自动化用例执行完后&#xff0c;使用pytest_terminal_summary钩子函数收集测试结果&#xff0c;存入本地status.txt文件中&#xff0c;供J…

点云可视化 .ply文件 | 方案汇总

前言 本文分析可视化点云.ply文件的几种方法&#xff0c;包括MeshLab软件、在线可视化点云.ply文件、通过PyntCloud库编程实现。 PLY是一种用于存储三维数据的文件格式&#xff0c;常用于点云数据和多边形网格。 被广泛应用于计算机图形学、3D扫描和3D打印等领域。PLY文件可…

React的Props、生命周期

Props 的只读性 “Props” 是 React 中用于传递数据给组件的一种机制&#xff0c;通常作为组件的参数进行传递。在 React 中&#xff0c;props 是只读的&#xff0c;意味着一旦将数据传递给组件的 props&#xff0c;组件就不能直接修改这些 props 的值。所以组件无论是使用函数…

【GD32】08 - IIC(以SHT20为例)

GD32中的IIC 今天来了解一下GD32中的硬件IIC&#xff0c;其实我个人是觉得软件IIC比较方便的&#xff0c;不过之前文章里用的都是软件IIC&#xff0c;今天就算是走出自己的舒适圈&#xff0c;我们来了解了解GD32中的硬件IIC。 我这里用的型号是GD32F407&#xff0c;不同型号的…