unity一键注释日志和反注释日志
开发背景:游戏中日志也是很大的开销,虽然有些日志不打印但是毕竟有字符串的开销,甚至有字符串拼接的开销,有些还有装箱和拆箱的开销,比如Debug.Log(1) 这种
因此需要注释掉,当然还需要提供反注释的功能,需要的时候能立马找回来。
也就是说我们只需要打包的时候处理即可。
1.先检测代码中是否存在不规范代码,类似if(a > 1) Debug.Log("11") 这种代码如果注释掉日志会引起业务逻辑问题需要先找出来,类似的不限于if else for foreach while这些 所以尽可能的要做这个检测,有这类问题直接报错。然后才可以进行后续流程
2.注释Debug.Log或者UnityEngine.Debug.Log,但是有一个问题就是注释之后再注释的问题,所以在调用这个接口前需要调用反注释然后调用注释,这样就可以保证注释是没有问题的。
3.需要提供反注释的代码,主要是配合2的功能
注意事项:大家写代码的过程中想打日志就打日志这个没有限制,打包的时候会自动处理日志。
如果是特别的日志可以打错误日志或者警告,或者自定义日志那种日志不去处理即可。
未来规划是用代码分析给日志加标签,平常开发还是按unity的习惯来我绝对不改变大家的unity标准,在做扩展的时候尽量做到神不知鬼不觉,不让大家有额外操作不让大家有压力。
接着上面扩展 :如下图所示这种else日志不被允许
那么如何进行csharp的语法分析呢 ,我们建一个c#的控制台程序,使用nuget安装3个包
Microsoft.CodeAnalysis.CSharp 和 Microsoft.CodeAnalysis.CSharp.Workspaces 和Microsoft.CodeAnalysis
分析c#代码检测是否存在if else for while foreach 后直接接Debug.Log或者UnityEngine.Debug.Log完整控制台程序如下
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace cs_test
{
internal class Program
{
public static long getCurrentTimeMillis()
{
long milliseconds = System.DateTime.Now.Ticks / System.TimeSpan.TicksPerMillisecond;
return milliseconds;
}
static long firstTime = 0;
const float LogWritetimeOutTime = 2f;
static List<string> strList = new List<string>();
static void SetTimer(float time = LogWritetimeOutTime)
{
System.Timers.Timer timer = new System.Timers.Timer();
timer.Interval = time;
timer.AutoReset = false;
timer.Enabled = true;
timer.Elapsed += (a, b) =>
{
using (var fs = new FileStream(logPath, FileMode.Append,FileAccess.Write,FileShare.Write))
{
using (var sw = new StreamWriter(fs, Encoding.UTF8))
{
lock (strList)
{
foreach (var item in strList)
{
sw.WriteLine(item);
}
strList.Clear();
}
}
}
};
}
static void Log(string str,bool useFileSystem = true)
{
if(useFileSystem && isLogToFile)
{
lock (strList)
{
strList.Add(str);
}
if (firstTime == 0)
{
firstTime = getCurrentTimeMillis();
SetTimer(LogWritetimeOutTime);
return;
}
if (getCurrentTimeMillis() - firstTime > LogWritetimeOutTime)
{
firstTime = 0;
SetTimer(LogWritetimeOutTime);
}
}
else
{
Console.WriteLine(str);
}
}
public class LogStatementWalker : CSharpSyntaxWalker
{
public List<Issue> Issues { get; } = new List<Issue>();
public override void VisitIfStatement(IfStatementSyntax node)
{
CheckBlockStatement(node.Statement, node.GetLocation());
if (node.Else != null)
{
CheckElseStatement(node.Else);
}
base.VisitIfStatement(node);
}
public override void VisitForStatement(ForStatementSyntax node)
{
CheckBlockStatement(node.Statement, node.GetLocation());
base.VisitForStatement(node);
}
public override void VisitForEachStatement(ForEachStatementSyntax node)
{
CheckBlockStatement(node.Statement, node.GetLocation());
base.VisitForEachStatement(node);
}
public override void VisitWhileStatement(WhileStatementSyntax node)
{
CheckBlockStatement(node.Statement, node.GetLocation());
base.VisitWhileStatement(node);
}
private void CheckElseStatement(ElseClauseSyntax elseClause)
{
if (elseClause.Statement is IfStatementSyntax)
{
// Skip checking if it's an "else if" statement
return;
}
CheckBlockStatement(elseClause.Statement, elseClause.GetLocation());
}
private void CheckBlockStatement(StatementSyntax statement, Location location)
{
if (statement is BlockSyntax)
{
return;
}
var logStatements = statement.DescendantNodes()
.OfType<InvocationExpressionSyntax>()
.Where(inv => inv.Expression is MemberAccessExpressionSyntax memberAccess &&
(memberAccess.Name.ToString() == "Log" || memberAccess.Name.ToString() == "Debug.Log"))
.ToList();
foreach (var logStatement in logStatements)
{
var lineSpan = logStatement.GetLocation().GetLineSpan();
Issues.Add(new Issue
{
LineNumber = lineSpan.StartLinePosition.Line + 1,
Message = $"Debug.Log or UnityEngine.Debug.Log found in a single-line {statement.Kind()} statement without braces."
});
}
}
}
public class Issue
{
public int LineNumber { get; set; }
public string Message { get; set; }
}
static string unityRoot;
static string logPath = "D:/tempLog.txt";
static bool isLogToFile = true;
static void Main(string[] args)
{
List<string> pathList = new List<string>();
if (args == null || args.Length == 0)
{
var path = System.Environment.CurrentDirectory;
path = path.Substring(0, path.IndexOf("tools"));
path = path.Replace("\\", "/");
args = new string[]
{
path + "Assets/HotUpdate"
};
}
unityRoot = args[0].Substring(0, args[0].IndexOf("Assets"));
logPath = unityRoot + "tools/tempLog.txt";
if(File.Exists(logPath))
{
File.Delete(logPath);
}
pathList.AddRange(args);
isLogToFile = bool.Parse(pathList[pathList.Count - 1]);
pathList.RemoveAt(pathList.Count - 1);
List<string> allCsharpFiles = new List<string>();
foreach(var path_item in pathList)
{
allCsharpFiles.AddRange(Directory.GetFiles(path_item, "*.cs", SearchOption.AllDirectories));
}
bool hasInsue = false;
strList.Clear();
foreach(var item in allCsharpFiles)
{
string sourceCode = File.ReadAllText(item);
SyntaxTree tree = CSharpSyntaxTree.ParseText(sourceCode);
CompilationUnitSyntax root = tree.GetCompilationUnitRoot();
var diagnostics = tree.GetDiagnostics();
if (diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error))
{
Log("Syntax errors found in the source code.");
return;
}
var walker = new LogStatementWalker();
walker.Visit(root);
foreach (var issue in walker.Issues)
{
hasInsue = true;
var itemName = item.Substring(item.IndexOf("Assets"));
Log($"请使用括号封装你的日志! at line {issue.LineNumber}: {itemName}");
}
}
if(!hasInsue)
{
Log("未发现不规范代码");
}
}
/// <summary>
/// 检测判断if语句后面是否带花括号,由于在for循环中经常会遇到if不带花括号的情况,故这个检测暂时放弃 不然
/// 会有非常多的代码提示
/// 这里的代码暂时封存
/// </summary>
/// <param name="args"></param>
static void Main2(string[] args)
{
if(File.Exists(logPath))
{
File.Delete(logPath);
}
File.Create(logPath).Dispose();
string directoryPath = "D:\\qiangsheng_wx\\Assets\\HotUpdate"; // 替换为你的目录路径
// 获取目录中的所有 .cs 文件
string[] files = Directory.GetFiles(directoryPath, "*.cs", SearchOption.AllDirectories);
foreach (string filePath in files)
{
Log($"Processing file: {filePath}");
string code = File.ReadAllText(filePath);
SyntaxTree tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();
var walker = new IfElseSyntaxWalker();
walker.Visit(root);
}
Console.ReadLine();
}
class IfElseSyntaxWalker : CSharpSyntaxWalker
{
public override void VisitIfStatement(IfStatementSyntax node)
{
base.VisitIfStatement(node);
// 检查 if 语句是否有花括号
if (node.Statement is BlockSyntax) { }
else
{
var str = $"警告: if 语句 at line {node.GetLocation().GetLineSpan().StartLinePosition.Line + 1} does not use 花括号";
Log(str);
}
// 检查 else 语句是否有花括号
if (node.Else != null)
{
if (node.Else.Statement is BlockSyntax) { }
else
{
var str = $"警告: else 语句 at line {node.Else.GetLocation().GetLineSpan().StartLinePosition.Line + 1} does not use 花括号";
Log(str);
}
}
}
}
}
}
控制台程序做完之后还需要对接到unity项目中,我是直接将微信api的代码分析回调日志直接写入了某个文件,让unity调用这个exe文件,你可以封装成bat将参数传给exe执行,unity那边读取文件将文本内容Debug.LogError输出即可。
上诉工作做完了之后就可以提供注释和反注释的代码接口了,将下述代码封装到你的编辑器工具类中或者某个编辑器类下的对象中
private const string LogPattern = @"(?s)(Debug\.Log\s*\(.*?\);|UnityEngine\.Debug\.Log\s*\(.*?\);)";
private const string unLogPattern = @"(?s)/\*(\s*Debug\.Log\s*\(.*?\);\s*|UnityEngine\.Debug\.Log\s*\(.*?\);\s*)\*/";
public static void ForbidLog(string filePath)
{
string fileContent = File.ReadAllText(filePath,Encoding.UTF8);
// 正则表达式匹配 Debug.Log 调用及其相关代码
string pattern = LogPattern;
string replacement = @"/*$1*/";
if(!Regex.IsMatch(fileContent, pattern))
{
return;
}
string newContent = Regex.Replace(fileContent, pattern, replacement);
using (StreamWriter writer = new StreamWriter(filePath, false, new UTF8Encoding(false)))
{
writer.Write(newContent);
}
}
public static void UnForbidLog(string filePath)
{
string fileContent = File.ReadAllText(filePath, Encoding.UTF8);
// 正则表达式匹配被注释的 Debug.Log 调用及其相关代码
string pattern = unLogPattern;
string replacement = @"$1";
if (!Regex.IsMatch(fileContent, pattern))
{
return;
}
string newContent = Regex.Replace(fileContent, pattern, replacement);
using (StreamWriter writer = new StreamWriter(filePath, false, new UTF8Encoding(false)))
{
writer.Write(newContent);
}
}
[MenuItem("Tools/日志/注释所有日志", priority = -1999)]
public static void ReMoveLog()
{
var listFolder = new List<string>();
listFolder.Add(System.Environment.CurrentDirectory + "/Assets/HotUpdate");
listFolder.Add(System.Environment.CurrentDirectory + "/Assets/SDKTool");
listFolder.Add(System.Environment.CurrentDirectory + "/Assets/DevWork");
var allCsharpFiles = new List<string>();
foreach(string folder in listFolder)
{
allCsharpFiles.AddRange(Directory.GetFiles(folder, "*.cs",SearchOption.AllDirectories));
}
string[] filters = new string[]
{
"InitCtrl.cs",
"Global.cs",
"LoginPanel.cs",
"LoginCode.cs",
"ProductModel.cs",
};
foreach(string csFile in allCsharpFiles)
{
var filename = Path.GetFileName(csFile);
if(filters.Contains(filename))
{
Debug.Log("跳过过滤文件" + csFile);
continue;
}
ForbidLog(csFile);
}
allCsharpFiles.Clear();
listFolder.Clear();
listFolder = null;
allCsharpFiles = null;
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
[MenuItem("Tools/日志/反注释所有日志", priority = -1999)]
public static void UnReMoveLog()
{
var listFolder = new List<string>();
listFolder.Add(System.Environment.CurrentDirectory + "/Assets/HotUpdate");
listFolder.Add(System.Environment.CurrentDirectory + "/Assets/SDKTool");
listFolder.Add(System.Environment.CurrentDirectory + "/Assets/DevWork");
var allCsharpFiles = new List<string>();
foreach (string folder in listFolder)
{
allCsharpFiles.AddRange(Directory.GetFiles(folder, "*.cs", SearchOption.AllDirectories));
}
foreach (string csFile in allCsharpFiles)
{
UnForbidLog(csFile);
}
allCsharpFiles.Clear();
listFolder.Clear();
listFolder = null;
allCsharpFiles = null;
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
}
运行截图:
相关文章:
unity一键注释日志和反注释日志
开发背景:游戏中日志也是很大的开销,虽然有些日志不打印但是毕竟有字符串的开销,甚至有字符串拼接的开销,有些还有装箱和拆箱的开销,比如Debug.Log(1) 这种 因此需要注释掉,当然还需要提供反注释的功能&am…...
VBA数据库解决方案第十五讲:Recordset集合中单个数据的精确处理
《VBA数据库解决方案》教程(版权10090845)是我推出的第二套教程,目前已经是第二版修订了。这套教程定位于中级,是学完字典后的另一个专题讲解。数据库是数据处理的利器,教程中详细介绍了利用ADO连接ACCDB和EXCEL的方法…...
甄选范文“论软件需求管理”,软考高级论文,系统架构设计师论文
论文真题 软件需求管理是一个对系统需求变更了解和控制的过程。需求管理过程与需求开发过程相互关联,初始需求导出的同时就要形成需求管理规划,一旦启动了软件开发过程,需求管理活动就紧密相伴。 需求管理过程中主要包含变更控制、版本控制、需求跟踪和需求状态跟踪等4项活…...
Android Studio Dolphin 中Gradle下载慢的解决方法
我用的版本Android Studio Dolphin | 2021.3.1 Patch 1 1.Gradle自身的版本下载慢 解决办法:修改gradle\wrapper\gradle-wrapper.properties中的distributionUrl 将https\://services.gradle.org/distributions为https\://mirrors.cloud.tencent.com/gradle dis…...
Excel实现省-市-区/县级联
数据准备 准备省份-城市映射数据,如下: 新建sheet页,命名为:省-市数据源,然后准备数据,如下所示: 准备城市-区|县映射数据,如下: 新建sheet页,命名为&#x…...
【优化代码结构】函数的参数归一化
某些封装的函数,其参数具有多样性,会导致函数中会增加非常多的分支,比如下面这个 format 函数有如下几种参数方式,其中 formatter 会有很多种情况 date:日期对象formatter: ‘date’:格式化日期…...
CSS中height设置100vh和100%的区别
文章目录 CSS中height设置100vh和100%的区别一、引言二、高度设置的区别1、100%1.1、父元素高度固定1.2、父元素高度未定义 2、100vh2.1、视口高度2.2、不受父元素限制 三、总结 CSS中height设置100vh和100%的区别 一、引言 在前端开发中,我们经常需要设置元素的高…...
红米k60至尊版工程固件 MTK芯片 资源预览 刷写说明 与nv损坏修复去除电阻图示
红米k60至尊版机型代码为:corot。 搭载了联发科天玑9200+处理器。此固件mtk引导为MT6985。博文将简单说明此固件的一些特点与刷写注意事项。对于NV损坏的机型。展示修改校验电阻的图示。方便改写参数等 通过博文了解 1💝💝💝-----此机型工程固件的资源刷写注意事项 2…...
QEMU使用Qemu-Guest-Agent传输文件、执行指令等
简介 之前介绍过qemu传输文件,使用的挂载 / samba方式 :Qemu和宿主机不使用外网进行文件传输。 这是一种方式,这里还有另一种方式:使用Qemu-Guest-Agent,后面简称qga。 官网介绍:https://www.qemu.org/d…...
【漏洞复现】金和OA C6 GeneralXmlhttpPage.aspx Sql注入漏洞
免责声明: 本文旨在提供有关特定漏洞的信息,以帮助用户了解潜在风险。发布此信息旨在促进网络安全意识和技术进步,并非出于恶意。读者应理解,利用本文提到的漏洞或进行相关测试可能违反法律或服务协议。未经授权访问系统、网络或应用程序可能导致法律责任或严重后果…...
复数表示的电场
Exm加是复振幅,这是用复数表示电场,并提取只与空间有关的项复振幅就是复数表示电场,且把与空间xyz有关的量提取出来 经过验证实数E0cos(wtδx)对t求导,等于E0e^j(wtδx)对t求导再取实部 实数表示电磁波cos…...
常用快捷键整理
用加粗标注的是我个人使用时常用的,其实这个全凭个人喜好,大家可以熟悉一下自己喜欢的,都多试试,把觉得有用的记一下,多使用,后续写代码效率就会提高一些) 常用 VS 运行调试程序快捷键 编译 . 编译程序&a…...
【Transformer】长距离依赖
在自然语言处理(NLP)中,长距离依赖(Long-Range Dependencies)指的是在文本中相隔较远的两个或多个元素之间的依赖关系。这些依赖关系可以是语法上的,也可以是语义上的。例如,在句子中࿰…...
Git傻傻分不清楚(下)
进入Idea编译器 File -> New -> Project from Version Control -> URL (这个路径是要拉取项目的Github路径哦~) 设置成maven项目...
golang学习笔记27-反射【重要】
本节也是GO核心部分,很重要。包括基本类型的反射,结构体类型的反射,类别方法Kind(),修改变量的值。 目录 一、概念,基本类型的反射二、结构体类型的反射三、类别方法Kind()四、修改变量的值 一、概念,基本…...
利用Puppeteer-Har记录与分析网页抓取中的性能数据
引言 在现代网页抓取中,性能数据的记录与分析是优化抓取效率和质量的重要环节。本文将介绍如何利用Puppeteer-Har工具记录与分析网页抓取中的性能数据,并通过实例展示如何实现这一过程。 Puppeteer-Har简介 Puppeteer是一个Node.js库,提供…...
YOLOv5改进系列(1)——添加CBAM注意力机制
一、如何理解注意力机制 假设你正在阅读一本书,同时有人在你旁边说话。当你听到某些关键字时,比如“你的名字”或者“你感兴趣的话题”,你会自动把注意力从书上转移到他们的谈话上,尽管你并没有完全忽略书本的内容。这就是注意力机…...
无头单向非循环java版的模拟实现
【本节目标】 1.ArrayList的缺陷 2.链表 1. ArrayList的缺陷 上节课已经熟悉了 ArrayList 的使用,并且进行了简单模拟实现。通过源码知道, ArrayList 底层使用数组来存储元素: public class ArrayList<E> extends AbstractList<…...
Bert Score-文本相似性评估
Bert Score Bert Score 是基于BERT模型的一种方法。它通过计算两个句子在BERT模型中的嵌入编码之间的余弦相似度来评估它们的相似度。BERTScore考虑了上下文信息和语义信息,因此能够更准确地衡量句子之间的相似度。 安装 pip install bert-score 使用例子 一个…...
Pyenv管理Python版本,conda之外的另一套python版本管理解决方案
简介 Pyenv 是一个 python 解释器管理工具,可以对计算机中的多个 python 版本进行管理和切换。为什么要用 pyenv 管理python呢,用过的 python 人都知道,python 虽然是易用而强大的编程语言,但是 python 解释器却有多个版本&#…...
快速实现AI搜索!Fivetran 支持 Milvus 作为数据迁移目标
Fivetran 现已支持 Milvus 向量数据库作为数据迁移的目标,能够有效简化 RAG 应用和 AI 搜索中数据源接入的流程。 数据是 AI 应用的支柱,无缝连接数据是充分释放数据潜力的关键。非结构化数据对于企业搜索和检索增强生成(RAG)聊天…...
css的页面布局属性
CSS Flexbox(Flexible Box Layout)是一种用于页面布局的CSS3规范,它提供了一种更加高效的方式来布置、对齐和分配容器内元素的空间,即使它们的大小是未知或者动态变化的。Flexbox很容易处理一维布局,即在一个方向上&am…...
RTE 大会报名丨AI 时代新基建:云边端架构和 AI Infra ,RTE2024 技术专场第二弹!
所有 AI Infra 都在探寻规格和性能的最佳平衡,如何构建高可用的云边端协同架构? 语音 AI 实现 human-like 的最后一步是什么? AI 视频的爆炸增长,给新一代编解码技术提出了什么新挑战? 当大模型进化到实时多模态&am…...
【React】入门Day01 —— 从基础概念到实战应用
目录 一、React 概述 二、开发环境创建 三、JSX 基础 四、React 的事件绑定 五、React 组件基础使用 六、组件状态管理 - useState 七、组件的基础样式处理 快速入门 – React 中文文档 一、React 概述 React 是什么 由 Meta 公司开发,是用于构建 Web 和原生…...
<<机器学习实战>>10-11节笔记:生成器与线性回归手动实现
10生成器与python实现 如果是曲线规律的数据集,则需要把模型变复杂。如果是噪音较大,则需要做特征工程。 随机种子的知识点补充: 根据不同库中的随机过程,需要用对应的随机种子: 比如 llist(range(5)) random.shuf…...
链表OJ经典题目及思路总结(一)
目录 前言1.移除元素1.1 链表1.2 数组 2.双指针2.1 找链表的中间结点2.2 找倒数第k个结点 总结 前言 解代码题 先整体:首先数据结构链表的题一定要多画图,捋清问题的解决思路; 后局部:接着考虑每一步具体如何实现,框架…...
初识chatgpt
GPT到底是什么 首先,我们需要了解GPT的全称:Generative Pre-trained Transformer,即三个关键词:生成式 预训练 变换模型。 (1)什么是生成式? 即能够生成新的文本序列。 (2&#…...
【60天备战2024年11月软考高级系统架构设计师——第33天:云计算与大数据架构——大数据处理框架的应用场景】
随着大数据技术的发展,越来越多的企业开始采用大数据处理框架来解决实际问题。理解这些框架的应用场景对于架构师来说至关重要。 大数据处理框架的应用场景 实时数据分析:使用Apache Kafka与Apache Spark结合,可以实现对实时数据流的处理与…...
如何设计具体项目的数据库管理
### 例三:足协的数据库管理算法 #### 角色: - **ESFP学生**:小明 - **ENTP老师**:张老师 #### 主题:足协的数据库管理算法 --- **张老师**:小明,今天我们来讨论一下足协的数据库管理算法。你…...
对于 Vue CLI 项目如何引入Echarts以及动态获取数据
🚀个人主页:一颗小谷粒 🚀所属专栏:Web前端开发 很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~ 目录 1、数据画卷—Echarts介绍 1.1 什么是Echarts? 1.2 Echarts官网地址 2、Vue CLI 项目…...
高级经济师/seo推广知识
Daniel2cscanf我不知道为什么,当我运行它时,它会跳过"书中有多少页" scanf并直接进入第二个循环"谁是作者".我确定这与空白有关,但我认为我用getcharfor循环的底部来解释这个问题.标题:struct bookInfo{char title[40];char author[25];float price;int pa…...
网站架设 数据库选用/大一html网页制作作业
阿里高级专家手推最新版Spring源码深度解析(第2版)笔记总结 特点: 全面剖析Spring源码,方便读者根据业务需求进行更改或扩展所有知识点秉承由浅入深、由易到难的讲解架构采用抽丝剥茧的方式来阐述复杂的逻辑,降低理解…...
鄱阳做网站/线上电商怎么做
计算机硬件一、硬件组成主板 CPU 硬盘 内存 显卡 声卡 网卡 光驱 机箱 显示器 键盘 鼠标 二、主机内部剖析1、CPU 中央处理器(针式和触点式)性能指标:主频、核心数、线程数、缓存主频:CPU内核工作的时钟频率缓存:缓解C…...
企业网站开发费是固定资产吗/网站友情链接自动上链
近日,不少《王者荣耀》玩家反映,收到人脸识别验证弹窗提醒。据了解,腾讯已经开始在包括《王者荣耀》《和平精英》在内的100多款手机游戏产品中进行人脸识别验证,旨在解决“孩子冒用家长身份信息绕过监管”的问题。登录过程中拒绝或…...
@wordpress/百度提交入口网址是什么
在XML中需要插入自己的字符串,但是发现&<>"这些字符无法替换掉,使用正常的Replace没有效果,网上也没有找到合适的。并且由于是控制台程序,无法引用NET类,经过查询MSDN得到些启发,写下这个转…...
自助建设网站/如何刷app推广次数
今天晚上打开笔记本,准备继续学习《PHP和MySQL Web开发》一书。打开xampp的控制面板,突然发现Apache无法启动。每次点击start按钮,都是很快闪出一个Running,然后立即又变成start了。本人算是百思不得其解,只有问问强大…...