多设备monkey测试工具_基于Tkinter GUI操作

tkinter Python(图形开发界面) Tkinter模块("Tk 接口")是Python的标准Tk GUI工具包的接口.Tk和Tkinter可以在大多数的Unix平台下使用,同样可以应用在Windows和Macintosh系统里.Tk8.0的后续版本可以实现本地窗口风格,并良好地运行在绝大多数平台中。 阅读详情

V1.0版本

先看下效果吧:

简单的介绍下:

1.设备列表区域:用于展示当前连接的所有设备

2.monkey参数区域:用于显示即将执行的monkey参数

3.日志区域:用于显示必要日志(其实没啥卵用,就是看着高大上点)

操作流程:

1.刷新设备,获取当前连接的所有设备(必须)

2.填写monkey参数:

  a).package:手动填写需要跑的APP包名;自动获取,需要现在左侧点击选择一台设备,打开设备上需要获取包名的应用,然后点击“获取包名”即可。

  b).seed:随机值,默认取当前日期,这个纯属个人习惯

  c).Throttle:操作间隔,大家都懂。。。

  d).Count:总的操作次数,我默认的50万,感觉这个差不多吧。

 3.点击“RUN”就好了,所有设备都会跑起来

4.没做离线功能,所以还是需要连着数据线的

结果查看:

  同目录下会生成一个result文件夹,里面包括Crash日志、内存日志、monkeylog信息,直接取就好了

代码:

import glob
import json
import logging
import os
import re
import time
import tkinter as tk
import tkinter.messagebox

window = tk.Tk()

window.title("Demo APP")

width = 640
height = 730

# 设置窗口适应属性
window.resizable(width=False, height=False)
window.attributes("-alpha",0.7)
window.attributes('-toolwindow', False, 
                '-alpha', 1, 
                '-fullscreen', False, 
                '-topmost', True)
# window.overrideredirect(True)  

# 获取当前屏幕分辨率
screenwidth = window.winfo_screenwidth()  
screenheight = window.winfo_screenheight() 

# 根据分辨率设置窗口大小
alignstr = '%dx%d+%d+%d' % (width, height, (screenwidth-width)/1, (screenheight-height)/5)
window.geometry(alignstr)

# ----frame-----
frame_main1 = tk.Frame(window, bg = "#F2F2F2", height=100, width=360)
frame_main1.grid(column=1,row=1,padx=10,pady=10)

frame_main2 = tk.Frame(window, bg = "#F2F2F2", height=100, width=400)
frame_main2.grid(column=2,row=1)

frame_main3 = tk.Frame(window, bg = "#F2F2F2", height=200, width=370)
frame_main3.grid(column=1,row=2)

frame_main4 = tk.Frame(window, bg ="#F2F2F2", height=200, width=360)
frame_main4.grid(column=2,row=2)

frame_main5 = tk.Frame(window, bg = "#F2F2F2", height=300, width=720)
frame_main5.grid(column=1,row=3,columnspan=2,padx=15,pady=15)

# -----menu菜单功能,暂时不要后续添加--------
# meun_main = tk.Menu()
# # meun_main.grid()
# filemenu = tk.Menu(meun_main, tearoff=1)
# meun_main.add_cascade(label='File', menu=filemenu)
# filemenu.add_command(label='Monkey')
# filemenu.add_command(label='Screen')
# filemenu.add_command(label='Instructions')
# filemenu.add_command(label='About')
# window.config(menu = filemenu)

# -------Log缓存区域,显示自定义日志--------
lable_log=tk.Label(frame_main5,text="日志区",font = ("微软雅黑",14))
lable_log.grid(column=1,row=1)

text_log = tk.Text(frame_main5,bg="#5B5B5B",fg="#FFFFFF")
text_log.grid(column=1,row=2)


def clear_log():
    text_log.delete("1.0","end")

button_clearLog = tk.Button(frame_main5,text="Clear",font = ("微软雅黑",8),bg="#836FFF", fg= "#FFFFFF",command=clear_log)
button_clearLog.grid(column=2,row=2,sticky="n")

# text_log.insert("end","adasd21dadd")


# ------主页lable显示引导---------------
# 刷新设备说明
lable_main1 = tk.Label(frame_main1, justify="left", height=5, width=35, bg="#E5E5E5", font = ("微软雅黑",10), text="准备工作:\n1.进入开发者选项打开测试机USB调试功能\n2.点击刷新设备\n3.手机端确认USB调试(若未提示则跳过此步骤)\n4.设备连接成功")
lable_main1.grid()
# monkey参数说明
lable_main2 = tk.Label(frame_main2, justify="left", height=5, width=40, bg="#E5E5E5", font = ("微软雅黑",10), text="参数说明:\n1.Package:包名,可点击获取,也可以手动输入\n2.Seeds:随机值,默认取当天日期,也可手动输入\n3.Throttle:点击频率,默认300ms,可手动输入\n4.Count:总操作数,默认50万,可手动修改")
lable_main2.grid()

# ----listbox用于显示当前设备列表-----
lb = tk.Listbox(frame_main3,height=9, width=28, bg="#CDCDB4", fg="#551A8B")
lb.grid(column=0,row=0,padx=10,pady=10)


# -----frame4上的参数配置-----
lab1 = tk.Label(frame_main4,text="配置项",bg="#F2F2F2",font=(11))
lab2 = tk.Label(frame_main4,text="Package",bg="#F2F2F2" )
lab3 = tk.Label(frame_main4,text="Seeds",bg="#F2F2F2" )
lab4 = tk.Label(frame_main4,text="Throttle",bg="#F2F2F2" )
lab5 = tk.Label(frame_main4,text="Count",bg="#F2F2F2" )
lab6 = tk.Label(frame_main4,text="参数",bg="#F2F2F2",font=(11) )  #第二列标题哦


lab1.grid(column=1,row=1,padx=5,pady=5)
lab2.grid(column=1,row=2,padx=5,pady=5,sticky="w")
lab3.grid(column=1,row=3,padx=5,pady=5,sticky="w")
lab4.grid(column=1,row=4,padx=5,pady=5,sticky="w")
lab5.grid(column=1,row=5,padx=5,pady=5,sticky="w")
lab6.grid(column=2,row=1,padx=1,pady=1)


# -----entry monkey参数配置-------
entry1 = tk.Entry(frame_main4)
entry2 = tk.Entry(frame_main4)
entry3 = tk.Entry(frame_main4)
entry4 = tk.Entry(frame_main4)

entry1.grid(column=2,row=2,padx=5,pady=1,sticky="w")
entry2.grid(column=2,row=3,padx=5,pady=1,sticky="w")
entry3.grid(column=2,row=4,padx=5,pady=1,sticky="w")
entry4.grid(column=2,row=5,padx=5,pady=1,sticky="w")


# ------一些可能用到的全局变量------
select_var = ""
pkg_details = ""
button_state = ""
model_name = ""
# 创建一个空list存储设备
list_device = []
# monkey配置参数
p = ""
s = ""
t = ""
c = ""

def find_and_init():
    # def find_devices():
    global list_device,model_name
    list_device.clear()
    logging.basicConfig(level=logging.INFO)
    d_lists = os.popen("adb devices").readlines()
    logging.error(d_lists)
    d_lists.remove(d_lists[len(d_lists)-1])
    d_lists.remove(d_lists[0])
    lb.delete("0","end")
    logging.info(d_lists)
    # time.sleep(3)
    if len(d_lists)==0:
        lb.insert("end","未检测到设备,请连接测试机...")
        text_log.insert("end","未检测到设备,请连接测试机...\n")
        text_log.yview_moveto(1)
        text_log.update()
    elif "unauthorized" in d_lists[0]:
        lb.insert("end","请在手机上点击允许USB调试...")
        text_log.insert("end","请在手机上点击允许USB调试...\n")
        text_log.yview_moveto(1)
        text_log.update()
    else:
        
        text_log.insert("end","设备已连接,请选择设备进行后续操作!\n\n")
        text_log.yview_moveto(1)
        text_log.update()
        for i in d_lists:
            reg_serial = re.findall("(.*?)device", i)
            model_org = os.popen("adb -s "+str(reg_serial[0])+" shell getprop ro.product.model").readlines()
            logging.info(reg_serial[0].replace("\t","")+":"+model_org[0].replace("\n",""))

            # 将设备信息添加到listbox中
            per_list = reg_serial[0].replace("\t","")
            print("设备列表为:",per_list)
            os.system("adb -s "+str(per_list)+" logcat -c")
            lb.insert("end",model_org[0].replace("\n","").replace(" ","")+":"+per_list)
            model_name = model_org[0].replace("\n","").replace(" ","")
            list_device.append(per_list)


# ------读写配置文件-----------
    # def init_config():
    entry1.delete(0,"end")
    entry2.delete(0,"end")
    entry3.delete(0,"end")
    entry4.delete(0,"end")
    # time.sleep(3)
    if os.path.exists("config.json"):
        with open('config.json',encoding="utf-8") as ff:
            json_data = json.load(ff)
            entry1.insert("end",json_data["pkg"])
            entry2.insert("end", time.strftime("%Y%m%d%H%M%S", time.localtime())[2:8])
            entry3.insert("end", json_data["thr"])
            entry4.insert("end", json_data["total"])
            
    else:
        config_file = open('config.json','w',encoding='utf-8')
        # json_data = {"pkg":"请在此处填写包名...","sd":time.strftime("%Y%m%d%H%M%S", time.localtime())[2:8],"thr":"300","total":"500000"}
        json_data = {"pkg":"请在此处填写包名...", "thr":"300","total":"500000"}
        json.dump(json_data,config_file,ensure_ascii=False)
        config_file.close()

    # p=entry1.get()
    # s=entry2.get()
    # t=entry3.get()
    # c=entry4.get()

#  -----------按钮事件--------
# 刷新设备按钮
button = tk.Button(frame_main3,text="刷新设备",font = ("微软雅黑",15),bg="#8FBC8F", fg= "#FFFFFF", command=find_and_init)
button.grid(column=0,row=1)


# --------获取当前包名---------
def get_pkg():
    global select_var
    global pkg_details
    logging.info(select_var)
    # global select_var
    selected=lb.get(lb.curselection())

    print("我也不知道这里会不会报错 ",selected)

    print("原始的",selected,type(selected))
    reg_device=".*?:(.*)"
    select_device = re.findall(reg_device, selected)
    print("选择的设备是",select_device[0])
    select_var=select_device[0]
    
    try:
        cur = os.popen("adb -s "+str(select_var)+" shell dumpsys window | findstr mCurrentFocus").readline()
        result=re.findall("mCurrentFocus=Window{.*?u0 (.*?)/.*?}", cur)
        pkg_details = result[0]
        logging.info(cur)

    except IndexError:
        entry1.delete(0,"end")
        entry1.insert("end","获取失败...")
        tk.messagebox.showerror("Error了吧,哈哈哈!","请注意:\n  1.确认设备已经连接。\n  2.需要获取包名的应用处于前台状态!")
        entry1.delete(0,"end")
        entry1.insert("end","点击重试...")

    except TypeError:
        entry1.delete(0,"end")
        entry1.insert("end","未选择设备哦!")
        tk.messagebox.showerror("Error,这个不是Bug...","没有选择设备哦!")
        entry1.delete(0,"end")
        entry1.insert("end","点击重试...")


    else:
        
        entry1.delete(0,"end")
        entry1.insert("end",pkg_details)
        text_log.insert("end","获取包名成功!!!\n\n")
        text_log.yview_moveto(1)
        text_log.update()

def run_monkey():

    now_time = time.strftime("%Y%m%d%H%M%S", time.localtime())[4:12]

    if not os.path.exists("./Result/"+str(now_time)):
        text_log.insert("end","创建必要资源文件夹....\n\n")
        text_log.yview_moveto(1)
        text_log.update()
        os.makedirs("./Result/"+str(now_time))

    if not os.path.exists("./script/"+str(now_time)):
        text_log.insert("end","创建必要资源文件夹....\n\n")
        text_log.yview_moveto(1)
        text_log.update()
        os.makedirs("./script/"+str(now_time))
    global p,s,t,c
    p=entry1.get()
    s=entry2.get()
    t=entry3.get()
    c=entry4.get()

    global button_state
    button_state = "disabled"
    print(pkg_details)
    text_log.insert("end","即将开始初始化资源,请稍等...\n")
    if len(list_device)>0:

        if entry1.get() in (u"请在此处填写包名...",""):
            tk.messagebox.showerror("你又错了!!!","不出意外的话你包名肯定不对!!")

        else:

            for d in list_device:
                bat_monkey = str(d[:8])+"_monkey_"+str(pkg_details)+".bat"
                with open ("script\\"+str(now_time)+"\\"+bat_monkey, "w") as f:
                    text_log.insert("end","生成"+str(d)+"---monkey脚本...\n")
                    text_log.yview_moveto(1)
                    text_log.update()
                    f.write('@echo off\necho start  monkey test\ntitle Monkey\nadb -s '+str(d)+' shell monkey -s '+str(s)+' -p '+str(p)+' --throttle '+str(t)+' --ignore-crashes --ignore-native-crashes --ignore-security-exceptions --ignore-timeouts --monitor-native-crashes -v -v -v '+str(c)+' > ./Result/'+str(now_time)+'/'+str(d[:8])+'_monkey.log')
                bat_fc = str(d[:8])+"_fc_"+str(pkg_details)+".bat"
                with open ("script\\"+str(now_time)+"\\"+bat_fc, "w") as ff:
                    text_log.insert("end","生成"+str(d)+"---crash监控脚本...\n")
                    text_log.yview_moveto(1)
                    text_log.update()
                    ff.write('@echo off\necho Start collecting crash logs\ntitle Crash\nadb -s '+str(d)+' logcat -s AndroidRuntime > ./Result/'+str(now_time)+'/'+str(d[:8])+'_crash.log')
                bat_memory = str(d[:8])+"_memory_"+str(pkg_details)+".bat"
                with open ("script\\"+str(now_time)+"\\"+bat_memory, "w") as fff:
                    text_log.insert("end","生成"+str(d)+"---内存监控脚本...\n\n")
                    text_log.yview_moveto(1)
                    text_log.update()
                    fff.write('@echo off\necho Start collecting memory logs\ntitle Memory\n:memory\nadb -s '+str(d)+' shell dumpsys meminfo '+str(p)+' | findstr TOTAL: > ./Result/'+str(now_time)+'/'+str(d[:8])+'_memory.log\nping -n 30 127.0.0.1>nul\ngoto memory')
            
            bat_finally = glob.glob("script\\"+str(now_time)+"\\*.bat")
            print("最终的bat有",bat_finally)
            time.sleep(2)
            text_log.insert("end","一切准备就绪,即将启动所有骚操作!!!\n\n")
            text_log.yview_moveto(1)
            text_log.update()

            text_log.insert("end","\n包名:"+p)
            text_log.insert("end","\nseeds值:"+s)
            text_log.insert("end","\nthrottle值:"+t)
            text_log.insert("end","\n总测试数:"+c)
            
            for gogo in bat_finally:
                os.system("start "+gogo)
                time.sleep(1)
    else:
        # len(list_device)==0:
        tk.messagebox.showerror("你又错了!!!","请注意:\n  1.确认设备已经连接。\n  2.确认包名不为空!!")

# 获取包名按钮
button_getpkg = tk.Button(frame_main4,text="获取包名", fg= "#FFFFFF",font = ("",9),bg="#8FBC8F",command=get_pkg)
button_getpkg.grid(column=3,row=2,padx=5,pady=5,sticky="w")
notice_lable = tk.Label(frame_main4,text="获取包名前,\n先选择设备。", justify="left",fg="red")
notice_lable.grid(column=3,row=3)

button_make_res = tk.Button()

# Monkey启动按钮
if button_state == "disabled":
    button_runmonkey = tk.Button(frame_main4,text="Run!!!",state="disabled", fg= "#FFFFFF",font = ("微软雅黑",15),bg="#8FBC8F",command=run_monkey)
    button_runmonkey.grid(column=2,row=7,padx=25,pady=5,sticky="w")
else:
    button_runmonkey = tk.Button(frame_main4,text="Run!!!", fg= "#FFFFFF",font = ("微软雅黑",15),bg="#8FBC8F",command=run_monkey)
    button_runmonkey.grid(column=2,row=7,padx=25,pady=5,sticky="w")
    button_state = "disabled"

window.mainloop()

V2.0版本 

更新日志:

1.新增支持离线脱机测试,monkey开启后等待5s左右拔掉数据线即可

2.新增支持一键导出测试结果

3.暂不支持多设备同时执行了

import glob
import json
import logging
import os
import re
import time
import tkinter as tk
import tkinter.messagebox
from tkinter.filedialog import askdirectory

import _thread

window = tk.Tk()

window.title("便捷monkey测试工具")

width = 670
height = 730

# 设置窗口适应属性
window.resizable(width=False, height=False)
window.attributes("-alpha",0.7)
window.attributes('-toolwindow', False, 
                '-alpha', 1, 
                '-fullscreen', False, 
                '-topmost', True)
# window.overrideredirect(True)  

# 获取当前屏幕分辨率
screenwidth = window.winfo_screenwidth()  
screenheight = window.winfo_screenheight() 

# 根据分辨率设置窗口大小
alignstr = '%dx%d+%d+%d' % (width, height, (screenwidth-width)/1, (screenheight-height)/5)
window.geometry(alignstr)


# ----frame-----
frame_main1 = tk.Frame(window, bg = "#F2F2F2", height=100, width=360)
frame_main1.grid(column=1,row=1,padx=10,pady=10)

frame_main2 = tk.Frame(window, bg = "#F2F2F2", height=100, width=400)
frame_main2.grid(column=2,row=1)

frame_main3 = tk.Frame(window, bg = "#F2F2F2", height=200, width=370)
frame_main3.grid(column=1,row=2)

frame_main4 = tk.Frame(window, bg ="#F2F2F2", height=200, width=360)
frame_main4.grid(column=2,row=2)

frame_main5 = tk.Frame(window, bg = "#F2F2F2", height=300, width=720)
frame_main5.grid(column=1,row=3,columnspan=2,padx=15,pady=15)

# 选择路径的frame
# frame_main6 = tk.Frame(window, bg = "#F2F2F2", height=300, width=720)
# frame_main6.grid(column=1,row=4,columnspan=2,padx=15,pady=15)

# -----menu菜单功能,暂时不要后续添加--------
# meun_main = tk.Menu()
# # meun_main.grid()
# filemenu = tk.Menu(meun_main, tearoff=1)
# meun_main.add_cascade(label='File', menu=filemenu)
# filemenu.add_command(label='Monkey')
# filemenu.add_command(label='Screen')
# filemenu.add_command(label='Instructions')
# filemenu.add_command(label='About')
# window.config(menu = filemenu)

# -------Log缓存区域,显示自定义日志--------
# lable_log=tk.Label(frame_main6,text="日志区",font = ("微软雅黑",14))
# lable_log.grid(column=1,row=1,sticky="W")


# 控制台输出显示的位置
text_log = tk.Text(frame_main5,bg="#5B5B5B",fg="#FFFFFF")
text_log.grid(column=1,columnspan=3,row=2)

# ----------导出日志模块------------
path = ""
local_location = ""
def selectPath():
    global path, local_location
    path = askdirectory()
    print("选择的位置:"+path)
    local.delete(0,"end")
    local.insert("end",path)
    local_location=path
    text_log.insert("end","==========================注意==========================\n\n1、导出日志暂不支持多台设备同时操作,请连接一台设备操作\n\n2、相同路径导出会覆盖原结果,请手动备份或选择不同目录\n\n\n")
    text_log.yview_moveto(2)
    text_log.update()

local = tk.Entry(frame_main5, textvariable = path)
local.grid(row = 1, column = 2, sticky="W")
local.insert("end"," 点击选择导出路径......")
tk.Button(frame_main5, text = "选择路径:",bg="#AB82FF", command = selectPath).grid(row = 1, column = 1, sticky="E")


def pull_log():
    print("本地位置:"+local_location)
    os.system("adb pull sdcard/Auto/crash.log "+local_location)
    os.system("adb pull sdcard/Auto/meminfo.log "+local_location)
    os.system("adb pull sdcard/Auto/monkey.log "+local_location)
    os.system("adb pull sdcard/Auto/meminfomini.log "+local_location)
    text_log.insert("end","\n导出成功,请去目录下查看~~~~~\n")
    text_log.yview_moveto(1)
    text_log.update()

tk.Button(frame_main5, text = "导出结果",bg="#AB82FF", command = pull_log).grid(row = 1, column = 3, sticky="W")


def clear_log():
    text_log.delete("1.0","end")

button_clearLog = tk.Button(frame_main5,text="Clear",font = ("微软雅黑",8),bg="#836FFF", fg= "#FFFFFF",command=clear_log)
button_clearLog.grid(column=4,row=2,sticky="n")

# text_log.insert("end","adasd21dadd")


# ------主页lable显示引导---------------
# 刷新设备说明
lable_main1 = tk.Label(frame_main1, justify="left", height=5, width=35, bg="#E5E5E5", font = ("微软雅黑",10), text="准备工作:\n1.进入开发者选项打开测试机USB调试功能\n2.点击刷新设备\n3.手机端确认USB调试(若未提示则跳过此步骤)\n4.设备连接成功")
lable_main1.grid()
# monkey参数说明
lable_main2 = tk.Label(frame_main2, justify="left", height=5, width=40, bg="#E5E5E5", font = ("微软雅黑",10), text="参数说明:\n1.Package:包名,可点击获取,也可以手动输入\n2.Seeds:随机值,默认取当天日期,也可手动输入\n3.Throttle:点击频率,默认300ms,可手动输入\n4.Count:总操作数,默认50万,可手动修改")
lable_main2.grid()

# ----listbox用于显示当前设备列表-----
lb = tk.Listbox(frame_main3,height=9, width=28, bg="#CDCDB4", fg="#551A8B")
lb.grid(column=0,row=0,padx=10,pady=10)


# -----frame4上的参数配置-----
lab1 = tk.Label(frame_main4,text="配置项",bg="#F2F2F2",font=(11))
lab2 = tk.Label(frame_main4,text="Package",bg="#F2F2F2" )
lab3 = tk.Label(frame_main4,text="Seeds",bg="#F2F2F2" )
lab4 = tk.Label(frame_main4,text="Throttle",bg="#F2F2F2" )
lab5 = tk.Label(frame_main4,text="Count",bg="#F2F2F2" )
lab6 = tk.Label(frame_main4,text="参数",bg="#F2F2F2",font=(11) )  #第二列标题哦


lab1.grid(column=1,row=1,padx=5,pady=5)
lab2.grid(column=1,row=2,padx=5,pady=5,sticky="w")
lab3.grid(column=1,row=3,padx=5,pady=5,sticky="w")
lab4.grid(column=1,row=4,padx=5,pady=5,sticky="w")
lab5.grid(column=1,row=5,padx=5,pady=5,sticky="w")
lab6.grid(column=2,row=1,padx=1,pady=1)


# -----entry monkey参数配置-------
entry1 = tk.Entry(frame_main4)
entry2 = tk.Entry(frame_main4)
entry3 = tk.Entry(frame_main4)
entry4 = tk.Entry(frame_main4)

entry1.grid(column=2,row=2,padx=5,pady=1,sticky="w")
entry2.grid(column=2,row=3,padx=5,pady=1,sticky="w")
entry3.grid(column=2,row=4,padx=5,pady=1,sticky="w")
entry4.grid(column=2,row=5,padx=5,pady=1,sticky="w")



# ------一些可能用到的全局变量------
select_var = ""
pkg_details = ""
button_state = ""
model_name = ""
# 创建一个空list存储设备
list_device = []
# monkey配置残水
p = ""
s = ""
t = ""
c = ""

def find_and_init():
    # def find_devices():
    global list_device,model_name
    list_device.clear()
    logging.basicConfig(level=logging.INFO)
    d_lists = os.popen("adb devices").readlines()
    logging.error(d_lists)
    d_lists.remove(d_lists[len(d_lists)-1])
    d_lists.remove(d_lists[0])
    lb.delete("0","end")
    logging.info(d_lists)
    # time.sleep(3)
    if len(d_lists)==0:
        lb.insert("end","未检测到设备,请连接测试机...")
        text_log.insert("end","未检测到设备,请连接测试机...\n")
        text_log.yview_moveto(1)
        text_log.update()
    elif "unauthorized" in d_lists[0]:
        lb.insert("end","请在手机上点击允许USB调试...")
        text_log.insert("end","请在手机上点击允许USB调试...\n")
        text_log.yview_moveto(1)
        text_log.update()
    else:
        
        text_log.insert("end","设备已连接,请选择设备进行后续操作!\n\n")
        text_log.yview_moveto(1)
        text_log.update()
        for i in d_lists:
            reg_serial = re.findall("(.*?)device", i)
            model_org = os.popen("adb -s "+str(reg_serial[0])+" shell getprop ro.product.model").readlines()
            logging.info(reg_serial[0].replace("\t","")+":"+model_org[0].replace("\n",""))

            # 将设备信息添加到listbox中
            per_list = reg_serial[0].replace("\t","")
            print("设备列表为:",per_list)
            os.system("adb -s "+str(per_list)+" logcat -c")
            lb.insert("end",model_org[0].replace("\n","").replace(" ","")+":"+per_list)
            model_name = model_org[0].replace("\n","").replace(" ","")
            list_device.append(per_list)


# ------读写配置文件-----------
    # def init_config():
    entry1.delete(0,"end")
    entry2.delete(0,"end")
    entry3.delete(0,"end")
    entry4.delete(0,"end")
    # time.sleep(3)
    if os.path.exists("config.json"):
        with open('config.json',encoding="utf-8") as ff:
            json_data = json.load(ff)
            entry1.insert("end",json_data["pkg"])
            entry2.insert("end", time.strftime("%Y%m%d%H%M%S", time.localtime())[2:8])
            entry3.insert("end", json_data["thr"])
            entry4.insert("end", json_data["total"])
            
    else:
        config_file = open('config.json','w',encoding='utf-8')
        # json_data = {"pkg":"请在此处填写包名...","sd":time.strftime("%Y%m%d%H%M%S", time.localtime())[2:8],"thr":"300","total":"500000"}
        json_data = {"pkg":"请在此处填写包名...", "thr":"300","total":"500000"}
        json.dump(json_data,config_file,ensure_ascii=False)
        config_file.close()

    # p=entry1.get()
    # s=entry2.get()
    # t=entry3.get()
    # c=entry4.get()

#  -----------按钮事件--------
# 刷新设备按钮
button = tk.Button(frame_main3,text="刷新设备",font = ("微软雅黑",15),bg="#8FBC8F", fg= "#FFFFFF", command=find_and_init)
button.grid(column=0,row=1)


# --------获取当前包名---------
def get_pkg():
    global select_var
    global pkg_details
    logging.info(select_var)
    # global select_var
    selected=lb.get(lb.curselection())

    print("我也不知道这里会不会报错 ",selected)

    print("原始的",selected,type(selected))
    reg_device=".*?:(.*)"
    select_device = re.findall(reg_device, selected)
    print("选择的设备是",select_device[0])
    select_var=select_device[0]
    
    try:
        cur = os.popen("adb -s "+str(select_var)+" shell dumpsys window | findstr mCurrentFocus").readline()
        result=re.findall("mCurrentFocus=Window{.*?u0 (.*?)/.*?}", cur)
        pkg_details = result[0]
        logging.info(cur)

    except IndexError:
        entry1.delete(0,"end")
        entry1.insert("end","获取失败...")
        tk.messagebox.showerror("Error了吧,哈哈哈!","请注意:\n  1.确认设备已经连接。\n  2.需要获取包名的应用处于前台状态!")
        entry1.delete(0,"end")
        entry1.insert("end","点击重试...")

    except TypeError:
        entry1.delete(0,"end")
        entry1.insert("end","未选择设备哦!")
        tk.messagebox.showerror("Error,这个不是Bug...","没有选择设备哦!")
        entry1.delete(0,"end")
        entry1.insert("end","点击重试...")


    else:
        entry1.delete(0,"end")
        entry1.insert("end",pkg_details)
        text_log.insert("end","获取包名成功,请在包名栏查看确认!!!\n\n")
        text_log.yview_moveto(1)
        text_log.update()

def run_monkey():

    now_time = time.strftime("%Y%m%d%H%M%S", time.localtime())[4:12]

    if not os.path.exists("./script/"+str(now_time)):
        text_log.insert("end","创建必要资源文件夹....\n\n")
        text_log.yview_moveto(1)
        text_log.update()
        os.makedirs("./script/"+str(now_time))
    global p,s,t,c
    p=entry1.get()
    s=entry2.get()
    t=entry3.get()
    c=entry4.get()

    global button_state
    button_state = "disabled"
    print(pkg_details)
    text_log.insert("end","即将开始初始化资源,请稍等...\n")
    if len(list_device)>0:

        if entry1.get() in (u"请在此处填写包名...",""):
            tk.messagebox.showerror("你又错了!!!","不出意外的话你包名肯定不对!!")

        else:
            sh_monkey = "_monkey_"+str(pkg_details)+".sh"
            with open ("script\\"+str(now_time)+"\\"+sh_monkey, "w") as f:
                text_log.insert("end","生成monkey脚本...\n")
                text_log.yview_moveto(1)
                text_log.update()
                f.write('#!/system/bin/sh\na=`monkey -s '+str(s)+' -p '+str(p)+' --throttle '+str(t)+' --ignore-crashes --ignore-native-crashes --ignore-security-exceptions --ignore-timeouts --monitor-native-crashes -v -v -v '+str(c)+' > /sdcard/Auto/monkey.log`;b=`sh common.sh &`')
                

            sh_fc = "_fc_"+str(pkg_details)+".sh"
            with open ("script\\"+str(now_time)+"\\"+sh_fc, "w") as ff:
                text_log.insert("end","生成crash监控脚本...\n")
                text_log.yview_moveto(1)
                text_log.update()
                ff.write('#!/system/bin/sh\nlogcat -s AndroidRuntime>>crash.log')


            sh_memory = "_memory_"+str(pkg_details)+".sh"
            with open ("script\\"+str(now_time)+"\\"+sh_memory, "w") as fff:
                text_log.insert("end","生成内存监控脚本...\n")
                text_log.yview_moveto(1)
                text_log.update()
                fff.write('#!/system/bin/sh\nwhile true;do if [ -e "1" ];then echo `date`" -- monkey finished!!!">>meminfo.log;break;else dumpsys meminfo '+str(p)+'>>meminfo.log;sleep 30;continue;fi; done')
                

            # sh_memorymini = "_memorymini_"+str(pkg_details)+".sh"
            # with open ("script\\"+str(now_time)+"\\"+sh_memorymini, "w") as ffff:
            #     text_log.insert("end","生成内存监控脚本mini...\n\n")
            #     text_log.yview_moveto(1)
            #     text_log.update()
            #     ffff.write('#!/system/bin/sh\nwhile true;sleep 30; do if [ -e "1" ];then echo `date`" -- monkey finished!!!">>meminfo.log;break;else dumpsys meminfo '+str(p)+' | grep TOTAL:>>meminfomini.log;continue;fi; done')
       
       
        #这里区分设备,后面从这里开始  
        for d in list_device:

            os.system("adb -s "+str(d)+" shell rm -r sdcard/Auto/")
            text_log.insert("end","初始化设备环境...\n")
            text_log.yview_moveto(1)
            text_log.update()

            os.system('adb -s '+str(d)+' shell "cd sdcard && mkdir Auto')   
            text_log.insert("end","创建资源文件夹...\n")
            text_log.yview_moveto(1)
            text_log.update()


            sh_finally = glob.glob("script\\"+str(now_time)+"\\*.sh")
            for sh in sh_finally:
                print("=============")
                print("adb -s "+str(d)+" push "+"script\\"+str(now_time)+"\\"+str(sh)+" sdcard/Auto")
                os.system("adb -s "+str(d)+" push "+str(sh)+" sdcard/Auto")
            text_log.insert("end","推送测试脚本到设备...\n")
            print("=============\n\n\n")
            text_log.yview_moveto(1)
            text_log.update()

            os.system("adb -s "+str(d)+" push "+"script\\common.sh sdcard/Auto")
            text_log.insert("end","推送测试脚本到设备......\n")
            text_log.yview_moveto(1)
            text_log.update()


            # 多线程启动执行命令
            def monkey_thread():
                text_log.insert("end","启动monkey......\n")
                text_log.yview_moveto(1)
                text_log.update()
                logging.info('adb -s '+str(d)+' shell "cd sdcard && cd Auto && sh '+str(sh_monkey)+' &"')
                os.system('adb -s '+str(d)+' shell "cd sdcard && cd Auto && sh '+str(sh_monkey)+' &"')
                time.sleep(2)

            def crash_thread():
                text_log.insert("end","启动crash脚本......\n")
                text_log.yview_moveto(1)
                text_log.update()
                logging.info('adb -s '+str(d)+' shell "cd sdcard && cd Auto && sh '+str(sh_fc)+' &"')
                os.system('adb -s '+str(d)+' shell "cd sdcard && cd Auto && sh '+str(sh_fc)+' &"')
                time.sleep(2)

            def mem_thread():
                text_log.insert("end","启动内存脚本......\n")
                text_log.yview_moveto(1)
                text_log.update()
                logging.info('adb -s '+str(d)+' shell "cd sdcard && cd Auto && sh '+str(sh_memory)+' &"')
                os.system('adb -s '+str(d)+' shell "cd sdcard && cd Auto && sh '+str(sh_memory)+' &"')
            #     # os.system('adb -s '+str(d)+' shell "cd sdcard && cd Auto && sh '+str(sh_memorymini)+' &"')
                time.sleep(2)


            # monkey_thread()
            # crash_thread()
            # mem_thread()

            _thread.start_new_thread(monkey_thread, ())  
            _thread.start_new_thread(crash_thread, ())
            _thread.start_new_thread(mem_thread, ())

            logging.info("执行完毕!!!!!!!")
            time.sleep(5)
            text_log.insert("end","\n当前设备执行完毕,确认monkey开始后可拔掉数据线,执行下一台设备......\n")
            text_log.yview_moveto(1)
            text_log.update()

            # time.sleep(2)
            # print("开始启动监控脚本")
            # _thread.start_new_thread(common_thread, ())
            # os.system('adb -s '+str(d)+' shell "cd sdcard && cd Auto && sh common.sh &"')
            
            text_log.insert("end","一切准备就绪,即将启动所有骚操作!!!\n\n")
            text_log.yview_moveto(1)
            text_log.update()

            text_log.insert("end","==============================\n包名:"+p)
            text_log.insert("end","\nseeds值:"+s)
            text_log.insert("end","\nthrottle值:"+t)
            text_log.insert("end","\n总测试数:"+c+"\n\n==============================\n\n更新动态关注:https://blog.youkuaiyun.com/botree_chan/article/category/9091329\n\n")
            text_log.yview_moveto(3)
            text_log.update()

    else:
        # len(list_device)==0:
        tk.messagebox.showerror("你又错了!!!","请注意:\n  1.确认设备已经连接。\n  2.确认包名不为空!!")

# 获取包名按钮
button_getpkg = tk.Button(frame_main4,text="获取包名", fg= "#FFFFFF",font = ("",9),bg="#A020F0",command=get_pkg)
button_getpkg.grid(column=3,row=2,padx=5,pady=5)
notice_lable = tk.Label(frame_main4,text="要获取包名,\n请先选设备。", fg="red")
notice_lable.grid(column=3,row=3)

button_make_res = tk.Button()

# Monkey启动按钮
if button_state == "disabled":
    button_runmonkey = tk.Button(frame_main4,text="执行测试",state="disabled", fg= "#FFFFFF",font = ("微软雅黑",15),bg="#8FBC8F",command=run_monkey)
    button_runmonkey.grid(column=2,row=7,padx=25,pady=5,sticky="w")
else:
    button_runmonkey = tk.Button(frame_main4,text="执行测试", fg= "#FFFFFF",font = ("微软雅黑",15),bg="#8FBC8F",command=run_monkey)
    button_runmonkey.grid(column=2,row=7,padx=25,pady=5,sticky="w")
    button_state = "disabled"


window.mainloop()

 

AI赋能ADB:构建智能自动化测试与设备管理助手 自动化测试是提升软件交付效率与质量的关键技术,其核心在于通过脚本或工具模拟用户操作,执行预设用例。其原理通常基于对系统接口或UI层的控制,实现重复任务的自动执行。这项技术的价值在于解放人力、提升测试覆盖率和一致性,广泛应用于移动应用、Web服务和嵌入式系统的质量保障。随着大语言模型(LLM)等人工智能技术的发展,自动化测试正迎来智能化升级。通过将LLM强大的自然语言理解和序列生成能力与Android调试桥(ADB)等底层设备控制工具结合,可以实现从“自然语言指令”到“可执行命令序列”的自动转化与安全执行。这 阅读详情

相关推荐

monkey可视化工具

Monkey是Android SDK自带的自动化测试工具,可以运行在模拟器里或实际设备中,它向系统发送随机的用户事件流,如按键输入、触摸屏输入、手势输入、Sensor 事件等,实现对正在开发的应用程序进行压力测试。 本工具为mongkey的可视化工具,可以避免记忆monkey命令,只需要在配置文件里面设置好包名即可使用。

在pc端同时执行多台手机设备跑monkey

monkey

qq_59914293的博客 681

Testin第三期-你真的了解Monkey吗(陈晔)

行业很多人都想了解Android的自动化,说到Android自动化第一个想到的就是Monkey测试,Monkey测试不仅仅是一个命令行工具,也不仅仅是几个参数组合,本节让大家学习如何用Monkey做测试,让质量视野更开拓。

Android-monkey稳定性测试(多台设备同时进行)

1.目的(原创文章,转载请注明出处~) 主要为指引开展android平台应用的稳定性测试,尽可能地在应用发布前发现crash及anr等问题并修复,android平台的应用均适用并可定制接入。 主要达到下班前执行,第二天上班时查看分析测试日志的目的,实现了以下核心功能: a、可批量同时对多台android手...

weixin_34381687的博客 1488

Eplan P8自动线号生成全攻略:从电位定义点到连接编号的完整流程

本文详细解析了Eplan P8自动线号生成的全流程,从电位定义点的创建配置到连接编号的标准化管理,帮助电气设计人员提升图纸规范性和工作效率。重点介绍了自动编号方案的定义与调整技巧,以及企业级标准化实施建议,是掌握Eplan P8自动线号功能的实用指南。

weixin_29254029的博客 510

2022-2028年中国美容仪行业发展战略规划及投资机会预测报告

报告类型:产业研究 报告格式:电子版、纸介版 出品单位:智研咨询-产业信息网 美容仪是一种根据人体生理机能进行调节改善身体和面部的机器,它按照功能来说有美白、嫩肤、祛斑、祛皱、脱毛、减肥等多种,按照名称来说有超声波导入、光子嫩肤、高周波电疗、RF 电波拉皮、电子祛斑点痣、E 光永久脱毛嫩肤、贾法尼营养导入导出等。 智研咨询发布的《2022-2028年中国美容仪行业发展战略规划及投资机会预测报告》共十七章。首先介绍了中国美容仪行业市场发展环境、美容仪整体运行态势等,接着分析了中...

kt346的博客 468

多机Monkey全自动测试尝试

说起Monkey测试,很多人第一印象是,那不简单,一条命令跑起来就可以了。如同厨师毕业考试,要做一盘麻婆豆腐。看似简单,真正要做到好,就不见得有那么容易了。     确实,如果很简单的monkey测试,一条命令就够了。但是跑这条命令的时候,你是不是需要手动做很多准备工作和考虑一下几个方面? 1. 下载安装build 2. 如果有登录,你还得手工先登录好, monkey可做不来

passionboyxie的专栏 1774

R语言计算植物群落重要值:从基础概念到优势种判定

在植被生态学研究中,重要值是衡量物种在群落中地位的核心指标,它综合了相对多度、相对频度与相对显著度三个维度,能够客观反映物种的数量优势、分布广度与空间占据能力。理解重要值的生态学逻辑,是开展群落分类、优势种判定及多样性分析的基础。而R语言凭借其批量处理、可复现和与后续多元统计无缝衔接的优势,成为生态学研究者处理样方数据的高效工具。从野外记录的宽表到标准化长表,再到分组汇总计算相对指标,R语言能够显著提升数据处理效率与准确性。本文以实测群落数据为例,系统梳理重要值的两种算法口径、基于dplyr和tidyr的完

weixin_42634811的博客 269

Monkey测试——当前机器连接多个设备时,指定某一设备进行测试

当你的机器连接了多个设备时: [c:\~]$ adb devices List of devices attached emulator-5638 host    //模拟器 2390d880 device     //真机 想连接真机肿么办?so easy! [c:\~]$ adb -s 2390d880 shell shell@Coolpad9190_T00:/ $

美丽测试师的博客 3636

AUTOSAR BswM配置实战:手把手教你用Vector Davinci Configurator搭建第一个模式管理规则

本文详细介绍了如何使用Vector Davinci Configurator工具配置AUTOSAR BswM模块,实现车身控制ECU的模式管理规则。通过实战案例,从环境准备、核心概念梳理到具体配置步骤,包括ModeRequestPort、ModeCondition、Logical Expression和Rules的构建,帮助工程师快速掌握BswM配置技巧,提升汽车电子软件开发效率。

weixin_28678831的博客 353

lp–mspm0g3507开发板进入低功耗模式怎么退出?烧程序烧着烧着就进来了,跳线帽没动过,且报错:Texas Instruments Debugger Error: DAP Co...如何解决?

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!

**My Coding Family** 1570

Monkey使用详解

Monkey 非常详细的使用教程

eosclover的博客 4833

使用 Python 调用 GitHub REST API 爬取 GitHub 仓库信息(完整指南)

GitHub 提供了功能强大的 REST API,允许开发者通过编程方式访问 GitHub 上的仓库、用户、提交记录、PR(Pull Request)等信息。GitHub REST API 是 GitHub 官方提供的 API 接口,支持通过 HTTP 请求来操作 GitHub 上的资源。在本文中,我们将使用 Python 编写一个完整的爬虫程序,调用 GitHub 的 REST API 来爬取 GitHub 仓库的信息。目前 GitHub 使用的主要是 v3 版本的 REST API。

2201_76125261的博客 1112

基于Tkinter GUI操作

# ------主页lable显示引导---------------# 刷新设备说明lable_main1 = tk.Label(frame_main1, justify="left", height=5, width=35, bg="#E5E5E5", font = ("微软雅黑",10), text="准备工作:\n1.进入开发者选项打开测试机USB调试功能\n2.点击刷新设备\n3.手机端确...

weixin_30867015的博客 174

华为OD机考双机位C卷 - 螺旋数字矩阵 (Java & Python& JS & C/C++ & GO )

华为OD机考双机位C卷 - 数字螺旋矩阵(OD上机考试双机位C卷)。提供在线OJ刷题。Java、C++、Python、GO语言、C语言、JavaScript题解,并提供完整思路讲解。

算法大师! 3219

使用 MATLAB/Simulink 构建一个分布式光伏-储能联合系统在配电网中的协同控制模型

本文介绍了使用MATLAB/Simulink构建分布式光伏-储能联合系统协同控制模型的方法。该系统包含光伏发电、储能、负荷和控制器等模块,通过协同控制实现能量优化管理。建模过程详细说明了从创建项目、搭建组件到实现控制策略的步骤,包括光伏特性模拟、储能参数设置和负荷需求建模。重点设计了协同控制器算法,实时调节电力分配,并展示了系统运行指标和经济效益分析。该方法为多能互补系统在配电网中的应用提供了技术验证,未来可进一步优化控制算法和场景适应性。

MHD0815的博客 310

python GUI基于tkinter测试工具开发(ADB部分代码)

python GUI基于tkinter测试工具开发(ADB部分代码)开发环境功能截图代码结构ADB调试部分代码AdbView.pyAdbPageHomePage.pymain.py 开发环境 Windows python3.7 adb环境 需要注意:mac环境目前尚未兼容,无法展示tab栏,后续会重构用QT开发,视情况兼容 功能截图 代码结构 ADB调试部分代码 AdbView.py # _*_ coding: utf-8 _*_ """ Author: keyliangh (95后老大爷

weixin_41676880的博客 883

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

多设备monkey测试工具

import globimport jsonimport loggingimport osimport reimport timeimport tkinter as tkimport tkinter.messagebox window = tk.Tk() window.title("Demo APP") width = 640height = 730 # 设置窗口适应属性window.re...

weixin_30596023的博客 199

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Python GUI框架Tkinter简介

Python GUI框架Tkinter简介

byszy的专栏 3487

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Python开发测试工具(一)—Monkey

文章首发:我的博客背景最近在测试安卓的时候,经常会用到Monkey,Monkey作为安卓的基础工具,必须要到命令行去敲敲敲,做起来非常非常麻烦,于是我就想能不能利用学会的Python知识直接开发一个带有界面的安卓测试工具。思路整个实现流程并不困难,一个界面填写数据,提交到后台,然后Python调用os模块,执行shell命令,然后就可以实现GUI端的Monkey了。Web端前段时间一直都在看web端

点点寒彬的博客 7230

Python制作的GUI的monkey

这主要根据tkinter,pyautogui,traceback编写的一个程序,下面是代码,我就不介绍了,因为有注释,所以你们应该能看懂 #coding=gbk import tkinter as t from tkinter.ttk import * import pyautogui,random,time,threading,traceback,csv,os from tkinter.messagebox import * import ctypes """ 更改任务栏图标,任务栏图标等于窗口图标

qq_58370894的博客 419

python GUI基于tkinter测试工具开发(Tkinter+Request+Locust)(开几个新坑,不定期更新)

Python测试工具开发(Tkinter+Request+Locust)一.开发背景二.功能图三.代码结构四.开发环境准备 一.开发背景 由于目前所在公司主要以C端产品为主,对于产品的质量把关相对B端而言要求要高,且测试时间缩水很厉害,在改变不了当前环境的情况下只能去提高测试效率。 1.0 版本:对于一些重复操作例如安装APP,查看包名,monkey测试等,首先想到adb命令能够很好的解决调试问题 2.0 版本:后台产品涉及到了加解密,删号这些业务,无非是去请求接口,第一反应是postman但后来实际应用频

weixin_41676880的博客 784

python GUI编程(tkinter)基于adb 编写安卓助手

基于python+adb 编写的安卓助手工具。支持录屏、截图、日志抓取、性能数据查看等功能。目前的功能不是很完善,只是单纯的打个样儿。感谢张兴老师做出的努力。不善言辞,直接上图和代码了。链接手机界面:连接成功后,功能如图:直接上代码。目录结构如图:.........

喵酱 5799

python结束任务之后如何关闭_Python Tkinter Threading-如何在用户关闭GUI时结束/杀死/停止线程...

If I have a non for loop threaded task being run alongside tkinter such as time.sleep(seconds) how can I safely end the task before it has concluded, or, before the time.sleep() has ended.If you run t...

weixin_39635432的博客 340

AndroidTestTool开发笔记

前言这段时间在Testerhome上看了一些有关性能测试的帖子,看别人的东西,始终是别人的,只有自己写一遍才能体会其中的细节,虽然说不要重复造轮子,但是这种基础的东西,造一次轮子能够学会很多东西,最近看的东西也比较多,拿来实战一下也未尝不可。整个工程下来难度其实不大,主要是一些基本知识,只不过涉及的面比较广,需要的要素如下:开发相关操作系统: Mac OS X EI capitanPython: 2

点点寒彬的博客 1662

安卓性能自动化<一:性能测试项窗口搭建>

由于最近的工作都是性能方面的自动化测试,所以就考虑看自己能不能写一个简陋的全自动化,第一步先弄个性能测试项选择的窗口 然后将button与函数进行绑定,这个过程很简单 import tkinter as tk class Canvsa(): def canvsa(self): # 实例化object,建立窗口window window = tk.Tk() window.title('安卓性能自动化') window.geome

m0_48523578的博客 332
上一篇: bat中如何运行adb shell以后的命令
下一篇: adb 模拟电量
Botreechan
博客等级 码龄11年 52粉丝 30原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值