• 《自拍教程44》Python adb一键截取Logcat日志


    ​本篇文章难度较大,阅读时间较长,历劫进阶。。。

    Android系统测试或App测试过程中,
    有的Android终端设备由于磁盘太小,未开启随系统自启动logcat序列log,
    即未实时在后台截取Logcat log,所以需要测试人员进行手动截取Logcat Log,
    这种情况下, 一般我们是直接cmd, adb logcat -v threadtime > D:logcat.txt,
    通过以上命令来进行手动截取Logcat 日志。
    我曾经考虑过一种更方便快捷的截取一键截取Logcat的Python脚本,
    直接点击运行该Python脚本即可,无需手动敲以上命令!
    本周重点介绍的是通过是subprocess.Popen()类进行命令行的调用,销毁(杀进程)!


    准备阶段
    1. adb logcat -v threadtime > D:logcat_20200310_101112.txt可以打印按线程时间log并保存到一个文件。
    2. 由于adb logcat命令是一个持续输出的命令,
      它如果没被销毁(杀进程),会一直持续截取下去。
    3. os.system()函数和os.popen()函数并不适合做子进程销毁,
      这时候就要考虑更高端的subprocess.Popen()类来实现进程销毁。

    subprocess.Popen()类介绍

    Python官方推荐使用subprocess.Popen()类来调起其他命令(即创建子进程),
    其具备优质的子进程管理(创建,销毁,进行标准输入,进行标准输出,进行错误输出等)特性。
    为什么说他是一个类,而不是一个函数,请看源码截图:

    既然是类,则首先先看下这个类的初始化的源代码:

        def __init__(self, args, bufsize=-1, executable=None,
                     stdin=None, stdout=None, stderr=None,
                     preexec_fn=None, close_fds=True,
                     shell=False, cwd=None, env=None, universal_newlines=None,
                     startupinfo=None, creationflags=0,
                     restore_signals=True, start_new_session=False,
                     pass_fds=(), *, encoding=None, errors=None, text=None):
    

    我们只需要了解以下几个初始化的参数即可:

    参数 释义 参数初始化举例
    args 需要子进程执行的命令或者命令数组 args="adb reboot"
    args=["adb", "reboot"]
    stdin standard input
    对某个子进程的标准输入对象进行定义
    如果我们的子进程后续是不需要进行交互式输入的:
    stdin=None (默认值)
    如果我们的子进程是需要进行交互式输入的:
    stdin=subprocess.PIPE
    stdin=None

    stdin=subprocess.PIPE
    stdout standard output
    对某个子进程的标准输出进行定义。
    如果我们不需要捕捉标准输出则:stdout=None(默认值)
    如果需要对标准输出进行捕捉及分析:stdout=subprocess.PIPE
    如果需要对标准输出进行存储:stdout=open("log.txt","wb")

    stdout=None

    stdout=subprocess.PIPE

    stdout=open("log.txt","wb")
    stderr standard error
    对某个子进程的标准异常错误提示信息进行定义,
    如果我们不需要捕异常错误信息则:stderr=None(默认值)
    如果需要对异常错误进行捕捉及分析:stderr=subprocess.PIPE
    如果需要对异常错误进行存储:stderr=open("log.txt","wb")

    stderr=None

    stderr=subprocess.PIPE

    stderr=open("log.txt","wb")
    shell 如果子进程是必须通过cmd /c
    或者/system/bin/sh -c 来执行的,则必须shell=True。
    我们一般的命令,请直接用shell=False(默认值)即可。

    比如如果在windows上执行adb命令,
    如果shell=True, 将额外调用起cmd进程,然后是adb进程
    如果shell=False, 将只会调用起adb进程(建议用这个)

    shell=False

    shell=True

    是类,则肯定需要实例化成对象后才能进行具体的操作,
    且是类,则就涉及类的属性(变量)定义,方法(函数)定义,
    我们假定初始化后的对象名是p_obj, 释义如下:

    属性(变量)列表 释义
    p_obj.pid 子进程对象的:进程pid
    p_obj.returncode 子进程对象的:当前的状态
    None:代表子进程命令还在执行中,无法获取其状态
    0:代表正常执行了子进程命令并正常结束
    1:代表异常结束,比如被kill或者terminate了
    p_obj.args 子进程对象执行的:具体命令

    方法(函数)列表 释义
    方法(函数)列表 释义
    p_obj.kill() Windows系统上,与terminate()功能相同,都是杀掉进程
    Linux系统上,发送的是SIGTKILL信号,即强制杀掉你这个进程,类似kill -9

    万能建议用法(顺序执行以下2个函数):
    p_obj.terminate()
    p_obj.kill()
    p_obj.terminate() Windows系统上,与terminate()功能相同,都是杀掉进程
    Linux系统上,发送的是SIGTERM信号,即通知子进程赶紧自行结束,类似kill -15

    万能用法(顺序执行以下2个函数):
    p_obj.terminate()
    p_obj.kill()

    Python批处理脚本形式
    # coding=utf-8
    import os
    import subprocess
    from datetime import datetime
    
    command = "adb logcat -v threadtime"
    
    now_time = datetime.now().strftime("%Y%m%d_%H%M%S")
    log_file = "Logcat_" + now_time + ".txt"
    
    hf = open("%s" % log_file, "wb")
    # 因为没指定具体路径,默认就是在当前脚本运行的路径下创建这个log_file
    
    # 开始执行adb 命令
    p_obj = subprocess.Popen(
            args=command,
            stdin=None, stdout=hf,
            stderr=hf, shell=False)
    
    print("Logcat catching...")
    # 持续询问是否需要停止截取
    judgement = input("If you think it is enough, please input Y:")
    while (judgement != "Y" and judgement != "y"):  ###这里必须是and.
        print("Invalid input, please input Y")
        judgement = input("If you think it is enough, please input Y:")
    else:
        if (judgement == "Y" or judgement == "y"):  # 如果收到停止Yes信号,则开始结束截取
            p_obj.terminate()
            p_obj.kill()
    
    hf.close()
    print("Logcat log has saved to %s" % os.path.abspath(log_file))
    os.system("pause")
    

    Python面向过程函数形式

    本次优化,将COMMAND作为常量,常量一般是编码风格是全部大写。
    涉及文件open读写的,尽量用with形式。

    # coding=utf-8
    import os
    import subprocess
    from datetime import datetime
    
    COMMAND = "adb logcat -v threadtime"
    
    
    def catch_logcat():
        now_time = datetime.now().strftime("%Y%m%d_%H%M%S")
        log_file = "Logcat_" + now_time + ".txt"
        # 因为没指定具体路径,默认就是在当前脚本运行的路径下创建这个log_file
    
        # 开始执行adb 命令
        with open("%s" % log_file, "wb") as hf:
            p_obj = subprocess.Popen(
                    args=COMMAND,
                    stdin=None, stdout=hf,
                    stderr=hf, shell=False)
    
            print("Logcat catching...")
            # 持续询问是否需要停止截取
            judgement = input("If you think it is enough, please input Y:")
            while (judgement != "Y" and judgement != "y"):  ###这里必须是and.
                print("Invalid input, please input Y")
                judgement = input("If you think it is enough, please input Y:")
            else:
                if (judgement == "Y" or judgement == "y"):  # 如果收到停止Yes信号,则开始结束截取
                    p_obj.terminate()
                    p_obj.kill()
    
        return os.path.abspath(log_file)
    
    
    print("Logcat log has saved to %s" % catch_logcat())
    os.system("pause")
    

    Python面向对象形式

    本次优化:

    1. 在类的初始化的时候,把必须要做的事做完。
      LogcatCatcher()类初始化不需要任何参数输入,但是需要新建2个属性,
      且必须在初始化的时候把这2个属性新建好。
    2. 非必要暴露给其他模块的函数(或属性),尽量考虑私有化,加_即可,比如_create_hf(self)函数,就是私有函数。
    # coding=utf-8
    import os
    import subprocess
    from datetime import datetime
    
    COMMAND = "adb logcat -v threadtime"  # 常量
    
    
    class LogcatCathcher():
        def __init__(self):
            self.log_file = None
            self.hf = None
            self._create_hf()
            self.p_obj = subprocess.Popen(
                    args=COMMAND,
                    stdin=None, stdout=self.hf,
                    stderr=self.hf, shell=False)
    
        def _create_hf(self):
            now_time = datetime.now().strftime("%Y%m%d_%H%M%S")
            self.log_file = "Logcat_" + now_time + ".txt"
            # 因为没指定具体路径,默认就是在当前脚本运行的路径下创建这个log_file
            self.hf = open("%s" % self.log_file, "wb")
    
        def catch_logcat(self):
            print("Logcat catching...")
            # 持续询问是否需要停止截取
            judgement = input("If you think it is enough, please input Y:")
            while (judgement != "Y" and judgement != "y"):  ###这里必须是and.
                print("Invalid input, please input Y")
                judgement = input("If you think it is enough, please input Y:")
            else:
                if (judgement == "Y" or judgement == "y"):  # 如果收到停止Yes信号,则开始结束截取
                    self.p_obj.terminate()
                    self.p_obj.kill()
                    self.hf.close()  # 关闭文件句柄
            return os.path.abspath(self.log_file)
    
    
    if __name__ == '__main__':
        l_obj = LogcatCathcher()
        print("Logcat log has saved to %s" % l_obj.catch_logcat())
        os.system("pause")
    

    代码运行方式

    确保Android车机设备通过USB线与电脑连接了,adb设备有效连接,
    以上代码的3种实现形式都可以直接运行,比如保存为catch_logcat.py并放在桌面,
    建议python catch_logcat.py运行,当然也可以双击运行。
    运行效果如下:


    碰到复杂的源代码,不要着急,慢慢地按以上方法去剖析,
    非常有必要查看Python官方的subprocess模块文档或源代码上的注释文档;
    如果还是不懂,可以看python关于subprocess的源码(C:Users
    AdministratorAppDataLocalProgramsPythonPython37-32Libsubprocess.py),
    如果想知道别人是怎么调用的,可以用findstr或者grep搜下python37-32整个文件夹下,
    有多少源代码的模块调用了subprocess.Popen,看别人是怎么调用的。
    如果自己在调用的方法上没问题,但是报错了,可以去www.bing.com上搜下代码错误的原因,
    方法总是比困难多,各位加油!


    更多更好的原创文章,请访问官方网站:www.zipython.com
    自拍教程(自动化测试Python教程,武散人编著)
    原文链接:https://www.zipython.com/#/detail?id=d6b406f34b4f470a9d95eddc426a273c
    也可关注“武散人”微信订阅号,随时接受文章推送。

  • 相关阅读:
    linux下so动态库一些不为人知的秘密(中二)
    linux下so动态库一些不为人知的秘密(中)
    linux下so动态库一些不为人知的秘密(上)
    Linux下gcc编译控制动态库导出函数小结
    解决Linux动态库版本兼容问题
    MySQL按天,按周,按月,按时间段统计【转载】
    MySQL统计函数记录——按月、按季度、按日、时间段统计以及MySQL日期时间函数大全
    RequestMapping中produces属性作用
    出现 java.net.ConnectException: Connection refused 异常的原因及解决方法
    Springboot应用中@EntityScan和@EnableJpaRepositories的用法
  • 原文地址:https://www.cnblogs.com/zipython/p/12512992.html
Copyright © 2020-2023  润新知