关于移动web开发:什么是移动应用的证书指纹

挪动利用的证书指纹是在SSL/TLS(安全套接层/传输层平安)协定下用于验证数字证书有效性的一种形式。在挪动利用开发中,证书指纹用于确保利用与服务器之间的通信是平安且受信赖的。它是一个加密哈希值,用于标识数字证书的公钥。通过比拟利用中存储的证书指纹与服务器返回的证书指纹,开发者能够验证服务器的身份,从而缩小中间人攻打和歹意劫持的危险。 为了更好地了解证书指纹的概念,让咱们通过一个具体的例子来阐明: 假如你是一名挪动利用开发人员,正在为一个银行创立一款挪动银行利用。该利用须要与银行的服务器进行平安通信,以便用户能够查看账户余额、转账等操作。在这种状况下,你须要应用SSL/TLS来确保通信的机密性和完整性。 以下是创立挪动利用证书指纹的步骤: 从服务器获取数字证书:银行会向你提供一个数字证书,其中蕴含银行的公钥和其余身份信息。该证书由受权的证书颁发机构(CA)签发,以确保其可信度。提取证书指纹:你从数字证书中提取公钥并计算其SHA-256哈希值,生成证书指纹。这个指纹就是一个64个字符的十六进制字符串,它惟一地标识了这个证书。在利用中嵌入证书指纹:将生成的证书指纹嵌入到挪动利用的源代码或配置文件中。这样,利用就能够将此指纹与从服务器接管到的证书进行比拟。与服务器进行通信:当用户关上利用并尝试与银行服务器通信时,利用会向服务器发送申请。服务器会返回数字证书,其中蕴含其公钥和其余信息。验证证书指纹:利用会提取服务器返回的数字证书中的公钥,并计算其SHA-256哈希值,失去服务器的证书指纹。而后,利用会将此指纹与事后嵌入的证书指纹进行比拟。比拟指纹:如果两个证书指纹匹配,意味着服务器的身份失去验证,通信是平安的。如果指纹不匹配,利用会正告用户可能存在平安危险,因为服务器的身份可能受到了假装。综上所述,证书指纹在挪动利用开发中扮演着重要的角色,确保利用与服务器之间的通信是平安可信的。它避免了中间人攻打和歹意劫持,确保用户的敏感信息不会被未经受权的人获取。 总结:挪动利用的证书指纹是通过计算数字证书公钥的SHA-256哈希值得到的加密字符串,用于验证服务器的身份,确保通信安全。开发人员将证书指纹嵌入利用中,并在通信时比拟服务器返回的证书指纹,以确保身份验证。这是保障挪动利用安全性的重要措施之一。

August 28, 2023 · 1 min · jiezi

关于移动web开发:移动互联网高级开发正式课VIP课程码牛第二期

download:挪动互联网高级开发正式课VIP课程-码牛第二期Java高性能序列化工具Kryo序列化概述Kryo 是一个疾速序列化/反序列化工具,依赖于字节码生成机制(底层使用了 ASM 库),因此在序列化速度上有肯定的劣势,但正因如此,其使用也只能限度在基于 JVM 的语言上。和 Hessian 类似,Kryo 序列化出的后果,是其自定义的、独有的一种格局。因为其序列化出的后果是二进制的,也即 byte[],因此像 Redis 这样可能存储二进制数据的存储引擎是可能间接将 Kryo 序列化进去的数据存进去。当然你也可能抉择转换成 String 的形式存储在其余存储引擎中(性能有损耗)。基础用法介绍了这么多,接下来咱们就来看看 Kryo 的基础用法吧。其实对于序列化框架来说,API 基本都差不多,毕竟入参和出参通常都是必定的(需要序列化的对象/序列化的后果)。在使用 Kryo 之前,咱们需要引入相应的依赖。 com.esotericsoftware kryo 5.2.0复制代码基本使用如下所示import com.esotericsoftware.kryo.Kryo;import com.esotericsoftware.kryo.io.Input;import com.esotericsoftware.kryo.io.Output;import java.io.*; public class HelloKryo { static public void main(String[] args) throws Exception { Kryo kryo = new Kryo(); kryo.register(SomeClass.class); SomeClass object = new SomeClass(); object.value = "Hello Kryo!"; Output output = new Output(new FileOutputStream("file.bin")); kryo.writeObject(output, object); output.close(); Input input = new Input(new FileInputStream("file.bin")); SomeClass object2 = kryo.readObject(input, SomeClass.class); input.close(); System.out.println(object2.value);} ...

June 9, 2022 · 3 min · jiezi

关于移动web开发:移动互联网高级开发正式课VIP课程码牛第二期源码齐全

download:挪动互联网高级开发正式课VIP课程-码牛第二期一个Java注解@Recover搞定俊俏的循环重试代码 使用背景在实际我的项目中其中一部分逻辑可能会因为调用了内部服务或者等待锁等情况下出现不可预料的异样,在这个时候咱们可能需要对调用这部分逻辑进行重试,代码外面次要就是使用for循环写一大坨重试的逻辑,各种硬编码,各种辣眼睛的补丁。 特地是针对重试的逻辑,到处都有。所以我决定用一个重试组件spring-retry优化一波。它的出现,解决掉这部分俊俏的代码! 2开始上代码首先引入依赖: org.springframework.retryspring-retry1.3.2复制代码因为该组件是依赖于 AOP 给你的,所以还需要引入这个依赖(如果你其余 jar 包中引用过了,当然也就不需要再次引用了): org.springframework.bootspring-boot-starter-aop2.6.1复制代码开启重试:@SpringBootApplication@EnableRetrypublic class ApplicationStarter { public static void main(String[] args) { SpringApplication.run(ApplicationStarter.class); }}复制代码Controller层@RestControllerpublic class TestController {@Autowiredprivate IRecursiveCallService recursiveCallService; @GetMapping("test2")public Object test2() { return recursiveCallService.testService();}}复制代码Service层public interface IRecursiveCallService { /** * 测试service * * @return */List testService();}复制代码Service层具体实现@Servicepublic class RecursiveCallServiceImpl implements IRecursiveCallService { @Override@Retryable(recover = "testService3")public List testService() { System.out.println("到此一游!"); System.out.println(1 / 0); return null;}@Recoverpublic List testService1() { System.out.println("谬误的返回"); return Collections.singletonList("S");}@Recoverpublic List testService2(String i) { System.out.println("正确的返回"); return Collections.singletonList(1);}@Recoverpublic List testService3() { System.out.println("正确的返回2"); return Collections.singletonList(2);}} ...

May 10, 2022 · 1 min · jiezi

关于移动web开发:移动互联网高级开发正式课VIP课程码牛第二期

挪动互联网高级开发正式课VIP课程-码牛第二期获取资源:网盘链接Python 开发者可能都据说过鸭子类型和猴子补丁这两个词,即使没听过,也大概率写过相干的代码,只不过并不了解其背地的技术要点是这两个词而已。 我最近在面试候选人的时候,也会问这两个概念,很多人答的也并不是很好。然而当我向他们解释完之后,广泛都会恍然大悟:“哦,是这个啊,我用过”。 所以,我决定来写一篇文章,讨论一下这两个技术。 鸭子类型引用维基百科中的一段解释: 鸭子类型(duck typing)在程序设计中是动静类型的一种风格。在这种风格中,一个对象无效的语义,不是由继承自特定的类或实现特定的接口,而是由"以后方法和属性的会合"决定。 更通俗一点的说: 当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可能被称为鸭子。 也就是说,在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型。 咱们看一个例子,更形象地展示一下: 这是一个鸭子(Duck)类class Duck: def eat(self): print("A duck is eating...")def walk(self): print("A duck is walking...")这是一个狗(Dog)类class Dog: def eat(self): print("A dog is eating...")def walk(self): print("A dog is walking...")def animal(obj): obj.eat()obj.walk()if name == '__main__': animal(Duck())animal(Dog())复制代码程序输入: A duck is eating...A duck is walking...A dog is eating...A dog is walking...复制代码Python 是一门动静语言,没有严格的类型查看。只需 Duck 和 Dog 别离实现了 eat 和 walk 方法就可能间接调用。 再比如 list.extend() 方法,除了 list 之外,dict 和 tuple 也可能调用,只需它是可迭代的就都可能调用。 ...

May 5, 2022 · 1 min · jiezi

关于移动web开发:移动互联网高级开发正式课VIP课程码牛第二期

挪动互联网高级开发正式课VIP课程-码牛第二期链接:https://pan.baidu.com/s/1DGD8... 提取码:sm0f --来自百度网盘超级会员V4的分享Java 8之后的那些新个性(一):局部变量var 在IDEA中2021年的一个考察中,程序员中使用Java的版本中,Java 8仍是支流。新的长期反对版Java 11,Java 17并未有Java 8流行。 我并不认为肯定得使用新版的Java,但咱们也要意识到Java 8是在2014年公布的,距今已经是8年之久了。而在这8年中,类似Kotlin,Swift,TypeScript语言都在不断的更新优化自己的语言个性。 这使得Java 8相比起来,在让代码更简洁斯文上越来越有所差距。好在,Java并未停止它前进的步调,从Java 8之后的许多个版本,在借鉴参考其它语言优良的个性的基础之上,Java发展出了新的能让代码更简洁的语法个性。 变量与常量在申明变量这个事件上,大家所熟知的Java变量申明形式是: //变量EntityRepository entityRepository = new EntityRepositoryJPA();//常量final String httpMethod = "post"复制代码Java变量申明的形式是类 + 名称的形式来进行申明 ,如果是常量,则以final关键字来申明。 咱们可能对比下其它语言的变量申明形式 Kotlin中是以var申明变量,val申明常量 //变量var entityRepository = EntityRepositoryJPA()//常量val httpMehod = "post"复制代码TypeScript是以let来申明变量,const来申明常量 //变量let entityRepository = new EntityRepositoryJPA()//常量const httpMethod = "post"复制代码Swift中是由var定义变量,let来定义常量 //变量var entityRepository = EntityRepositoryJPA()//常量let httpMethod = "post"复制代码从下面对比可能看出,相较于Java的类型 + 名称的定义形式,新的语言都偏好关键字 + 名称的模式。 类型主动判定事实上,古代编程语言,都非常喜爱最大限度的使用类型主动判定,也就是关键字 +名称这种模式。 类型推定的基本原则是:只需通过上下文能猜想到的,就不需要明确申明它的类型 因为,一个不言而喻的点是,这样的代码确实更简洁。 咱们如果用关键字 + 名称的写法来重写上述Java代码中的变量与常量定义,那咱们的代码就是是如此: //使用(关键字 + 名称)的模式重写//变量var entityRepository = new EntityRepositoryJPA();//常量var httpMethod = "post"复制代码依据类型主动判定的逻辑,编译器和咱们程序员,都会很不言而喻的猜想到,entityRepository的类型是EntityRepositoryJPA类的实例,而httpMethod则是一个String类型。 ...

April 5, 2022 · 1 min · jiezi

关于移动web开发:移动互联网高级开发正式课VIP码牛第二期

download:挪动互联网高级开发正式课VIP-码牛第二期Jetpack架构演变(一):初步使用flow,附加经典案例在jetpack体系中 livedata的角色纯纯粹粹是个桥接器,DataSource中获取到数据,而后由viewmodel进行逻辑处理,最初被livedata.postValue到view层,唯一的价值是绑定了lifecycle, 只在页面活跃(start)的时候接受数据官网的一篇介绍可能参考:从 LiveData 迁徙到 Kotlin 数据流 - 掘金对于初学者来说使用lieveData的好处是足够简略和绝对安全引入flow次要因为以下几点: 具备更敌对的API,学习成本较低跟Kotlin协程、LiveData拆散更紧密,Flow能够转换成LiveData,在ViewModel中间接使用拆散协程的作用域,当协程被勾销时,Flow也会被勾销,避免内存泄漏flow库从属于kotlin, livedata属于Android, 托付Android平台的限度对于未来跨平台发展无利 【flow是个冷数据流】所谓冷流,即上游无消费行为时,上游不会产生数据,只有上游开始生产,上游才开始产生数据。而所谓热流,即无论上游是否有消费行为,上游都会自己产生数据。下边通过一个经典场景粗疏描述下flow(单纯的flow,而stateFlow会在后续章节中讲解)的使用案例:一个菜谱利用app中,我想在一个页面展示一个列表(recyclerview) ,此列表的每个item是个子列表,子列表顺次为计划菜谱列表;收藏菜谱列表;根据食材筛选的菜谱列表;根据食材获取用户偏好的菜谱列表; 四个子列表需要四个接口来获取,组装好起初刷新最初的列表其中每个列表都有可能是空,是emptylist的话这行就不浮现了,因为四个接口数据量大小不同,所以不会同一时间返回,同时又要保障这四个子列表按申请的次序来展示。思路:设计数据结构,最外层的data:data class ContainerData(val title : String , val list: List)复制代码其中Recipe实体是每个菜谱data class Recipe(val id: String, val name: String, val cover: String, val type: Int, val ingredients: List? = mutableListOf(), val minutes: Int, val pantryItemCount : Int )复制代码模拟四个请求为:val plannlist = Request.getPlannlist()val favouritelist= Request.getFavouritelist()... 以此类推如果按照申请四个请求返回秩序不同,同时申请在列表中按次序浮现,如果实现?打算一:可能等待四个请求都返回后而后组装数据,刷新列表可能利用协程的await方法:val dataList = MutableLiveData<List>() viewModelScope.launch { // planner val plannerDefer = async { Request.getPlannlist() } // favourite val favouriteDefer = async { Request.getFavouritelist() } val plannerData = plannerDefer.await() val favouriteData = favouriteDefer.await() ...

March 10, 2022 · 1 min · jiezi

移动端H5调试技巧

chrome://inspect/#devices

July 10, 2019 · 1 min · jiezi

手机模拟浏览器

opera-mobile-emulatorhttp://www.download82.com/get...

June 3, 2019 · 1 min · jiezi

移动端的textoverflow多行文本溢出显示省略号…

大家应该都知道用text-overflow:ellipsis属性来实现单行文本的溢出显示省略号(…)。当然部分浏览器还需要加宽度width属性。 overflow: hidden;text-overflow: ellipsis;white-space: nowrap;但是这个属性并不支持多行文本溢出显示省略号,这里根据应用场景介绍几个方法来实现这样的效果。 WebKit浏览器或移动端的页面在WebKit浏览器或移动端(绝大部分是WebKit内核的浏览器)的页面实现比较简单,可以直接使用WebKit的CSS扩展属性(WebKit是私有属性)-webkit-line-clamp ;注意:这是一个 不规范的属性(unsupported WebKit property),它没有出现在 CSS 规范草案中。-webkit-line-clamp用来限制在一个块元素显示的文本的行数。 为了实现该效果,它需要组合其他的WebKit属性。常见结合属性: display: -webkit-box; 必须结合的属性 ,将对象作为弹性伸缩盒子模型显示 。-webkit-box-orient 必须结合的属性 ,设置或检索伸缩盒对象的子元素的排列方式 。text-overflow: ellipsis;,可以用来多行文本的情况下,用省略号“…”隐藏超出范围的文本 。overflow : hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical;这个属性比较合适WebKit浏览器或移动端(绝大部分是WebKit内核的)浏览器。具体例子可以查看http://www.css88.com/webkit/-webkit-line-clamp/ 跨浏览器兼容的方案比较靠谱简单的做法就是设置相对定位的容器高度,用包含省略号(…)的元素模拟实现;例如: p { position:relative; line-height:1.4em; /* 3 times the line-height to show 3 lines */ height:4.2em; overflow:hidden;}p::after { content:"..."; font-weight:bold; position:absolute; bottom:0; right:0; padding:0 20px 1px 45px; background:url(http://www.css88.com/wp-content/uploads/2014/09/ellipsis_bg.png) repeat-y;}这里注意几点: height高度真好是line-height的3倍;结束的省略好用了半透明的png做了减淡的效果,或者设置背景颜色;IE6-7不显示content内容,所以要兼容IE6-7可以是在内容中加入一个标签,比如用<span class="line-clamp">...</span>去模拟;要支持IE8,需要将::after替换成:after;JavaScript 方案用js也可以根据上面的思路去模拟,实现也很简单,推荐几个做类似工作的成熟小工具: 1.Clamp.js下载及文档地址:https://github.com/josephschmitt/Clamp.js使用也非常简单: var module = document.getElementById("clamp-this-module");$clamp(module, {clamp: 3});2.jQuery插件-jQuery.dotdotdot这个使用起来也很方便: ...

May 9, 2019 · 1 min · jiezi

腾讯视频国际版(Android)电量测试方法研究与总结

本文由云+社区发表作者:腾讯移动品质中心TMQ1、研究背景:在2017年Google I/O大会上,Google发布了Google Play管理中心的新功能:Android vitals。当app在大量设备上运行时,Android vitals会收集与应用性能相关的各种匿名数据,比如:与app稳定性相关的数据、app启动时间、电量使用情况、渲染时间以及权限遭拒等等,这些数据会被分析整理后展示在Google Play管理中心的Android vitals dashboard中。Android vitals 中需要开发者重点关注的核心指标有:crash率、ANR率、excessive wakeups(过渡唤醒)、stuck wake locks(唤醒锁定卡住)。其他指标,需根据应用类型选择性关注(Android vitals中的指标总览见图1-1)。若app某些指标表现很差,会影响用户体验,并且会导致应用在Google Play商店中的等级很低、排名靠后(APP指标异常示例图见图1-2)。开发者可以通过分析Android vitals中提供的一些参照指标,采取相应的措施来优化app。图1-1 Android vitals平台检测指标总览图1-2 某APP指标异常示例图2、核心指标详细信息:要对APP的指标进行监控,首先要明确该指标在Android vitals中是如何进行统计的,这一节主要介绍电量相关核心指标的基本概念和计算方式。2.1 Stuck partial wake locks(部分唤醒锁定卡住)A.WakeLock(唤醒锁)基本概念:Android系统本身为了优化电量的使用,会在没有操作时进入休眠状态, 来节省电量。为了便于开发(很多应用不可避免的希望在灭屏后还能运行一些事儿,或是要保持屏幕一直亮着–比如播放视频),Android提供了一个PowerManager.WakeLock的东西。我们可以用WakeLock来保持CPU运行,或是防止屏幕变暗/关闭,让手机可以在用户不操作时依然可以做一些事儿。然而,获取WakeLock很容易,释放不好就会成为难题,消耗电量。例如我们获取了一个WakeLock来保持CPU运转,做一个复杂运算并将数据上传到后台服务器, 然后释放该WakeLock。然而这个过程可能并不像我们想象的那么快,可能因为比如服务器挂掉,计算出了异常等等WakeLock没有释放。问题就来了,CPU会一直得不到休眠,而大大增加耗电。唤醒锁可划分为并识别四种用户唤醒锁:自 API 等级 17 开始,FULL_WAKE_LOCK 将被弃用。应用应使用FLAG_KEEP_SCREEN_ON。相关链接:https://developer.android.com…B.Partial wake locks(部分唤醒锁):部分唤醒锁可确保CPU正常运行,但屏幕和键盘背光可以关闭。如果运行在后台的APP长时间持有某个部分唤醒锁,就导致部分唤醒锁卡住。这种情况十分消耗设备电量,因为它会阻止设备进入低电量状态。Android vitals重点关注了stuck partial wake locks这项指标,当你的APP存在唤醒锁定卡住的现象时,它会通过Play管理中心给出告警(APP出现部分唤醒锁定卡住示例图见图2-1),并从各个维度给出相关的详细统计图(如图2-2中给出每个工作时段后台wake lock最长持续时间分布图)。当出现以下情况时,Android vitals会报告唤醒锁定卡住:至少70%以上的battery sessions发生过至少一次、长达一小时以上的部分唤醒锁定。当只在后台运行时,至少10%以上的battery sessions发生过至少一次、长达一小时以上的部分唤醒锁定。(ps:battery session指两次电池充满电之间的时间间隔,Android vitals展示的battery sessions是所有app测试用户的battery session合计。)相关链接:https://developer.android.goo…图2-1 某APP出现部分唤醒锁定卡住(后台)示例图图2-2 每个工作时段后台wake lock最长持续时间的分布图2.2 Excessive wakeups(过渡唤醒)A.Wakeups 基本概念Wakeups 是AlarmManager API中的一种机制,开发者可以设置一个alarm在特定的时间来唤醒设备。当某个唤醒alarm触发,设备会走出低电量模式,在执行alarm的onRecieve()或onAlarm()方法的时候,Alarm Manager会持有一个部分唤醒锁。如果wake alarms频繁触发,会耗尽设备电量。Android vitals中展示了app的过渡唤醒次数。Alarm有以下四种类型:1)RTC_WAKEUP 在指定的时刻(设置Alarm的时候),唤醒设备来触发Intent。2)RTC 在一个显式的时间触发Intent,但不唤醒设备。 3)ELAPSED_REALTIME 从设备启动后,如果流逝的时间达到总时间,那么触发Intent,但不唤醒设备。流逝的时间包括设备睡眠的任何时间。注意一点的是,时间流逝的计算点是自从它最后一次启动算起。 4)ELAPSED_REALTIME_WAKEUP 从设备启动后,达到流逝的总时间后,如果需要将唤醒设备并触发Intent。在Android vitals中只列出了RTC_WAKEUP和ELAPSED_REALTIME_WAKEUP两种类型的唤醒数据,Google会统计每小时发生10次以上wakeup的电池工作时段百分比(APP发生过渡唤醒示例见图2-3)。分别从应用版本、wakeup标记、设备、Android版本等几个维度统计每小时的Alarm Manager wakeup次数(每个工作时段中每小时的wackup分布图见图2-4)。图2-3 某APP发生过渡唤醒示例图图2-4 每个工作时段每小时wakeup次数分布图3、测试方法研究3.1 传统电量测试方法回顾我们之前也对腾讯视频主线版本进行过电量测试,之前关注的重点在于APP在各场景中耗电量是否正常,是从比较宏观的角度去进行测试的,采取的测试方法主要是物理仪器测试法和GT测试法。A.物理仪器测试法(电流表等)在保持电压恒定的情况下,获取各场景平均电流值来统计系统耗电情况,通过此方法可以从大体上看出APP电量消耗是否正常,若仪器精度大,此方法测出的电量值是最准确的。缺陷:此方法只能测试整个手机的电流,不能区分APP,受影响的因素多,如屏幕亮度大小、音量大小等等,要保证每次测试的环境完全一致是不可能的。图3-1 物理仪器测电量B.GT测试法GT(随身调)是由MIG专项测试组自主研发的APP随身调测平台,它是直接运行在手机上的“集成调测环境”(IDTE, Integrated Debug Environment)。利用GT,仅凭一部手机,无需连接电脑,您即可对APP进行快速的性能测试(CPU、内存、流量、电量、帧率/流畅度等等)、开发日志的查看、Crash日志查看、网络数据包的抓取、APP内部参数的调试、真机代码耗时统计等。通过GT,可以采集手机耗电量相关数据:电流、电压、电量、温度等,通过分析这些数据,可以对整个手机的电量使用情况进行分析。缺点:和物理仪器测试方法一样,采用GT测试也只能获取到整个手机的电量数据,无法只关注单独APP,且受各种因素影响较大。3.2 国际版电量测试方法预研由于国际版APP在Google Play上发布,我们做电量测试不仅仅需要关注整个APP的电量使用情况是否正常,还需要关注APP持有 wack lock和使用alarm的情况。因此,传统的电量测试方法已经无法满足我们的需求,我们需要在此基础上增加额外的测试方法。A.Batterystats/ bugreportAndroid5.0后,电量数据可通过dumpsys batterystats获取。Android系统统计耗电量的基本公式是W=UIt。在手机中,U一般恒定不变,因此可以单独通过Q(电容量)=I*t来表示电量。核心类BatterStatsImpl提供App各部件运行时间、PowerProfile提供部件电流数值。Android部件电流信息存于:power_profile.xml文件中,每个OEM厂商都有私有的power_profile.xml文件,PowerProfile通过读取该文件获取访问部件电流数值(图3-3是samsung某型号的power_profile.xml)。Android系统以uid为单位,依次统计每个apk的使用cpu使用耗电量、wake lock耗电量、移动数据耗电量、wifi数据耗电量、wifi维持耗电量、wifi扫描耗电量、各传感器耗电量。其中wake lock消耗的电量只统计了持有Partial wake lock的耗电量,正好是我们需要关注的唤醒类型,因此我们可以通过分析batterystats获得的电量数据来测试app持有Partial wake lock情况。Android为了方便开发人员分析整个系统平台和某个app在运行一段时间之内的所有信息,专门开发了bugreport工具。bugreport文件中记录了系统允许过程中的各种log信息,其中也包括了耗电量信息。通过分析bugreport中的电量相关数据也能获取APP持有Partial wake lock的信息。ps:Uid与App关系:2个App签名和sharedUserId相同,则在运行时,他们拥有相同Uid。就是说processAppUsage统计的可能是多个App的耗电量数据,对于普通App,出现这种情况的几率较少,而对于Android系统应用则较为常见。图3-2 wack lock耗电量计算源代码图3-3 sumsung某型号power_profile.xml数据准备:先断开adb服务,然后开启adb服务。(1)adb kill-server(2)adb start-server由于开发时做电量记录时会打开很多可能造成冲突的东西,为了保险起见,重启adb命令。重置电池数据、收集数据(3) adb shell dumpsys batterystats –enable full-wake-history(4) adb shell dumpsys batterystats –reset(5) adb shell logcat -c通过以上命令来打开电池数据的获取以及重置,清除干扰的数据,清除历史日志。获取电量报告把数据线拔掉,防止数据线造成充放电数据干扰。然后做一些测试的case,经过一段时间后,重新连接手机确认adb连上了,运行以下命令来将bugreport的信息保存到txt文件中。(6) adb bugreport >D:/bugreport.txt或者用下面的命令也可以,官网上记述的内容,经实践,无法被读取…(7) adb shell dumpsys batterystats > batterystats.txt(8) adb shell dumpsys batterystats > com.example.app(包名) >batterystats.txtps:在此注意一定要等到该条命令执行完(稍微会有些慢)后,再打开bugreport.txt文件,之前遇到过没有导出完,就点开,信息缺失的情况,导致无法成功生成图表。B.battery historian生成的bugreport文件有的时候异常庞大,能够达到15M+,想一想对于一个txt文本格式的文件内容长度达到了15M+是一个什么概念,如果使用文本工具打开查看将是一个噩梦。因此google针对android 5.0(api 21)以上的系统开发了一个叫做battery historian的分析工具,这个工具就是用来解析这个txt文本文件,然后使用web图形的形式展现出来,这样出来的效果更加人性化,更加可读。我们可以使用该工具对bugreport文件进行解析,更轻松的获取电量相关数据。battery historian的安装可以参考以下链接:https://github.com/google/bat…https://developer.android.com…也可以直接使用在线版本:https://bathist.ef.lc/数据分析:(1)选择腾讯视频app(2)Wacklocks表格中展示app持有的wacklock,持有时间及数量,通过这个表格我们可以看到我们APP是否有持有一小时以上的wack_lock。(3)Wakeup alarm info表格中展示了APP运行过程中触发的wakeup alarm名字和个数,通过该分析工具也可以统计app的闹钟唤醒次数。C.QAPMQAPM是SNG开发的致力于解放专项测试人员的工具平台,该平台带有电量监控功能,在电量个例菜单中会统计前台30分钟、后台5分钟两个场景下的wacklock持有信息。该平台上的数据可以作为我们电量测试的参考对象,具体的统计方法还需后续深入了解。D.dumpsys命令Android提供的dumpsys工具能够用于查看感兴趣的系统服务信息与状态,手机连接电脑后能够直接命令行运行adb shell dumpsys 查看电池、电量相关信息。adb shell dumpsys power通过该条命令可以看到手机中所有的wack_lock持有信息adb shell dumpsys alarm此命令会提供设备上的alarm系统服务相关信息。其中Alarm Stats列出了应用设置alarm的情况,其中有系统被该应用所有alarm消耗的时间以及被闹钟唤醒的次数。可以通过获取一小时内的电量数据来分析用户在每小时的唤醒次数。相关链接:https://blog.csdn.net/memoryj…该方法与通过burgreport文件统计电量信息类似,都是通过Android系统中提供的工具来输出电量的消耗情况,且该种方式输出的报告也比较复杂,可读性查,可在测试过程中作为参考。4、国际版电量测试方法总结与实践4.1 测试方法总结根据上一节的测试方法研究,我们打算首先用GT测试各个场景中APP电量消耗是否有异常。接下来采用battery historian分析工具对手机里获取的bugreport文件进行分析,统计app中持有超过一小时的wack_lock和一小时内发生的wackup数。QAPM中采集到的数据作为我们的辅助分析数据,我们可以比较两份数据,看我们通过battery historian统计的wack_lock数据是否准确。我们也可以通过使用dumpsys命令,查看app电量相关信息作为测试辅助方法。4.2 测试方法实践腾讯视频国际版1.0.0已经发布,我们已经使用该方法对其进行了一次电量测试,具体测试过程如下:A.GT测试:测试场景:启动-播放-前台静置测试机器:nexus测试结果分析:从以下电流趋势变化图中可以看出,播放过程和前台静置过程,电流曲线平稳,无较大波动,无明显异常。从播放到退出播放前台静置,使用电流明显变小,符合预期。B.Battery Historian测试:测试场景:app前台静置2小时app后台静置2小时全屏播放2小时测试型号:Y7 Pro 2018 (LDN-LX2)OPPO F7 (CPH1819)测试结果分析:三个场景中,仅播放场景下会持有WindowManager这个wakelock超过1小时以上。而Android Vitals中关注的是app运行在后台时,长时间持有部分唤醒锁的情况,播放这个场景可以排除在外,因此得出结论,国际版APP持有唤醒锁情况正常。场景机型持有1小时以上的wack lockapp前台华为Y7 Pro 2018 (LDN-LX2)无OPPO F7 (CPH1819)无 app后台华为Y7 Pro 2018 (LDN-LX2)无OPPO F7 (CPH1819)无 全屏播放华为Y7 Pro 2018 (LDN-LX2)WindowManagerOPPO F7 (CPH1819)WindowManager 2. 测试过程中没有统计到alarm数据,说明国际版APP暂时没有使用到AlarmManager定时任务。C.测试结论:GT电流测试显示国际版APP各应用场景电量使用情况正常。场景启动APP播放退出播放,前台静置结论启动过程需加载图片等资源,电流较大,正常播放过程电流平稳无异常退出播放电流变小,静置过程平稳无异常2. Battery Historian分析电量数据得出,前台静置、后台静置、播放三个场景中仅播放场景会持有wack lock1小时以上,不属于Android Vitals统计范畴,不会影响到国际版APP在Google Play商店的排名。场景机型stuck wake locksexcessive wakeups结论前台静置华为Y7 Pro无唤醒锁定卡住无过渡唤醒正常OPPO F7无唤醒锁定卡住无过渡唤醒正常 后台静置华为Y7 Pro无唤醒锁定卡住无过渡唤醒正常OPPO F7无唤醒锁定卡住无过渡唤醒正常 播放华为Y7 Pro持有唤醒锁1小时以上无过渡唤醒正常OPPO F7持有唤醒锁1小时以上无过渡唤醒正常 5、总结与展望由于腾讯视频国际版目前功能比较少,用到wack_lock和alarm的情况比较少,我们只测试了前台静置、后台静置、播放三个场景,电量测试的结果也显示APP电量使用情况正常,无部分唤醒锁定卡住和过渡唤醒的情况出现,后续国际版功能会日渐丰富,可能需要补充push、下载等测试场景,持有wack_lock和alarm的情况也会更加复杂,因此我们会根据实际情况不断改进和完善我们的电量测试方法。此文已由腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

March 18, 2019 · 1 min · jiezi

原生js实现移动端点击、长按、左滑、右滑、上滑、下滑等事件模拟

github地址:https://github.com/xubaodian/…为什么要模拟这些事件?1、上述这些事件中,浏览器直接支持的事件只有点击,而其它事件使用频率也很高。2、移动端web原生点击事件会有300ms的延迟,因为用户肯能双击,为了判断用户是单击还是双击,所以会有这个延迟,这个延迟会衍生很多问题,例如点击穿透。所以我们可以不用原生的点击事件,而使用模拟点击事件。如何模拟这些事件呢?我们可以总结这些操作,都是手指先触摸屏幕,然后在离开。不同点在于滑动事件手指有位移,而点击事件手指没有位移。首先想到的就是所有浏览器都是支持touchstart、touchmove和touchend事件的,我们可以利用这些事件来模拟上述事件。原理如下:1、监听dom的touchstart和touchend事件。2、分别记录touchstart、touchend事件的位置和时间,计算位移delta(包括x和y)和时间间隔timegap。3、根据delta和timegap的值,判断属于哪种事件。有两种情况:delta中x和y都很小这是点击操作,用户点击按钮等时,理论上是不会有位移的,但是实际中也可能发生一个很小的位移,毕竟手指不是精密仪器。如果时间间隔timegap较小,则属于点击,如果timegap较大,属于长按操作。delta中的x或y比较大这种情况下,就是手指发生滑动操作了,至于是左右滑动,还是上下滑动,根据x和y的大小来判断。|x| > |y|(|x|代表x的绝对值),左右滑动,x>0,右滑,反之左滑。|x| <= |y|,上下滑动,y>0,下滑,反之上滑。这样就模拟了移动端web中的这几个事件了。代码如下:/** * 用touch事件模拟点击、左滑、右滑、上拉、下拉等时间, * 是利用touchstart和touchend两个事件发生的位置来确定是什么操作。 * 例如: * 1、touchstart和touchend两个事件的位置基本一致,也就是没发生位移,那么可以确定用户是想点击按钮等。 * 2、touchend在touchstart正左侧,说明用户是向左滑动的。 * 利用上面的原理,可以模拟移动端的各类事件。 / const EventUtil = (function() { //支持事件列表 let eventArr = [’eventswipeleft’, ’eventswiperight’, ’eventslideup’, ’eventslidedown’, ’eventclick’, ’eventlongpress’]; //touchstart事件,delta记录开始触摸位置 function touchStart(event) { this.delta = {}; this.delta.x = event.touches[0].pageX; this.delta.y = event.touches[0].pageY; this.delta.time = new Date().getTime(); } / * touchend事件,计算两个事件之间的位移量 * 1、如果位移量很小或没有位移,看做点击事件 * 2、如果位移量较大,x大于y,可以看做平移,x>0,向右滑,反之向左滑。 * 3、如果位移量较大,x小于y,看做上下移动,y>0,向下滑,反之向上滑 * 这样就模拟的移动端几个常见的时间。 * */ function touchEnd(event) { let delta = this.delta; delete this.delta; let timegap = new Date().getTime() - delta.time; delta.x -= event.changedTouches[0].pageX; delta.y -= event.changedTouches[0].pageY; if (Math.abs(delta.x) < 5 && Math.abs(delta.y) < 5) { if (timegap < 1000) { if (this[’eventclick’]) { this[’eventclick’].map(function(fn){ fn(event); }); } } else { if (this[’eventlongpress’]) { this[’eventlongpress’].map(function(fn){ fn(event); }); } } return; } if (Math.abs(delta.x) > Math.abs(delta.y)) { if (delta.x > 0) { if (this[’eventswipeleft’]) { this[’eventswipeleft’].map(function(fn){ fn(event); }); } } else { this[’eventswiperight’].map(function(fn){ fn(event); }); } } else { if (delta.y > 0) { if (this[’eventslidedown’]) { this[’eventslidedown’].map(function(fn){ fn(event); }); } } else { this[’eventslideup’].map(function(fn){ fn(event); }); } } } function bindEvent(dom, type, callback) { if (!dom) { console.error(‘dom is null or undefined’); } let flag = eventArr.some(key => dom[key]); if (!flag) { dom.addEventListener(’touchstart’, touchStart); dom.addEventListener(’touchend’, touchEnd); } if (!dom[’event’ + type]) { dom[’event’ + type] = []; } dom[’event’ + type].push(callback); } function removeEvent(dom, type, callback) { if (dom[’event’ + type]) { for(let i = 0; i < dom[’event’ + type].length; i++) { if (dom[’event’ + type][i] === callback) { dom[’event’ + type].splice(i, 1); i–; } } if (dom[’event’ + type] && dom[’event’ + type].length === 0) { delete dom[’event’ + type]; let flag = eventArr.every(key => !dom[key]); if (flag) { dom.removeEventListener(’touchstart’, touchStart); dom.removeEventListener(’touchend’, touchEnd); } } } } return { bindEvent, removeEvent } })();在闭包中定义了几个事件处理操作,EventUtil有两个方法,bindEvent绑定事件,removeEvent是移除事件绑定。支持六个事件:swipeleft是左滑事件,swiperight是右滑事件,slideup是上滑事件,slidedown下滑事件,click点击事件,longpress长按点击事件。使用案例如下:在页面中引用上述代码:<script src="./EventUtil.js"></script>测试代码如下,代码中有注释,可以看到如何应用这些模拟事件:<!DOCTYPE html><html><head> <meta charset=“utf-8” /> <meta http-equiv=“X-UA-Compatible” content=“IE=edge”> <title>Page Title</title> <meta name=“viewport” content=“width=device-width, initial-scale=1”> <style> #main{ width: 100%; height: 300px; background: blue; color: white; font-size: 20px; text-align: center; } </style> <!–引入事件–> <script src="./EventUtil.js"></script></head><body style=“font-size: 14px;"> <div id=“main”></div> <button id=“test”>接触左滑绑定</button> <script> //获取dom let domContent = document.querySelector(’#main’); //定义各类事件,为了可以解除事件绑定,事件回调不使用匿名函数 function handleClick() { alert(‘点击事件’); } function handleLeft() { alert(‘左滑事件’); } function handleRight() { alert(‘右滑事件’); } function handleUp() { alert(‘下滑事件’); } function handleDown() { alert(‘上滑事件’); } function handleLong() { alert(‘长按事件’); } //绑定点击事件 EventUtil.bindEvent(domContent, ‘click’, handleClick); //绑定两次左滑事件 EventUtil.bindEvent(domContent, ‘swipeleft’, handleLeft); EventUtil.bindEvent(domContent, ‘swipeleft’, handleLeft); //绑定右滑事件 EventUtil.bindEvent(domContent, ‘swiperight’, handleRight); //上滑事件 EventUtil.bindEvent(domContent, ‘slideup’, handleUp); //下滑事件 EventUtil.bindEvent(domContent, ‘slidedown’, handleDown); //长按点击事件 EventUtil.bindEvent(domContent, ’longpress’, handleLong); //接触绑定按钮 let btnTest = document.querySelector(’#test’); function removeLeft() { //接触左滑事件绑定 EventUtil.removeEvent(domContent, ‘swipeleft’, handleLeft); } //绑定点击事件 EventUtil.bindEvent(btnTest, ‘click’, removeLeft); </script></body></html>测试效果如下:有疑问的可以留言或发送邮件至472784995@qq.com。 ...

January 31, 2019 · 2 min · jiezi

谈谈iOS获取调用链

本文由云+社区发表iOS开发过程中难免会遇到卡顿等性能问题或者死锁之类的问题,此时如果有调用堆栈将对解决问题很有帮助。那么在应用中如何来实时获取函数的调用堆栈呢?本文参考了网上的一些博文,讲述了使用mach thread的方式来获取调用栈的步骤,其中会同步讲述到栈帧的基本概念,并且通过对一个demo的汇编代码的讲解来方便理解获取调用链的原理。一、栈帧等几个概念先抛出一个栈帧的概念,解释下什么是栈帧。应用中新创建的每个线程都有专用的栈空间,栈可以在线程期间自由使用。而线程中有千千万万的函数调用,这些函数共享进程的这个栈空间,那么问题就来了,函数运行过程中会有非常多的入栈出栈的过程,当函数返回backtrace的时候怎样能精确定位到返回地址呢?还有子函数所保存的一些寄存器的内容?这样就有了栈帧的概念,即每个函数所使用的栈空间是一个栈帧,所有的栈帧就组成了这个线程完整的栈。栈帧下面再抛出几个概念:寄存器中的fp,sp,lr,pc。寄存器是和CPU联系非常紧密的一小块内存,经常用于存储一些正在使用的数据。对于32位架构armv7指令集的ARM处理器有16个寄存器,从r0到r15,每一个都是32位比特。调用约定指定他们其中的一些寄存器有特殊的用途,例如:r0-r3:用于存放传递给函数的参数;r4-r11:用于存放函数的本地参数;r11:通常用作桢指针fp(frame pointer寄存器),栈帧基址寄存器,指向当前函数栈帧的栈底,它提供了一种追溯程序的方式,来反向跟踪调用的函数。r12:是内部程序调用暂时寄存器。这个寄存器很特别是因为可以通过函数调用来改变它;r13:栈指针sp(stack pointer)。在计算机科学内栈是非常重要的术语。寄存器存放了一个指向栈顶的指针。看这里了解更多关于栈的信息;r14:是链接寄存器lr(link register)。它保存了当目前函数返回时下一个函数的地址;r15:是程序计数器pc(program counter)。它存放了当前执行指令的地址。在每个指令执行完成后会自动增加;不同指令集的寄存器数量可能会不同,pc、lr、sp、fp也可能使用其中不同的寄存器。后面我们先忽略r11等寄存器编号,直接用fp,sp,lr来讲述 如下图所示,不管是较早的帧,还是调用者的帧,还是当前帧,它们的结构是完全一样的,因为每个帧都是基于一个函数,帧伴随着函数的生命周期一起产生、发展和消亡。在这个过程中用到了上面说的寄存器,fp帧指针,它总是指向当前帧的底部;sp栈指针,它总是指向当前帧的顶部。这两个寄存器用来定位当前帧中的所有空间。编译器需要根据指令集的规则小心翼翼地调整这两个寄存器的值,一旦出错,参数传递、函数返回都可能出现问题。其实这里这几个寄存器会满足一定规则,比如:fp指向的是当面栈帧的底部,该地址存的值是调用当前栈帧的上一个栈帧的fp的地址。lr总是在上一个栈帧(也就是调用当前栈帧的栈帧)的顶部,而栈帧之间是连续存储的,所以lr也就是当前栈帧底部的上一个地址,以此类推就可以推出所有函数的调用顺序。这里注意,栈底在高地址,栈向下增长而由此我们可以进一步想到,通过sp和fp所指出的栈帧可以恢复出母函数的栈帧,不断递归恢复便恢复除了调用堆栈。向下面代码一样,每次递归pc存储的*(fp + 1)其实就是返回的地址,它在调用者的函数内,利用这个地址我们可以通过符号表还原出对应的方法名称。while(fp) { pc = *(fp + 1); fp = fp;}二、汇编解释下如果你非要问为什么会这样,我们可以从汇编角度看下函数是怎么调用的,从而更深刻理解为什么fp总是存储了上一个栈帧的fp的地址,而fp向前一个地址为什么总是lr?写如下一个demo程序,由于我是在mac上做实验,所以直接使用clang来编译出可执行程序,然后再用hopper工具反汇编查看汇编代码,当然也可直接使用clang的-S参数指定生产汇编代码。demo源码#import <Foundation/Foundation.h>int func(int a);int main (void){ int a = 1; func(a); return 0;}int func (int a){ int b = 2; return a + b;}汇编语言 ; ================ B E G I N N I N G O F P R O C E D U R E ================ ; Variables: ; var_4: -4 ; var_8: -8 ; var_C: -12 _main:0000000100000f70 push rbp0000000100000f71 mov rbp, rsp0000000100000f74 sub rsp, 0x100000000100000f78 mov dword [rbp+var_4], 0x00000000100000f7f mov dword [rbp+var_8], 0x10000000100000f86 mov edi, dword [rbp+var_8] ; argument #1 for method _func0000000100000f89 call _func0000000100000f8e xor edi, edi0000000100000f90 mov dword [rbp+var_C], eax0000000100000f93 mov eax, edi0000000100000f95 add rsp, 0x100000000100000f99 pop rbp0000000100000f9a ret ; endp0000000100000f9b nop dword [rax+rax] ; ================ B E G I N N I N G O F P R O C E D U R E ================ ; Variables: ; var_4: -4 ; var_8: -8 _func:0000000100000fa0 push rbp ; CODE XREF=_main+250000000100000fa1 mov rbp, rsp0000000100000fa4 mov dword [rbp+var_4], edi0000000100000fa7 mov dword [rbp+var_8], 0x20000000100000fae mov edi, dword [rbp+var_4]0000000100000fb1 add edi, dword [rbp+var_8]0000000100000fb4 mov eax, edi0000000100000fb6 pop rbp0000000100000fb7 ret需要注意,由于是在mac上编译出可执行程序,指令集已经是x86-64,所以上文的fp、sp、lr、pc名称和使用的寄存器发生了变化,但含义基本一致,对应关系如下:fp—-rbpsp—-rsppc—-rip接下来我们看下具体的汇编代码,可以看到在main函数中在经过预处理和参数初始化后,通过call _func来调用了func函数,这里call _func其实等价于两个汇编命令:Pushl %rip //保存下一条指令(第41行的代码地址)的地址,用于函数返回继续执行Jmp _func //跳转到函数foo于是,当main函数调用了func函数后,会将下一行地址push进栈,至此,main函数的栈帧已经结束,然后跳转到func的代码处开始继续执行。可以看出,rip指向的函数下一条地址,即上文中所说的lr已经入栈,在栈帧的顶部。而从func的代码可以看到,首先使用push rbp将帧指针保存起来,而由于刚跳转到func函数,此时rbp其实是上一个栈帧的帧指针,即它的值其实还是上一个栈帧的底部地址,所以此步骤其实是将上一个帧底部地址保存了下来。下一句汇编语句mov rbp, rsp将栈顶部地址rsp更新给了rbp,于是此时rbp的值就成了栈的顶部地址,也是当前栈帧的开始,即fp。而栈顶部又正好是刚刚push进去的存储上一个帧指针地址的地址,所以rbp指向的时当前栈帧的底部,但其中保存的值是上一个栈帧底部的地址。至此,也就解释了为什么fp指向的地址存储的内容是上一个栈帧的fp的地址,也解释了为什么fp向前一个地址就正好是lr。另外一个比较重要的东西就是出入栈的顺序,在ARM指令系统中是地址递减栈,入栈操作的参数入栈顺序是从右到左依次入栈,而参数的出栈顺序则是从左到右的你操作。包括push/pop和LDMFD/STMFD等。三、获取调用栈步骤其实上面的几个fp、lr、sp在mach内核提供的api中都有定义,我们可以使用对应的api拿到对应的值。如下便是64位和32位的定义_STRUCT_ARM_THREAD_STATE64{ __uint64_t __x[29]; / General purpose registers x0-x28 / __uint64_t __fp; / Frame pointer x29 / __uint64_t __lr; / Link register x30 / __uint64_t __sp; / Stack pointer x31 / __uint64_t __pc; / Program counter / __uint32_t __cpsr; / Current program status register / __uint32_t __pad; / Same size for 32-bit or 64-bit clients /};_STRUCT_ARM_THREAD_STATE{ __uint32_t r[13]; / General purpose register r0-r12 / __uint32_t sp; / Stack pointer r13 / __uint32_t lr; / Link register r14 / __uint32_t pc; / Program counter r15 / __uint32_t cpsr; / Current program status register */};于是,我们只要拿到对应的fp和lr,然后递归去查找母函数的地址,最后将其符号化,即可还原出调用栈。总结归纳了下,获取调用栈需要下面几步:1、挂起线程thread_suspend(main_thread);2、获取当前线程状态上下文thread_get_state_STRUCT_MCONTEXT ctx;#if defined(x86_64) mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT; thread_get_state(thread, x86_THREAD_STATE64, (thread_state_t)&ctx.__ss, &count);#elif defined(arm64) _STRUCT_MCONTEXT ctx; mach_msg_type_number_t count = ARM_THREAD_STATE64_COUNT; thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t)&ctx.__ss, &count);#endif3、获取当前帧的帧指针fp#if defined(x86_64) uint64_t pc = ctx.__ss.__rip; uint64_t sp = ctx.__ss.__rsp; uint64_t fp = ctx.__ss.__rbp;#elif defined(arm64) uint64_t pc = ctx.__ss.__pc; uint64_t sp = ctx.__ss.__sp; uint64_t fp = ctx.__ss.__fp;#endif4、递归遍历fp和lr,依次记录lr的地址while(fp) { pc = *(fp + 1); fp = fp;}这一步我们其实就是使用上面的方法来依次迭代出调用链上的函数地址,代码如下void t_fp[2];vm_size_t len = sizeof(record);vm_read_overwrite(mach_task_self(), (vm_address_t)(fp),len, (vm_address_t)t_fp, &len);do { pc = (long)t_fp[1] // lr总是在fp的上一个地址 // 依次记录pc的值,这里先只是打印出来 printf(pc) vm_read_overwrite(mach_task_self(),(vm_address_t)m_cursor.fp[0], len, (vm_address_t)m_cursor.fp,&len);} while (fp);上面代码便会从下到上依次打印出调用栈函数中的地址,这个地址总是在函数调用地方的下一个地址,我们就需要拿这个地址还原出对应的符号名称。5、恢复线程thread_resumethread_resume(main_thread);6、还原符号表这一步主要是将已经获得的调用链上的地址分别解析出对应的符号。主要是参考了运行时获取函数调用栈 的方法,其中用到的dyld链接mach-o文件的基础知识,后续会专门针对这里总结一篇文章。enumerateSegment(header, [&](struct load_command *command) { if (command->cmd == LC_SYMTAB) { struct symtab_command *symCmd = (struct symtab_command *)command; uint64_t baseaddr = 0; enumerateSegment(header, [&](struct load_command *command) { if (command->cmd == LC_SEGMENT_64) { struct segment_command_64 *segCmd = (struct segment_command_64 *)command; if (strcmp(segCmd->segname, SEG_LINKEDIT) == 0) { baseaddr = segCmd->vmaddr - segCmd->fileoff; return true; } } return false; }); if (baseaddr == 0) return false; nlist_64 *nlist = (nlist_64 *)(baseaddr + slide + symCmd->symoff); uint64_t strTable = baseaddr + slide + symCmd->stroff; uint64_t offset = UINT64_MAX; int best = -1; for (int k = 0; k < symCmd->nsyms; k++) { nlist_64 &sym = nlist[k]; uint64_t d = pcSlide - sym.n_value; if (offset >= d) { offset = d; best = k; } } if (best >= 0) { nlist_64 &sym = nlist[best]; std::cout << “SYMBOL: " << (char *)(strTable + sym.n_un.n_strx) << std::endl; } return true; } return false;});参考函数调用栈空间以及fp寄存器函数调用栈也谈栈和栈帧 运行时获取函数调用栈 深入解析Mac OS X & iOS 操作系统 学习笔记此文已由作者授权腾讯云+社区在各渠道发布获取更多新鲜技术干货,可以关注我们腾讯云技术社区-云加社区官方号及知乎机构号 ...

January 14, 2019 · 3 min · jiezi

原生 js 实现移动端 Touch 滑动反弹

移动端 Touch 滑动反弹什么是Touch滑动?就是类似于PC端的滚动事件,但是在移动端是没有滚动事件的,所以就要用到Touch事件结合js去实现,效果如下:1. 准备工作什么是移动端的Touch事件?在移动端Touch事件可以细分成三种,分别是:touchstart、touchmove和touchend,并且touch事件必须要用addEventListener去监听。touchStart当手指触碰到屏幕的时候触发touchmove当手指在屏幕上不断移动的时候触发touchend当手指离开屏幕的时候触发Touch 事件触发的 Event 对象:// 手指触碰到屏幕时触发element.addEventListener(’touchstart’, function (e) { // 打印的事件对象 console.log(e);})changedTouches、targetTouches、touches都是伪数组,里面装的是手指列表三种返回对象的区别:其实这三种返回的对象,都是表示用户触摸事件时的手指信息,之所以是一个伪数组,是因为有可能出现多指同时触摸,但是在实际工作中一般不去考虑多指的情况。而它们唯一的区别就是在touchstart和touchmove事件的时候,changedTouches、targetTouches、touches都能获取到手指的信息,但是在touchend事件的时候,targetTouches、touches对象是不能返回离开屏幕时的手指信息,只有changedTouches对象能返回。有哪些手指信息?我们可以看下上面的图片,在changedTouche[0]中,有些值:clientX:74 // 触摸点相对于浏览器的 viewport 左边缘的 x 坐标,不会包括左边的滚动距离。clientY:73 // 触摸点相对于浏览器的 viewport 上边缘的 Y 坐标,不会包括上边的滚动距离。screenX:2202 // 触摸点相对于屏幕左边缘的 x 坐标。screenY:327 // 触摸点相对于屏幕上边缘的 Y 坐标。pageX:65 // 触摸点相对于 document 的左边缘的 x 坐标,包括左边的滚动距离pageY:18 // 触摸点相对于 document 的上边缘的 Y 坐标,包括左边的滚动距离2. 基本结构此案例模拟的是移动端的一种滑动菜单效果。HTML部分:<aside class=“main”> <div class=“draw” id=“draw”> <ul> <li style=“background:orange”>列表一</li> <li style=“background:yellowgreen”>列表二</li> <li style=“background:yellow”>列表三</li> <li style=“background:cyan”>列表四</li> <li style=“background:orangered”>列表五</li> <li style=“background:pink”>列表六</li> <li style=“background:red”>列表七</li> <li style=“background:purple”>列表八</li> <li style=“background:violet”>列表九</li> <li style=“background:brown”>列表十</li> </ul> </div></aside>css部分:在列表的父盒子上设定一个overflow: hidden属性,使超出盒子部分的列表暂时隐藏掉,后面会通过js去实现滑动。/* 样式初始化 / { margin: 0; padding: 0;}html,body { width: 100%;}aside { height: 100%; width: 100%;}/* 列表的父盒子,限制宽高 // 注意设置overflow: hidden;样式后,超出这个盒子的ul将不会显示 /.draw { width: 60px; height: 500px; border: 2px solid #ccc; overflow: hidden; position: fixed; left: 10px; top: 50%; transform: translateY(-50%);}/ li 设置了浮动, 所以 ul 要清除浮动 /ul:after { content: “”; display: block; visibility: hidden; height: 0; clear: both;}ul { zoom: 1;}li { list-style: none; float: left; width: 60px; height: 60px; line-height: 60px; text-align: center;}效果图:3. 首次滑动手指触摸到列表向下滑动的时候,列表应该跟着向下滑动,当手指离开屏幕的时候,列表应该停在滑动的位置。这里就会用到上面准备阶段的知识点了,不明白的可以参考上面的概念。实现原理:1、touchstart的时候,获取手指触摸的落点A,通过这个点对象里面的clientY属性,获取距离顶部可视区的距离;2、touchmove的时候,获取手指的点B,同样的获取移动时距离顶部可视区的距离;3、touchmove的时候,还要做另一件事情,就是获取两点的差值(B.clientY-A.clientY),将这个差值动态赋值给ul,ul只需要设置向Y轴方向偏移这个距离,就能实现列表随手指滑动先来张示意图,怎么通过 js 让列表滑动起来:示例代码:var draw = document.querySelector(’#draw’);var ul = draw.children[0];// touchstart 时,记录手指在 Y 轴上的落点距离可视顶部距离var startY = 0ul.addEventListener(’touchstart’, function (e) { startY = e.changedTouches[0].clientY;})// touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离ul.addEventListener(’touchmove’, function (e) { // 获取差值 var dy = e.changedTouches[0].clientY - startY; // 设置 ul 在 Y 轴上的偏移 ul.style.transform = ’translateY(’ + dy + ‘px)’;})效果图:4. 再次滑动上面的效果图,细心的朋友可能已经发现了问题,在第一次的时候,得到了我们想要的效果,但是在第二次的时候,我们继续向下移动了一段距离,但是ul并没有接着第一次的位置继续向下,而是瞬间跳了上去。问题分析:虽然第二次是继续向下移动了一段距离,但是触摸结束后,最终是将此时的差值,重新赋值给了ul的Y轴偏移,所以视觉上“跳了上去”。解决方法:每一次滑动结束之后,都应该记录下此次滑动的距离,与之前的进行累加,待下一次滑动的时候,ul在Y轴的偏移值应该是之前的距离加上本次滑动的距离。新增touchend事件,在该事件里同样的可以获取到本次滑动的距离,将它与上一次的距离相加,赋值给一个全局变量;在touchmove事件里有点小改动,就是在给ul设置偏移值的时候,除了本次滑动的差值还要加上这个上一次的值;示意图:示例代码:var draw = document.querySelector(’#draw’);var ul = draw.children[0];var startY = 0 // 刚触碰到屏幕的时的手指信息var centerY = 0 // 用来记录每次触摸时上一次的偏移距离// touchstart 时,记录手指在 Y 轴上的落点距离可视顶部距离ul.addEventListener(’touchstart’, function (e) { startY = e.changedTouches[0].clientY;})// touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离ul.addEventListener(’touchmove’, function (e) { // 获取差值 var dy = e.changedTouches[0].clientY - startY; // 上次的滑动距离加上本次的滑动距离 var tempY = centerY + dy; // 设置 ul 在 Y 轴上的偏移 ul.style.transform = ’translateY(’ + tempY + ‘px)’;})// touchend 时,记录此时手指在 Y 轴上的落点距离可视顶部距离ul.addEventListener(’touchend’, function (e) { // 获取差值 var dy = e.changedTouches[0].clientY - startY; // 记录移动的距离 centerY = centerY + dy;})效果图:5. 限制滑动区间到上面一步,我们已经可以实现列表的滑动了,但是也存在一个问题,就是向上或者向下的时候没有限制,上下可以无限的滑动,甚至再用点力,就看不到列表了。为了美观和实用,这样肯定不行的,需要给它设定一个区间,设定向上或者向下最多只能留白多少。限制向下滑动最大区间:设定向下最大区间的值比较简单,直接设定一个值,当上一次滑动的距离加上本次滑动的距离大于这个值的时候,就不让它再继续往下滑了,让他直接等于这个设定的值。示例代码:var maxDown = 50; // 设定一个最大向下滑动的距离// touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离ul.addEventListener(’touchmove’, function (e) { // 获取差值 var dy = e.changedTouches[0].clientY - startY; // 上次的滑动距离加上本次的滑动距离 var tempY = centerY + dy; // 当上次滑动的距离加上本次滑动的距离 大于 设定的最大向下距离的时候 if (tempY > maxDown) { // 直接让偏移的值 等于这个设定值 tempY = maxDown; } // 设置 ul 在 Y 轴上的偏移 ul.style.transform = ’translateY(’ + tempY + ‘px)’;})限制向上滑动最大区间:向上滑动时,当ul的底部距盒子底部的距离大于设定值的时候,不让其继续向上滑动,关键是这个值怎么去判断?求出向上滑动最大值:注意:因为ul是向上滑动的,所以求得的距离前面要加上一个负号(-)示例代码:// 设定一个最大向下滑动的距离var maxDown = 50; // 求得一个最大向上滑动的距离var maxUp = -(ul.offsetHeight - draw.offsetHeight + maxDown); // touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离ul.addEventListener(’touchmove’, function (e) { // 获取差值 var dy = e.changedTouches[0].clientY - startY; // 上次的滑动距离加上本次的滑动距离 var tempY = centerY + dy; // 当上次滑动的距离加上本次滑动的距离 大于 设定的最大向下距离的时候 if (tempY > maxDown) { tempY = maxDown; } // 当上次滑动的距离加上本次滑动的距离 小于 设定的最大向上距离的时候 else if (tempY < maxUp) { // 直接让偏移的值 等于这个设定值 tempY = maxUp; } // 设置 ul 在 Y 轴上的偏移 ul.style.transform = ’translateY(’ + tempY + ‘px)’;})效果图:认真观察上图,虽然成功的设置了最大滑动区间,但是你有没有发现,一直往一个方向滑动的时候,虽然列表不会继续往下滑动,但是接着往相反方向滑动的时候,感觉列表滑不动,需要滑一段距离后,列表才会跟着走,这是为什么呢?因为滑动的过程centerY是一直变的,列表虽然视觉上不动了,但是在touchend事件的时候,它的centerY值一直在累加。解决方法请往下看:6. 设定反弹区间“滑动反弹”,这里的反弹是本篇文章的最后一步,上面说到的问题,就在这里解决。因为每一次触发touchend事件的时候,centerY值就累加一次,所以需要在touchend事件里做判断。我们设定一个反弹区间,就是当centerY的值大于或者小于某个值的时候,让它触发反弹。设定向上反弹值:向上的值比较简单,设置成“0”。为什么是“0”呢?我们限定只要手指离开时,上一次的滑动距离加上本次的距离> 0的时候,就让它触发反弹,并且反弹回0点的位置,也就是两次滑动的距离和= 0。示例代码:// 向上反弹var maxUpBounce = 0;// touchend 时,记录此时手指在 Y 轴上的落点距离可视顶部距离ul.addEventListener(’touchend’, function (e) { // 获取差值 var dy = e.changedTouches[0].clientY - startY; // 记录移动的距离 centerY = centerY + dy; // 两次滑动的距离 大于 设定的 向上 反弹值时 if (centerY > maxUpBounce) { // 让两次滑动的距离 等于 设置的值 centerY = maxUpBounce; // 添加过渡 ul.style.transition = ’transform .5s’; ul.style.transform = ’translateY(’ + centerY + ‘px)’; }})设定向下反弹值:向下的值其实跟之前求滑动区间差不多,我们参考下图,当列表向上滑动,滑动到列表底部的时候,只要此时再向上滑动,就让它向下反弹。向下反弹值就是-(ul.offsetHeight - draw.offsetHeight),只要滑动的差值小于这个设定值,就让它向下反弹,并且反弹回设定值的位置。示例代码:// 向上反弹值var maxUpBounce = 0; // 向下反弹值var maxDownBounce = -(ul.offsetHeight - draw.offsetHeight);// touchend 时,记录此时手指在 Y 轴上的落点距离可视顶部距离ul.addEventListener(’touchend’, function (e) { // 获取差值 var dy = e.changedTouches[0].clientY - startY; // 记录移动的距离 centerY = centerY + dy; // 两次滑动的距离 大于 设定的 向上 反弹值时 if (centerY > maxUpBounce) { // 让两次滑动的距离 等于 设置的值 centerY = maxUpBounce; // 添加过渡 ul.style.transition = ’transform .5s’; ul.style.transform = ’translateY(’ + centerY + ‘px)’; } // 两次滑动的距离 小于 设定的 向下 反弹值时 else if (centerY < maxDownBounce) { // 让两次滑动的距离 等于 设置的值 centerY = maxDownBounce; // 添加过渡 ul.style.transition = ’transform .5s’; ul.style.transform = ’translateY(’ + centerY + ‘px)’; }})注意: 在touchend事件的时候,给列表添加了transition属性才会有反弹的效果,但是,下一次滑动的时候,touchmove事件的时候,这个属性还存在,所以就会出现滑动的时候有顿挫感,所以在touchmove事件的时候,一进来就清一下过渡ul.style.transition = ’none’;。完成后效果图:7. 完整代码<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <meta name=“viewport” content=“width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0”> <meta http-equiv=“X-UA-Compatible” content=“ie=edge”> <title>移动端 Touch 滑动反弹</title> <style> / 样式初始化 / * { margin: 0; padding: 0; } html, body { width: 100%; } aside { height: 100%; width: 100%; } / 列表的父盒子,限制宽高 / / 注意设置overflow: hidden;样式后,超出这个盒子的ul将不会显示 / .draw { width: 60px; height: 500px; border: 2px solid #ccc; overflow: hidden; position: fixed; left: 10px; top: 50%; transform: translateY(-50%); } / li 设置了浮动, 所以 ul 要清除浮动 */ ul:after { content: “”; display: block; visibility: hidden; height: 0; clear: both; } ul { zoom: 1; } li { list-style: none; float: left; width: 60px; height: 60px; line-height: 60px; text-align: center; } </style></head><aside class=“main”> <div class=“draw” id=“draw”> <ul> <li style=“background:orange”>列表一</li> <li style=“background:yellowgreen”>列表二</li> <li style=“background:yellow”>列表三</li> <li style=“background:cyan”>列表四</li> <li style=“background:orangered”>列表五</li> <li style=“background:pink”>列表六</li> <li style=“background:red”>列表七</li> <li style=“background:purple”>列表八</li> <li style=“background:violet”>列表九</li> <li style=“background:brown”>列表十</li> </ul> </div></aside><body> <script> var draw = document.querySelector(’#draw’); var ul = draw.children[0]; var startY = 0; // 刚触碰到屏幕的时的手指信息 var centerY = 0; // 用来记录每次触摸时上一次的偏移距离 var maxDown = 50; // 设定一个最大向下滑动的距离 var maxUp = -(ul.offsetHeight - draw.offsetHeight + maxDown); // 求得一个最大向上滑动的距离 var maxUpBounce = 0; // 向上反弹值 var maxDownBounce = -(ul.offsetHeight - draw.offsetHeight); // 向下反弹值 // touchstart 时,记录手指在 Y 轴上的落点距离可视顶部距离 ul.addEventListener(’touchstart’, function (e) { startY = e.changedTouches[0].clientY; }) // touchmove 时,记录此时手指在 Y 轴上的落点距离可视顶部距离 ul.addEventListener(’touchmove’, function (e) { // 清除过渡 ul.style.transition = ’none’; // 获取差值 var dy = e.changedTouches[0].clientY - startY; // 上次的滑动距离加上本次的滑动距离 var tempY = centerY + dy; // 当上次滑动的距离加上本次滑动的距离 大于 设定的最大向下距离的时候 if (tempY > maxDown) { tempY = maxDown; } // 当上次滑动的距离加上本次滑动的距离 小于 设定的最大向上距离的时候 else if (tempY < maxUp) { tempY = maxUp; } // 设置 ul 在 Y 轴上的偏移 ul.style.transform = ’translateY(’ + tempY + ‘px)’; }) // touchend 时,记录此时手指在 Y 轴上的落点距离可视顶部距离 ul.addEventListener(’touchend’, function (e) { // 获取差值 var dy = e.changedTouches[0].clientY - startY; // 记录移动的距离 centerY = centerY + dy; // 两次滑动的距离 大于 设定的 向上 反弹值时 if (centerY > maxUpBounce) { // 让两次滑动的距离 等于 设置的值 centerY = maxUpBounce; // 添加过渡 ul.style.transition = ’transform .5s’; ul.style.transform = ’translateY(’ + centerY + ‘px)’; } // 两次滑动的距离 小于 设定的 向下 反弹值时 else if (centerY < maxDownBounce) { // 让两次滑动的距离 等于 设置的值 centerY = maxDownBounce; // 添加过渡 ul.style.transition = ’transform .5s’; ul.style.transform = ’translateY(’ + centerY + ‘px)’; } }) </script></body></html> ...

January 11, 2019 · 5 min · jiezi

chrome:移动端中的 100vh 始终等于地址栏隐藏时的高度

The first time when I know vh I was very excited. Finally, we can do this by css instead of js. However, still too naive.As we all know, scroll bar would hide automatically on mobile. So, it wouldn’t affect the layout like on desktop.However, the address bar would also hide when scrolling. Like images below:And the code is:<!DOCTYPE html><html lang=“en”> <head> <meta charset=“UTF-8” /> <meta name=“viewport” content=“width=device-width, initial-scale=1.0” /> <meta http-equiv=“X-UA-Compatible” content=“ie=edge” /> <title>Document</title> <style> * { margin: 0; padding: 0; } html, body, .app { /* height: 100%; / height: 100vh; } .app { width: 100%; border: 10px solid orange; box-sizing: border-box; position: relative; } .app__footer { height: 100px; width: 100%; box-sizing: border-box; border: 10px solid pink; position: absolute; bottom: 0; } </style> </head> <body> <div id=“app” class=“app”> <header class=“app__header”></header> <main class=“app__main”></main> <footer class=“app__footer”>app__footer</footer> </div> </body></html>The viewport size changes when scrolling. In my Mi6X, the smaller is 659px while the larger one is 715px when the address bar is hidden.According to test on chrome 70, height:100% and window.innerHeight is always equal to the smaller one. I think it is correct. I also thought 100vh would act like 100%. However, it’s not.According to developers.google the vh is always calculated as if the URL bar is hidden since Chrome version 56.So, 100vh is equal to the larger one which is 715px on my phone. That’s why images above would happen. In this case, if we use something like bottom:0; with 100vh we would meet situation like image one. Part of app__footer was covered. Instead, if we use height:100%, it won’t happen.However, as we all know it wouldn’t be possible to use 100% when we were in nested css modules. So, in this case, how can we get the 100% in nested css modules?Of course, we can save the 100% to rem like:document.documentElement.style.fontSize = window.innerHeight * 0.01 + ‘px’But I think the better way is using CSS_variables. For example:html,body,.app { / height: 100%; / / height: 100vh; */ height: calc(var(–vh) * 100);}document.documentElement.style.setProperty( ‘–vh’, window.innerHeight * 0.01 + ‘px’)Also, if you are worried about the compatibility. Here is the polyfill.Original PostReferencethe-trick-to-viewport-units-on-mobileCSS3 100vh not constant in mobile browser ...

December 5, 2018 · 2 min · jiezi