【利用基于 Lint 的启动代码监控 Android 进房间流程】iOS 平台的可行性调研

一、目的 通过标识关键代码,利用Lint定期检测关键代码,当关键代码发生变更时,可以在报告中体现,避免引入意外的修改。 二、Lint Lint介绍 Lint 作为一种工具程序,主要负责静态源码分析,负责代码规范、代码缺陷等检测。(https://zh.wikipedia.org/wiki/Lint) Android 原生已经提供了 Lint 静态分析工具,而 iOS 则有对应的 OCLint 和 SwiftLint。 三、android实现代码监控的原理 Android Lint可以对Java/Kotlin源码使用uast进行语法分析,最后生成一棵AST抽象语法树,这棵树的每个节点都是一个方法,包含方法签名和方法体。 通过自定义的Lint规则,找到应用启动的入口方法。从入口方法开始遍历整棵调用树,即可对每个方法节点的代码段进行分析。 1. 获取关键代码段 open fun shouldMonitor(node: UMethod): Boolean { return node.getAnnotations().find { it.qualifiedName == LINT_MONITOR_ANNOTATION } != null } android通过注解的方式,标识需要检测的方法实现。通过Android Lint的自定义规则判断注解标识的方法,找到入口方法,然后对代码段进行分析。 iOS对应的OCLint也可以通过自定义规则进行静态分析,但是iOS没有注解,iOS如果需要标识关键代码段,目前想到的只能hardcode检测列表(包括类和方法名)来实现标识关键代码段的效果。但是这样的维护成本比较大。 iOS可以利用类似以下的代码片段进行类和方法的判断: for(auto it = parent->meth_begin(), end = parent->meth_end(); it != end; ++it) { const auto method = *it; if(declHasEnforceAttribute(method, *this)) { const auto selector = method->getSelector(); if(!implementation->getMethod(selector, method->isInstanceMethod())) { const string className = parent->getNameAsString(); const string methodName = selector.getAsString(); addViolation(implementation, this, "subclasses of " + className + " must implement " + methodName); } } } 注:在生成OCLint自定义规则工程时,Xcode 12会因架构问题出错,暂未找到解决方案,可暂时先切换Xcode 11生成OCLint自定义规则工程 ...

2020年8月20日 · Darren Ou

利用 git worktree 并行开发

一、什么是worktree? Manage multiple working trees attached to the same repository. A git repository can support multiple working trees, allowing you to check out more than one branch at a time. 管理连接到同一存储库的多个工作树。一个git仓库可以支持多个工作树,允许一次迁出多个分支。 二、为什么要用worktree? 场景:正在开发新的需求,这时候还需要对已提测功能的bug fixes 方法1:先stash正在开发的代码,再check out到原分支进行bug fixes 缺点:分支切换后需要重新pod install,然后xcode重建索引并重新编译,耗时耗力 方法2:clone两个仓库 缺点:.git目录会占空间,且两个仓库之间毫无关联,无法相互查看本地提交 方法3:git worktree 三、worktree的相关命令 ➜ worktree_test git:(develop) git worktree -h usage: git worktree add [<options>] <path> [<commit-ish>] or: git worktree list [<options>] or: git worktree lock [<options>] <path> or: git worktree move <worktree> <new-path> or: git worktree prune [<options>] or: git worktree remove [<options>] <worktree> or: git worktree unlock <path> 四、常用操作 1、添加一个worktree git worktree add ../live_protocol_isolation team_live/live_protocol_isolation ...

2020年5月10日 · Darren Ou

树莓派简介

一、树莓派是什么 树莓派是一款基于ARM的微型电脑主板,系统基于Linux,以MicroSD卡为磁盘,树莓派主板有4个USB接口,一个以太网接口,一个3.5mm音频口,一个HDMI视频接口,40-pin的GPIO连接端口 https://www.raspberrypi.org/products/ 二、树莓派可以干什么 可以当做一个微型服务器,作为FTP文件服务器、网站服务器、代码托管等 游戏机(刷入游戏模拟器系统,可玩大部分复古游戏)https://www.jianshu.com/p/feffa629084a 遥控车(手机通过socket连接控制小车,且小车装有红外线传感器,可以自动避障), 可以结合其他传感器搭建智能家居 三、DHT11温湿度传感器 传感器文档 价格便宜,体积小,功耗低,缺点是精度不高 有四个针脚:正极、负极、串行数据总线、空脚 DHT11温湿度传感器数据读取原理 代码: __author__ = 'DuShu' import time import RPi.GPIO as GPIO def getIntVal(binayList): idx = 0 binaryStr = '' while idx < len(binayList): binaryStr += str(binayList[idx]) idx += 1 return int(binaryStr ,2) def getHumidity(): GPIO_DATA = 18 GPIO_HIGH = GPIO.HIGH GPIO_LOW = GPIO.LOW GPIO.setmode(GPIO.BCM) #一秒后开始工作 time.sleep(1) #设置GPIO接口为输出数据模式 GPIO.setup(GPIO_DATA,GPIO.OUT) #输出一个低电平信号 GPIO.output(GPIO_DATA, GPIO.LOW) time.sleep(0.02) #0.018 #0.02秒后输出一个高电平信号,启动模块测量 GPIO.output(GPIO_DATA, GPIO.HIGH) #设置GPIO接口为读取读取数据模式 GPIO.setup(GPIO_DATA,GPIO.IN) #等待,获取到高电平信号 while GPIO.input(GPIO_DATA) == GPIO_HIGH: pass #等待,获取到低电平信号 while GPIO.input(GPIO_DATA) == GPIO_LOW: pass #获取到高底电平信号后,开始读取模块获取数据 data = [] cnt = 40 dataArr = [] while cnt > 0: cnt = cnt - 1 while GPIO.input(GPIO_DATA) == GPIO_LOW: pass k = 0 while GPIO.input(GPIO_DATA) == GPIO_HIGH: k += 1 if k > 100: break dataArr.append(k) if k < 10: data.append(0) else: data.append(1) print(dataArr) #根据公式定义,解析温湿度数据 val1 = getIntVal(data[0:8]) val2 = getIntVal(data[8:16]) val3 = getIntVal(data[16:24]) val4 = getIntVal(data[24:32]) val5 = getIntVal(data[32:40]) if (val5 == val1 + val2 + val3 + val4): return [val1, val3] else: return [-1, -1] GPIO.cleanup() if __name__ == '__main__': hum = getHumidity() print("hum:", hum[0], " temp:", hum[1]) 四、参考 https://blog.csdn.net/guyang1995/article/details/54692810 https://zhuanlan.zhihu.com/p/74440206

2020年3月20日 · Darren Ou

直播间横屏技术方案

一、背景 目前直播间已支持PC推流直播,但只支持竖屏播放,导致观看体验不佳,故需要把横屏模式加上。 二、面临的问题 1、只有两天开发时间,时间紧迫; 2、屏幕切换时存在UI异常风险; 3、横屏模式需要应用全局支持横屏方向,存在影响其他业务风险; 三、方案 1、切换横屏入口的显示(加开关) joinChannel/joinGroup成功 -> roomType == BSRoomType_PCRoom -> 判断开关 -> 显示入口 2、横竖屏切换初步方案 1.项目配置: 在AppDelegate里重写方法: - (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window { UIViewController *topViewController = [BaseViewController bl_findTopMostViewController]; if (topViewController && [topViewController isKindOfClass:NSClassFromString(@"DSLandscapeViewController")]) { return topViewController.supportedInterfaceOrientations; } return UIInterfaceOrientationMaskPortrait; } 2.竖屏转横屏: 新建一个支持横屏的DSLandscapeViewController并重写UIViewController的方法: - (UIInterfaceOrientationMask)supportedInterfaceOrientations { return UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortrait; // 这里加上竖屏,是因为横屏转竖屏时,需要把当前vc先转过来,后面会讲到 } - (BOOL)shouldAutorotate { return YES; } 从竖屏vc(DSLiveShowViewController)旋转时,初始化横屏vc(DSLandscapeViewController),并把竖屏vc当前持有的BLLiveRoomAudienceSession传递给横屏vc。然后在当前navigationController的子vc中,把竖屏vc替换成横屏vc(原竖屏vc会销毁)。 - (void)switchToLandscapeView { DSLandscapeViewController *viewController = [[DSLandscapeViewController alloc] initWithAudienceSession:self.audienceSession]; NSMutableArray *viewControllers = [[NSMutableArray alloc] initWithArray:self.navigationController.viewControllers]; [viewControllers removeObject:self]; [viewControllers addObject:viewController]; [self.navigationController setViewControllers:viewControllers animated:YES]; self.audienceSession = nil; } 横屏vc初始化videoView,并调用session的方法传递给mediasdk([self.audienceSession changeRemoteViewForGroup:self.videoView];) ...

2020年1月4日 · Darren Ou

iOS 项目接入 CallKit 框架

CallKit是什么 苹果在WWDC 2016发布了iOS 10的新框架CallKit,它允许开发者在VoIP类型APP整合系统原生语音界面,以获得更好的用户体验。 接入CallKit后,APP里面的通话会被写入系统通话记录,而且APP通话时的优先级比一般VoIP的APP优先级要高。 CallKit主要类 我们可以把系统提供的CallKit框架理解成一个通话的界面UI,我们做的只是把这个通话界面跟我们的应用联系起来, 比如有一个语音电话过来了,那么我们就呼起这个界面,用户在这个界面上点击了静音按钮,我们相应地做静音的处理。 同样地,如果用户在应用里面点击了静音按钮,也要把状态同步到系统的界面。 呼起CallKit & CallKit回调 CXProviderConfiguration 描述系统通话界面的标题、logo、来电铃声等 CXCallUpdate 描述来电信息,如来电用户名、是否视频聊天等 CXProvider 同步连接状态到CallKit CXProviderDelegate 用户在系统通话界面上动作的回调 应用把状态同步到系统: CXCallAction 用户不同动作的描述,如:呼出电话、接听、挂断、静音等(分别对应CXStartCallAction、CXAnswerCallAction、CXEndCallAction、CXSetMutedCallAction) CXTransaction 把用户的不同动作打包成CXTransaction再传递给系统 CXCallController 把CXTransaction传递给系统 原理 1、呼入 来电 1.CallKitTestApp接收到服务器的来电信息(后台时VoIP Push) 2.创建CXCallUpdate对象记录来电信息,并记录该通话唯一标识 3.通过CXProvider对象把来电信息通知系统 4.系统接收到来电信息后显示原生来电UI 响应来电 1.用户在原生来电UI上点击接听按钮(或挂断按钮) 2.系统把动作封装成CXAnswerCallAction,通过CXProvider的Delegate回调到CallKitTestApp 3.CallKitTestApp收到回调后会开始配置mediasdk,调整UI等操作,最后开始通话 结束来电 1.CallKitTestApp在前台时,用户点击挂断按钮 2.创建CXTransaction对象,用于包装挂断动作CXEndCallAction(包含该通话唯一标识) 3.通过CXCallController对象把挂断动作通知系统 4.系统成功挂断该通话后,通过CXProvider的Delegate回调到CallKitTestApp 5.CallKitTestApp收到回调后会开始释放mediasdk(这里要注意释放顺序,不能在系统回调前把sdk释放掉),调整UI等操作 2、呼出 1.用户在CallKitTestApp呼出电话 2.创建CXTransaction对象,用于包装呼出动作CXStartCallAction(包含该通话唯一标识、呼出号码等信息) 3.通过CXCallController对象把呼出动作通知系统 4.系统成功呼出该通话后,通过CXProvider的Delegate回调到CallKitTestApp 5.CallKitTestApp收到回调后会开始配置mediasdk,调整UI等操作,最后开始通话 以上呼出逻辑是CallKitTestApp中IP-IP电话的处理逻辑,因为CallKitTestApp里面还有自动转换IP-PSTN逻辑,涉及到mediasdk资源释放问题,导致应用不稳定,内测版中暂时把呼出部分屏蔽。 CallKitTestApp如何接入 1、mediasdk做兼容 1.系统资源申请成功后再配置mediasdk 2.等系统资源释放后再释放mediasdk 为满足以上两点,mediasdk开放了下面几个接口,在CallKit模式下,等系统回调后再做mediasdk配置、激活、释放等操作 // In CallKit, it should be called by `perform Answer/start CallAction` bool callKit_test_configAudio(MEDIA_HANDLE handle); // In CallKit, it should be Called by `didActivateAudioSession` bool callKit_test_startAudio(MEDIA_HANDLE handle); // In CallKit, it should be Called by `didDeactivateAudioSession` void callKit_test_stopAudio(MEDIA_HANDLE handle); // In CallKit, it should be Called by `performEndCallAction` void callKit_test_releaseAudio(MEDIA_HANDLE handle); 2、CallKitTestApp在前台状态 1.在CallKitTestApp项目中添加BOProviderDelegate对象,作为与CallKit通信的桥梁。 ...

2019年5月10日 · Darren Ou

多任务页面底部 banner 出现的种类

多任务页面底部banner出现的种类 1、handoff(接力) 2、xxx,早上好 3、基于您的当前位置 4、“耳机"已连接 5、(未知。。。) 实现 handoff 设置 -> 通用 -> 接力 -> 打开接力 通过“接力”功能,您在某设备开始操作后,可立即使用iCloud账号从另一设备继续相同操作。您需要的应用会显示在应用切换器和Mac的程序坞上。 这种一般应用在需要跨设备共享编辑的应用上,例如“印象笔记”,在手机上编辑了一段文字后,可以切换到Mac上继续编辑。 官方简介:https://support.apple.com/zh-cn/HT204681 这是开发者唯一能控制的底部banner种类 Siri应用建议 xxx,早上好 基于您的当前位置 “耳机”已连接 关于前两种情况: 系统会记录用户长期的使用应用的习惯,自主地向用户推荐相关的应用。例如,早上起床后我会习惯打开微博,系统早上就会给我推荐微博;来到公司我会习惯打开微信,系统也会根据地理位置给我推荐微信。系统会通过记录用户的使用频率、时间、地理位置等维度,向用户推荐应用。 关于“耳机”已连接 手机连接蓝牙耳机或者有线耳机后,系统会弹出 “耳机”已连接 的banner,如果手机装有多个音乐应用,目前观察到的情况是会弹出最常用的那个应用。但是有时候又不会弹,而且弹出维持的时间也没有规律。 国外用户如是说: This is a feature suggesting which app you normally use in a certain situation. Like location, time of day, headphones or Bluetooth connected. Phone tries to learn if you always do something. Like I always open maps when I connect to my car Bluetooth. Or always open music when by headphones are plugged in. Or always open weather at 7am. ...

2018年11月12日 · Darren Ou

iOS 学习笔记

一、内存分配 结构体分配内存,内存对齐(8的倍数) 对象分配内存,内存对齐(16的倍数) 一个NSObject对象占用多少内存?(一个指针变量所占用的大小(64bit,8个字节,32bit,4个字节)) class_getInstanceSize() 至少需要多少内存 malloc_size() 实际分配了多少内存 二、对象 对象类型(实例对象、类对象、元类对象) 对象的isa指针指向哪里?(instance对象的isa指向class对象;class对象的isa指向meta-class对象;meta-class对象的isa指向基类的meta-class对象) OC的类信息存放在哪里?(对象方法、属性、成员变量、协议信息,存放在class对象中;类方法存放在meta-class对象中;成员变量的具体值存放在instance对象中) isa & ISA_MASK 才是真正的类对象或元类对象地址(arm64下是0x0000000ffffffff8ULL) 三、KVO、KVC KVO的本质(利用runtime动态生成一个子类,重写set方法、class、dealloc、isKVOA方法,并且让instance对象的isa指向这个全新的子类;当修改instance对象的属性时,会调用_NSSetXXXValueAndNotify函数;willChangeValueForKey/setter/didChangeValueForKey) 如何手动触发KVO(手动调用willChangeValueForKey/didChangeValueForKey,必须两个方法都调用) 直接修改成员变量会触发KVO吗(不会) KVC set原理(查找set方法:setKey -> _setKey;查看accessInstanceVariablesDirectly;查找成员变量:_key -> _isKey -> key -> isKey) KVC get原理(查找get方法:getKey -> key -> isKey -> _key;查看accessInstanceVariablesDirectly;查找成员变量:_key -> _isKey -> key -> isKey) 四、Category Category 原理(编译成struct category_t,存储这对象方法、类方法、属性、协议信息,程序运行时会将Category的数据合并到类信息中) Category和Class Extension区别(Class Extension在编译的时候数据就包含在类信息中;Category在运行时才会将数据合并到类信息中) +load方法的调用顺序(类:先编译的先调用;先调用父类的 分类:按照编译顺序调用) +initialize方法在类第一次接收到消息时调用,先调用父类的+initialize,再调用子类的+initialize +load和+initialize最大的区别是,+load通过函数地址直接调用,+initialize通过objc_msgSend进行调用,遵循消息发送的机制(如果分类实现了+initialize,则调用分类的;如果子类没有实现,则调用父类的+initialize;父类的可能被调用多次) 五、关联对象 key的设置(@selector(key) / static const char key; &key; / static const void *key = &key / @“key") 关联对象不是存储在被关联对象本身内存中,而是存储在全局的统一的一个AssociationManager中 AssciationsManager -> AssociationHashMap -> object: ObjectAssociationMap -> key: ObjectAssociation -> policy/value 对象销毁时会把对象对应的ObjectAssociationMap销毁 六、Block 本质是结构体对象(__main_block_impl_0) block类型(NSGlobalBlock:没有访问auto变量;NSStackBlock:访问了auto变量;NSMallocBlock:NSStackBlock copy) 捕获auto变量(NSStackBlock:不会强引用auto变量,栈block随时释放,没必要强引用) NSMallocBlock:当block拷贝到堆上时,会调用desc里面的copy函数,把auto变量拷贝到堆上;当从堆上移除时,会调用desc里面的dispose函数,release auto变量 __block 修饰符,会将其修饰的变量包装成一个对象;不能修饰全局变量、静态变量 MRC下,block不会对__block修饰的对象进行强引用(MRC下,可用__block解决循环引用问题) __weak:不会产生强引用,指向的对象销毁时会自动置nil; __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变 ...

2018年3月8日 · Darren Ou

iOS 项目集成 Reveal

Reveal是一款UI调试工具,在iOS开发过程中可查看UI的层级关系并可动态修改界面,可以有效提高开发效率。这篇文章主要介绍如何把Reveal集成到实际的项目中。 一、Reveal主要有两种集成方式: 1、framework集成(可忽略,直接看第二点) (1)打开Reveal,选择 Reveal –> Help –> Show Reveal Library in Finder –> iOS Library (2)把Reveal.framework导入到项目中 (3)配置Target,Target –> Build Setting –> Other Linker Flags 添加以下4项: (4)运行项目 –> 打开Reveal –> 选择连接设备 项目的UI图层就可以出来了。 这种方法有几个弊端:1、需要配置项目的Build Setting,修改Other Linker Flags,多人协作开发的话如果有人没有配置就会编译出错;2、如果不留意容易打包的时候把Reveal的framework也打包进去,增大包的体积。 以下推荐另一种集成方法。 2、LLDB集成(推荐!!) (1)运行项目 –> 点击Pause program execution。 (2)在LLDB依次输入以下两条命令 # expr (Class)NSClassFromString(@“IBARevealLoader”) == nil ? (void*)dlopen("/Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib", 0x2) : ((void*)0) # expr (void)[(NSNotificationCenter *)[NSNotificationCenter defaultCenter] postNotificationName:@“IBARevealRequestStart” object:nil]; 第一条命令用于加载Reveal的动态链接库; 第二条命令用于启动Reveal调试服务。 ...

2017年12月21日 · Darren Ou

iOS 利用 Speech Kit 实现语音识别

一、前言 前一段时间彩云小译上了App Store的推荐,我下载试玩了一下,效果还是非常不错的。它可以实现实时翻译的功能,我自己粗浅地分析了一下彩云小译的实现原理,其中最重要的一步就是声音转文字。 目前市面上也有很多服务商提供声音转文字的服务,有收费的有免费的,但是毕竟是第三方的服务商,接口的性能和稳定性都不一定能保证。 2016年Apple在发布重磅产品iOS10的同时也发布了Speech Kit语音识别框架,大名鼎鼎的Siri的语音识别就是基于Speech Kit实现的。有了Speech Kit,我们就可以非常简单地实现声音转文字的功能。下面我就简单介绍一下Speech Kit的用法。 二、实现 1、页面布局 因为只是实现一个Demo,页面不需要多复杂,只需要在Storyboard上拖入两个控件:一个UITextView用于展示声音转文字的结果,一个UIButton用于触发语音识别,最好布置好约束即可。具体效果如下图: 2、申请用户权限 首先需要引入Speech Kit框架 #import <Speech/Speech.h> 申请权限非常简单,在识别前(viewDidLoad:)加入以下代码即可申请语音识别的权限: - (void)viewDidLoad { [super viewDidLoad]; // 请求权限 [SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) { NSLog(@"status %@", status == SFSpeechRecognizerAuthorizationStatusAuthorized ? @"授权成功" : @"授权失败"); }]; } 这时候运行起来会崩溃,原因是在iOS10后需要在info.plist文件中添加麦克分和语音识别权限申请信息: <key>NSSpeechRecognitionUsageDescription</key> <string>请允许语音识别</string> <key>NSMicrophoneUsageDescription</key> <string>请打开麦克风</string> 运行项目,会提示打开语音识别和打开麦克风权限,至此我们已经完成了权限的申请。 3、初始化语音识别引擎 添加以下代码: - (void)initEngine { if (!self.speechRecognizer) { // 设置语言 NSLocale *locale = [NSLocale localeWithLocaleIdentifier:@"zh-CN"]; self.speechRecognizer = [[SFSpeechRecognizer alloc] initWithLocale:locale]; } if (!self.audioEngine) { self.audioEngine = [[AVAudioEngine alloc] init]; } AVAudioSession *audioSession = [AVAudioSession sharedInstance]; [audioSession setCategory:AVAudioSessionCategoryRecord mode:AVAudioSessionModeMeasurement options:AVAudioSessionCategoryOptionDuckOthers error:nil]; [audioSession setActive:YES withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil]; if (self.recognitionRequest) { [self.recognitionRequest endAudio]; self.recognitionRequest = nil; } self.recognitionRequest = [[SFSpeechAudioBufferRecognitionRequest alloc] init]; self.recognitionRequest.shouldReportPartialResults = YES; } 初始化SFSpeechRecognizer时需要传入一个NSLocle对象,用于标识用户输入的语种,如"zh-CN"代表普通话,“en_US"代表英文。 AVAudioEngine是音频引擎,用于音频输入。 利用AVAudioSession对象进行音频录制的配置。 在语音识别产生最终结果之前可能产生多种结果,设置SFSpeechAudioBufferRecognitionRequest对象的shouldReportPartialResult属性为YES意味着每产生一种结果就马上返回。 4、启动语音识别引擎 添加以下代码: ...

2017年12月10日 · Darren Ou

在树莓派上部署 ATC 网络模拟工具(Augmented Traffic Control)

一、前言 作为移动开发者的我们,为了良好的用户体验,经常需要模拟手机应用在比较差的网络环境下的表现,模拟网络环境的方式有很多,比如使用Charles,或者在手机的开发者模式下模拟网络环境等等,但是这些都有一定的门槛。 使用Charles,首先你得连接WiFi,然后设置代理,接着开启网络模拟模式,最后测试完了如果忘了关闭代理,可能手机就上不了网了。而且都连上你电脑的代理的话就一次只能模拟一种网络环境。 使用手机的开发者模式,首先你的手机能进入开发者模式。 如果你需要随便抓一位不懂技术同事帮你测试,这些方式都不太友好,有没有一种方式可以连上WiFi就可以使用的测试方式呢?有!接下来就介绍Facebook出品的一款网络模拟工具ATC。 二、简介 Augmented Traffic Control (ATC) is a tool to simulate network conditions. It allows controlling the connection that a device has to the internet. Developers can use ATC to test their application across varying network conditions, easily emulating high speed, mobile, and even severely impaired networks. ATC全名叫Augmented Traffic Control,是Facebook出品的一款网络模拟工具,移动开发者可以通过这款工具模拟不同条件下的网络环境,可以通过网页自由地模拟网络带宽(bandwidth)、延迟(latency)、丢包率(packet loss)、错包率(corrupted packets)和乱序率(packets ordering)。 而且!!!更牛逼的是:不同的设备连接到同一WiFi还可以模拟不同的网络环境互不影响。 github地址 三、准备 1、树莓派3(已内置有无线网卡) 2、已刷入最新RASPBIAN系统的SD卡 四、安装 安装主要有两步: 1、让树莓派有发射AP热点的能力; 2、安装ATC。 1、让树莓派有发射AP热点的能力 1.安装hostapd虚拟热点程序和dnsmasq配置DHCP、DNS服务程序: sudo apt-get install dnsmasq hostapd ...

2017年4月11日 · Darren Ou