C# Timer用法有哪些呢?我们在使用C# Timer时都会有自己的一些总结,那么这里向你介绍3种方法,希望对你了解和学习C# Timer使用的方法有所帮助。

关于C# Timer类  在C#里关于定时器类就有3个

C# Timer使用的方法1.定义在System.Windows.Forms里

C# Timer使用的方法2.定义在System.Threading.Timer类里  "

C# Timer使用的方法3.定义在System.Timers.Timer类里

下面我们来具体看看这3种C# Timer用法的解释:

◆System.Windows.Forms.Timer

应用于WinForm中的,它是通过Windows消息机制实现的,类似于VB或Delphi中的Timer控件,内部使用API  SetTimer实现的。它的主要缺点是计时不精确,而且必须有消息循环,Console  Application(控制台应用程序)无法使用。

◆System.Timers.Timer

和System.Threading.Timer非常类似,它们是通过.NET  Thread  Pool实现的,轻量,计时精确,对应用程序、消息没有特别的要求。

◆System.Timers.Timer还可以应用于WinForm,完全取代上面的Timer控件。它们的缺点是不支持直接的拖放,需要手工编码。

            //实例化Timer类,设置间隔时间为10000毫秒; System.Timers.Timer t = new System.Timers.Timer(1000);//到达时间的时候执行注册事件t.Elapsed += (obj,e) => {Console.WriteLine("ok");};//设置是执行一次(false)还是一直执行(true); t.AutoReset = true;//是否执行System.Timers.Timer.Elapsed注册事件;   t.Enabled = true;Console.ReadKey();

javascript 对应的

setInterval(funciont(){xxxxx},1000)一直执行

setTimeout(function(){xxxxx},1000)只执行一次

需求:规定时间启动定时器(比如早上下午6点启动,上午就管理员运行程序 )

code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Threading;namespace CsDemo
{class Program{static System.Timers.Timer t = null;static Thread td = null;static bool isOpenTimers = false;static bool isThreadFalg = true;static void Main(string[] args){Timers(statrDay() * msecTime);//statrDay() * msecTime读取配置文件或者指定时间
Threads();}private static void Threads(){td = new Thread(ThreadMethod);td.IsBackground = true;td.Start(td);}/// <summary>定时器/// </summary>public static void Timers(double Time){t = new System.Timers.Timer(Time);t.Elapsed += (obj, e) =>{//Do something
            };t.AutoReset = true;t.Enabled = true;}/// <summary>线程 指定时间启动定时器/// </summary>private static void ThreadMethod(object td){var temp = td as Thread;while (isThreadFalg){int hour = DateTime.Now.Hour;if (hour >= hourTime)//hourTime 读取配置文件 下午6点
                {if (!isOpenTimers){t.Start();Thread.Sleep(1000);temp.Abort();}}Thread.Sleep(300000);}}}
}

2:问题 定时器里面方法没有执行完 下一次又启动了 但是我希望排队的方式运行 (上次任务结束才开始)

code:

        /// <summary>定时器/// </summary>public static void Timers(double Time){t = new System.Timers.Timer(Time);t.Elapsed += (obj, e) =>{t.Stop();//Do something
                t.Start();};t.AutoReset = true;t.Enabled = true;t.Start();}

3:需求3 假设定时器7天运行一次 1号开启 突然bug或者意外3号天挂了  第4天开启 这个时候  7号继续执行 不能等到4+7 11号执行

原理:

每次运行定时器动态计算时间:异常时候写入奔溃时间

1:

   Timers(statrDay() * msecTime);//mescTime 为 86400000 一天 这样项目原因不考虑public double statrDay(){DateTime lastDay = DateTime.Parse(nextDayStar);TimeSpan ts = lastDay - DateTime.Now;var dayTemp = ts.Days;if (dayTemp >= pushTime || dayTemp <= 0) dayTemp = pushTime;return dayTemp; }

2:Timer Elapsed方法每次运行写入下次启动时间

Logging.GetInstance().InsertNextDayStart(DateTime.Now.AddDays(pushTime).ToString());pushTime=7 

3:程序奔溃写入下次启动时间

  public static void InsertLog(){ThreadPool.QueueUserWorkItem(o =>{while (true){try{if (tempLog.Count > 0){string errorMsg = tempLog.Dequeue();Logging.GetInstance().WriteLog(errorMsg, path);Logging.GetInstance().InsertNextDayStart(DateTime.Now.AddDays(frm.pushTime).ToString());Thread.Sleep(30);}else{Thread.Sleep(30000);}}catch (Exception ex){tempLog.Enqueue("系统错误" + GetExceptionMsg(ex as Exception, ex.ToString()));}}});}

为队列 public static Queue<string> tempLog = new Queue<string>();

详细代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using DAL;
using Redslide.HttpLib;
using System.Web;
using System.Threading;
using System.Collections.Concurrent;
using System.Reflection;
using System.Threading.Tasks;
using Tool.Entity;
using System.Diagnostics;
using System.Configuration;
using System.IO;
using System.Web.Script.Serialization;namespace IllegalMsgPush
{public partial class frWz : Form{#region 全局变量System.Timers.Timer t = null;Thread td = null;bool isOpenTimers = false;bool isThreadFalg = true;string urlCopy = null; ConcurrentDictionary<string, userList> dicUser=null;static Task tk = null;/// <summary>/// 违章消息推送时间启动(单位小时)/// </summary>public int hourTime = 0;/// <summary>/// 违章消息推送定时推送(单位天)/// </summary>public int pushTime = 0;/// <summary>/// 下次启动时间(day)/// </summary>public string nextDayStar =null;Action<string> acCarMsgShow = null;public int holdID;public int msecTime;/// <summary>同步锁用于删除队列信息与添加消息/// </summary>private static object _objfrWzMsgLock = new object();#endregionpublic frWz(){InitializeComponent();this.SizeChanged += (o, e) =>{if (this.WindowState == FormWindowState.Minimized){this.Hide();this.notifyIcon1.Visible = true;}};InitializationParameter();Timers(statrDay() * msecTime);Threads();}#region      -sql command/// <summary>用户以及子用户下面的车/// </summary>private DataTable LoadCarChildList(){var sql = string.Format(@"with cte as(select userName ,userID from std_UserInfo where isDeleted=0) select a.ObjectID,a.vehicleNum,d.EngineCode,d.ShelfCode ,c.userNamefrom dbo.Table_F_GetObjectInfoByObjUserHoldID(0,1,1) a left join std_ObjAppend d with (nolock) on  a.ObjectID=d.ObjectID left join std_UserObj   b with (nolock)  on  d.ObjectID=b.ObjectID left hash join cte c on  b.userID=c.userID where c.userName is not null", holdID);return SqlHelper.ExecuteDataset(sql).Tables[0];}public void InsertPushInfo(string objectID, string vehclieNum, string userName){Task.WaitAll(tk);StringBuilder sql = new StringBuilder();var temp = objectID + "_" + userName;if (ContainsKey(temp)){sql.Append(@"INSERT INTO personal_PushInfo(Contype,context,UserName,OBDTerminalNo ,voiceName             ,isDeleted ,RcvTime ,TypeID ,ID ,VehicleNum,ItemID,voiceDetail,GPSTime ,CerFileName,CerPassword,PushServerType)");sql.AppendFormat(@"VALUES('违章','您有一条新的违章消息','{0}','{1}','109.wav',0,getdate(),2,'{2}','{3}',109,'您有一条新的违章消息',getdate(),'{4}','{5}',1)", dicUser[temp].UserName              //UserName, dicUser[temp].OBDTerminalNo         //OBDTerminalNo, int.Parse(objectID)                 //ID  -车辆ID ObjectID, vehclieNum                          //VehicleNum, dicUser[temp].CerFileName           //CerFileName 安卓null , dicUser[temp].CerPassword           //CerPassword
                                          );SqlHelper.ExecuteNonQuery(sql.ToString());}else{string str = "key不存在异常出现在ID: " + objectID + "车牌:" + vehclieNum + "userName:" + userName;Logging.GetInstance().WriteLog(str, Program.carListLog);}}public void LoadUserList(){dicUser = new ConcurrentDictionary<string, userList>();tk = Task.Factory.StartNew(() =>{var sql = "SELECT UserName,OBDTerminalNo,ObjectID,CerFileName,CerPassword  FROM std_UserObj  a left hash join std_UserInfo b on a.UserID=b.UserID WHERE b.isDeleted=0";var temp = SqlHelper.ExecuteDataset(sql).Tables[0];if (temp != null || temp.Rows.Count > 0){var dataType = typeof(userList);var infos = dataType.GetProperties(BindingFlags.Instance | BindingFlags.Public);foreach (DataRow row in temp.Rows.AsParallel()){var obj = (userList)Activator.CreateInstance(dataType);foreach (DataColumn col in temp.Columns.AsParallel()){try{foreach (PropertyInfo info in infos){if (info.Name.ToLower() == col.ColumnName.ToLower()){if (row[col].GetType().FullName != "System.DBNull"){info.FastSetValue(obj, row[col]);}}}}catch{}}Add(obj.ObjectID.ToString() + "_" + obj.UserName, obj);}}});}public void csDemo(){if (this.txtContent.InvokeRequired){this.txtContent.Invoke(acCarMsgShow, "执行一次");}string str = @"INSERT INTO personal_PushInfo(Contype,context,UserName,OBDTerminalNo ,voiceName,isDeleted ,RcvTime ,TypeID ,ID ,VehicleNum,ItemID ,voiceDetail,GPSTime ,CerFileName,CerPassword,PushServerType)VALUES('报警','车辆设防','chen','d3e0d773cd1dde1a939bde8b4ea5605ffb0a9aa3dd07be1dbb8213f6f7b64862','79.wav',0,getdate(),1,73252,'粤Blyq', 79,'您有一条新的违章消息',getdate(),'carGeniusP_erp_dev.pem','123456',0)";SqlHelper.ExecuteNonQuery(str);}#endregionprivate void HttpGet(){var dt = LoadCarChildList();if (dt == null || dt.Rows.Count == 0) return;JavaScriptSerializer js = new JavaScriptSerializer();foreach (DataRow item in dt.Rows){lock (_objfrWzMsgLock){var carCode = item["EngineCode"].ToString();if (string.IsNullOrEmpty(carCode)) continue;var carNumber = item["ShelfCode"].ToString();if (string.IsNullOrEmpty(carNumber)) continue;var objectID = item["ObjectID"].ToString();var vehclieNum = item["vehicleNum"].ToString();var userName = item["userName"].ToString();string url = string.Format(urlCopy, HttpUtility.UrlEncode(vehclieNum, System.Text.Encoding.UTF8), GetLastStr(carCode, 6), GetLastStr(carNumber, 6));Request.Get(url, null, result =>{var json = (Dictionary<string, object>)js.DeserializeObject(result);if (json == null) return;if (bool.Parse(json["Success"].ToString()) && bool.Parse(json["HasData"].ToString())){var tempMsg = "车牌ID:" + objectID  + "--车牌号:" + vehclieNum + "--用户名:" + userName;if (this.txtContent.InvokeRequired) this.txtContent.Invoke(acCarMsgShow, tempMsg);InsertPushInfo(objectID, vehclieNum, userName);Logging.GetInstance().WriteLog(tempMsg, Program.carListLog);}else {Logging.GetInstance().WriteLog(result.ToString(), Program.carListLog);}}, e =>{string str = "http请求错误" + Program.GetExceptionMsg(e, string.Empty);Logging.GetInstance().WriteLog(str, Program.carListLog);});}}}#region    eventprivate void button1_Click(object sender, EventArgs e){if (MessageBox.Show("要重新启动嘛?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes){t = null;td = null;notifyIcon1 = null;Application.Restart();}}private void frWz__Shown(object sender, EventArgs e){this.Visible = true;notifyIcon1.Visible = true;notifyIcon1.Icon = this.Icon;}private void frWz_FormClosing(object sender, FormClosingEventArgs e){if (notifyIcon1 != null){e.Cancel = true;this.Visible = false;}}private void ToolStripMenuItem_Show_Click(object sender, EventArgs e){this.Show();this.WindowState = FormWindowState.Normal;}private void ToolStripMenuItem_Exit_Click(object sender, EventArgs e){if (MessageBox.Show("你确定退出吗?", "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes){t = null;td = null;dicUser = null;notifyIcon1 = null;this.Close();}}private void notifyIcon1_MouseClick(object sender, MouseEventArgs e){if (e.Button == MouseButtons.Left){notifyIcon1.Visible = true;this.Show();this.WindowState = FormWindowState.Normal;}else{Point pt = new Point();pt = Control.MousePosition;contextMenuStrip1.Show(pt);}}#endregionprivate void Threads(){td = new Thread(ThreadMethod);td.IsBackground = true;td.Start(td);}/// <summary>定时器/// </summary>public void Timers(double Time){t = new System.Timers.Timer(Time);t.Elapsed += (obj, e) =>{t.Stop();Logging.GetInstance().InsertNextDayStart(DateTime.Now.AddDays(pushTime).ToString());isOpenTimers = true;LoadUserList();HttpGet();t.Interval = pushTime * msecTime;t.Start();};t.AutoReset = true;t.Enabled = true;}/// <summary>初始化参数和变量/// </summary>private void InitializationParameter(){Logging.GetInstance().CreateFile(Program.path, Program.carListLog);urlCopy = ConfigRead.GetInstance().pushUrlValue();hourTime = ConfigRead.GetInstance().hourTimeValue();pushTime = ConfigRead.GetInstance().pushDayValue();nextDayStar = Logging.GetInstance().ReadNextDayStart();holdID = ConfigRead.GetInstance().holdIDValue();msecTime = ConfigRead.GetInstance().msecTimeValue();acCarMsgShow = (strMsg) =>{txtContent.AppendText("请求成功消息:" + strMsg + "\r\n");};}/// <summary>线程 指定时间启动定时器/// </summary>private void ThreadMethod(object td){var temp = td as Thread;while (isThreadFalg){int hour = DateTime.Now.Hour;if (hour >= hourTime){if (!isOpenTimers){t.Start();Thread.Sleep(1000);temp.Abort();}}Thread.Sleep(300000);}}#region IDictionary<string,ResultType> 成员public void Add(string key, userList value){dicUser.TryAdd(key, value);}public bool ContainsKey(string key){return dicUser.ContainsKey(key);}public ICollection<string> Keys{get { return dicUser.Keys; }}public bool Remove(string key){userList val;return dicUser.TryRemove(key, out val);}public bool TryGetValue(string key, out userList value){return dicUser.TryGetValue(key, out value);}public ICollection<userList> Values{get { return dicUser.Values; }}public userList this[string key]{get{return dicUser[key];}set{dicUser[key] = value;}}#endregionpublic void updateConfig(string key, string value){var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);if (config.AppSettings.Settings[key] != null){config.AppSettings.Settings[key].Value = value;}else{config.AppSettings.Settings.Add(key, value);}config.Save(ConfigurationSaveMode.Modified);ConfigurationManager.RefreshSection("appSettings");}public double statrDay(){DateTime lastDay = DateTime.Parse(nextDayStar);TimeSpan ts = lastDay - DateTime.Now;var dayTemp = ts.Days;if (dayTemp >= pushTime || dayTemp <= 0) dayTemp = pushTime;return dayTemp; }public string GetLastStr(string str, int num){if (str.Length > num){str = str.Substring(str.Length - num, num);}return str;}}public class userList{public string UserName { get; set; }public string OBDTerminalNo { get; set; }public int ObjectID { get; set; }public string CerPassword { get; set; }public string CerFileName { get; set; }}
}

View Code

Program

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Text;
using Tool.Entity;
using System.Diagnostics;
using System.Threading;
using System.IO;
using System.Collections;namespace IllegalMsgPush
{static class Program{public static frWz frm = null;public static string path = "";public static string carListLog = "";public static Queue<string> tempLog = new Queue<string>();/// <summary>/// 应用程序的主入口点。/// </summary>
        [STAThread]static void Main(){#region 应用程序的主入口点try{bool flag;Mutex mutex = new Mutex(true, Application.ProductName, out flag);if (flag){path = string.Format("{0}{1}", AppDomain.CurrentDomain.BaseDirectory, @"/log/");carListLog = string.Format("{0}{1}", AppDomain.CurrentDomain.BaseDirectory, @"/carListLog/");InsertLog();//设置应用程序处理异常方式:ThreadException处理
                    Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);//处理UI线程异常Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);//处理非UI线程异常AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);frm = new frWz();Application.Run(frm);//释放 System.Threading.Mutex 一次
                    mutex.ReleaseMutex();}else{MessageBox.Show(null, "相同的程序已经在运行了,请不要同时运行多个程序!\n\n这个程序即将退出!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Warning);Application.Exit();}}catch (Exception ex){string str = "系统错误" + GetExceptionMsg(ex, string.Empty);tempLog.Enqueue(str);}#endregion}static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e){string str = "系统错误" + GetExceptionMsg(e.Exception, e.ToString());tempLog.Enqueue(str);}static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e){string str = "系统错误" + GetExceptionMsg(e.ExceptionObject as Exception, e.ToString());tempLog.Enqueue(str);}public static string GetExceptionMsg(Exception ex, string backStr){StringBuilder sb = new StringBuilder();sb.AppendLine("****************************异常文本****************************");sb.AppendLine("【出现时间】:" + DateTime.Now.ToString());if (ex != null){sb.AppendLine("【异常类型】:" + ex.GetType().Name);sb.AppendLine("【异常信息】:" + ex.Message);sb.AppendLine("【堆栈调用】:" + ex.StackTrace);}else{sb.AppendLine("【未处理异常】:" + backStr);}sb.AppendLine("***************************************************************");return sb.ToString();}public static void InsertLog(){ThreadPool.QueueUserWorkItem(o =>{while (true){try{if (tempLog.Count > 0){string errorMsg = tempLog.Dequeue();Logging.GetInstance().WriteLog(errorMsg, path);Logging.GetInstance().InsertNextDayStart(DateTime.Now.AddDays(frm.pushTime).ToString());Thread.Sleep(30);}else{Thread.Sleep(30000);}}catch (Exception ex){tempLog.Enqueue("系统错误" + GetExceptionMsg(ex as Exception, ex.ToString()));}}});}}
}

View Code

配置文件:

<?xml version="1.0"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/></startup><appSettings><!--路精灵所属单位ID配置 start--><add key="holdID" value="1"/><!--路精灵所属单位ID配置end--><!--违章消息推送时间天数start (正整数)--><add key="pushDay" value="14"/><!--违章消息推送时间天数end (正整数)--><!--违章消息推送几点启动start (正整数0-24)--><add key="hourTime" value="6"/><!--违章消息推送几点启动结束end (正整数)--><!--违章消息URL start--><add key="pushUrl" value="xxxx"/><!--违章消息URL end--><!--违章消息推送毫秒 (默认86400000 测试可调整修改)--><add key="msecTime" value="86400000"/><!--违章消息推送毫秒end (正整数)--></appSettings><connectionStrings><!--数据库连接串开始--><add name="ConnStr" connectionString="xxxxx"/><!--数据库连接串结束-->        </connectionStrings>
</configuration>

View Code

转载于:https://www.cnblogs.com/y112102/archive/2013/06/06/3121615.html

c#定时器Timer相关推荐

  1. 【Android 异步操作】Timer 定时器 ( Timer 与 TimerTask 基本使用 | Timer 定时器常用用法 | Timer 源码分析 )

    文章目录 一.Timer 定时器基本使用 二.Timer 定时器常用用法 三.Timer 源码分析 四.Timer 部分源码注释 五.源码及资源下载 参考文档 : Timer 定时器 API 文档 T ...

  2. Java定时器Timer

    Java定时器Timer 在JDK库中,Timer类主要负责计划任务的功能,也就是在指定的时开始执行某一个任务.Timer类的主要作用就是设置计划任务,但封装任务的类却是TimerTask类,执行计划 ...

  3. python定时器 是线程吗_python线程定时器Timer(32)

    相对前面几篇python线程内容而言,本片内容相对比较简单,定时器 – 顾名思义,必然用于定时任务. 一.线程定时器Timer原理 原理比较简单,指定时间间隔后启动线程!适用场景:完成定时任务,例如: ...

  4. python定时器timer_python通过线程实现定时器timer的方法

    本文实例讲述了python通过线程实现定时器timer的方法.分享给大家供大家参考.具体分析如下: 这个python类实现了一个定时器效果,调用非常简单,可以让系统定时执行指定的函数 下面介绍以thr ...

  5. 第5章 定时器Timer

    第5章 定时器Timer 标签: Java多线程编程 <Java多线程编程核心技术> 个人笔记 第5章 定时器Timer 定时器Timer的使用 方法scheduleTimerTask t ...

  6. python中实现定时器Timer

    实现定时器最简单的办法是就是循环中间嵌time.sleep(seconds), 这里我就不赘述了 # encoding: UTF-8 import threading #Timer(定时器)是Thre ...

  7. Python 线程定时器 Timer - Python零基础入门教程

    目录 一.Python 线程定时器 Timer 原理 二.Python 线程定时器 Timer 使用 三.Python 线程定时器 Timer 总结 四.猜你喜欢 零基础 Python 学习路线推荐 ...

  8. 定时器Timer的实现

    定时器Timer的实现 定时器在实际项目中会用的比较平凡.因此,本文首先介绍定时器Timer的windows版本,跨平台的定时器将在下一篇文章中介绍.它们的源代码均用C++编写.源代码详见:https ...

  9. Java定时器Timer学习之一

    2019独角兽企业重金招聘Python工程师标准>>> 种类: 接通延时型定时器:接通延时型定时器是各种PLC(可编程控制器)中最常见最基本的定时器,这种定时器在Siemens的PL ...

  10. python threading模块中的timer_threading中定时器Timer方法

    threading中定时器Timer 定时器功能:在设置的多少时间后执行任务,不影响当前任务的执行 常用方法 from threading import Timer t = Timer(interva ...

最新文章

  1. Android跳转intent简单教程
  2. 请求https错误: unable to find valid certification
  3. C#——WPF的菜单栏、工具栏、状态栏DEMO
  4. c++标准I/O输出流
  5. Android开发Android studio之gradle打包apk重命名的方法
  6. killall命令_没想到Linux命令也有“吓人”的一面……
  7. Mustache.js语法
  8. 求100以内素数C++
  9. 计算机平板传输软件,如何在iPad和电脑之间无线传输文件
  10. 测试工程师进阶之测试用例发散思维(二)
  11. 百度热点排行榜 ---JS
  12. rⅰd的意思_自动挡车型上的P、R、N、D、S、L是什么意思?你懂吗?
  13. 数据结构,关于链表的问题,为何直接free()不会造成断链。引用的好处
  14. 微信小程序css之盒子(box)模型
  15. 微信小程序 主题皮肤切换(switch开关)
  16. 小区数字IP广播系统
  17. 电脑控制手机教你实现多个手机同时自动安装卸载软件
  18. vue 在数组中添加字段
  19. sql char和varchar的区别
  20. socket通信之listen函数

热门文章

  1. 玻色量子与Menlo Systems共同开展光量子计算研发
  2. 人脸识别考勤机软件驱动安装和设置
  3. SpringBoot项目解决 log4j2 核弹漏洞
  4. c语言流程图文本,c语言流程图
  5. MySQL中使用update更新替换某个字符串
  6. xdf文件转换成pdf_PDF文件转换成PPT演示文稿教程
  7. python代码的注释只有一种方式、那就是使用#符号_知到智慧树营养与食疗学多选题答案...
  8. idea 搜索快捷键
  9. 炫酷而不复杂,RDP报表实用又方便
  10. QCC300x学习笔记:自定义一个GATT client