关于android:还不知道如何学习音视频

音视频的利用越来越宽泛,特地是挪动端的音视频利用,包含短视频、音视频直播、音视频通话等;挪动端的音视频开发需要也会十分大。作为一名挪动开发者,学习和理解音视频开发也是十分必要的。 但 Android 音视频开发这块目前没有比拟零碎的教程和书籍,这里给大家分享两份音视频材料《Android音视频开发进阶指南》《音视频精编源码解析》,置信能够给大家在音视频的学习上提供一些帮忙。 音视频开发学习路线图 Android音视频开发进阶指南《Android音视频开发进阶指南》分为五个大章节:Android音视频硬解码篇、应用OpenGL渲染视频画面篇、Android FFmpeg音视频解码篇、直播零碎聊天技术、直播零碎聊天技术、阿里IM技术分享。最初两个章节分享了包含阿里电商,微信,百度等技术实际。一、Android音视频硬解码篇音视频基础知识音视频硬解码流程:封装根底解码框架音视频播放:音视频同步音视频解封和封装:生成一个MP4二、应用OpenGL渲染视频画面篇初步理解OpenGL ES应用OpenGL渲染视频画面OpenGL渲染多视频,实现画中画深刻理解OpenGL之EGLOpenGL FBO数据缓冲区Android音视频硬编码:生成一个MP4三、Android FFmpeg音视频解码篇FFmpeg so库编译Android 引入FFmpegAndroid FFmpeg视频解码播放Android FFmpeg+OpenSL ES音频解码播放Android FFmpeg+OpenGL ES播放视频FFmpeg简略合成MP4:视屏解封与从新封装Android FFmpeg 视频编码四、直播零碎聊天技术百万在线的美拍直播弹幕零碎的实时推送技术实际之路阿里电商IM音讯平台,在群聊、直播场景下的技术实际微信直播聊天室单房间1500万在线的音讯架构演进之路百度直播的海量用户实时音讯零碎架构演进实际微信小游戏直播在Android端的跨过程渲染推流实际五、阿里IM技术分享企业级IM王者——钉钉在后端架构上的过人之处闲鱼IM基于Flutter的挪动端跨端革新实际闲鱼亿级IM音讯零碎的架构演进之路闲鱼亿级IM音讯零碎的牢靠投递优化实际音视频精编源码解析《音视频精编源码解析》,内容分为7个章节,涵盖 WebRTC Native 源码导读、X264 源码解读、FFmpeg、ijkplayer 源码剖析系列、jsmpeg 源码解析、Live555 源码解析、Opus 源码解析,一共 675 页。第一章 WebRTC Native 源码导读第一节-安卓相机采集实现剖析第二节-安卓预览实现剖析第三节-安卓视频硬编码实现剖析第四节-VideoCRE 与内存抖动优化第五节-安卓 P2P 连贯过程和 DataChannel 应用第六节-视频数据 native 层之旅第七节-混音第八节-P2P 连贯过程齐全解析第九节-API 概览第十节-RTP H.264 封包与解包 第二章 X264源码解读第一节-概述第二节-x264命令行工具第三节-编码器骨干局部-2第四节-x264_slice_write()第五节-滤波(Filter)局部第六节-宏块剖析(Analysis)局部-帧内宏块(Intra) 第三章 FFmpeg第一节-FFmpeg 编译和集成第二节-FFmpeg + ANativeWindow 实现视频解码播放第三节-FFmpeg + OpenSLES 实现音频解码播放第四节-FFmpeg + OpenGLES 实现音频可视化播放第五节-FFmpeg + OpenGLES 实现视频解码播放和视频滤镜第六节-FFmpeg 播放器实现音视频同步的三种形式第七节-FFmpeg + OpenGLES 实现 3D 全景播放器第八节-FFmpeg 播放器视频渲染优化第九节-FFmpeg、x264以及fdk-aac 编译整合第十节-FFmpeg 视频录制 - 视频增加滤镜和编码第十一节-FFmpeg + Android AudioRecorder 音频录制编码第十二节-Android FFmpeg 实现带滤镜的微信小视频录制性能 ...

December 16, 2021 · 1 min · jiezi

关于android:Android入门教程-MediaCodec-编解码使用方式

Android MediaCodec 编解码应用形式应用 MediaCodec 进行编解码。输出 H.264 格局的数据,输入帧数据并发送给监听器。 接下来咱们简称 MediaCodec 为 codec H.264的配置创立并配置 codec。配置 codec 时,若手动创立 MediaFormat 对象的话,肯定要记得设置 "csd-0" 和 "csd-1" 这两个参数。 "csd-0" 和 "csd-1" 这两个参数肯定要和接管到的帧对应上。 输出数据给 codec 输出数据时,如果对输出数据进行排队,须要查看排队队列的状况。 例如一帧数据暂用 1M 内存,1秒30帧,排队队列有可能会暂用30M的内存。当内存暂用过高,咱们须要采取肯定的措施来减小内存占用。 codec 硬解码时会受到手机硬件的影响。若手机性能不佳,编解码的速度有可能慢于原始数据输出。不得已的状况咱们能够将排队中的旧数据摈弃,输出新数据。 解码器性能对视频实时性要求高的场景,codec 没有可用的输出缓冲区,mCodec.dequeueInputBuffer 返回 -1。 为了实时性,这里会强制开释掉输入输出缓冲区 mCodec.flush()。 问题与异样问题1 - MediaCodec输出数据和输入数据数量之间有没有特定的关系 对于 MediaCodec,输出数据和输入数据数量之间有没有特定的关系?假如输出10 帧的数据,能够失去多少次输入? 实测发现,不能百分百保障输入输出次数是相等的。例如 vivo x6 plus,输出30 帧,能失去 28 帧后果。或者 300 次输出,失去 298 次输入。 异样1 - dequeueInputBuffer(0) 始终返回 -1 某些手机长时间编解码后,可能会呈现尝试获取 codec 输出缓冲区时下标始终返回 -1。 例如 vivo x6 plus,运行约 20 分钟后,mCodec.dequeueInputBuffer(0) 始终返回 -1。 ...

December 16, 2021 · 6 min · jiezi

关于android:在线教育报告上线助力职业与成人教育行业高效运营

继MMO游戏、RPG游戏、静止衰弱行业、通用行业报告及埋点模板上线后,华为剖析服务6.1.0版本新增在线教育-职业与成人教育行业报告与埋点模板,力求贴合使用者的实在利用场景,给企业提供场景化、智能化、个性化的数据服务,帮忙产品和经营人员轻松上手自助式用户行为剖析。 次要亮点包含: 新增教育行业报告与埋点模板:残缺、全面的指标体系,配套的埋点代码样例,帮您洞察用户规模变动、付费转化、学习与考试、流动经营等状况,数据驱动用户体验晋升。 反对转化事件回传HUAWEI Ads:您可通过将“用户首次启动、用户登录、利用内领取”等转化事件回传HUAWEI Ads,无效掂量广告成果,并进行深层优化。 1.职业教育行业报告上线,欠缺、成熟的指标体系开箱即用互联网经济时代,信息量、常识量的更迭速度放慢,对于职场人技能的要求也越来越高、越来越多样化,加之疫情的催化,推动了在线职业教育市场的凋敝,迎来了一波新的用户增量。但如何抓住这来之不易的机会,将这些用户短暂的留在APP内,一直晋升其粘性、用户价值,对企业来说是个不小的考验。职场人自身的工夫较为碎片化,在线教育平台如果能依据用户爱好为其精准举荐适宜的课程,并辅以肯定的优惠力度,一站式解决用户的自我晋升需要,就能更容易的留住用户。 基于对在线职业教育行业的深刻调研、对标杆企业实操办法论证的提炼,华为剖析6.1.0版本上线了在线教育行业报告,并提供了配套的埋点体系,旨在帮忙企业全面洞察用户行为的同时,最大水平节俭埋点工作量,晋升数据采集与剖析、利用的效率。 1.1报告内容概览报告分为四个板块:数据概览、付费转化、学习与考试、流动经营,帮您全面理解用户规模变动、拜访时长散布、新增会员渠道散布、人均付费金额、完课率、热门课程、热门付费课程、课程销售额、用户付费情、首购付费转化周期散布、到期会员散布、会员购买门路、领取完成率等。 该报告能够帮忙您建设残缺的在线职业教育指标体系,大大降低数据分析的应用门槛,助力企业实现全员自助式多维数据分析,对症下药做增长。 *数据概览——华为剖析报告示意 *付费转化——华为剖析报告示意图 *学习与考试——华为剖析报告示意图 *流动经营——华为剖析报告示意图 ### 1.2 开启智能埋点 在“智能数据接入-埋点开发”菜单下,您能够抉择“教育-职业与成人”模板,预置了4个场景的埋点模板及代码样例——“数据概览、付费转化、学习与考试、流动经营”,开箱即用。依照模板提供的事件及参数进行埋点后,即可查看上述职业与成人报告数据。 *职业与成人埋点模板示意 反对代码埋点与可视化埋点,其中代码埋点提供了复制代码埋点、表格埋点、工具埋点三种形式,可视化埋点须要您集成DTM Kit,以将App或Web界面同步至DTM控制台,通过可视化圈选的形式增加埋点事件与参数。 *埋点开发示意 当实现了选定模板的埋点开发后,可通过埋点验证性能疾速验证已埋点事件与参数是否正确,及时发现埋点脱漏及异常情况,进步埋点开发的准确度,升高业务危险。 *埋点验证性能示意 埋点实现后,您可通过埋点治理菜单理解埋点验证事件数、注册事件数详情,埋点事件配额占比和参数占比等,及时理解埋点进度、埋点构造,做到一站式埋点闭环治理。 *埋点治理性能示意 2.转化事件回传HUAWEI Ads,深度优化广告成果广告投放是企业拉新的次要形式,联合华为剖析和HUAWEI Ads,您能够依据业务需要设置有价值的“转化事件”,来跟踪广告带来的用户中产生转化事件的比例,以此掂量广告成果。 通过华为剖析,将转化事件(如“首次启动、用户登录、用户注册、利用内购买”等)回传HUAWEI Ads后,可对广告成果进行深层次的优化,更精准的圈选指标人群,确保广告投放的用户是对产品感兴趣且有价值的用户,晋升ROI。 比方,您发现某些广告工作的曝光数/点击数高,而新增启动数低,阐明该广告的成果差强人意,应该立刻暂停,尝试调整广告素材或关键词,来吸引更精准的受众群体。 欢送拜访华为剖析服务官网,收费体验Demo:华为剖析 | 一站式用户行为剖析平台 | Demo体验 。 获取开发领导文档:Android、iOS、Web、快利用 更多精彩内容,请见华为开发者官方论坛→https://developer.huawei.com/...

December 16, 2021 · 1 min · jiezi

关于android:高级Android插件化最全开源项目看这一篇就够了

前言插件化技术倒退到当初其实曾经很成熟了,然而相应的问题,如果没有真正地去实际过,基本不理解其中有多少问题,会牵涉到多少技术细节,多少被外人膜拜的表面光鲜的技术大牛都被『插件化』这三个字折磨地死去活来,这对于 Android 整个生态的侵害也让人无奈漠视。 那么这个组件化是什么意思呢?我说下我本人的了解,可能不对,还请指教: 通过 gradle 配置的形式,将打 debug 包和 release 包离开。这样会有一个益处,开发一个模块,在 debug 的时候,能够打成一个 apk ,独立运行测试,能够齐全独立于整个宿主 APP 的其余所有组件;待到要打 release 包的时候,再把这个模块作为一个 library ,打成 aar ,作为整个宿主 APP 的一部分。而 debug 和 release 的切换都是通过 gradle 配置,能够做到无缝切换。至于模块之间的跳转,能够用别名的形式,而不是用 Activity 和 Fragment 类名。这样所有的模块和宿主 APP 都是齐全解耦的,彻底解决了并行开发的可能造成的穿插依赖等问题。依照这个思路,咱们再来看看一些其余的细节: 在 Android 里有一个比拟爽的一点是,作为 library 的时候,aar 里的援用依赖,在宿主 Application 里也有同样的援用依赖,并不会打包两份到宿主 Application 里;模块之间的跳转,除了应用别名的形式,我能想到的还有另外一种形式,同样是通过 gradle 脚本,将跳转用到的类打成一个 jar ,作为一个 API 服务提供给其余模块作为编译期依赖(provided)引入;各个 library 在 debug 的时候作为 apk ,要独立打包运行测试,这时就须要有一个启动 Activity ,而 library 是不须要的,我的想法是搁置两个 AndroidManifest.xml ,应用 sourceSets 别离在 debug 和 release 的时候加载不同的 AndroidManifest.xml。对于Android开发者而言,插件化技术曾经是进阶Android高级工程师的必备技能之一。我这里有一份【高级Android插件化强化实战】材料,心愿能帮到大家! ...

December 16, 2021 · 1 min · jiezi

关于android:存量时代用户增长怎么做唤醒和召回很关键

用户增长的疲软态势让企业的经营重心缓缓由公域转向私域,存量的精细化经营帮忙他们在流量见顶的当下开掘了更多增长契机。昂扬的流量老本让拉新举步维艰,缄默和散失预警用户的持续性召回与唤醒势在必行。 在用户生命周期模型中,缄默期介于成熟期和散失期之间,一直地通过经营伎俩触达并放弃这部分用户沉闷,是决定产品是否放弃高留存的要害阶段。精准的用户标签联合多模式、多样化的促活伎俩,可帮忙咱们建设更加牢固的客户关系,从而留住用户。 策略制订以行为量化为根据缄默和濒临散失边缘的用户,他们的相干属性特色和后期产品应用行为不仅是咱们划分用户生命周期阶段的根据,同时,通过进一步下钻剖析,更能够为咱们制订迷信的针对性召回唤醒策略提供参考。 某电商平台,依靠于预测服务能力,将高概率散失人群提前圈定。在深度洞察剖析该局部人群特色的时候,他们发现:相比于长期缄默用户,这部分人群在近一周仍有低频会话次数,阐明近期他们仍然有购买需要,但以后库存、竞品价格优势等因素可能会造成他们的购买散失。 针对这部分人群以及他们的行为量化剖析论断,经营人员打算给他们推送相干优惠音讯达到刺激回流并产生付费行为的目标。 智能经营晋升转化通过智能经营画布,创立一个自动化经营流动,将预测的高概率散失人群作为本次流动的指标受众。 为了使优惠活动的推送音讯更具针对性,依据用户近期的消费行为数据,他们做了进一步的条件分流个性化编排。对近30天付费金额等级高的重点用户,推送“品质专场上新”,残余其余用户依据其近期的应用次数和时长做进一步分流: 例如,对于”近30天均匀日应用次数<1”的用户,他们已属于散失高危人群,面临随时散失危险,急需通过“无门槛优惠券”、“特价商品”这类的大额促销流动音讯来及时召回和唤醒;而对于”近30天沉闷天数>15”这类用户,应用频率尚可,可通过“满减券”这样的小额促销来放弃沉闷。 除依据高概率散失用户的登录频次、生产金额等指标个性化分流经营外,近期浏览品类商品的上新、举荐、库存告急等不同内容,也是经营在设计召回文案时思考的方向。除了通过Push的触达形式,对未点击音讯的其余高概率散失用户,该电商平台还通过短信模式做了进一步补充触达,使针对性的促活音讯尽可能笼罩到所有指标人群。 在大数据精准人群圈选的根底上,联合智能经营平台,共性多样的人群分流和多渠道的用户触达形式,帮忙该电商平台在存量时代仍旧放弃着用户快速增长。 欲了解更多智能经营服务能力,请参阅: 智能经营介绍 更多精彩内容,请见华为开发者官方论坛→https://developer.huawei.com/...

December 16, 2021 · 1 min · jiezi

关于android:Android-官方架构组件一Lifecycle

什么是Lifecycle?Lifecycle 组件指的是 android.arch.lifecycle 包下提供的各种类与接口,能够让开发者构建能感知其余组件(次要指Activity 、Fragment)生命周期(lifecycle-aware)的类。 为什么要引进Lifecycle?后面说了,Lifecycle可能让开发者构建能感知其余组件(次要指Activity 、Fragment)生命周期(lifecycle-aware)的类。划重点,让开发者构建能感知其余组件(次要指Activity 、Fragment)生命周期(lifecycle-aware)的类。在android开发的过程中,咱们经常须要让一些操作可能感知Activity/Fragment的生命周期,从而实现在活动状态下容许操作,而在销毁状态下须要主动禁止操作,开释资源,避免内存泄露。例如赫赫有名的图片加载框架Glide在Acticiy/Fragment处于前台的时候加载图片,而在不可见的状态下进行图片的加载,又例如咱们心愿RxJava的Disposable可能在Activity/Fragment销毁是主动dispose。Lifecycle的呈现,让开发者们可能轻易地实现上述的性能。 一个用Lifecycle革新的MVP例子比方咱们当初须要实现这样一个性能:监听某个 Activity 生命周期的变动,在生命周期扭转的时候打印日志。 个别做法结构回调的形式先定义根底IPresent接口: public interface IPresent { void onCreate(); void onStart(); void onResume(); void onPause(); void onStop(); void onDestory();}而后在自定义的Present中继承IPresent接口: public class MyPresent implements IPresent { private String TAG = "tag"; @Override public void onCreate() { LogUtil.i(TAG, "onCreate"); } @Override public void onStart() { LogUtil.i(TAG, "onStart"); } @Override public void onResume() { LogUtil.i(TAG, "onResume"); } @Override public void onPause() { LogUtil.i(TAG, "onPause"); } @Override public void onStop() { LogUtil.i(TAG, "onStop"); } @Override public void onDestory() { LogUtil.i(TAG, "onDestory"); }最初在Activity顺次调用回调办法散发事件: ...

December 16, 2021 · 11 min · jiezi

关于android:我们距离真正的互联互通还有多远

喜大普奔,互联互通政策终于有了停顿,11月29日晚,微信发表履行凋谢了电商类内部链接间接拜访的性能。当初在微信内,终于能够间接关上淘宝外链了! 此前,咱们曾预测在微信放开淘宝链接后,在微信里iOS端大概率能够间接关上淘宝App。但令人悲观的是,只管微信用户目前能够在聊天群里关上淘宝的商品链接,然而仅能在微信内的h5页面下单,想要通过微信唤起淘宝App下单,只能点击链接通过右上角浏览器中,间接关上淘宝。在Android端的用户体验不佳难能可贵,但在IOS端也遇到此类问题着实让人感到困惑。 Android端唤起App个别采取URL Scheme形式,但微信设定的门槛极高,只定向凋谢了多数的App,如:京东、拼多多。iOS唤端个别采取Universal link形式,此形式微信始终反对。所以,在失常状况下,大部分的App在iOS端的微信内是能够间接唤起App的。 但,目前在iOS端的微信内依然无奈唤起淘宝App! 据悉,淘宝App曾经适配Universal link的唤起形式,但在这段时间内,却频繁被微信封闭。而相似识货、得物、拼多多、京东等其余电商App,分享体验非常敌对。分享链路通常为:App-微信小程序-微信小程序或App-微信H5-App的微信闭环分享链路。 同时,京东和拼多多的分享形式体验也较好,会以小卡片的模式出现,蕴含商品详细信息等,而淘宝的链接临时只裸奔在对话框内。 据公开音讯称:淘宝特价版此前虽频繁申请小程序,但始终未被微信官网通过。加之H5唤起App链路被微信封闭,因而淘系App要想在微信生态内打造欠缺体验良好的分享裂变链路还须要很长的路要走,但对于很多开发者来说,互联互通下的新业务场景,能够借助技术工具实现裂变式的增长,通过小程序+APP+H5减速全平台端矩阵联动经营。微信凋谢淘宝外链是一个很重要的里程碑,但对于平台之间的互联互通仍任重道远。 对于这个问题你怎么看?欢送在评论区留言哦。 点击上面链接退出友盟+ 技术社群 与超过1000+挪动开发者独特探讨挪动开发最新动静 点击退出友盟+技术群 欢送点击【友盟+】,理解友盟+ 最新挪动技术 欢送关注【友盟全域数据】公众号

December 16, 2021 · 1 min · jiezi

关于android:如何改善应用启动性能-Facebook-应用的经验分享

作者 / Google 和 Facebook 团队撰稿 / Google Android 团队的 Kateryna Semenova 和 Facebook 团队的 Tim Trueman、Steven Harris、Subramanian Ramaswamy 简介缩短利用的启动工夫并非小事,咱们必须深刻理解其影响因素。往年,Google Android 团队和 Facebook 利用团队始终在单干钻研这方面的量化指标,并共享优化办法,以改善利用启动状况。Google Android 的公开文档中蕴含了很多对于 利用启动优化 的信息。这里咱们想进一步分享其在 Facebook 利用中的实际状况,以及哪些因素有助于改善利用启动性能。 当初,每个月有超过 29 亿人应用 Facebook。Facebook 帮忙人们构建社区,并让世界更严密地分割在一起。用户会在这里分享生存的霎时,理解和探讨正在产生的事件,建设和造就人际关系,独特单干以发明支出机会。 Facebook 利用开发者则致力于确保用户享受最佳体验,并让利用在任意设施、任何国家/地区和不同网络条件下都能流畅运行。Google Android 团队和 Facebook 团队精诚合作,在利用启动工夫的指标定义和最佳实际上达成共识,并在这里分享给大家。 从哪里开始首先天然是测量利用的启动工夫。您可借此获悉用户启动体验的衰弱水平,追踪启动工夫好转的状况,并计算进行改良须要投入的资源量。归根结底,您的启动工夫须要与用户满意度、参与度或用户增长相关联,以确定投入的优先秩序。 Android 定义了两个掂量利用启动工夫的指标: 齐全显示所用工夫 (TTFD) 和 初步显示所用工夫(TTID)。尽管您能够进一步将其划分为冷/暖启动工夫,但本文不会解释它们之间的区别,而 Facebook 的办法是,掂量和优化与利用交互的所有用户所经验的启动工夫 (有些是冷启动,有些是暖启动)。 齐全显示所用工夫 (Time-To-Full-Display, TTFD) TTFD 会记录您的利用实现渲染并可供用户交互和应用时所需的工夫,可能包含显示本地存储或来自网络上的内容所需的工夫。如果网络较慢,这可能会破费一段时间,并会视用户的应用设施而有所差别。因而,咱们有必要立刻展现一些内容,让用户看到利用启动的过程,而这就要提到 TTID 了…… 初步显示所用工夫 (Time-To-Initial-Display, TTID) TTID 会记录您的利用显示背景、导航、可疾速加载的本地内容、加载较慢的本地或网络内容的占位块所须要的工夫。TTID 应该是用户能够到处导航并返回其指标的所需工夫。 不要扭转太多: 有一件事须要留神,就是在 TTID 和 TTFD 之间利用内容的视觉变动问题,例如在页面里先展现的是已缓存的内容,而后在网络内容加载实现后忽然切换页面内容。这种忽然的变动可能会让用户感到不快和丧气,所以请确保您的利用可在 TTID 期间显示足够有意义的内容,尽可能地向用户展现其将在 TTFD 期间看到的内容。达成用户指标用户拜访您的利用是为了获取内容,这可能须要一段时间实现加载,而您心愿利用能够尽快把这些内容出现给他们。 ...

December 16, 2021 · 2 min · jiezi

关于android:OkHttp源码走心解析很细-很长

前言本文是对OkHttp开源库的一个具体解析,如果你感觉本人不够理解OkHttp,想进一步学习一下,置信本文对你会有所帮忙。 本文蕴含了具体的申请流程剖析、各大拦截器解读以及本人的一点反思总结,文章很长,欢送大家一起交换探讨。 应用办法应用办法非常简略,别离创立一个OkHttpClient对象,一个Request对象,而后利用他们创立一个Call对象,最初调用同步申请execute()办法或者异步申请enqueue()办法来拿到Response。 private final OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("https://github.com/") .build(); //同步申请 Response response = client.newCall(request).execute(); //todo handle response //异步申请 client.newCall(request).enqueue(new Callback() { @Override public void onFailure(@NotNull Call call, @NotNull IOException e) { //todo handle request failed } @Override public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { //todo handle Response } });根本对象介绍正如应用办法中所述,咱们先后构建了 OkHttpClient对象、Request对象、Call对象,那这些对象都是什么意思,有什么作用呢?这个就须要咱们进一步学习理解了。 OkHttpClient一个申请的配置类,采纳了建造者模式,不便用户配置一些申请参数,如配置callTimeout,cookie,interceptor等等。 open class OkHttpClient internal constructor( builder: Builder) : Cloneable, Call.Factory, WebSocket.Factory { constructor() : this(Builder()) class Builder constructor() { //调度器 internal var dispatcher: Dispatcher = Dispatcher() //连接池 internal var connectionPool: ConnectionPool = ConnectionPool() //整体流程拦截器 internal val interceptors: MutableList<Interceptor> = mutableListOf() //网络流程拦截器 internal val networkInterceptors: MutableList<Interceptor> = mutableListOf() //流程监听器 internal var eventListenerFactory: EventListener.Factory = EventListener.NONE.asFactory() //连贯失败时是否重连 internal var retryOnConnectionFailure = true //服务器认证设置 internal var authenticator: Authenticator = Authenticator.NONE //是否重定向 internal var followRedirects = true //是否从HTTP重定向到HTTPS internal var followSslRedirects = true //cookie设置 internal var cookieJar: CookieJar = CookieJar.NO_COOKIES //缓存设置 internal var cache: Cache? = null //DNS设置 internal var dns: Dns = Dns.SYSTEM //代理设置 internal var proxy: Proxy? = null //代理选择器设置 internal var proxySelector: ProxySelector? = null //代理服务器认证设置 internal var proxyAuthenticator: Authenticator = Authenticator.NONE //socket配置 internal var socketFactory: SocketFactory = SocketFactory.getDefault() //https socket配置 internal var sslSocketFactoryOrNull: SSLSocketFactory? = null internal var x509TrustManagerOrNull: X509TrustManager? = null internal var connectionSpecs: List<ConnectionSpec> = DEFAULT_CONNECTION_SPECS //协定 internal var protocols: List<Protocol> = DEFAULT_PROTOCOLS //域名校验 internal var hostnameVerifier: HostnameVerifier = OkHostnameVerifier internal var certificatePinner: CertificatePinner = CertificatePinner.DEFAULT internal var certificateChainCleaner: CertificateChainCleaner? = null //申请超时 internal var callTimeout = 0 //连贯超时 internal var connectTimeout = 10_000 //读取超时 internal var readTimeout = 10_000 //写入超时 internal var writeTimeout = 10_000 internal var pingInterval = 0 internal var minWebSocketMessageToCompress = RealWebSocket.DEFAULT_MINIMUM_DEFLATE_SIZE internal var routeDatabase: RouteDatabase? = null ···省略代码···Request同样是申请参数的配置类,也同样采纳了建造者模式,但相比于OkHttpClient,Request就非常简略了,只有四个参数,别离是申请URL、申请办法、申请头、申请体。 ...

December 16, 2021 · 13 min · jiezi

关于android:知识点OkHttp-原理-8-连问

前言OkHttp能够说是Android开发中最常见的网络申请框架,OkHttp使用方便,扩展性强,功能强大,OKHttp源码与原理也是面试中的常客 然而OKHttp的源码内容比拟多,想要学习它的源码往往千头万绪,一时抓不住重点. 本文从几个问题登程梳理OKHttp相干知识点,以便疾速构建OKHttp常识体系,如果对你有用,欢送点赞~ 本文次要包含以下内容 OKHttp申请的整体流程是怎么的?OKHttp散发器是怎么工作的?OKHttp拦截器是如何工作的?利用拦截器和网络拦截器有什么区别?OKHttp如何复用TCP连贯?OKHttp闲暇连贯如何革除?OKHttp有哪些长处?OKHttp框架中用到了哪些设计模式?1. OKHttp申请整体流程介绍首先来看一个最简略的Http申请是如何发送的。 val okHttpClient = OkHttpClient() val request: Request = Request.Builder() .url("https://www.google.com/") .build() okHttpClient.newCall(request).enqueue(object :Callback{ override fun onFailure(call: Call, e: IOException) { } override fun onResponse(call: Call, response: Response) { } })这段代码看起来比较简单,OkHttp申请过程中起码只须要接触OkHttpClient、Request、Call、 Response,然而框架外部会进行大量的逻辑解决。 所有网络申请的逻辑大部分集中在拦截器中,然而在进入拦截器之前还须要依附散发器来调配申请工作。 对于散发器与拦截器,咱们在这里先简略介绍下,后续会有更加具体的解说 散发器:外部保护队列与线程池,实现申请调配;拦截器:五大默认拦截器实现整个申请过程。 整个网络申请过程大抵如上所示 通过建造者模式构建OKHttpClient与 RequestOKHttpClient通过newCall发动一个新的申请通过散发器保护申请队列与线程池,实现申请调配通过五大默认拦截器实现申请重试,缓存解决,建设连贯等一系列操作失去网络申请后果2. OKHttp散发器是怎么工作的?散发器的次要作用是保护申请队列与线程池,比方咱们有100个异步申请,必定不能把它们同时申请,而是应该把它们排队分个类,分为正在申请中的列表和正在期待的列表, 等申请实现后,即可从期待中的列表中取出期待的申请,从而实现所有的申请 而这里同步申请各异步申请又略有不同 同步申请 synchronized void executed(RealCall call) { runningSyncCalls.add(call);}因为同步申请不须要线程池,也不存在任何限度。所以散发器仅做一下记录。后续依照退出队列的程序同步申请即可 异步申请 synchronized void enqueue(AsyncCall call) { //申请数最大不超过64,同一Host申请不能超过5个 if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) { runningAsyncCalls.add(call); executorService().execute(call); } else { readyAsyncCalls.add(call); }}当正在执行的工作未超过最大限度64,同时同一Host的申请不超过5个,则会增加到正在执行队列,同时提交给线程池。否则先退出期待队列。 每个工作实现后,都会调用散发器的finished办法,这外面会取出期待队列中的工作继续执行 ...

December 16, 2021 · 3 min · jiezi

关于android:Kotlin-线程同步的方法

面试的时候常常会被问及多线程同步的问题,例如: “ 现有 Task1、Task2 等多个并行任务,如何期待全副工作执行实现后,开始执行 Task3 ? ”Kotlin 中有多种实现形式可供选择,本文将所有这些形式做了整顿: Thread.joinSynchronizedReentrantLockBlockingQueueCountDownLatchCyclicBarrierCASFutureCompletableFutureRxjavaCoroutineFlow咱们先定义三个Task,模仿上述场景, Task3 基于 Task1、Task2 返回的后果拼接字符串,每个 Task 通过 sleep 模仿耗时: val task1: () -> String = {    sleep(2000)    "Hello".also { println("task1 finished: $it") }}val task2: () -> String = {    sleep(2000)    "World".also { println("task2 finished: $it") }}val task3: (String, String) -> String = { p1, p2 ->    sleep(2000)    "$p1 $p2".also { println("task3 finished: $it") }}1. Thread.join()Kotlin 兼容 Java,Java 的所有线程工具默认都能够应用。其中最简略的线程同步形式就是应用 Thread 的 join() : @Testfun test_join() {    lateinit var s1: String    lateinit var s2: String    val t1 = Thread { s1 = task1() }    val t2 = Thread { s2 = task2() }    t1.start()    t2.start()    t1.join()    t2.join()        task3(s1, s2)}2. Synchronized应用 synchronized 锁进行同步  @Test    fun test_synchrnoized() {        lateinit var s1: String        lateinit var s2: String        Thread {            synchronized(Unit) {                s1 = task1()            }        }.start()        s2 = task2()        synchronized(Unit) {            task3(s1, s2)        }    }然而如果超过三个工作,应用 synchrnoized 这种写法就比拟顺当了,为了同步多个并行任务的后果须要申明n个锁,并嵌套n个 synchronized。 3. ReentrantLockReentrantLock 是 JUC 提供的线程锁,能够替换 synchronized 的应用  @Test    fun test_ReentrantLock() {        lateinit var s1: String        lateinit var s2: String        val lock = ReentrantLock()        Thread {            lock.lock()            s1 = task1()            lock.unlock()        }.start()        s2 = task2()        lock.lock()        task3(s1, s2)        lock.unlock()    }ReentrantLock 的益处是,当有多个并行任务时是不会呈现嵌套 synchrnoized 的问题,但依然须要创立多个 lock 治理不同的工作, 4. BlockingQueue阻塞队列外部也是通过 Lock 实现的,所以也能够达到同步锁的成果  @Test    fun test_blockingQueue() {        lateinit var s1: String        lateinit var s2: String        val queue = SynchronousQueue<Unit>()        Thread {            s1 = task1()            queue.put(Unit)        }.start()        s2 = task2()        queue.take()        task3(s1, s2)    }当然,阻塞队列更多是应用在生产/生产场景中的同步。 5. CountDownLatchJUC 中的锁大都基于 AQS 实现的,能够分为独享锁和共享锁。ReentrantLock 就是一种独享锁。相比之下,共享锁更适宜本场景。例如 CountDownLatch,它能够让一个线程始终处于阻塞状态,直到其余线程的执行全副实现:  @Test    fun test_countdownlatch() {        lateinit var s1: String        lateinit var s2: String        val cd = CountDownLatch(2)        Thread() {            s1 = task1()            cd.countDown()        }.start()        Thread() {            s2 = task2()            cd.countDown()        }.start()        cd.await()        task3(s1, s2)    }共享锁的益处是不用为了每个工作都创立独自的锁,即便再多并行任务写起来也很轻松 6. CyclicBarrierCyclicBarrier 是 JUC 提供的另一种共享锁机制,它能够让一组线程达到一个同步点后再一起持续运行,其中任意一个线程未达到同步点,其余已达到的线程均会被阻塞。 与 CountDownLatch 的区别在于 CountDownLatch 是一次性的,而 CyclicBarrier 能够被重置后重复使用,这也正是 Cyclic 的命名由来,能够循环应用  @Test    fun test_CyclicBarrier() {        lateinit var s1: String        lateinit var s2: String        val cb = CyclicBarrier(3)        Thread {            s1 = task1()            cb.await()        }.start()        Thread() {            s2 = task1()            cb.await()        }.start()        cb.await()        task3(s1, s2)    }7. CASAQS 外部通过自旋锁实现同步,自旋锁的实质是利用 CompareAndSwap 防止线程阻塞的开销。因而,咱们能够应用基于 CAS 的原子类计数,达到实现无锁操作的目标。   @Test    fun test_cas() {        lateinit var s1: String        lateinit var s2: String        val cas = AtomicInteger(2)        Thread {            s1 = task1()            cas.getAndDecrement()        }.start()        Thread {            s2 = task2()            cas.getAndDecrement()        }.start()        while (cas.get() != 0) {}        task3(s1, s2)    }while 循环空转看起来有些浪费资源,然而自旋锁的实质就是这样,所以 CAS 仅仅实用于一些cpu密集型的短工作同步。 volatile看到 CAS 的无锁实现,兴许很多人会想到 volatile, 是否也能实现无锁的线程平安?   @Test    fun test_Volatile() {        lateinit var s1: String        lateinit var s2: String        Thread {            s1 = task1()            cnt--        }.start()        Thread {            s2 = task2()            cnt--        }.start()        while (cnt != 0) {        }        task3(s1, s2)    }留神,这种写法是谬误的volatile 能保障可见性,然而不能保障原子性,cnt-- 并非线程平安,须要加锁操作 8. Future下面无论有锁操作还是无锁操作,都须要定义两个变量s1、s2记录后果十分不不便。Java 1.5 开始,提供了 Callable 和 Future ,能够在工作执行完结时返回后果。 @Testfun test_future() {    val future1 = FutureTask(Callable(task1))    val future2 = FutureTask(Callable(task2))    Executors.newCachedThreadPool().execute(future1)    Executors.newCachedThreadPool().execute(future2)    task3(future1.get(), future2.get())}通过 future.get(),能够同步期待后果返回,写起来十分不便 9. CompletableFuturefuture.get() 尽管不便,然而会阻塞线程。Java 8 中引入了 CompletableFuture  ,他实现了 Future 接口的同时实现了 CompletionStage 接口。CompletableFuture 能够针对多个 CompletionStage 进行逻辑组合、实现简单的异步编程。这些逻辑组合的办法以回调的模式防止了线程阻塞: ...

December 16, 2021 · 1 min · jiezi

关于android:终于来了Android端个人中心页面滑动冲突优化方案

背景抖音首页右滑可进入“集体核心”页面,对于首页日活上亿的 APP 来说,这个页面的pv实践上应该不会太小。然而某些时候在此页面会呈现滑动抵触的小问题,不太利于用户体验,通过重复的把玩测试,找到了必现的操作,作为一个资深的抖迷和一个非资深的 Android 开发的我,产生了钻牛角尖的想法—想看看问题是怎么产生的,以及有没有可优化的计划。 问题景象首页右滑可进入“集体核心”页面,而后在底部的 RecylerView 上先左右滑动,然而不触发它们父布局 ViewPager 的切换,而后手指不抬起,进行高低滑动,此时 RecylerView 会接管滑动事件,导致滑动错位,如下图所示: 起因剖析问题明确了,接下来就是剖析是如何产生的了。我通过综合剖析发现,抖音用的是自定义 LinearLayout 的形式来布局 header + Viewpager + RecyclerView 的,进而通过拦挡 LinearLayout 的 disptachTouchEvent 来解决的嵌套滑动。整体的滑动流程如图所示: 当手指触摸屏幕时,记录地位,滑动后,判断是横向竖向,只判断一次如果是高低滑动,则判断是触发最外层 LinearLayout 的滑动,还是触发 RecyclerView 的本身滑动。触发本身的滑动就是调用本人的 scrollBy(0,dy),留神 此时的事件还是会往下传递到 RecyclerView ,然而因为绝对于 RecyclerView 本身来说滑动差值很小,视觉上可疏忽。不触发本身的滑动就会间接散发上来,此时 RecyclerView 本身来说竖向(dy)差值变动较大,失常滑动。呈现问题时,用户的手先触发左右滑动,这时候因为 RecyclerView 父布局 ViewPager 中的一些临界判断没被触发,所以没拦挡事件,事件还是到了 RecyclerView 中,此时如果再次高低滑动,因为1中的判断单次滑动周期内只触发了一次,还被认为是左右滑动事件,所以 LinearLayout 布局自身没有滚动,然而 RecyclerView 失常响应滚动,导致的呈现滑动偏差。优化计划问题剖析的差不多了,其实原本也就完结了,然而惊喜的发现原来这个自定义的滑动布局是扩大自开源库:https://github.com/cpoopc/ScrollableLayout 然而曾经长时间没人保护了。不过通过这个原始的库。能够看到外围逻辑还是统一的,通过调试编译发现,的确这个库也同样存在这个问题,那就基于此库着手试着解决一下吧。 开源库的本来代码: 依据剖析就是在图中 else 中其实又触发了高低滑动逻辑,而外层的自定义 LinearLayout 布局没有追随滑动导致的。那咱们是不是能够在外面加个判断,除去真正的左右滑动逻辑(ViewPager事件),剩下的事件就是触发 RecylcerView 滑动的了(相当于过滤了横向的,留下的竖向的),咱们再次判断外层的自定义 LinearLayout 布局是否须要联动,如果须要再次联动就好了。 站在伟人肩膀上,零碎控件的解决个别都能够借鉴,源码之下,所有清晰,横向的能够参考 ViewPager 的事件拦挡,竖向的能够参考 RecyclerView 的事件处理逻辑。剖析两个控件的 onIntercepetTouchEvent() , 拿到其外围的判断是否响应滑动的逻辑为咱们所用。 ViewPager 相干源码: 外围拦挡逻辑: 如果横向上有可滑动的子 View ,就不拦挡,让子 View 去解决横向的滑动超过临界值 mTouchSlop ,并且大于竖向滑动间隔的2倍,进行拦挡咱们须要把相干的判断代码都 copy 过去,而后退出到咱们自定义 LinearLayout 中 ...

December 16, 2021 · 1 min · jiezi

关于android:一本毕业的打工人大厂安卓开发2年被裁重新出发终于拿下腾讯offer

前言自我介绍下,自己就是个屌丝程序猿,大学很一般名字就不说了,软件工程业余。大学毕业后去了一家大公司面试,胜利的拿到了Offer。说实话,拿到Offer的那一刻,我的心田是十分开心冲动的,入职后也十分顺利,就是因为太顺利,导致我始终很劳碌,也对将来没有什么思考。 起初的起初,因为我始终以来的劳碌,本身的技术也始终是那样,公司的倒退须要更高技术的人才。很显然,我曾经适应不了公司的倒退,最终,我被解雇了。 被解雇后,我心田深受打击,对这座城市也心灰意冷。一番考虑过后我决定买高铁票回老家。回到老家后,我看着这座相熟的城市,情绪舒缓了许多。在老家待了几天,我感觉不能再这么颓丧上来了。然而近年来的劳碌工作状态,让我的技术没有一点出息,我自知这个状态上来想要进到互联网头部公司定是不事实的,毕竟学历不能代表全副,技术才是最重要的。我决定从新登程,晋升本人的技术。 自己目前曾经在腾讯入职了,过程十分艰苦,我深知这来之不易的胜利是我始终以来致力付出失去的。 上面是我面试中的一些流程和面试的问题,给小伙伴们一些教训,心愿能帮忙到你们。 面试流程腾讯一面(全程大概1h左右)自我介绍TCP与UDP的区别TCP三次握手说一下(把流程说一遍,这里认为会持续问为什么不是两次或者四次,后果没有)看你我的项目用到线程池,说一下线程池工作原理,工作拒接策略有哪几种过程和线程的区别ArrayList与LinkedList的区别线程平安与非线程平安汇合说一下,底层怎么实现的(hashmap,concurrenthashmap)数据库事务隔离级别说一下synchronized和lock区别,可重入锁与非可重入锁的区别说说乐观锁和乐观锁的区别手写进制转换算法,求出一个数的二进制数1的个数JAVA根底 equals多线程形式、threadlocal,各种锁,synchronized和lock设计模式、spring类加载形式、实例保留在哪、aop ioc、反射机制类加载器,双亲委派模型,热部署jvm内存模型,内存构造、堆的分代算法、堆的分区、gc算法、gc过程tcp ip 七层模型 rest接口标准 get和post区别,长度,平安tcp ip的arp协定,两个同一网络的主机如何取得对方的mac地址负载平衡、高并发、高可用的架构mysql的引擎区别redis缓存,redis的集群部署,热备份,主从备份,主从数据库,hash映射找到晓得指定节点理解云计算么,理解云容器docker么,容器和虚拟机的区别(面试官问了很多根底的问题,有些答复的并不是很流畅,不晓得还有没有心愿。) 二面是在星期四的一个下午,间隔一面过来大概有一个星期了吧。工夫都有那么久了,我认为一面可能凉了,后果就收到了面试的邀约。腾讯二面(大概45min)说一下你对哪个我的项目比拟相熟、为什么做这个我的项目我的项目采纳了什么架构,数据库如何设计的数据库由哪些表,为什么有这些表次要有哪些外围模块,模块之间如何通信的如何保留会话状态,有哪些形式、区别如何分布式session如何治理,你有哪些计划学过数据结构和算法吗(当然),你说说二分搜寻的过程说一下快排的过程,写一下伪代码理解哪设计模式,举例说说在jdk源码哪些用到了你说的设计模式(二面大部分问的都是我的项目技术上的。感觉我答复的并不是特地好,所以感觉没什么心愿了。) 就在我筹备从新投简历的时候,他们给我打电话了,让我约个工夫视频面试,而后就开始了第三轮面试。腾讯三面(视频面,全程大略1h左右)说下你平时看的一些技术博客,书籍linux 下的一些指令工作中你感觉最不爽的事件是什么说下你的优缺点有没有想过来守业公司写个 strcpy 函数说说你本人的性情给你一个零碎,后盾的逻辑曾经实现了,然而前端加载很慢,怎么检测当前可能要学习很多新技术,你怎么看我的项目中遇到的艰难(提前想好,并且把实现或者优化办法说分明)零碎的量级、pv、uv 等应答高并发的解决办法(分布式)在我的项目中次要负责了哪些工作nginx 的负载平衡分布式缓存的一致性,服务器如何扩容(哈希环)(第三轮面试整体感觉还行,没有什么特地大的压力) HR面(大概30min)平时怎么学习的兴趣爱好感觉本人后面几轮面试怎么样除了Java还钻研过其它什么技术(我说AI,区块链)跟我介绍一下区块链~怎么对待国内区块链的倒退跟我说一下你认为最具备挑战性的我的项目我做了哪些?最终顺利拿到offer的?1.跟着视频学,从新开始 2.坚固常识,增强本人的专业技能 3.刷面试题,相熟面试流程 面试倡议1.有急躁且被动 面试不要焦急着去问后果,个别在hr面的时候,她的态度多少可能猜个七七八八的,如果等上一周还没有告诉,那就能够被动去问了。 2.刷题是为了晋升本人的运气 运气在面试过程中是十分重要的,刷题的目标很简略,除了坚固咱们所把握的,另一个就是为了能进步在面试中咱们的运气,如果可能问到一样的题是再好不过了,当然这个方法是实用于职级中低岗位。 3.把握根底,留神深度 腾讯面试最喜爱问两类问题,一类是根底,另一类就是深度。根底局部,无非就是咱们所把握的技术根底内容,基本上只有是有筹备的都没有太大的问题。另一部分就是深度问题,大多波及到本人之前的工作、我的项目,面试官所问的问题不仅仅是停留在外表那么简略,背地的原理是什么才是面试官想要问的。 想要视频和大厂面试题的敌人能够点这里支付哦最初其实Android开发的知识点就那么多,面试问来问去还是那么点货色。所以面试没有其余的窍门,只看你对这些知识点筹备的充沛水平。so,进来面试时先看看本人温习到了哪个阶段就好。 对于程序员来说,要学习的常识内容、技术有太多太多,要想不被环境淘汰就只有一直晋升本人,素来都是咱们去适应环境,而不是环境来适应咱们! 认真温习,认真对待面试,准备充分,一直总结。切实不会你就背,虽说有些特地根底的知识点在理论开发中用不到,但面试就是面试,面试就是问这些,连根底的问题都答复不好,切实很难让你通过。

December 16, 2021 · 1 min · jiezi

关于android:Python打包setuptools

setuptoolsPython打包散发工具setuptools:已经 Python 的散发工具是 distutils,但它无奈定义包之间的依赖关系。setuptools 则是它的增强版,能帮忙咱们更好的创立和散发 Python 包,尤其是具备简单依赖关系的包。其通过增加一个根本的依赖零碎以及许多相干性能,补救了该缺点。他还提供了主动包查问程序,用来主动获取包之间的依赖关系,并实现这些包的装置,大大降低了装置各种包的难度,使之更加不便,将程序打包当前能够能够装置到本人的虚拟环境中,也能够上传到PyPI,这样十分不便大我的项目开发 setuptools应用pip 装置: $ pip install setuptools第一个安装文件 在目录 learn\_setup 下新建安装文件 setup.py,而后创立包 myapp 模仿要打包源码包: ├── myapp│ └── __init__.py└── setup.pysetup.py 文件内容如下: from setuptools import setupsetup( name='firstApp001', # 利用名 version='0.0.1', # 版本号 packages=['myapp'], # 包含在安装包内的 Python 包)应用安装文件创立 wheel 有了下面的 setup.py 文件,咱们就能够打出各种安装包,次要分为两类:sdist 和 bdist。 Source distribution 应用 sdist 能够打包成 source distribution,反对的压缩格局有: 应用形式为: $ python setup.py sdist --formats=gztar,zip目录下便会多出 dist 和 *.egg-info 目录,dist 内保留了咱们打好的包,下面命令应用 --formats 指定了打出 .tar.gz 和 .zip 包,如果不指定则如上表依据具体平台默认格局打包。 包的名称为 setup.py 中定义的 name, version以及指定的包格局,格局如:firstApp01-0.0.1.tar.gz。 ...

December 16, 2021 · 4 min · jiezi

关于android:nextTick的理解和作用

场景阐明最近应用Vue全家桶做后盾零碎的时候,遇到了一个很奇葩的问题:有一个输入框只容许输出数字,当输出其它类型的数据时,输出的内容会被重置为null。为了实现这一性能,应用了一个父组件和子组件。为了不便陈说,这里将业务场景简化,具体代码如下: // 父组件<template> <Input v-model="form.a" @on-change="onChange"></Input></template><script type="javascript">export default { data() { return { form: { a: null } } }, methods: { async onChange(value) { if (typeof value !== 'number') { // await this.$nextTick() this.form.a = null } } }}</script>// 子组件<template> <input v-model="currentValue" @input="onInput" /></template><script type="javascript">export default { name: 'Input', props: { value: { type: [Number, Null], default: null } }, data() { return { currentValue: null } }, methods: { onInput(event) { const value = event.target.value this.$emit('input', value) const oldValue = this.value if (oldValue === value) return this.$emit('on-change', value) } }, watch: { value(value, oldValue) { this.currentValue = value } }}</script>将以上代码放到我的项目中去运行,你会很神奇地发现,在输入框输出字符串'abc'之后,输入框的值并没有被重置为空,而是放弃为'abc'没有变动。在将正文的nextTick勾销正文当前,输入框的值被重置为空。真的十分神奇。 ...

December 16, 2021 · 9 min · jiezi

关于android:coroutineflowRetrofit的接口调用

初始化Retrofit,Okhttpabstract class BaseRetrofitClient { companion object { private const val TIME_OUT = 5 const val BASE_URL = "https://www.wanandroid.com" } private val client: OkHttpClient get() { val builder = OkHttpClient.Builder() val logging = HttpLoggingInterceptor() if (BuildConfig.DEBUG) { logging.level = HttpLoggingInterceptor.Level.BODY } else { logging.level = HttpLoggingInterceptor.Level.BASIC } builder.addInterceptor(logging) .addNetworkInterceptor(ResponseInterceptor()) .connectTimeout(TIME_OUT.toLong(), TimeUnit.SECONDS) // 能够依据本人的口味自行定制 handleBuilder(builder) return builder.build() } protected abstract fun handleBuilder(builder: OkHttpClient.Builder) fun <S> getService(serviceClass: Class<S>, baseUrl: String? = null): S { return Retrofit.Builder() .client(client) .addConverterFactory(GsonConverterFactory.create())// .addCallAdapterFactory(RxJava2CallAdapterFactory.create())// .addCallAdapterFactory(CoroutineCallAdapterFactory.invoke()) .baseUrl( if (baseUrl.isNullOrBlank()) { BASE_URL } else baseUrl ) .build().create(serviceClass) }}object RetrofitClient : BaseRetrofitClient() { val userService by lazy{ getService(UserService::class.java, BASE_URL) } val wanService by lazy { getService(WanService::class.java, BASE_URL) } private val cookieJar by lazy { PersistentCookieJar(SetCookieCache(), SharedPrefsCookiePersistor(App.CONTEXT)) } override fun handleBuilder(builder: OkHttpClient.Builder) { val httpCacheDirectory = File(App.CONTEXT.cacheDir, "responses") val cacheSize = 10 * 1024 * 1024L // 10 MiB val cache = Cache(httpCacheDirectory, cacheSize) builder.cache(cache) .cookieJar(cookieJar) .addInterceptor { chain -> var request = chain.request() if (!NetWorkUtils.isNetworkAvailable(App.CONTEXT)) { request = request.newBuilder() .cacheControl(CacheControl.FORCE_CACHE) .build() } val response = chain.proceed(request) if (!NetWorkUtils.isNetworkAvailable(App.CONTEXT)) { val maxAge = 60 * 60 response.newBuilder() .removeHeader("Pragma") .header("Cache-Control", "public, max-age=$maxAge") .build() } else { val maxStale = 60 * 60 * 24 * 7 // tolerate 4-weeks stale response.newBuilder() .removeHeader("Pragma") .header("Cache-Control", "public, only-if-cached, max-stale=$maxStale") .build() } response } } val mCookieJar:PersistentCookieJar by lazy { PersistentCookieJar(SetCookieCache(), SharedPrefsCookiePersistor(App.CONTEXT)) }}新建业务registory suspend fun getArticleList(page: Int, isRefresh: Boolean) = flow<BaseViewModel.BaseUiModel<ArticleList>> { RetrofitClient.wanService.getHomeArticles(page).doSuccess { emit(BaseViewModel.BaseUiModel(showSuccess = it, showLoading = false, isRefresh = isRefresh)) } }.flowOn(Dispatchers.IO) // 切换线程 .onStart { emit(BaseViewModel.BaseUiModel(showLoading = true)) }.catch { emit(BaseViewModel.BaseUiModel(showError = it.message, showLoading = false, showEnd = false)) }新建ViewModel在HomeViewModel中实例化HomeRepository,调用getArticleList class HomeViewModel : BaseViewModel() { val repository = HomeRepository() val articleState = UnPeekLiveData<BaseUiModel<ArticleList>>() fun getArticleList(page: Int, isRefresh: Boolean) { launchOnUI { repository.getArticleList(page, isRefresh).collect { articleState.postValue(it) } } }}open class BaseViewModel : ViewModel() { fun launchOnUI(block: suspend CoroutineScope.() -> Unit) { viewModelScope.launch { block() } } suspend fun <T> launchOnIO(block: suspend CoroutineScope.() -> T) { withContext(Dispatchers.IO) { block } } open class UiState<T>( val isLoading: Boolean = false, val isRefresh: Boolean = false, val isSuccess: T? = null, val isError: String?= null ) open class BaseUiModel<T>( var showLoading: Boolean = false, var showError: String? = null, var showSuccess: T? = null, var showEnd: Boolean = false, // 加载更多 var isRefresh: Boolean = false // 刷新 ) override fun onCleared() { super.onCleared() viewModelScope.cancel() }}回到UI层,依据数据展现uiHomeFragment ...

December 16, 2021 · 3 min · jiezi

关于android:Android入门教程-Audio-音频二

应用 MediaExtractor 和 MediaMuxer 解析和封装 mp4 文件简介MP4 或称 MPEG-4第14局部是一种规范的数字多媒体容器格局。 MP4中的音频格式通常为 AAC(audio/mp4a-latm) MediaExtractor MediaExtractor 可用于拆散多媒体容器中视频 track 和音频 track setDataSource() 设置数据源,数据源能够是本地文件地址,也能够是网络地址getTrackFormat(int index) 来获取各个track的MediaFormat,通过MediaFormat来获取track的详细信息,如:MimeType、分辨率、采样频率、帧率等等selectTrack(int index) 通过下标抉择指定的通道readSampleData(ByteBuffer buffer, int offset) 获取以后编码好的数据并存在指定好偏移量的buffer中MediaMuxer MediaMuxer 可用于混合根本码流。将所有的信道的信息合成一个视频。 目前输入格局反对 MP4,Webm,3GP。从 Android Nougat 开始反对向 MP4 中混入 B-frames。 提取并输入 MP4 文件中的视频局部从一个 MP4 文件中提取出视频,失去不含音频的 MP4 文件。 实现流程,首先是应用 MediaExtractor 提取,而后应用 MediaMuxer 输入 MP4 文件。 MediaExtractor设置数据源,找到并抉择视频轨道的格局和下标MediaMuxer设置输入格局为MUXER_OUTPUT_MPEG_4,增加后面选定的格局,调用start()启动MediaExtractor读取帧数据,不停地将帧数据和相干信息传入MediaMuxer最初进行并开释MediaMuxer和MediaExtractor最好放在子线程中操作。 /** * 提取视频 * * @param sourceVideoPath 原始视频文件 * @throws Exception 出错 */ public static void extractVideo(String sourceVideoPath, String outVideoPath) throws Exception { MediaExtractor sourceMediaExtractor = new MediaExtractor(); sourceMediaExtractor.setDataSource(sourceVideoPath); int numTracks = sourceMediaExtractor.getTrackCount(); int sourceVideoTrackIndex = -1; // 原始视频文件视频轨道参数 for (int i = 0; i < numTracks; ++i) { MediaFormat format = sourceMediaExtractor.getTrackFormat(i); String mime = format.getString(MediaFormat.KEY_MIME); Log.d(TAG, "MediaFormat: " + mime); if (mime.startsWith("video/")) { sourceMediaExtractor.selectTrack(i); sourceVideoTrackIndex = i; Log.d(TAG, "selectTrack index=" + i + "; format: " + mime); break; } } MediaMuxer outputMediaMuxer = new MediaMuxer(outVideoPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); outputMediaMuxer.addTrack(sourceMediaExtractor.getTrackFormat(sourceVideoTrackIndex)); outputMediaMuxer.start(); ByteBuffer inputBuffer = ByteBuffer.allocate(1024 * 1024 * 2); // 调配的内存要尽量大一些 MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); int sampleSize; while ((sampleSize = sourceMediaExtractor.readSampleData(inputBuffer, 0)) >= 0) { long presentationTimeUs = sourceMediaExtractor.getSampleTime(); info.offset = 0; info.size = sampleSize; info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME; info.presentationTimeUs = presentationTimeUs; outputMediaMuxer.writeSampleData(sourceVideoTrackIndex, inputBuffer, info); sourceMediaExtractor.advance(); } outputMediaMuxer.stop(); outputMediaMuxer.release();    // 进行并开释 MediaMuxer sourceMediaExtractor.release(); sourceMediaExtractor = null;   // 开释 MediaExtractor }如果下面的 ByteBuffer 调配的空间太小,readSampleData(inputBuffer, 0) 可能会呈现 IllegalArgumentException 异样。 ...

December 15, 2021 · 4 min · jiezi

关于android:如果2021能重开我会告诉自己去做这些

<img src="https://s2.loli.net/2021/12/15/LUywOR3ExnHmvs5.jpg"/> 多年来,Android 生态系统及其开发工具产生了巨大变化。 Eason过后就是走了很多弯路,导致本人Android学习之路十分波折。这篇文章将向大家介绍如何正确地开始 Android 开发生涯,包含该学习哪些重要和不重要的常识,以及如何做能力找到第一份工作。 应该学习 Java 还是 Kotlin?谷歌在 2017 年发表官网 Kotlin 反对 Android 开发。古代 Android 应用程序和库是用 Kotlin 编写的,只管依然应用 Java,次要是出于遗留起因。这两种语言彼此十分类似,一个我的项目能够应用这两种语言。Java 比 Kotlin 更古老。这意味着对 Java 有更多的工具和反对。Kotlin 是一种古代语言,应用起来更简略。 只管 Kotlin 是官网举荐的 Android 语言,但 Google 对这两种语言都提供了杰出的反对。在行业中,大多数公司向 Android 开发人员询问 Kotlin 常识和教训,而不是 Java。因而Eason激励大家专一于学习 Kotlin。 同时,不要漠视网上的Java资源和教程,毕竟有大量的旧我的项目和github资源都是采纳用 Java 编写的。 <img src="https://s2.loli.net/2021/12/15/QfsxdkW3Fw1Rjnr.png"/> Android Studio 是 Android 开发的官网 IDE,基于 JetBrains IntelliJ IDEA。它具备的一项很酷的性能是将 Java 代码主动转换为 Kotlin。 如何学习Android开发<img src="https://s2.loli.net/2021/12/15/YXtuQFlzeSr7bI3.png"/> 只有有电脑且能上网,就能够 100% 收费学习 Android 开发。大家无需领有任何 Android 设施即可开始学习或构建应用程序。 官网 Android 开发者网站是一个很好的终点。大家将在那里找到无关如何开始学习和公布 Android 应用程序的资源。例如,Kotlin 中的 Android Basics非常适合涵盖 Android 基础知识,而且它也是 100% 收费的。 ...

December 15, 2021 · 1 min · jiezi

关于android:大厂Android启动优化出其不意的优化手段

惯例的伎俩优化后,咱们能解决根本的问题,然而咱们得持续谋求极致,本章将分享一些意想不到的伎俩。 1 首页合并通常咱们启动的时候分为闪屏和首页两个页面,咱们将闪屏和首页合并成一个,通过fragment来操作实在页面,广告设计成一个dialog fragment浮在首页就行。根本的收益在100ms左右。 首页合并后发现几个问题:首页治理、唤端启动问题。 首页无奈应用singletask,singletask的问题咱们通过手动保护一个activity栈去治理,保障一个首页。唤端启动,第三方app唤端没有加newtask标签,导致页面启动在第三方app内,这个临时没有遇到很好的解决形式,还是在推动比拟大的第三方平台去解决。 2 渲染音讯优先级队列咱们数据申请后,通过handler将音讯发送到主线程,handler音讯队列内可能比拟忙碌,咱们思考的是将数据回调和图片回调的音讯发送到音讯队列后面。MessageQueue其实提供了一个办法handler.sendMessageAtFrontOfQueue的形式。 3 dex2oatAndroid ART虚拟机后,能够将dex文件事后的翻译解决成机器码间接运行,在Android 5 - 7版本,dex解决是在app装置的时候解决的,然而因为dex2oat性能影响较大,装置的时候将耗时过长,Android 7后改为装置的时候不做翻译,运行时还是解释执行,运行时的时候记录运行的函数等信息,在手机闲置的状况上来把这些热办法做dex2oat,下次运行间接运行机器码。然而零碎判断闲置的条件比拟刻薄,导致大部分状况下app没有被dex2oat,另外互联网app疾速倒退,发版速度较快,所以dex2oat的利用率低,通过app本人手动调用零碎dex2oat达到疾速将app的dex转化,进步代码执行效率,该计划的收益大略在10%。相干的具体常识能够网上搜寻一下。 零碎记录热办法的数据文件存在 /data/misc/profiles/cur/0/packageName/primary.prof下,咱们能够通过FileObserver监听这个这个文件批改判断是否须要dex2oat。dex2oat能够通过shell命令执行cmd package compile -m speed-profile -f packageName 4 RedexLinux 文件系统从磁盘读文件的时候,会以 block 为单位去磁盘读取,个别 block 大小是 4KB。也就是说一次磁盘读写大小至多是 4KB,而后会把 4KB 数据放到页缓存 Page Cache 中。如果下次读取文件数据曾经在页缓存中,那就不会产生实在的磁盘 I/O,而是间接从页缓存中读取,大大晋升了读的速度。所以下面的例子,咱们尽管读了 1000 次,但事实上只会产生一次磁盘 I/O,其余的数据都会在页缓存中失去。 Dex 文件用的到的类和安装包 APK 外面各种资源文件个别都比拟小,然而读取十分频繁。咱们能够利用零碎这个机制将它们依照读取程序重新排列,缩小实在的磁盘 I/O 次数。 类重排 启动过程类加载程序能够通过插装动态代码块获取。 class A { static { //记录 }}而后通过 ReDex 的Interdex调整类在 Dex 中的排列程序,最初能够利用 010 Editor 查看批改后的成果。 从多方拿到的数据来看,收益在0-6%,整体不是很显著,而且须要把redex工程化、思考和proguard的兼容等问题。 5 黑科技微信Hardcoder构建了App与零碎(ROM)之间牢靠的通信框架,让零碎晓得App的需要,能够让app获取更多的系统资源。 原理 1、其实质是让App跨过Framework间接跟厂商ROM通信。2、分为Client端和Server端,Server端由厂商零碎侧自行实现。3、它们间接采纳 LocalSocket 形式,Hardcoder是 Native 实现的,应用了Linux的Socket接口实现了一套本人的LocalSocket。整体收益不是特地显著。 ...

December 15, 2021 · 1 min · jiezi

关于android:欢迎体验-Wear-OS-版-Compose-开发者预览版

作者 / 开发者关系工程师 Jeremy Walker 在往年的 Google I/O 大会 上,咱们发表将 Jetpack Compose 的优良个性引入 Wear OS。在顺利公布多个 alpha 版本之后,Wear OS 版 Compose 现已推出开发者预览版。 Compose 能 简化并减速 UI 开发,Wear OS 版 Compose 也是如此,借助内置的 Material You 反对,您能够用更少的代码构建更精美的利用。 除此之外,您在应用 Jetpack Compose 构建挪动利用的教训,也能够间接使用在 Wear OS 版本上。就像在挪动设施上一样,欢迎您立刻着手测试,咱们也心愿在公布 Beta 版前,将您的 反馈 纳入库的晚期迭代中。 本文将回顾咱们构建的几个次要可组合项,并介绍帮忙您开始应用的多种资源。 当初就开始吧! 依赖项您对 Wear 设施作出的大部分更改都将位于顶部 架构分层。 这就意味着面向 Wear OS 设计时,您搭配 Jetpack Compose 应用的许多依赖项不会发生变化。例如,UI、运行工夫、编译器和动画依赖项都将放弃不变。 不过,您须要应用适合的 Wear OS Material、导航及根底开发库,这与您之前在挪动利用中所应用的开发库是不一样的。 下方是相干比照,可帮忙您辨别两者差别: Wear OS 依赖项 (androidx.wear.*)比照挪动依赖项 (androidx.*)androidx.wear.compose:compose-material替换androidx.compose.material:material¹androidx.wear.compose:compose-navigation替换androidx.navigation:navigation-composeandroidx.wear.compose:compose-foundation额定增加androidx.compose.foundation:foundation1. 开发者能够持续应用其余与 Material 相干的开发库,如 Material 涟漪和通过 Wear Compose Material 开发库进行扩大的 Material 图标。 ...

December 15, 2021 · 2 min · jiezi

关于android:炫酷Flutter仿Dribbble漂亮的扫描闹钟UI效果

在空闲冲浪的时候,无意间看到了这张设计图,眼睛一亮,感觉这个设计和创意十分酷,打算着手实现一下。对于设计图的作者没找到,如果有人晓得的话,请告知我,我会增加设计援用的,欢送来我的Github 设计图如下: 整体效果图如下: 源码Github: https://github.com/DingMouRen... 剖析设计,咱们分两局部来实现:1.表盘局部  2.下部的开关局部。以下为了省略的逻辑代码,须要残缺的代码的请移步Github 1.表盘局部1.1 察看表盘的款式,把须要做的工作划分突变的背景,用作秒针绘制红色的小圆点绘制带暗影的黄色大圆点,用作时针绘制工夫文字开启定时器,让时钟动起来表盘局部很多都是须要咱们本人绘制的,这里咱们能够应用CustomPaint来实现所有的绘制。CustomPaint提供了自定义widget的能力,它会裸露一个canvas,能够通过这个canvas来绘制widget,有没有很相熟,跟原生android的canvas是不是很类似。 咱们这里将表盘作为背景来绘制,也就是painter属性,自定义CustomPainter,重写paint(Canvas canvas,Size size)和shouldRepaint(covariant CustomPainter oldDelegate)函数,来实现咱们所有的绘制操作。好的,让咱们欢快的开始吧o( ̄▽ ̄)ブ。 1.2 突变的背景,用作秒针从图中咱们能够晓得,渐变色是扫描突变,并布满屏幕,同时地位在整个屏幕的上半局部。首先创立扫描突变对象,这个扫描突变能够创立出一个着色器,而后咱们将这个着色器附着在一个画笔上,通过画布去绘制。留神画布绘制时canvas.save()和canvas.restore()在对应机会的调用。要让秒针动起来,须要开启一个Timer定时工作,每一秒刷新一下视图。 var circle = Rect.fromCircle(center: Offset(0, 0), radius: _screenHeight);    //扫面突变    var sweepGradient = SweepGradient(      colors: [        _startColor,        _endColor      ],    );    //画笔对象   Paint  _paintGradient = Paint()      ..isAntiAlias = true      ..shader = sweepGradient.createShader(circle)      ..style = PaintingStyle.fill;    //获取以后的工夫    DateTime dateTime = DateTime.now();    var hour = dateTime.hour;    var minute = dateTime.minute;    var second = dateTime.second;    //画布位移    canvas.translate(_screenWidth / 2, _screenHeight / 100 * 35);    //绘制突变背景    canvas.save();    //每秒旋转对应角度,模仿秒针挪动    canvas.rotate(_getRotate(second));    canvas.drawCircle(Offset(0, 0), _screenHeight, _paintGradient);    canvas.restore(); 1.3 绘制红色的小圆点这里须要绘制24个红色小圆点,平均分360度,通过画布的旋转来绘制不同角度的红色小圆点。 for (double i = 0; i < _numPoint; i++) {      canvas.save();      //      double deg = 360 / _numPoint * i;      canvas.rotate(deg / 180 * pi);        _paintDial.color = Colors.white;       //绘制红色小圆点       canvas.drawCircle(Offset(_radius, 0), 3, _paintDial);       canvas.restore();      ......      canvas.restore();    } 1.4 绘制带暗影的黄色大圆点,用作时针黄色的大圆点作为时针来解决,因为时针和画布的角度不是吻合的,须要换算,另外咱们为大圆点增加暗影。 for (double i = 0; i < _numPoint; i++) {      canvas.save();      double deg = 360 / _numPoint * i;      canvas.rotate(deg / 180 * pi);      _paintDial.color = Colors.white;      canvas.drawCircle(Offset(_radius, 0), 3, _paintDial);      //isShowBigCircle(hour, i)是判断以后圆点是不是以后的时针地位      if (isShowBigCircle(hour, i)) {        //绘制暗影        Path path = Path()          ..addArc(Rect.fromCircle(center: Offset(_radius, 0), radius: 8), 0,              pi * 2);        canvas.drawShadow(path, Colors.yellow, 4, true);        //绘制小时的圆点        _paintDial.color = Colors.yellow;        canvas.drawCircle(Offset(_radius, 0), 8, _paintDial);      } else {        _paintDial.color = Colors.white;        canvas.drawCircle(Offset(_radius, 0), 3, _paintDial);      }      canvas.restore();      ......    }1.5 绘制工夫文字画布绘制文字调用canvas.drawParagraph(Paragraph Offset)函数,Paragraph 对象通过ParagraphBuilder来创立,字体款式能够通过ParagraphBuilder来设置 //设置文字款式    _timeParagraphBuilder = ParagraphBuilder(ParagraphStyle(        textAlign: TextAlign.center,        fontSize: 70,        maxLines: 1,        fontWeight: FontWeight.bold));   //获取以后的工夫   DateTime dateTime = DateTime.now();    var hour = dateTime.hour;    var minute = dateTime.minute;    var second = dateTime.second;      //绘制文字      canvas.save();      _timeParagraphBuilder.addText(_getTimeStr(hour, minute));      Paragraph paragraph = _timeParagraphBuilder.build();      paragraph.layout(ParagraphConstraints(width: 230));      canvas.drawParagraph(paragraph, Offset(-115,-42));      canvas.restore();2. 开关局部整体布局剖析,开关局部的UI绝对于整个屏幕来讲位于底部,应用 Stack 和 Align就能够实现这种布局款式。   //整体布局  @override  Widget build(BuildContext context) {    return Scaffold(        body: Stack(      children: [        CustomPaint(painter: DialPlate(context,Color.fromARGB(255, 70, 0, 144),Color.fromARGB(255, 121, 83, 254))),        _getAlarms(),      ],    ));  }//下部视图  _getAlarms() {    return Align(      alignment: Alignment.bottomLeft,      child: Container(        margin: EdgeInsets.only(left: 16, right: 16),        height: 200,        width: double.infinity,        child: Column(          children: [            _getRow1(),            _getRow2(),            _getRow3(),          ],        ),      ),    );  }//_getRow1()、_getRow2()、_getRow3()相似 _getRow1() {    return Container(      alignment: Alignment.centerLeft,      width: double.infinity,      height: 50,      child: Row(        children: [          Text(            '06:45',            style: TextStyle(                fontSize: 25,                color: _firstSwitch == true ? _colorOn : _colorOff),          ),          Padding(            padding: EdgeInsets.only(left: 18),            child: Text(              'Wake up',              style: TextStyle(                  fontSize: 18,                  color: _firstSwitch == true ? _colorOn : _colorOff),            ),          ),          Expanded(child: SizedBox()),          Container(            width: 90,            height: 10,            child: Switch(                value: _firstSwitch,                onChanged: (onChanged) {                  setState(() {_firstSwitch = onChanged;});                },                activeColor: _switchActiveColor,                activeTrackColor: Colors.black.withAlpha(100),                inactiveThumbColor: _switchInActiveColor,                inactiveTrackColor: Colors.black.withAlpha(20),            ),          )        ],      ),    );  }原文链接:https://juejin.cn/post/695909... 文末您的点赞珍藏就是对我最大的激励! 欢送关注我,分享Android干货,交换Android技术。 对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!

December 15, 2021 · 1 min · jiezi

关于android:作为-Android-开发者如何深入学习-Android-UI

前言Android 新技术层出不穷,要想不落后不被淘汰咱们只能不停的学! 作为好的安卓开发,首先明确Android是前端,重点是UI,做出稳固的利用是要害。 Compose 是 Android UI 的将来,如果将来你会持续在 Android 平台的话,你就肯定须要学习Compose。Flutter 的将来在于多平台,更稳固牢靠的多平台 UI 框架。如果你的路线方向是大前端或者多端开发者,那么 Flutter 就是不二之选。 不过,最初无论是先抉择哪一个,或者二者都学习,最终的目标都是晋升本身 UI 开发设计能力,解决问题和需要。想要玩转 Android UI ,就须要去全面的深刻理解 和把握UI 的底层原理。 如何深刻学习 Android UI?为了帮忙大家高效疾速学习高级 UI 框架底层关键技术及实现原理,这里给大家分享一份《Android高级UI开源框架进阶解密》,内容分为60个章节,一共333页。SmartRefreshLayoutAndroid之PullToRefresh控件源码解析Android-PullToRefresh下拉刷新库根本用法LoadSir-高效易用的加载反馈页治理框架Android通用LoadingView加载框架详解MPAndroidChart实现LineChart(折线图)hellocharts-android使用指南SmartTable使用指南开源我的项目android-uitableview介绍ExcelPanel 使用指南 Android开源我的项目SlidingMenu深切解析MaterialDrawer使用指南SwipeBackLayout的应用办法,右滑返回Android BoomMenu 使用指南PhotoView的使用指南SubsamplingScaleImageView使用指南CircleImageView用法及源码解析Android 图片裁切框架 uCrop 的用法Gif-drawable的应用Android抉择与上传图片之Matisse教程 Richeditor-Android应用阐明TextSurface源码解析Material-Dialogs应用阐明Taosty应用阐明DialogUtil源码解析FloatWindow源码解析ImmersionBar源码解析viewpagerindicator使用指南BottomBar应用阐明FlycoTabLayout使用指南 MagicIndicator使用指南Flexbox-Layout使用指南AndroidAutoSize使用指南FlowLayout应用阐明VLayout使用指南Rclayout使用指南AndroidSwipeLayout使用指南Android-ObservableScrollView使用指南UltimateRecyclerview使用指南 Android-PickerView使用指南DropDownMenu使用指南Android-ConvenientBanner使用指南AgentWeb使用指南500px-android-blur使用指南BlurKit-Android使用指南Android-Viewbadger使用指南BGABadgeView-Android使用指南Android-pathview使用指南RichPath使用指南 AndroidSlidingUpPanel使用指南AppIntro使用指南Android-Bootstrap使用指南Emojicon使用指南RippleEffect使用指南InfiniteCycleViewPager使用指南LoadingDrawable使用指南QMUI\_Android使用指南 因为篇幅无限,材料内容截取局部截图,有须要《Android高级UI开源框架进阶解密》完整版PDF的敌人能够点击这里收费支付! 最初 想要成为高级 Android 开发者,那么对于Android 高级 UI 必须要了然于心。UI 带给用户体验甚至把控着产品后续倒退的命根子。好的 UI 体验能够让用户爱不释手,蹩脚的 UI 体验则是分分钟把用户劝退。想要拥抱高薪,Android 高级UI 常识也是必不可少。

December 15, 2021 · 1 min · jiezi

关于android:自定义View属性动画实战-灵动的锦鲤

通过自定义View+属性动画 实现一个会动鱼 剖析: 1.画一条鱼 2.鱼原地动 3.鱼向点击处游动 画一条鱼 鱼分为:鱼头(圆) + 身材(两条直线+两条贝塞尔曲线) + 鱼鳍(一条直线+一个贝塞尔)+尾巴(两三角)+节肢*2 (梯形+两圆) 先把鱼程度朝右,画一个坐标系,鱼的重心为坐标系核心 先定下鱼的重心的坐标头圆半径的4.19倍,这个其实是本人定的,5f,6f都行,就是只扭转鱼的长度,用鱼头半径做初始单位有利于扭转整个鱼的大小。 因为重心坐标定下了,所以整个鱼的母布局ImageView的宽高,重心的两倍(鱼左右转都不会超出边界) 重点!求一个点的坐标。已知一个点、夹角、长度。求一个点的坐标初中常识: 所以能够得出:入参一个点、两点长度、对于x轴的角度。返回值 一个坐标 依据这个方程求解各个鱼身材。定义画笔 mPaint.setDither(true);防抖动 mPaint.setAntiAlias(true); 抗锯齿 画鱼头:找到鱼头圆心,入参:重心、鱼身长一半、鱼的朝向(默认180跟重心一个方向) 画鱼鳍 鱼鳍是一个直线+一个二阶贝塞尔曲线,所以重点就是求出三个点:鱼鳍左点、右点、贝塞尔控制点 通过鱼头的圆心求,间隔 0.9 * R ,角度110 。通过那个公式就能求进去 = 右鱼鳍点 左鱼鳍点 = 右鱼鳍点、间隔、角度-180 贝塞尔控制点 = (这个齐全靠本人试,只影响鱼鳍的胖瘦) 右鱼鳍点、间隔 * 1.8f、角度 115 试贝塞尔的网站:cubic-bezier.com/#.17,.67,.8… 三个点都有了,绘制鱼鳍: 绘制之前要将其余绘制重置: mPath.reset(); 而后mPath.moveTo()挪动到第一个点 mPath.lineTo()画直线 mPath.quadTo()画二阶贝塞尔曲线,入参第二个点、第三个点 最初 canvas.drawPath(mPath,mPaint);间接画进去 最初一个点不必关闭,零碎主动会关闭。 别的局部也都差不多,依据那个公式,通过参考点求出另一个点,而后求出各个局部的点,最初连线画就行 求出各个点 而后 mPath.reset()、 mPath.moveTo、 mPath.lineTo、canvas.drawPath ...

December 15, 2021 · 1 min · jiezi

关于android:Android-仿-Telegram-一样的上传文件炫酷动画

前段时间,我钻研了一个新性能:在 app 外部聊天中发送图片。这个性能自身很大,包含了多种货色,但实际上,最后并没有设计上传动画与勾销上传的性能。当我用到这部分的时候,我决定减少图片上传动画,所以咱们就给它们这个性能吧:) View vs. Drawable我在那里用了一个 Drawable。在我集体看来,StackOverflow 这里就有个很好的简洁的答案。 Drawable 只响应绘制操作,而 View 响应绘制和用户界面,比方触摸事件和敞开屏幕等等。 当初咱们来剖析一下,咱们想要做什么。咱们心愿有一条有限旋转的弧线做圆形动画,并且弧线的圆心角一直减少直到圆心角等于 2。我感觉一个 Drawable 应该可能帮上我的忙,而且实际上我也应该那样做,但我没有。 我没有这样做的起因在下面示例图片中的文字左边那三个小的点点的动画上。我曾经用自定义 View 实现了这个动画,并且我曾经为有限循环的动画筹备了背景。对我来说把动画筹备逻辑提取到父 View 中重用,而不是把所有货色都重写成 Drawable,应该是更简略的。所以我并不是说我的解决方案是正确的(其实没有什么是正确的),而是它满足了我的需要。 Base InfiniteAnimationView为了本人的须要,我将把想要的进度视图分成两个视图: ProgressView —— 负责绘制所需的进度 ViewInfiniteAnimateView  —— 形象 View,它负责动画的筹备、启动和进行。因为进度中蕴含了有限旋转的局部,咱们须要理解什么时候须要启动这个动画,什么时候须要进行这个动画在查看了 Android 的 ProgressBar 的源代码后,咱们能够最终失去这样的后果: // InfiniteAnimateView.ktabstract class InfiniteAnimateView @JvmOverloads constructor(    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {    private var isAggregatedVisible: Boolean = false    private var animation: Animator? = null    override fun onVisibilityAggregated(isVisible: Boolean) {        super.onVisibilityAggregated(isVisible)        if (isAggregatedVisible != isVisible) {            isAggregatedVisible = isVisible            if (isVisible) startAnimation() else stopAnimation()        }    }    override fun onAttachedToWindow() {        super.onAttachedToWindow()        startAnimation()    }    override fun onDetachedFromWindow() {        stopAnimation()        super.onDetachedFromWindow()    }    private fun startAnimation() {        if (!isVisible || windowVisibility != VISIBLE) return        if (animation == null) animation = createAnimation().apply { start() }    }    protected abstract fun createAnimation(): Animator    private fun stopAnimation() {        animation?.cancel()        animation = null    }}遗憾的是,次要出于 onVisibilityAggregated 办法的起因,它并无奈工作 —— 因为[这个办法在 API 24 以上才被反对](developer.android.com/reference/a… !isVisible || windowVisibility != VISIBLE 上的问题,当视图是可见的,但它的容器却不可见。所以我决定重写这个: // InfiniteAnimateView.ktabstract class InfiniteAnimateView @JvmOverloads constructor(    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {    private var animation: Animator? = null    /**     * 咱们不能够应用 `onVisibilityAggregated` 办法,因为它只在 SDK 24 以上被反对,而咱们的最低 SDK 是 21     */    override fun onVisibilityChanged(changedView: View, visibility: Int) {        super.onVisibilityChanged(changedView, visibility)        if (isShown) startAnimation() else stopAnimation()    }    override fun onAttachedToWindow() {        super.onAttachedToWindow()        startAnimation()    }    override fun onDetachedFromWindow() {        stopAnimation()        super.onDetachedFromWindow()    }    private fun startAnimation() {        if (!isShown) return        if (animation == null) animation = createAnimation().apply { start() }    }    protected abstract fun createAnimation(): Animator    private fun stopAnimation() {        animation?.cancel()        animation = null    }}可怜的是,这也没有用(尽管我感觉它应该可能失常工作的)。说实话,我不晓得问题的具体起因。可能在一般的状况下会无效,然而对于 RecyclerView 就不行了。前段时间我就遇到了这个问题:如果应用 isShown 来跟踪一些货色是否在 RecyclerView 中显示。因而可能我的最终解决方案并不正确,但至多在我的计划中,它能依照我的冀望工作: // InfiniteAnimateView.ktabstract class InfiniteAnimateView @JvmOverloads constructor(    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) {    private var animation: Animator? = null    /**     * 咱们不能够应用 `onVisibilityAggregated` 办法,因为它只在 SDK 24 以上被反对,而咱们的最低 SDK 是 21     */    override fun onVisibilityChanged(changedView: View, visibility: Int) {        super.onVisibilityChanged(changedView, visibility)        if (isDeepVisible()) startAnimation() else stopAnimation()    }    override fun onAttachedToWindow() {        super.onAttachedToWindow()        startAnimation()    }    override fun onDetachedFromWindow() {        stopAnimation()        super.onDetachedFromWindow()    }    private fun startAnimation() {        if (!isAttachedToWindow || !isDeepVisible()) return        if (animation == null) animation = createAnimation().apply { start() }    }    protected abstract fun createAnimation(): Animator    private fun stopAnimation() {        animation?.cancel()        animation = null    }    /**     * 可能这个函数上实现了 View.isShown,但我察觉到它有一些问题。     * 我在 Lottie lib 中也遇到了这些问题。不过因为咱们总是没有工夫去深入研究     * 我决定应用了这个简略的办法临时解决这个问题,只为确保它可能失常运行     * 我到底须要什么 = =     *     * 更新:尝试应用 isShown 代替这个办法,但没有胜利。所以如果你晓得     * 如何改良,欢送评论区讨论一下     */    private fun isDeepVisible(): Boolean {        var isVisible = isVisible        var parent = parentView        while (parent != null && isVisible) {            isVisible = isVisible && parent.isVisible            parent = parent.parentView        }        return isVisible    }    private val View.parentView: ViewGroup? get() = parent as? ViewGroup}进度动画筹备那么首先咱们来谈谈咱们 View 的构造。它应该蕴含哪些绘画组件?在以后情境下最好的表达方式就是申明不同的 Paint。 // progress_paints.ktprivate val bgPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {    style = Paint.Style.FILL    color = defaultBgColor}private val bgStrokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {    style = Paint.Style.STROKE    color = defaultBgStrokeColor    strokeWidth = context.resources.getDimension(R.dimen.chat_progress_bg_stroke_width)}private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {    style = Paint.Style.STROKE    strokeCap = Paint.Cap.BUTT    strokeWidth = context.resources.getDimension(R.dimen.chat_progress_stroke_width)    color = defaultProgressColor}为了展现我将扭转笔触的宽度和其余货色,所以你会看到某些方面的不同。这 3 个 Paint 就与 3 个要害局部的进度相关联: 左:background; 中:stroke; 右:progress 你可能想晓得为什么我要用 Paint.Cap.BUTT。好吧,为了让这个进度更 "Telegram"(至多在 iOS 设施上是这样),你应该应用 Paint.Cap.ROUND。让我来演示一下这三种可能的款式之间的区别(这里减少了描边宽度以让差别更显著)。 左:Cap.BUTT,中:Cap.ROUND,右:Cap.SQUARE 因而,次要的区别是,Cap.ROUND给笔画的角以非凡的圆角,而 Cap.BUTT 和 Cap.SQUARE只是切割。Cap.SQUARE 也和 Cap.ROUND 一样预留了额定的空间,但没有圆角成果。这可能导致 Cap.SQUARE 显示的角度与 Cap.BUTT 雷同但预留了额定的空间。 试图用 Cap.BUTT 和 Cap.SQUARE 来显示 90 度。 思考到所有这些状况,咱们最好应用 Cap.BUTT,因为它比 Cap.SQUARE 显示的角度示意更失当。 顺便说一下 Cap.BUTT 是画笔默认的笔刷类型。这里有一个官网的文档链接。但我想向你展现真正的区别,因为最后我想让它变成 ROUND,而后我开始应用 SQUARE,但我留神到了一些个性。Base Spinning动画自身其实很简略,因为咱们有 InfiniteAnimateView: ...

December 15, 2021 · 1 min · jiezi

关于android:继承ViewGroup学习onMeasure和onLayout

在继承ViewGroup类时,须要重写两个办法,别离是onMeasure和onLayout。 1,在办法onMeasure中调用setMeasuredDimension办法void android.view.View.setMeasuredDimension(int measuredWidth, int measuredHeight)在onMeasure(int, int)中,必须调用setMeasuredDimension(int width, int height)来存储测量失去的宽度和高度值,如果没有这么去做会触发异样IllegalStateException。2,在办法onMeasure中调用孩子的measure办法 void android.view.View.measure(int widthMeasureSpec, int heightMeasureSpec) 这个办法用来测量出view的大小。父view应用width参数和height参数来提供constraint信息。实际上,view的测量工作在onMeasure(int, int)办法中实现。因而,只有onMeasure(int, int)办法能够且必须被重写。参数widthMeasureSpec提供view的程度空间的规格阐明,参数heightMeasureSpec提供view的垂直空间的规格阐明。 3,解析onMeasure(int, int)办法 void android.view.View.onMeasure(int widthMeasureSpec, int heightMeasureSpec) 测量view及其内容来确定view的宽度和高度。这个办法在measure(int, int)中被调用,必须被重写来准确和无效的测量view的内容。 在重写这个办法时,必须调用setMeasuredDimension(int, int)来存储测量失去的宽度和高度值。执行失败会触发一个IllegalStateException异样。调用父view的onMeasure(int, int)是非法无效的用法。 view的根本测量数据默认取其背景尺寸,除非容许更大的尺寸。子view必须重写onMeasure(int, int)来提供其内容更加精确的测量数值。如果被重写,子类确保测量的height和width至多是view的最小高度和宽度(通过getSuggestedMinimumHeight()和getSuggestedMinimumWidth()获取)。 4,解析onLayout(boolean, int, int, int, int)办法 void android.view.ViewGroup.onLayout(boolean changed, int l, int t, int r, int b) 调用场景:在view给其孩子设置尺寸和地位时被调用。子view,包含孩子在内,必须重写onLayout(boolean, int, int, int, int)办法,并且调用各自的layout(int, int, int, int)办法。 参数阐明:参数changed示意view有新的尺寸或地位;参数l示意绝对于父view的Left地位;参数t示意绝对于父view的Top地位;参数r示意绝对于父view的Right地位;参数b示意绝对于父view的Bottom地位。. 5,解析View.MeasureSpec类android.view.View.MeasureSpecMeasureSpec对象,封装了layout规格阐明,并且从父view传递给子view。每个MeasureSpec对象代表了width或height的规格。MeasureSpec对象蕴含一个size和一个mode,其中mode能够取以下三个数值之一:UNSPECIFIED,1073741824 [0x40000000],未加规定的,示意没有给子view增加任何规定。EXACTLY,0 [0x0],准确的,示意父view为子view确定准确的尺寸。AT_MOST,-2147483648 [0x80000000],子view能够在指定的尺寸内尽量大。 在这里给大家举一个例子demo:第一步:自定义一个View实现ViewGroup接口,即自定义ViewGroup: import android.content.Context; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; public class MyViewGroup extends ViewGroup { public MyViewGroup(Context context) { super(context); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); } public MyViewGroup(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } /** * 计算控件的大小 */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measureWidth = measureWidth(widthMeasureSpec); int measureHeight = measureHeight(heightMeasureSpec); // 计算自定义的ViewGroup中所有子控件的大小 measureChildren(widthMeasureSpec, heightMeasureSpec); // 设置自定义的控件MyViewGroup的大小 setMeasuredDimension(measureWidth, measureHeight); } private int measureWidth(int pWidthMeasureSpec) { int result = 0; int widthMode = MeasureSpec.getMode(pWidthMeasureSpec);// 失去模式 int widthSize = MeasureSpec.getSize(pWidthMeasureSpec);// 失去尺寸 switch (widthMode) { /** * mode共有三种状况,取值别离为MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, * MeasureSpec.AT_MOST。 * * * MeasureSpec.EXACTLY是准确尺寸, * 当咱们将控件的layout_width或layout_height指定为具体数值时如andorid * :layout_width="50dip",或者为FILL_PARENT是,都是控件大小曾经确定的状况,都是准确尺寸。 * * * MeasureSpec.AT_MOST是最大尺寸, * 当控件的layout_width或layout_height指定为WRAP_CONTENT时 * ,控件大小个别随着控件的子空间或内容进行变动,此时控件尺寸只有不超过父控件容许的最大尺寸即可 * 。因而,此时的mode是AT_MOST,size给出了父控件容许的最大尺寸。 * * * MeasureSpec.UNSPECIFIED是未指定尺寸,这种状况不多,个别都是父控件是AdapterView, * 通过measure办法传入的模式。 */ case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = widthSize; break; } return result; } private int measureHeight(int pHeightMeasureSpec) { int result = 0; int heightMode = MeasureSpec.getMode(pHeightMeasureSpec); int heightSize = MeasureSpec.getSize(pHeightMeasureSpec); switch (heightMode) { case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = heightSize; break; } return result; } /** * 覆写onLayout,其目标是为了指定视图的显示地位,办法执行的前后程序是在onMeasure之后,因为视图必定是只有晓得大小的状况下, * 能力确定怎么摆放 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { // 记录总高度 int mTotalHeight = 0; // 遍历所有子视图 int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); // 获取在onMeasure中计算的视图尺寸 int measureHeight = childView.getMeasuredHeight(); int measuredWidth = childView.getMeasuredWidth(); childView.layout(l, mTotalHeight, measuredWidth, mTotalHeight + measureHeight); mTotalHeight += measureHeight; } } }第二步,布局文件: ...

December 15, 2021 · 2 min · jiezi

关于android:Pythonlogging总结

在部署我的项目时,不可能间接将所有的信息都输入到控制台中,咱们能够将这些信息记录到日志文件中,这样不仅不便咱们查看程序运行时的状况,也能够在我的项目呈现故障时依据运行时产生的日志疾速定位问题呈现的地位。 1、日志级别Python 规范库 logging 用作记录日志,默认分为六种日志级别(括号为级别对应的数值),NOTSET(0)、DEBUG(10)、INFO(20)、WARNING(30)、ERROR(40)、CRITICAL(50)。咱们自定义日志级别时留神不要和默认的日志级别数值雷同,logging 执行时输入大于等于设置的日志级别的日志信息,如设置日志级别是 INFO,则 INFO、WARNING、ERROR、CRITICAL 级别的日志都会输入。 2、logging 流程官网的 logging 模块工作流程图如下: 从下图中咱们能够看出看到这几种 Python 类型,Logger、LogRecord、Filter、Handler、Formatter。 类型阐明: Logger:日志,裸露函数给应用程序,基于日志记录器和过滤器级别决定哪些日志无效。 LogRecord :日志记录器,将日志传到相应的处理器解决。 Handler :处理器, 将(日志记录器产生的)日志记录发送至适合的目的地。 Filter :过滤器, 提供了更好的粒度管制,它能够决定输入哪些日志记录。 Formatter:格式化器, 指明了最终输入中日志记录的布局。 判断 Logger 对象对于设置的级别是否可用,如果可用,则往下执行,否则,流程完结。创立 LogRecord 对象,如果注册到 Logger 对象中的 Filter 对象过滤后返回 False,则不记录日志,流程完结,否则,则向下执行。LogRecord 对象将 Handler 对象传入以后的 Logger 对象,(图中的子流程)如果 Handler 对象的日志级别大于设置的日志级别,再判断注册到 Handler 对象中的 Filter 对象过滤后是否返回 True 而放行输入日志信息,否则不放行,流程完结。如果传入的 Handler 大于 Logger 中设置的级别,也即 Handler 无效,则往下执行,否则,流程完结。判断这个 Logger 对象是否还有父 Logger 对象,如果没有(代表以后 Logger 对象是最顶层的 Logger 对象 root Logger),流程完结。否则将 Logger 对象设置为它的父 Logger 对象,反复下面的 3、4 两步,输入父类 Logger 对象中的日志输入,直到是 root Logger 为止。3、日志输入格局日志的输入格局能够认为设置,默认格局为下图所示。 ...

December 15, 2021 · 4 min · jiezi

关于android:建议收藏为什么公司宁愿-30K-重新招人也不给你加到-20K原因太现实

前言我的一个敌人在阿里下班,勤勤恳恳工作了两三年,公司却迟迟不给他涨工资。他来找到我,他说他很苦恼,说公司最近新来了一个员工都比他的工资要高,就因为他对性能调优这方面很善于。 什么是性能优化?在同一个手机外面,同样性能的app,哪个跑的快,哪个不卡,哪个就性能高;咱们要找到性能低的中央,并且把这些中央解决掉,这个就是性能优化;当初有很多软件开发公司以及开发者谋求的最终目标不再是简略地实现性能,更多的是提供更好的用户体验,而性能问题在用户体验中扮演着重要角色。 为什么要进行性能调优?如果用户想要实现一个同样的操作,一个 App 须要 10 秒,而同类 App 仅须要 3 秒,作为用户, 会怎么选?此外,欠佳的性能还可能导致 ANR(Application Not Responding,指应用程序无响应)状况的呈现。再加上一旦产生卡顿,就意味着接下来可能产生手机发热、电量疾速耗费等关联问题,这些都很可能导致用户的散失。 因而,改善 App 性能不容忽视。零碎性能调优不仅能够进步零碎性能,还能为公司节俭资源。这也是咱们做性能调优的最间接的目标。所以,公司也更违心招聘有这方面技术的人才. 上面给大家分享一份 722页的《360°全方面性能调优》文档,文档次要有四个大章节,设计思维与代码品质优化; 程序性能优化;开发效率优化;APP 性能优化实际;第一章 设计思维与代码品质优化一,六大准则繁多职责准则 里氏替换准则 依赖倒转准则 接口隔离准则 迪米特法令 合成复用准则 二,设计模式结构型模式 创立型模式 数据结构 三,数据结构数组 栈 队列 链表 树 图 堆 散列表 四,算法排序算法 查找算法 第二章 程序性能优化一,启动速度与执行效率优化冷启动和热启动解析 APP启动黑白屏解决办法 APP 卡顿问题剖析及解决方案 启动速度与执行效率优化之StrictMode 二,布局检测与优化布局层级优化 适度渲染 三,内存优化内存抖动和内存透露 内存小户,Bitmap 内存优化 Profile 内存监测工具 Mat 大对象与透露检测 四,耗电优化Doze&Standby Battery Historian JobScheduler、WorkManager 五,网络传输与数据存储优化google 序列化工具 protobuf 7z 极限压缩 六,APK 大小优化APK 瘦身 微信资源混同原理 ...

December 15, 2021 · 1 min · jiezi

关于android:Android换肤功能实现

Android换肤性能已不是什么新鲜事了,市面上有很多第三方的换肤库和实现计划。 之所以抉择腾讯的QMUI库来演示APP的换肤性能,次要起因: 1、换肤性能的实现过程较简略、容易了解; 2、能轻松适配Android 10 提供的Dark Mode(深色模式) ; 3、还能白嫖QMUI的各种组件、成果(这才是重要的,哈哈~); 1、换肤流程实现:1.1、新建工程通过AndroidStudio新建一个空工程(新建工程的过程,略),并增加QMUI依赖: implementation 'com.qmuiteam:qmui:2.0.0-alpha10' 1.2、定义 attr 以及其实现 style(重点)这一步须要咱们与设计师合作,整顿一套色彩、背景资源等供 App 应用。之后咱们在 xml 里以 attr 的模式给它命名,本工程案例: src/main/res/values/styles.xml: <resources> <attr name="colorPrimary" format="color" /> <attr name="colorBg1" format="color" /> <attr name="colorBg2" format="color" /> <attr name="colorBg3" format="color" /> <attr name="colorTextWhite" format="color" /> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <item name="colorPrimary">@color/colorPrimaryDefault</item> <item name="colorBg1">@color/colorBgDefault1</item> <item name="colorBg2">@color/colorBgDefault2</item> <item name="colorBg3">@color/colorBgDefault3</item> <item name="colorTextWhite">@color/colorTextWhite</item> </style> <style name="app_skin_1" parent="AppTheme"> <item name="colorPrimary">@color/colorPrimarySkin1</item> <item name="colorBg1">@color/colorBgDefault1Skin1</item> <item name="colorBg2">@color/colorBgDefault1Skin2</item> <item name="colorBg3">@color/colorBgDefault1Skin3</item> </style> <style name="app_skin_2" parent="AppTheme"> <item name="colorPrimary">@color/colorPrimarySkin2</item> <item name="colorBg1">@color/colorBgDefault2Skin1</item> <item name="colorBg2">@color/colorBgDefault2Skin2</item> <item name="colorBg3">@color/colorBgDefault2Skin3</item> </style> </resources>src/main/res/values/colors.xml: <?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimaryDefault">#FCE4EC</color> <color name="colorBgDefault1">#F06292</color> <color name="colorBgDefault2">#EC407A</color> <color name="colorBgDefault3">#880E4F</color> <color name="colorTextWhite">#FFFFFF</color> <color name="colorPrimarySkin1">#E3F2FD</color> <color name="colorBgDefault1Skin1">#90CAF9</color> <color name="colorBgDefault1Skin2">#42A5F5</color> <color name="colorBgDefault1Skin3">#0D47A1</color> <color name="colorPrimarySkin2">#FAFAFA</color> <color name="colorBgDefault2Skin1">#757575</color> <color name="colorBgDefault2Skin2">#424242</color> <color name="colorBgDefault2Skin3">#212121</color> </resources>style 是反对继承的, 以上述为例,app\_skin\_1 继承自 AppTheme, 在通过 attr 寻找其值时,如果在 app\_skin\_1 没找到,那么它就会去 AppTheme 寻找。因而咱们能够把 App 的 theme 作为咱们的一个 skin, 其它 skin 都继承自这个 skin。 ...

December 15, 2021 · 4 min · jiezi

关于android:Android-AAC架构实践

LiveData如何实现数据更新LiveData如何实现同activity申明周期绑定viewModel如何实现数据共享viewModel如何实现数据保留本文就如上问题联合aac框架源码进行逐渐解析 ##一.LiveData实现数据更新 既然是监测数据更新,必定是应用到观察者模式 观察者 GenericLifecycleObserver,其中LifecycleObserver为空接口public interface GenericLifecycleObserver extends LifecycleObserver { void onStateChanged(LifecycleOwner source, Lifecycle.Event event);}被观察者public abstract class Lifecycle { public abstract void addObserver(@NonNull LifecycleObserver observer); @MainThread public abstract void removeObserver(@NonNull LifecycleObserver observer); @MainThread @NonNull public abstract State getCurrentState(); @SuppressWarnings("WeakerAccess") public enum Event { /** * Constant for onCreate event of the {@link LifecycleOwner}. */ ON_CREATE, /** * Constant for onStart event of the {@link LifecycleOwner}. */ ON_START, /** * Constant for onResume event of the {@link LifecycleOwner}. */ ON_RESUME, /** * Constant for onPause event of the {@link LifecycleOwner}. */ ON_PAUSE, /** * Constant for onStop event of the {@link LifecycleOwner}. */ ON_STOP, /** * Constant for onDestroy event of the {@link LifecycleOwner}. */ ON_DESTROY, /** * An {@link Event Event} constant that can be used to match all events. */ ON_ANY } /** * Lifecycle states. You can consider the states as the nodes in a graph and * {@link Event}s as the edges between these nodes. */ @SuppressWarnings("WeakerAccess") public enum State { /** * Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch * any more events. For instance, for an {@link android.app.Activity}, this state is reached * <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call. */ DESTROYED, /** * Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is * the state when it is constructed but has not received * {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet. */ INITIALIZED, /** * Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state * is reached in two cases: * <ul> * <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call; * <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call. * </ul> */ CREATED, /** * Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state * is reached in two cases: * <ul> * <li>after {@link android.app.Activity#onStart() onStart} call; * <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call. * </ul> */ STARTED, /** * Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state * is reached after {@link android.app.Activity#onResume() onResume} is called. */ RESUMED; /** * Compares if this State is greater or equal to the given {@code state}. * * @param state State to compare with * @return true if this State is greater or equal to the given {@code state} */ public boolean isAtLeast(@NonNull State state) { return compareTo(state) >= 0; } }}咱们来看看Lifecycle实现类LifecycleRegistry,次要看增加观察者以及接管被观察者,对应如下两个办法 ...

December 15, 2021 · 10 min · jiezi

关于android:2021年秋招小米Android面经已获offer

前言当代大部分打工人的一个现状——“降职有望、支出见顶、生存开销飙升、财务危机如影随形”。自己毕业三年,三年Android开发。往年7月面试了小米,半个月拿到offer,记录一下整个面试过程,须要的能够看一下。 小米面试分享:一面(9.5 60min)事件散发自定义view给了个布局问你的实现形式有没有理解过新的布局有没有理解过新的布局实习经验Android布局优化适度绘制及优化讲讲你认为你Android里了解最深的点理解过framework吗讲讲二叉树前中后序遍历数据库 写了个简略的sql触发器类加载的过程kotlin扩大办法 扩大属性看过哪些开源库(聊了聊retrofit)实习过程中最有成就感的事算法 反转链表(没写进去 我是个傻子吧)删除公共字符串冒泡排序怎么排的 稳固吗一面完感觉还不错能预料到会有二面,问的都比拟中规中矩 二面(9.8 50min)实习经验 做了哪些需要有什么播种对前人留下的代码有什么想法,怎么解决的Android 滑动工夫抵触解决handler原理Android跨过程通信Activity生命周期Android为啥要分四大组件弹一个dialog时Activity生命周期变动onstart onresume别离执行什么类型的业务Java 手写单例hashmap源码多线程,锁操作系统 过程和线程的区别算法 之字形打印二叉树(又没写进去 我是*)面的时候刚从天津坐车回来,头有点晕,感觉有点拉跨,答得很个别,没想到还有三面 三面(9.9 60min)Java 封装继承多态,重点说了解及利用static重写和重载的区别、了解及利用hashmap底层,把面试官当小白给面试官讲Android 四大组件的了解activity生命周期、横竖屏生命周期、有没有不让activity销毁的办法启动模式两种service有啥区别service执行耗时操作会咋样、咋解决intentservice底层service保活broadcastreciver权限(不会)Android跨过程形式intent底层是怎么跨过程的罕用布局,重点说了解及利用Android动画有哪几种,有没有底层钻研自定义view、本人写过的demo内存透露场景及解决办法网络 TCP三次握手/四次挥手 讲讲有没有间接在TCP层做过操作操作系统 过程和线程的区别闲聊 实习最大的播种是什么你当初在团队里算是外围吗(我一个实习生外围才怪)有没有感觉对本人能力晋升特地大的需要如果给你offer你来吗如果要来的话来到当初的团队融入新的团队你有什么想法三面的面试官说跟前两面面试官没怎么交换,可能问题有反复,理论也的确有反复,然而感觉更多的还是往深了问,面很广而且很深,很多问题都没答复上来,一下子给我整懵了。幸好之前连夜做了做功课,看了很多大佬总结的技术性问题,拓宽了对试题的知识面。 在这里我精心收录整顿了一些对于Android开发的知识点、面试题的一个总结,举荐给大家化解成长的懊恼。 《Android 高级开发面试题以及答案》1.Activity2.Service3.BroadcastReceive.4.ContentProvider5.Handler6.View绘制.7.View事件散发8.RecycleView 9.Viewpager&Fragment10.WebView11.动画12.Bitmap13.mvc&mvp&mvvm14.Binder15.内存透露&内存溢出16.性能优化 17.Window&WindowManager18.AMS19.系统启动20.App启动&打包&装置21.序列化22.Art & Dalvik及其区别23.模块化&组件化24.热修复&插件化25.AOP26.iectpack总结面试胜利的因素,我感觉还是要多多看技术博客,器重每一次面试,不在同一个问题上栽倒两次。每场面试也会有一两道平时不器重的一些细枝末节的问题,但每次挂完电话/面完 回去都会认真再针对性温习这一块的知识点,确保下一次被问到这类问题不会再被坑。 因为文章篇幅无限,文档资料内容较多,本能够提供链接下载,但无奈容易被谐和,所以全副存档,须要这些文档这里的敌人,能够点击我的【Gitee】,心愿可能共同进步,共勉! 最初,祝大家都能拿到心仪的offer~

December 15, 2021 · 1 min · jiezi

关于android:Android-Gradle-学习笔记整理

前言Gradle 是将软件编译、测试、部署等步骤分割在一起自动化构建工具。 对于Android开发人员曾经理解build.gradle 的 android{} 和 dependencies{} ,然而他的编译过程是什么样的?这个过程中能够干些什么事理解吗? 此文是学习Gradle时的学习笔记,让你重新认识Gradle,让Gradle放慢并提效构建你的我的项目。此时分享给大家,与大家共勉 此笔记次要内容如下Gradle 最根底的一个我的项目配置Groovy 根底语法 并解释 apply plugin: 'xxxx'和dependencies{}Gradle Project/Task 并自定义Task和Plugin自定义一个重命名APP名的插件 流程APT 技术- Java AbstractProcessorAndroid 字节码加强技术 - Transform (Android 中应用字节码加强技术)文章内容略长,如果你曾经把握Gradle基础知识,能够间接通过目录查看你想看的内容,回顾或者学习都还不错。 初识Gradle 我的项目构建配置gralde我的项目构造 如图所示,是一个比拟小的gradle配置,这里次要说两局部 绿色局部: gralde版本配置及gralde所须要的脚本,其中gradlew为linux/mac下的脚本,gradle.bat为windows下所需的脚本红色局部:settings.gradle 为根我的项目的我的项目配置,外层的build.gradle为根我的项目的配置,内层的build.gradle为子项目的配置gradle 配置程序gralde的我的项目配置是先辨认 settings.gradle,而后在配置各个build.gradle. 为了阐明构建执行程序,在上述最根底的gradle我的项目构造外面设置了对应的代码 // settings.gradleprintln "settings.gradle start"include ':app'println "settings.gradle end"//root build.gradleprintln "project.root start"buildscript { repositories { } dependencies { }}allprojects {}println "project.root end"//app build.gradleprintln "project.app start"project.afterEvaluate { println "project.app.afterEvaluate print"}project.beforeEvaluate { println "project.app.beforeEvaluate print"}println "project.app end"如果是mac/linux,执行./gradlew 失去如下后果: ...

December 14, 2021 · 6 min · jiezi

关于android:Android开发ComposeUI如何解决布局嵌套原理解析

概述背景:布局层级过多、布局适度的嵌套会导致测量工夫呈指数级增长,最终影响布局性能。现状:Compose没有上述布局嵌套问题,因为它基本上解决了布局层级对布局性能的影响。解决原理:Compose界面只容许一次测量,即随着布局层级的加深,测量工夫仅线性增长。本文将次要阐明: 布局嵌套过多如何影响布局性能?ComposeUI如何解决嵌套问题?为什么ComposeUI能够只容许一次测量?ComposeUI测量过程的源码剖析1. 布局嵌套过多如何影响布局性能?起因次要是:ViewGroup会对子View进行屡次测量。假如:父布局的布局属性是wrap_content、子布局是match_parent,此时的布局过程是: 父布局先以0为强制宽度测量子View、而后持续测量剩下的其余子View再用其余子View里最宽的宽度,二次测量这个match_parent的子 View,最终得出它的尺寸,并把这个宽度作为本人最终的宽度。即 这个过程就对单个子View进行了二次测量。 「而布局嵌套对性能影响则是指数模式的」,即:父布局会对每个子view做两次测量,子view也会对上面的子view进行两次测量,即相当于是 O(2)测量。 2. ComposeUI如何解决嵌套问题?ComposeUI规定:只容许一次测量,不容许反复测量。即每个父布局只对每个子组件测量一次,即测量复杂度变成了:O(n)。 3. 为什么ComposeUI能够只容许一次测量?ComposeUI引入了:固有个性测量(Intrinsic Measurement)。即 Compose 容许父组件在对子组件测量前先测量子组件的“固有尺寸”,这相当于下面说的两次测量的 第一次 “粗略测量“。 而这种固定个性测量是对整个组件布局树进行一次测量即可,从而防止了随着层级的加深而减少测量次数。 4. ComposeUI测量过程的源码剖析此处次要剖析:固定个性测量的测量过程。此处先介绍LayoutNodeWrapper链构建 4.1 LayoutNodeWrapper先来看两个外围论断: 子View都是以LayoutNode的模式,存在于Parent - children中的给Layout的设置的modifier会以LayoutNodeWrapper链的模式存储在LayoutNode中,而后后续做相应变换上面阐明LayoutNodeWrapper的构建: 默认的LayoutNodeWrapper链即由LayoutNode , OuterMeasurablePlaceable, InnerPlaceable 组成当增加了modifier时,LayoutNodeWrapper链会更新,modifier会作为一个结点插入到其中internal val innerLayoutNodeWrapper: LayoutNodeWrapper = InnerPlaceable(this)private val outerMeasurablePlaceable = OuterMeasurablePlaceable(this, innerLayoutNodeWrapper)override fun measure(constraints: Constraints) = outerMeasurablePlaceable.measure(constraints)override var modifier: Modifier = Modifier    set(value) {        // …… code        field = value        // …… code            // 创立新的 LayoutNodeWrappers 链        // foldOut 相当于遍历 modifier        val outerWrapper = modifier.foldOut(innerLayoutNodeWrapper) { mod /* modifier*/ , toWrap ->            var wrapper = toWrap            if (mod is OnGloballyPositionedModifier) {                onPositionedCallbacks += mod            }            if (mod is RemeasurementModifier) {                mod.onRemeasurementAvailable(this)            }            val delegate = reuseLayoutNodeWrapper(mod, toWrap)            if (delegate != null) {                wrapper = delegate            } else {                  // …… 省略了一些 Modifier判断                   if (mod is KeyInputModifier) {                    wrapper = ModifiedKeyInputNode(wrapper, mod).assignChained(toWrap)                }                if (mod is PointerInputModifier) {                    wrapper = PointerInputDelegatingWrapper(wrapper, mod).assignChained(toWrap)                }                if (mod is NestedScrollModifier) {                    wrapper = NestedScrollDelegatingWrapper(wrapper, mod).assignChained(toWrap)                }                // 布局相干的 Modifier                if (mod is LayoutModifier) {                    wrapper = ModifiedLayoutNode(wrapper, mod).assignChained(toWrap)                }                if (mod is ParentDataModifier) {                    wrapper = ModifiedParentDataNode(wrapper, mod).assignChained(toWrap)                }                           }            wrapper        }        outerWrapper.wrappedBy = parent?.innerLayoutNodeWrapper        outerMeasurablePlaceable.outerWrapper = outerWrapper        ……    }// 假如:给Layout设置一些modifierModifier.size(100.dp).padding(10.dp).background(Color.Blue)对应的LayoutNodeWrapper链如下图所示 <figcaption style="margin: 5px 0px 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; text-align: center; font-size: 13px;">image.png</figcaption> 这样始终链式调用下一个的measure,直到最初一个结点InnerPlaceable,最终调用到了自定义Layout时写的measure() 4.2 固有个性测量-实现原理论断:LayoutNodeWrapper链中插入了一个Modifier @Stablefun Modifier.height(intrinsicSize: IntrinsicSize) = when (intrinsicSize) {    IntrinsicSize.Min -> this.then(MinIntrinsicHeightModifier)    IntrinsicSize.Max -> this.then(MaxIntrinsicHeightModifier)}private object MinIntrinsicHeightModifier : IntrinsicSizeModifier { override fun MeasureScope.measure(        measurable: Measurable,        constraints: Constraints    ): MeasureResult {     //正式测量前先依据固有个性测量取得一个束缚        val contentConstraints = calculateContentConstraints(measurable, constraints)        //正式测量        val placeable = measurable.measure(            if (enforceIncoming) constraints.constrain(contentConstraints) else contentConstraints        )        return layout(placeable.width, placeable.height) {            placeable.placeRelative(IntOffset.Zero)        }    }    override fun MeasureScope.calculateContentConstraints(        measurable: Measurable,        constraints: Constraints    ): Constraints {        val height = measurable.minIntrinsicHeight(constraints.maxWidth)        return Constraints.fixedHeight(height)    }    override fun IntrinsicMeasureScope.maxIntrinsicHeight(        measurable: IntrinsicMeasurable,        width: Int    ) = measurable.minIntrinsicHeight(width)}汇总阐明: IntrinsicSize.Min其实也是个ModifierMinIntrinsicHeightModifier会在测量之间,先调用calculateContentConstraints计算束缚calculateContentConstraints中则会递归地调用子项的minIntrinsicHeight,并找出最大值,这样父项的高度就确定了固有个性测量实现后,再调用measurable.measure,开始真正的递归测量至此,对于Compose UI解决布局嵌套层级问题及其原理解说结束。 最初给大家分享我收集的Android源码解析学习材料,心愿对你有用,期待与大家一起提高 1.深刻解析微信 MMKV 源码 2.深刻解析阿里巴巴路由框架 ARouter源码 3.深刻解析 AsyncTask 源码(一款Android 内置的异步工作执行库) ...

December 14, 2021 · 1 min · jiezi

关于android:写一个MVVM快速开发框架谈一谈单Activity多Fragment模式

单Activity+多Fragment模式自从晓得这一招之后我根本不太违心应用activity了,fragment能够疾速创立和治理,能够正当设计页面跳转,设计炫酷的跳转动画,一些操作能够对立进行治理。用Fragment代替Activity以前大部分时候都是将Activity作为页面,Fragment作为页面中的子页面(过后称之为碎片),基本上大部分性能由activity实现,比方老版本的淘宝app就是有上百个activity,过后卡顿的不要不要的。随着技术迭代,咱们发现activtiy创立、切换、销毁所耗费的性能远比fragment要大,fragment现在也能代替activity实现大部分性能。 将Activity作为容器我了解的单Activity+多Fragment模式并不是指一个App肯定只有一个activity,对于一些业务相干的场景,能够整合成一个单Activity+多Fragment模块,将activity作为fragment的容器,让fragment去做UI绘制工作。 治理Fragment栈咱们能够应用navigation治理fragment,fragment之间的跳转、栈治理都轻而易举,navigation还能够设置切换动画、页面间的数据传递。 Navigation组件Navigation是Jetpack组件之一,很早之前iOS就是采纳的这种跳转形式,过后就在想Android为啥没有,没多久Navigation就面世了。Navigation能够了解为以一个治理fragment的容器,在容器中各个fragment能够实现任意跳转, 根底应用:咱们须要在布局中创立Fragment容器: <androidx.fragment.app.FragmentContainerView android:id="@+id/main_fragment_container" android:layout_width="match_parent" android:layout_height="0dp" android:name="androidx.navigation.fragment.NavHostFragment" app:defaultNavHost="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/nav_main"/>创立navigation.xml文件<?xml version="1.0" encoding="utf-8"?><navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/navigation_main" app:startDestination="@+id/mainFragment"> <fragment android:id="@+id/mainFragment" android:name="com.example.mvvm_develop.MainFragment" android:label="MainFragment" /></navigation>应用NavControllerval navController = (childFragmentManager.findFragmentById(R.id.module_fragment_container) as NavHostFragment).navController//跳转navController.navigate(R.id.mainFragment)一些具体参数和用法:navGraph这个值指向xml文件,在xml文件中咱们能够定义fragment,跳转行为,目的地等。 创立、新增Fragment: 创立跳转行为: 动画、目的地、返回栈配置: NavController字面意思就是导航控制器,NavController能够管制跳转、返回、动画、监听等操作。咱们能够应用它进行灵便的跳转,Google还出了一些Navigation Demo演示如何配合Toolbar和底部导航栏进行应用。 对于具体的用法这里不解说了,很多文章都有,也能够参考官网。Navigation存在的问题:重走生命周期Navigation目前有个问题:Fragment回退重走生命周期,这个问题可能是Google想让Fragment和activity领有同样的工作模式,单重走生命周期真的很烦,咱们能够自定义NavHostFragment去修复这个问题,具体参考我的项目代码 批改之后应用如下: android:name="androidx.navigation.fragment.NavHostFragment" 批改为咱们自定义的NavHostFragment: android:name="com.example.baselibrary.navigation.NavHostFragment" <fragment android:id="@+id/navigation_main" android:name="com.example.baselibrary.navigation.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/nav_main"/>组件化中应用Navigation咱们通常应用底部导航栏将app划分出不同的性能,这些都是独自的module,然而在navigation中怎么进行module间的跳转呢? 比方: 其布局文件就是一个FragmentContainerView+BottomNavigationView,切换上面按钮的时候须要切换到不同的moduel页面。首先咱们将不同的moduel视为一个“单activity+多fragment”的模块,或者也能够省略activity。 形式一: google的demo中是在MainActivity中创立一个main\_navGraph,其中蕴含了不同子moduel的navGraph ,如下: <?xml version="1.0" encoding="utf-8"?><navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/nav_module" app:startDestination="@+id/navi_home"> <include app:graph="@navigation/navi_home"/> <include app:graph="@navigation/navi_collection"/> <include app:graph="@navigation/navi_center"/></navigation>navi_home 、navi_collection 、navi_center是子moduel中的navGraph文件,这种做法要求其指定startDestination,而且只能跳转到startDestination ...

December 14, 2021 · 1 min · jiezi

关于android:Android入门教程-Audio-音频一

音频 PCM 数据的采集和播放,读写音频 wav 文件应用 AudioRecord 和 AudioTrack 实现音频 PCM 数据的采集和播放,并读写音频wav 文件 筹备工作Android 提供了 AudioRecord 和 MediaRecord。MediaRecord 可抉择录音的格局。 AudioRecord 失去 PCM 编码格局的数据。AudioRecord 可能设置模拟信号转化为数字信号的相干参数,包含采样率和量化深度,同时也包含通道数目等。PCM PCM 是在由模拟信号向数字信号转化的一种罕用的编码格局,称为脉冲编码调制,PCM 将模拟信号依照肯定的间距划分为多段,而后通过二进制去量化每一个间距的强度。 PCM 示意的是音频文件中随着工夫的流逝的一段音频的振幅。Android 在WAV 文件中反对 PCM 的音频数据。 WAV WAV,MP3 等比拟常见的音频格式,不同的编码格局对应不通过的原始音频。为了不便传输,通常会压缩原始音频。 为了分别出音频格式,每种格局有特定的头文件(header)。 WAV 以 RIFF 为规范。RIFF 是一种资源替换档案规范。RIFF 将文件存储在每一个标记块中。 根本形成单位是trunk,每个 trunk 由标记位,数据大小,数据存储,三个局部形成。 PCM 打包成 WAV PCM 是原始音频数据,WAV 是 windows 中常见的音频格式,只是在 pcm 数据中增加了一个文件头。 起始地址占用空间本地址数字的含意00H4byteRIFF,资源交换文件标记。04H4byte从下一个地址开始到文件尾的总字节数。高位字节在前面,这里就是001437ECH,换成十进制是1325036byte,算上这之前的8byte就正好1325044byte了。08H4byteWAVE,代表wav文件格式。0CH4byteFMT ,波形格局标记10H4byte00000010H,16PCM,我的了解是用16bit的数据表示一个量化后果。14H2byte为1时示意线性PCM编码,大于1时示意有压缩的编码。这里是0001H。16H2byte1为单声道,2为双声道,这里是0001H。18H4byte采样频率,这里是00002B11H,也就是11025Hz。1CH4byteByte率=采样频率*音频通道数*每次采样失去的样本位数/8,00005622H,也就是22050Byte/s=11025 * 1 * 16/220H2byte块对齐=通道数*每次采样失去的样本位数/8,0002H,也就是 2 == 1 * 16/822H2byte样本数据位数,0010H即16,一个量化样本占2byte。24H4bytedata,一个标记而已。28H4byteWav文件理论音频数据所占的大小,这里是001437C8H即1325000,再加上2CH就正好是1325044,整个文件的大小。2CH不定量化数据AudioRecord AudioRecord 可实现从音频输出设施记录声音的性能。失去PCM格局的音频。 读取音频的办法有 read(byte[], int, int), read(short[], int, int) 或 read(ByteBuffer, int)。 可依据存储形式和需要抉择应用这项办法。 ...

December 14, 2021 · 7 min · jiezi

关于android:Android-Camera-Framework层分析

Camera利用调用Framework Camera类API在Android Kitkat原生Camera2利用(packages/apps/Camera2/)的PhotoModule, VideoModule, WideAnglePanoramaModule类中用CameraUtil.open()办法来关上Camera。而后顺次调 用:CameraHolder的open()办法,AndroidCameraManagerImpl的cameraOpen()方 法,CameraHandler的handleMessage()【message为OPEN_CAMERA】,直到调用Framework Camera类(frameworks/base/core/java/android/hardware/Camera.java)的open()方 法。在这里,Camera2应用程序暂不做剖析,咱们着重看程序向下调用的服务申请过程。 mCameraDevice = CameraUtil.openCamera( mActivity, mCameraId, mHandler, mActivity.getCameraOpenErrorCallback()); // (1)public class CameraUtil { public static CameraManager.CameraProxy openCamera( Activity activity, final int cameraId, Handler handler, final CameraManager.CameraOpenErrorCallback cb) { try { throwIfCameraDisabled(activity); return CameraHolder.instance().open(handler, cameraId, cb); // (2) } catch (CameraDisabledException ex) { handler.post(new Runnable() { @Override public void run() { cb.onCameraDisabled(cameraId); } }); } return null; }}public class CameraHolder { public synchronized CameraProxy open( Handler handler, int cameraId, CameraManager.CameraOpenErrorCallback cb) { ………… if (mCameraDevice == null) { Log.v(TAG, "open camera " + cameraId); if (mMockCameraInfo == null) { mCameraDevice = CameraManagerFactory .getAndroidCameraManager().cameraOpen(handler, cameraId, cb); // (3) ………… } else { ………… } mCameraOpened = true; mHandler.removeMessages(RELEASE_CAMERA); ………… return mCameraDevice; }}class AndroidCameraManagerImpl implements CameraManager { public CameraManager.CameraProxy cameraOpen( Handler handler, int cameraId, CameraOpenErrorCallback callback) { mCameraHandler.obtainMessage(OPEN_CAMERA, cameraId, 0, CameraOpenErrorCallbackForward.getNewInstance( handler, callback)).sendToTarget(); // (4) ………… }} private class CameraHandler extends Handler { @Override public void handleMessage(final Message msg) { try { switch (msg.what) { case OPEN_CAMERA: mCamera = android.hardware.Camera.open(msg.arg1); // (5) ………… return; } } } }JNI层调用 ...

December 14, 2021 · 4 min · jiezi

关于android:更好地适配大屏幕设备-2021-Android-开发者峰会

作者 / 工程经理 Clara Bayarri 往年的 Android 开发者峰会 带来了许多 Android 大屏幕设施开发 的最新资讯,包含可折叠设施和平板电脑上的 Android 12L 性能更新 的一系列内容: 针对大屏幕设施优化的 Android 12 零碎性能、更好的开发者工具以及专为大屏幕设施提供的 Google Play 更新。接下来,咱们将为您介绍 Android 大屏幕设施开发的三项重要更新。 实用于大屏幕设施的 Android 12L 性能更新Android 12L 让 Android 12 在大屏幕设施上的体现更加杰出,告诉和锁屏等界面均已实现一系列优化。针对开发者而言还蕴含以下重要内容: 从新强调多任务处理,这意味着所有的利用当初都能够进入分屏模式,无论它们是否能够调整大小;针对兼容模式的更新改良;新的 Activity Embedding API,让您能够同时显示多个 Activity,从而更轻松地在现有利用中构建大屏幕设施优化布局。理解更多最新消息,您能够观看下方 "大屏幕设施和可折叠设施的新性能" 的介绍视频或查看咱们之前的推文《详解 Android 12L|更好地适配大屏幕设施》。 更轻松地构建大屏幕设施Android 判若两人的反对大屏幕设施,当初咱们公布了几个新工具来帮忙您扩大利用界面,以适应更大屏幕的设施类型。 针对大屏幕设备设计的新 Material Design 指南,蕴含生态系统中常见的 布局模式 的定义,有助于激发您的利用设计灵感;窗口大小类 (Window Size Classes) 是一种新型断点框架,其代表了生态系统中典型设施场景的大多数状况更新 SlidingPaneLayout 以反对导航;新的 Compose API 让开发自适应和响应式 UI 变得简略,包含对 导航栏 的反对;Android Studio 参考设施,一组新的设施配置文件,代表了生态系统中最宽泛的设施测试范畴;Android Studio 布局验证 (Layout Validation) 引入一个新的可视化的 lint 工具,用以检测大屏幕设施布局问题;全新的可调整大小模拟器,能够在参考设施间迅速切换。欢迎您观看 "为任何尺寸的屏幕构建 Android 界面" 和 "在可折叠设施和大屏幕设施上设计精美利用" 的技术分享视频,理解对于这些我的项目的更多信息。您还能够查看最新的 大屏幕设施指南 和 在 Compose 中构建自适应布局指南,理解更多相干信息。您也能够查看 "可折叠设施上视频利用的最佳实际" 和 "Android 开发者故事: 助力 Spotify 构建跨设施利用" 视频,理解如何让利用适配大屏幕设施。 ...

December 14, 2021 · 1 min · jiezi

关于android:Activity之间的通信

假如咱们有这样一个罕用的场景:有两个Activity,第一个Activity展现一段文本点击“编辑”按钮启动第二个Activity,并把这段文本当做参数传递到第二个Activity在第二个Activity编辑这个字符串编辑实现后点击保留将后果返回到第一个Activity第一个Activity展现批改后的字符串如下图: 这是一个非常简单和常见的场景,咱们个别通过 startActivityForResult 的形式传递参数,并在 onActivityResult 接管编辑后的后果,代码也很简略,如下: //第一个Activity启动编辑ActivitybtnEditByTradition.setOnClickListener { val content = tvContent.text.toString().trim() val intent = Intent(this, EditActivity::class.java).apply { putExtra(EditActivity.ARG_TAG_CONTENT, content) } startActivityForResult(intent, REQUEST_CODE_EDIT)} //EditActivity回传编辑后的后果 btnSave.setOnClickListener { val newContent = etContent.text.toString().trim() setResult(RESULT_OK, Intent().apply { putExtra(RESULT_TAG_NEW_CONTENT, newContent) }) finish()}//第一个Activity中承受编辑后的后果,并展现override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { REQUEST_CODE_EDIT -> { if (resultCode == RESULT_OK && data != null) { val newContent = data.getStringExtra(EditActivity.RESULT_TAG_NEW_CONTENT) tvContent.text = newContent } } else -> super.onActivityResult(requestCode, resultCode, data) }}那这种形式有什么毛病呢?代码扩散,可读性差封装不彻底,调用方须要到EditActivity能力晓得须要传递什么参数,类型是什么,key是什么调用方须要晓得EditActivity是如何返回的参数类型和key是什么能力正确解析约束性差,各种常量的定义(REQUEST\_CODE,PARAM\_KEY等),若项目管理不谨严,反复定义,导致前期重构和保护比拟麻烦那有没有一种形式能解决下面的毛病呢?咱们冀望的是:一个对外提供某些性能的Activity应该有足够的封装性,调用者像调用一般办法一样,一行代码即可实现调用办法的参数列表就是调用本服务须要传递的参数(参数数量,参数类型,是否必须)办法的返回参数就是本服务的返回后果提供服务的Activity像一个组件一样,能对外提供性能都是以一个个办法的模式体现通过Kotlin 协程和一个不可见的Fragment来实现。btnEditByCoroutine.setOnClickListener { GlobalScope.launch { val content = tvContent.text.toString().trim() // 调用EditActivity的 editContent 办法 // content为要编辑的内容 // editContent 即为编辑后的后果 val newContent = EditActivity.editContent(this@MainActivity, content) if (!newContent.isNullOrBlank()) { tvContent.text = newContent } }}通过下面的代码,咱们看到,通过一个办法即可实现调用,根本实现了上文提到的冀望。 那 editContent 办法外部是如何实现的呢?看如下代码: ...

December 14, 2021 · 2 min · jiezi

关于android:为什么有两年Android开发经验却抵不过一个实习生原因太现实

前言最近有一个敌人找到我,说他最近去阿里面试,没有通过。然而跟他一起面试的有一个实习生,被录取了。他说他学了两年的Android开发,有足够的教训,还做过一些我的项目,却抵不过一个没有教训的实习生,起初理解到起因是面试官问了他几个对于Android组件化的问题,刚好碰上他的弱点了。 听到他说的这些,我示意很感叹,当初安卓开发的前景和市场是处于向前倒退的趋势。大厂也对组件化这方面的问题比拟器重,他们更违心招聘有这方面技术的人才。为什么当初公司很重视组件化这个问题呢?举个例子来说,一个我的项目须要好几个业务代码,如果某一个业务代码出了问题,则须要在好几个当中去寻找,工程师须要理解各个业务的性能,防止代码的改变而影响其它的业务性能,势必无形中减少了我的项目保护的老本。如果让他们都绝对独立,咱们只须要保护好每个组件,须要用到该组件的性能时,一建援用集成就能够了。 近年来,为什么这么多团队要进行组件化实际呢?组件化到底能给咱们的工程、代码带来什么益处?咱们认为组件化可能带来两个最大的益处: 进步组件复用性可能有些人会感觉,进步复用性很简略,间接把须要复用的代码做成Android Module,打包AAR并上传代码仓库,那么这部分性能就能被不便地引入和应用。然而咱们感觉仅仅这样是不够的,上传仓库的AAR库是否不便被复用,须要组件化的规定来束缚,这样能力进步复用的便捷性。 升高组件间的耦合咱们须要通过组件化的规定把代码拆分成不同的模块,模块要做到高内聚、低耦合。模块间也不能间接调用,这须要组件化通信框架的反对。升高了组件间的耦合性能够带来两点间接的益处: 1.代码更便于保护;2.升高了模块的Bug率。所以说,学好组件化对一个开发工程师来说至关重要。我这里有一份高级Android组件化强化实战材料,心愿能帮到你们。 第一章 Android 组件化初识组件化和模块化的区别 组件化和插件化的区别 组件化开发的劣势 业务逻辑层 组件化开发要遇到的问题 从组件化实战来解决问题 Android 组件化根底 第二章 Android组件化初探组件化演示案例 概述 模块化和组件化 组件化Demo 组件application和library动静切换 WanAndroid APP 组件化我的项目实战(附demo) 简介 版本更新 效果图 次要性能 我的项目目录构造 次要开源框架 第三章 架构演变(大厂篇)组件化作为 Android 客户端技术的一个重要分支,近年来始终是业界积极探索和实际的方向。每个大厂外部的各个Android 开发团队也在尝试和实际不同的组件化计划,并且在组件化通信框架上也有很多高质量的产出。 从智行 Android 我的项目看组件化架构实际 组件化调整的起因和指标 组件化架构调整的整体规划 组件化架构调整中遇到的一些问题 组件化架构的实际成绩 失去 App Android彻底组件化demo公布 Android彻底组件化—代码和资源隔离 组件化:代码隔离也难不倒组件的按序初始化 微信 App 微信Android模块化架构重构实际 微信Android架构历史 为何再次重构微信 重塑模块化 取舍和抉择 代码之外,架构之内 蘑菇街 App 实现形式 组件生命周期治理 壳工程 遇到的问题 继续集成 周边设施 爱奇艺 App 对于组件化 ...

December 14, 2021 · 1 min · jiezi

关于android:高级工程师的不二之选Github常年霸榜的超强框架Retrofit

前言每个Android开发者在产品开发的过程中,都须要用到网络和服务器进行交互。而对于网络框架的应用和了解,往往能够看出一个开发者到底处于什么段位: 高级工程师:尽管也会应用OkHttp、Retrofit等框架,但根本都是机械的套用,对其源码、外围原理所知甚少,须要付出大量的工夫钻研源码。中级工程师:对于常见网络框架的外部原理有些理解,在我的项目开发过程中往往都能依据业务需要选用适合的框架。但遇到问题的时候还是常常须要寻求开源计划反对,通常这个时候他们会自行设计一个尽可能小的封装。高级工程师:个别会在我的项目中负责其架构选取的工作。他们对对http申请、线程池、缓存的常识了然于心,对网络框架的抉择规范,肯定是高性能,且简洁易用。所以基于OkHttp进行封装的Retrofit根本是他们的不二之选,且在反对converter扩大和rxjava,扩展性不好对他们高超的重构和封装技巧基本不是问题。而且对于Retrofit的核心思想熟络于心,能够很好地迁徙到其余的开发工作中。Retrofit为何成为高工最爱?作为最为宽泛应用的网络申请框架,OkHttp其实曾经十分弱小。然而在理论开发过程中,大家还是会遇到不少问题:无奈适配自动线程的切换、调用简单、网络申请接口配置繁琐、缓存生效……。而Retrofit的二次封装,能够很好地解决这些问题,为OkHttp锦上添花。 Retrofit是一个十分弱小的封装框架,能够配置不同HTTP client来实现网络申请,如OkHttp、httpclient等;能够定制申请办法的参数注解;能够同步或异步RxJava;轻松实现超级解耦;配置不同的反序列工具来解析数据,而且设计模式颇多,应用十分不便灵便: Retrofit没有扭转网络申请的实质,这部分仍旧由OkHttp实现。它最次要的特点在于设计模式十分丰盛,能够通过注解间接配置申请,能够适配不同的http客户端,而且通过不同的Json Converter 来序列化数据,同时对RxJava提供反对。所以Retrofit + OkHttp + RxJava是以后高工最钟意的一套框架。 但这套框架其实是有肯定门槛的,想要用好这套框架,对于Retrofit的外围原理肯定要非常相熟。 为什么举荐你学?当初BATJ等大厂的面试套路都是一样的:他们会让你谈谈Retrofit的具体应用,你有在本人的我的项目中应用过吗?而后扩大到和这个知识点相干的更深层次的知识点细节,会对Retrofit刨根问底: 用过哪些网络加载库?Retrofit外围实现原理?如果让你实现Retrofit的某些外围性能,你会思考怎么去实现?Retrofit的注解是怎么解析的 ?Retrofit网络申请层用的什么?Retrofit中应用了哪些设计模式?Retrofit在OkHttp上做了哪些封装?动静代理和动态代理的区别,是怎么实现的?Android开发Repository层如何拿到retrofit返回的数据?直到问的你答不上来为止,以此来探寻你的技术边际,这样就能更深刻地理解你的技术能力。其实,想要成为真正的高级架构师除了纯熟Retrofit外往往还须要把握其余框架常识。 如何学习Retrofit?其实很简略, 我这里有一份Android源码解析学习材料,心愿能帮忙大家更好地学习把握Retrofit的外围原理。 1.深刻解析 Retrofit 源码 2.与其余网络申请开源库比照 3.Retrofit 的实质流程 4.创立网络申请接口的实例 5.外观模式 因为篇幅较长,细节内容比拟多,临时只展现这些;有须要学习材料的敌人能够点击这里收费获取! 明天的文章就到这里,感谢您的浏览,喜爱的话不要忘了三连。大家的反对和认可,是我分享的最大能源。

December 14, 2021 · 1 min · jiezi

关于android:Android-优雅处理重复点击建议收藏

个别手机上的 Android App,次要的交互方式是点击。用户在点击后,App 可能做出在页面内更新 UI、新开一个页面或者发动网络申请等操作。Android 零碎自身没有对反复点击做解决,如果用户在短时间内屡次点击,则可能呈现新开多个页面或者反复发动网络申请等问题。因而,须要对反复点击有影响的中央,减少解决反复点击的代码。 之前的解决形式之前在我的项目中应用的是 RxJava 的计划,利用第三方库 RxBinding 实现了避免反复点击: fun View.onSingleClick(interval: Long = 1000L, listener: (View) -> Unit) {    RxView.clicks(this)        .throttleFirst(interval, TimeUnit.MILLISECONDS)        .subscribe({            listener.invoke(this)        }, {            LogUtil.printStackTrace(it)        })}然而这样有一个问题,比方应用两个手指同时点击两个不同的按钮,按钮的性能都是新开页面,那么有可能会新开两个页面。因为 Rxjava 这种形式是针对单个控件实现避免反复点击,不是多个控件。 当初的解决形式当初应用的是工夫判断,在工夫范畴内只响应一次点击,通过将上次单击工夫保留到 Activity Window 中的 decorView 里,实现一个 Activity 中所有的 View 共用一个上次单击工夫。 fun View.onSingleClick(    interval: Int = SingleClickUtil.singleClickInterval,    isShareSingleClick: Boolean = true,    listener: (View) -> Unit) {    setOnClickListener {        val target = if (isShareSingleClick) getActivity(this)?.window?.decorView ?: this else this        val millis = target.getTag(R.id.single_click_tag_last_single_click_millis) as? Long ?: 0        if (SystemClock.uptimeMillis() - millis >= interval) {            target.setTag(                R.id.single_click_tag_last_single_click_millis, SystemClock.uptimeMillis()            )            listener.invoke(this)        }    }}private fun getActivity(view: View): Activity? {    var context = view.context    while (context is ContextWrapper) {        if (context is Activity) {            return context        }        context = context.baseContext    }    return null}参数 isShareSingleClick 的默认值为 true,示意该控件和同一个 Activity 中其余控件共用一个上次单击工夫,也能够手动改成 false,示意该控件本人独享一个上次单击工夫。 mBinding.btn1.onSingleClick {    // 解决单次点击}mBinding.btn2.onSingleClick(interval = 2000, isShareSingleClick = false) {    // 解决单次点击}其余场景解决反复点击间接设置点击除了间接在 View 上设置的点击监听外,其余间接设置点击的中央也存在须要解决反复点击的场景,比如说富文本和列表。 为此将判断是否触发单次点击的代码抽离进去,独自作为一个办法: fun View.onSingleClick(    interval: Int = SingleClickUtil.singleClickInterval,    isShareSingleClick: Boolean = true,    listener: (View) -> Unit) {    setOnClickListener { determineTriggerSingleClick(interval, isShareSingleClick, listener) }}fun View.determineTriggerSingleClick(    interval: Int = SingleClickUtil.singleClickInterval,    isShareSingleClick: Boolean = true,    listener: (View) -> Unit) {    ...}间接在点击监听回调中调用 determineTriggerSingleClick 判断是否触发单次点击。上面拿富文本和列表举例。 富文本继承 ClickableSpan,在 onClick 回调中判断是否触发单次点击: inline fun SpannableStringBuilder.onSingleClick(    listener: (View) -> Unit,    isShareSingleClick: Boolean = true,    ...): SpannableStringBuilder = inSpans(    object : ClickableSpan() {        override fun onClick(widget: View) {            widget.determineTriggerSingleClick(interval, isShareSingleClick, listener)        }        ...    },    builderAction = builderAction)这样会有一个问题, onClick 回调中的 widget,就是设置富文本的控件,也就是说如果富文本存在多个单次点击的中央, 就算 isShareSingleClick 值为 false,这些单次点击还是会共用设置富文本控件的上次单击工夫。 因而,这里须要非凡解决,在 isShareSingleClick 为 false 的时候,创立一个假的 View 来触发单击事件,这样富文本中多个单次点击 isShareSingleClick 为 false 的中央都有一个本人的假的 View 来独享上次单击工夫。 class SingleClickableSpan(    ...) : ClickableSpan() {    private var mFakeView: View? = null    override fun onClick(widget: View) {        if (isShareSingleClick) {            widget        } else {            if (mFakeView == null) {                mFakeView = View(widget.context)            }            mFakeView!!        }.determineTriggerSingleClick(interval, isShareSingleClick, listener)    }    ...}在设置富文本的中央,应用设置 onSingleClick 实现单次点击: mBinding.tvText.movementMethod = LinkMovementMethod.getInstance()mBinding.tvText.highlightColor = Color.TRANSPARENTmBinding.tvText.text = buildSpannedString {    append("normalText")    onSingleClick({        // 解决单次点击    }) {        color(Color.GREEN) { append("clickText") }    }}列表列表应用 RecyclerView 控件,适配器应用第三方库 BaseRecyclerViewAdapterHelper。 ...

December 14, 2021 · 1 min · jiezi

关于android:效果炸了Android自定义View实现一个炫酷的时钟

一、背景1.1、控件成果要实现的自定义控件成果大抵如下,实现过程中用到了比拟多的自定义View的API,感觉比拟有代表性,就分享进去也当做学习总结 我的项目代码已上传github :https://github.com/DaLeiGe/An... 1.2、从性能上剖析一下这个控件,大抵有以下特点随机静止粒子从圆周向圆心静止,并与切线方向有正负30°的角度差,粒子透明度、半径、静止速度随机,静止超过肯定间隔或者工夫隐没背景圆有一个从内到外的渐变色计时模式下圆环有一个色彩突变的顺时针rotate动画整个背景圆色彩随着扇形角度变动而变动指针色彩变动数字变动是高低切换动画1.3、从构造上剖析这个控件能够拆分为两个局部,由背景圆+数字控件两个局部形成的组合控件,之所以把数字控件独自拆分进去,也是为了不便做数字上下跳动的动画,毕竟通过管制drawText的地位实现动画感觉不不便,间接通过View的属性动画更好实现 二、 背景圆实现2.1、实现粒子静止应用AnimPoint.java示意静止粒子,它具备x,y坐标,半径,角度,静止速度,透明度等属性,通过这些属性就能够画出一个动态的粒子 public class AnimPoint implements Cloneable {    /**     * 粒子原点x坐标     */    private float mX;    /**     * 粒子原点y坐标     */    private float mY;    /**     * 粒子半径     */    private float radius;    /**     * 粒子初始地位的角度     */    private double anger;    /**     * 一帧挪动的速度     */    private float velocity;    /**     * 总共挪动的帧数     */    private int num = 0;    /**     * 透明度 0~255     */    private int alpha = 153;    /**     * 随机偏移角度     */    private double randomAnger = 0;}粒子的初始地位位于随机角度的圆周,且一个粒子具备随机的半径,透明度,速度等,通过init()办法,实现初始化粒子如下 public void init(Random random, float viewRadius) {        anger = Math.toRadians(random.nextInt(360));        velocity = random.nextFloat() * 2F;        radius = random.nextInt(6) + 5;        mX = (float) (viewRadius * Math.cos(anger));        mY = (float) (viewRadius * Math.sin(anger));        //随机偏移角度-30°~30°        randomAnger = Math.toRadians(30 - random.nextInt(60));        alpha = 153 + random.nextInt(102);    }想让粒子静止起来,应用update更新粒子的这些坐标属性就能实现,比方粒子当初坐标在(5,5)`,通过update()扭转粒子的坐标到(6,6),联合属性动画不停地调用update()则就能不停的扭转x,y的坐标,实现粒子静止,而后当粒子挪动超过肯定间隔,或者调用update超过肯定次数,再从新调用init()让粒子从新从圆周上开始下一个生命周期静止 public void updatePoint(Random random, float viewRadius) {        //每一帧偏移的像素大小        float distance = 1F;        double moveAnger = anger + randomAnger;        mX = (float) (mX - distance * Math.cos(moveAnger) * velocity);        mY = (float) (mY - distance * Math.sin(moveAnger) * velocity);        //模仿半径逐步变小        radius = radius - 0.02F * velocity;        num++;        //如果到了最大值 则从新给静止粒子一个轨迹属性        int maxDistance = 180;        int maxNum = 400;        if (velocity * num > maxDistance || num > maxNum) {            num = 0;            init(random, viewRadius);        }    }在View中大抵实现如下 /**     * 初始化动画     */    private void initAnim() {        //绘制静止的粒子        AnimPoint mAnimPoint = new AnimPoint();        for (int i = 0; i < pointCount; i++) {            //通过clone创建对象,防止反复创立            AnimPoint cloneAnimPoint = mAnimPoint.clone();            //先给每个粒子初始化各类属性            cloneAnimPoint.init(mRandom, mRadius - mOutCircleStrokeWidth / 2F);            mPointList.add(cloneAnimPoint);        }        //画静止粒子        mPointsAnimator = ValueAnimator.ofFloat(0F, 1F);        mPointsAnimator.setDuration(Integer.MAX_VALUE);        mPointsAnimator.setRepeatMode(ValueAnimator.RESTART);        mPointsAnimator.setRepeatCount(ValueAnimator.INFINITE);        mPointsAnimator.addUpdateListener(animation -> {            for (AnimPoint point : mPointList) {                //通过属性动画不停的计算下粒子的下一个坐标                point.updatePoint(mRandom, mRadius);            }            invalidate();        });        mPointsAnimator.start();    }    @Override    protected void onDraw(final Canvas canvas) {        super.onDraw(canvas);        canvas.save();        canvas.translate(mCenterX, mCenterY);        //画静止粒子        for (AnimPoint animPoint : mPointList) {            mPointPaint.setAlpha(animPoint.getAlpha());            canvas.drawCircle(animPoint.getmX(), animPoint.getmY(),                    animPoint.getRadius(), mPointPaint);        }     }2.2、实现渐变色圆实现圆从内到外突变应用RadialGradient 大抵实现形式如下 float[] mRadialGradientStops = {0F, 0.69F, 0.86F, 0.94F, 0.98F, 1F};mRadialGradientColors[0] = transparentColor;mRadialGradientColors[1] = transparentColor;mRadialGradientColors[2] = parameter.getInsideColor();mRadialGradientColors[3] = parameter.getOutsizeColor();mRadialGradientColors[4] = transparentColor;mRadialGradientColors[5] = transparentColor;mRadialGradient = new RadialGradient(                    0,                    0,                    mCenterX,                    mRadialGradientColors,                    mRadialGradientStops,                    Shader.TileMode.CLAMP);mSweptPaint.setShader(mRadialGradient);...//onDraw()绘制canvas.drawCircle(0, 0, mCenterX, mSweptPaint);2.3、展现背景圆的扇形区域本来想通过DrawArc实现这个成果,然而DrawArc无奈实现到圆心的区域 那么如何实现这么一个不规则的形态呢,能够应用canvas.clipPath()实现裁剪不规则的形态,所以只有失去扇形的Path就能实现,通过圆点+弧形再闭合path就能实现 /**     * 绘制扇形path     *     * @param r 半径     * @param startAngle 开始角度     * @param sweepAngle 扫过的角度     */private void getSectorClip(float r, float startAngle, float sweepAngle) {        mArcPath.reset();        mArcPath.addArc(-r, -r, r, r, startAngle, sweepAngle);        mArcPath.lineTo(0, 0);        mArcPath.close();    }//而后再onDraw()中,裁剪画布 canvas.clipPath(mArcPath);2.4、实现指针变色指针是不规则形态,无奈通过绘制几何图形实现,所以选用drawBitmap实现 至于如何实现bitmap指针图片的色彩变动呢,本来的计划是应用AvoidXfermode扭转指定像素通道范畴内的色彩,然而AvoidXfermode在API 24曾经被移除,所以这计划有效 最终采纳图层混合模式实现指针图片变色 通过PorterDuff.Mode.MULTIPLY模式能够实现bitmap色彩,源图像为要批改的指针色彩,指标图像为红色指针,通过获取两个图像的重叠局部实现变色 大抵实现如下 /**     * 初始化指针图片的Bitmap     */    private void initBitmap() {        float f = 130F / 656F;        mBitmapDST = BitmapFactory.decodeResource(getResources(), R.drawable.indicator);        float mBitmapDstHeight = width * f;        float mBitmapDstWidth = mBitmapDstHeight * mBitmapDST.getWidth() / mBitmapDST.getHeight();        //初始化指针的图层混合模式        mXfermode = new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY);        mPointerRectF = new RectF(0, 0, mBitmapDstWidth, mBitmapDstHeight);        mBitmapSRT = Bitmap.createBitmap((int) mBitmapDstWidth, (int) mBitmapDstHeight, Bitmap.Config.ARGB_8888);        mBitmapSRT.eraseColor(mIndicatorColor);    }    @Override    protected void onDraw(final Canvas canvas) {        super.onDraw(canvas);        //画指针       canvas.translate(mCenterX, mCenterY);       canvas.rotate(mCurrentAngle / 10F);       canvas.translate(-mPointerRectF.width() / 2, -mCenterY);       mPointerLayoutId = canvas.saveLayer(mPointerRectF, mBmpPaint);       mBitmapSRT.eraseColor(mIndicatorColor);       canvas.drawBitmap(mBitmapDST, null, mPointerRectF, mBmpPaint);       mBmpPaint.setXfermode(mXfermode);       canvas.drawBitmap(mBitmapSRT, null, mPointerRectF, mBmpPaint);       mBmpPaint.setXfermode(null);       canvas.restoreToCount(mPointerLayoutId);    }2.5、实现背景圆色彩随扇形角度变动把圆形控件拆成3600°,每一个角度对应控件一种具体色彩值,那么如何计算特定角度他具体的色彩值呢? 参考属性动画中的变色动画android.animation.ArgbEvaluator实现形式,计算两个色彩中具体某一个点的色彩值形式如下 public Object evaluate(float fraction, Object startValue, Object endValue) {     int startInt = (Integer) startValue;     float startA = ((startInt >> 24) & 0xff) / 255.0f;     float startR = ((startInt >> 16) & 0xff) / 255.0f;     float startG = ((startInt >>  8) & 0xff) / 255.0f;     float startB = ( startInt        & 0xff) / 255.0f;     int endInt = (Integer) endValue;     float endA = ((endInt >> 24) & 0xff) / 255.0f;     float endR = ((endInt >> 16) & 0xff) / 255.0f;     float endG = ((endInt >>  8) & 0xff) / 255.0f;     float endB = ( endInt        & 0xff) / 255.0f;     // convert from sRGB to linear     startR = (float) Math.pow(startR, 2.2);     startG = (float) Math.pow(startG, 2.2);     startB = (float) Math.pow(startB, 2.2);     endR = (float) Math.pow(endR, 2.2);     endG = (float) Math.pow(endG, 2.2);     endB = (float) Math.pow(endB, 2.2);     // compute the interpolated color in linear space     float a = startA + fraction * (endA - startA);     float r = startR + fraction * (endR - startR);     float g = startG + fraction * (endG - startG);     float b = startB + fraction * (endB - startB);     // convert back to sRGB in the [0..255] range     a = a * 255.0f;     r = (float) Math.pow(r, 1.0 / 2.2) * 255.0f;     g = (float) Math.pow(g, 1.0 / 2.2) * 255.0f;     b = (float) Math.pow(b, 1.0 / 2.2) * 255.0f;     return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b); }控件中总共有四个色彩段,3600/4=900,所以 fraction = progressValue % 900 / 900;而后判断以后的角度位于第几段色彩值中,通过android.animation.ArgbEvaluator.evaluate(float fraction, Object startValue, Object endValue)就能回去具体的色彩值 大抵实现过程如下 private ProgressParameter getProgressParameter(float progressValue) {        float fraction = progressValue % 900 / 900;        if (progressValue < 900) {            //第一个色彩段            mParameter.setInsideColor(evaluate(fraction, insideColor1, insideColor2));            mParameter.setOutsizeColor(evaluate(fraction, outsizeColor1, outsizeColor2));            mParameter.setProgressColor(evaluate(fraction, progressColor1, progressColor2));            mParameter.setPointColor(evaluate(fraction, pointColor1, pointColor2));            mParameter.setBgCircleColor(evaluate(fraction, bgCircleColor1, bgCircleColor2));            mParameter.setIndicatorColor(evaluate(fraction, indicatorColor1, indicatorColor2));        } else if (progressValue < 1800) {            //第二个色彩段            mParameter.setInsideColor(evaluate(fraction, insideColor2, insideColor3));            mParameter.setOutsizeColor(evaluate(fraction, outsizeColor2, outsizeColor3));            mParameter.setProgressColor(evaluate(fraction, progressColor2, progressColor3));            mParameter.setPointColor(evaluate(fraction, pointColor2, pointColor3));            mParameter.setBgCircleColor(evaluate(fraction, bgCircleColor2, bgCircleColor3));            mParameter.setIndicatorColor(evaluate(fraction, indicatorColor2, indicatorColor3));        } else if (progressValue < 2700) {            //第三个色彩段            mParameter.setInsideColor(evaluate(fraction, insideColor3, insideColor4));            mParameter.setOutsizeColor(evaluate(fraction, outsizeColor3, outsizeColor4));            mParameter.setProgressColor(evaluate(fraction, progressColor3, progressColor4));            mParameter.setPointColor(evaluate(fraction, pointColor3, pointColor4));            mParameter.setBgCircleColor(evaluate(fraction, bgCircleColor3, bgCircleColor4));            mParameter.setIndicatorColor(evaluate(fraction, indicatorColor3, indicatorColor4));        } else {            //第四个色彩段            mParameter.setInsideColor(evaluate(fraction, insideColor4, insideColor5));            mParameter.setOutsizeColor(evaluate(fraction, outsizeColor4, outsizeColor5));            mParameter.setProgressColor(evaluate(fraction, progressColor4, progressColor5));            mParameter.setPointColor(evaluate(fraction, pointColor4, pointColor5));            mParameter.setBgCircleColor(evaluate(fraction, bgCircleColor4, bgCircleColor5));            mParameter.setIndicatorColor(evaluate(fraction, indicatorColor4, indicatorColor5));        }        return mParameter;    }三、跳动数字动画实现3.1、属性动画+2个TextView实现数字高低切换动画实现数字切换动画,本来打算用RecycleView实现,然而思考到动效上未来可能面临UI小姐姐各种骚操作,所以最终决定就用两个TextView做高低translation动画,这样可控性高,对View执行属性动画也简略 NumberView应用FrameLayout包裹两个TextView,widget_progress_number_item_layout.xml <?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="wrap_content"    android:layout_height="wrap_content">    <TextView        android:id="@+id/tv_number_one"        style="@style/progress_text_font"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:gravity="center"        android:padding="0dp"        android:text="0"        android:textColor="@android:color/white" />    <TextView        style="@style/progress_text_font"        android:id="@+id/tv_number_tow"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_gravity="center"        android:gravity="center"        android:text="1"        android:textColor="@android:color/white" /></FrameLayout>而后通过属性动画管制两个TextView高低切换 mNumberAnim = ValueAnimator.ofFloat(0F, 1F);        mNumberAnim.setDuration(400);        mNumberAnim.setInterpolator(new OvershootInterpolator());        mNumberAnim.setRepeatCount(0);        mNumberAnim.setRepeatMode(ValueAnimator.RESTART);        mNumberAnim.addUpdateListener(animation -> {            float value = (float) animation.getAnimatedValue();            if (UP_OR_DOWN_MODE == UP_ANIMATOR_MODE) {                //数字变大,向下挪动                mTvFirst.setTranslationY(-mHeight * value);                mTvSecond.setTranslationY(-mHeight * value);            } else {                //数字变小,向上挪动                mTvFirst.setTranslationY(mHeight * value);                mTvSecond.setTranslationY(-2 * mHeight + mHeight * value);            }        });这样NumberView就能实现一位数字的变动是高低切换动画,具备个十百位还有时钟冒号的通过容器布局AnimNumberView组合布局的形式实现示意工夫和个十百位数 四、我的项目源码博客只是大抵讲了实现思路,具体实现请浏览源码 https://github.com/DaLeiGe/An...原文链接:https://juejin.cn/post/698407... 文末您的点赞珍藏就是对我最大的激励!欢送关注我,分享Android干货,交换Android技术。对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!

December 14, 2021 · 1 min · jiezi

关于android:Android自定义view流程

Android自定义view流程,次要目标是总结实现过程中的思路以及一些须要留神的中央。首先,咱们先来看一张效果图: 实现逻辑从新指定View宽高绘制外圆圆弧背景及进度绘制中圆圆弧背景及进度绘制内圆圆弧背景及进度知识点onMeasure用于测量View的大小。创立时View无需测量,当将这个View放入一个容器(父控件)时候才须要测量,而测量方法由父控件调用。当控件的父控件要搁置该控件的时候,父控件会调用子控件的onMeasure办法确定子控件须要的空间大小,而后传入widthMeasureSpec和heightMeasureSpec来通知子控件可取得的空间大小,子控件通过这两个参数就能够测量本身的宽高了。setMeasuredDimension用于从新设置View宽高Canvas#drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)绘制以oval为边界的圆弧onDraw用来确定View长什么样。onDraw绘制过程如下: Draw the background(绘制背景)If necessary, save the canvas’ layers to prepare for fading(如果须要,为保留这层为边缘的滑动成果作筹备)Draw view’s content(绘制内容)Draw children(绘制子View)If necessary, draw the fading edges and restore layers(如果须要,绘制边缘成果并且保留图层)Draw decorations (scrollbars for instance)(绘制边框,比方scrollbars,TextView)次要代码@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 依据父控件传递的widthMeasureSpec和heightMeasureSpec调用MeasureSpec.getSize测量本身宽高 int measureWidth = MeasureSpec.getSize(widthMeasureSpec); int measureHeight = MeasureSpec.getSize(heightMeasureSpec); int finalWidth = measureWidth; int finalHeight = measureHeight; // 依据本身宽高从新计算新的宽高,使新的宽高比为2:1 if (measureWidth >= measureHeight * 2) { finalWidth = measureHeight * 2; } else { finalHeight = measureWidth / 2; } // 设置View新的宽高 setMeasuredDimension(finalWidth, finalHeight);}/** * 绘制圆弧 * @param canvas * @param progress 进度 * @param color 进度色彩 * @param radius 圆弧半径 */private void drawArc(Canvas canvas, float progress, int color, float radius){ // 圆心 mXCenter = getWidth() / 2; mYCenter = getHeight() ; mPaint.setColor(mBackgroundArcColor); // 结构边界矩形 RectF oval = new RectF(); oval.left = (mXCenter - radius); oval.top = (mYCenter - radius); oval.right = mXCenter + radius; oval.bottom = radius * 2 + (mYCenter - radius); //绘制圆弧背景 canvas.drawArc(oval, -180, 180, false, mPaint); //绘制圆弧进度 float showDegree = progress / 100 * 180; mPaint.setColor(color); canvas.drawArc(oval, -180, showDegree, false, mPaint);}@Overrideprotected void onDraw(Canvas canvas) { super.onDraw(canvas); // 初始半径 float originalRadius = getWidth() * .5f; // 画笔半宽 float halfArcStokeWidth = mArcStrokeWidth * .5f; // 外圆环半径=初始半径-画笔半宽 float outSideArcRadius = originalRadius - halfArcStokeWidth; drawArc(canvas, mOutsideProgress, mOutsideArcColor, outSideArcRadius); // 中圆环半径=外圆的半径-圆环偏移值-画笔半宽 float middleArcRadius = outSideArcRadius - mArcOffset - halfArcStokeWidth; drawArc(canvas, mMiddleProgress, mMiddleArcColor, middleArcRadius); // 内圆环半径=中圆的半径-圆环偏移值-画笔半宽 float insideArcRadius = middleArcRadius - mArcOffset - halfArcStokeWidth; drawArc(canvas, mInsideProgress, mInsideArcColor, insideArcRadius);}全副代码ThreeArcView.javapublic class ThreeArcView extends View { //圆弧画笔 private Paint mPaint; //背景圆环色彩 private int mBackgroundArcColor; //外圆环色彩 private int mOutsideArcColor; //中圆环色彩 private int mMiddleArcColor; //内圆环色彩 private int mInsideArcColor; //外圆展现弧度 private float mOutsideProgress; //中圆展现弧度 private float mMiddleProgress; //内圆展现弧度 private float mInsideProgress; //圆弧宽度 private float mArcStrokeWidth; //圆偏移值 private float mArcOffset; // 圆心x坐标 private int mXCenter; // 圆心y坐标 private int mYCenter; public ThreeArcView(Context context) { this(context, null); } public ThreeArcView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public ThreeArcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initAttrs(context, attrs); initVariable(); } private void initAttrs(Context context, AttributeSet attrs) { TypedArray typeArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ThreeArcView, 0, 0); mArcStrokeWidth = typeArray.getDimension(R.styleable.ThreeArcView_ts_strokeWidth, dp2px(context, 20)); // 圆环背景色彩 mBackgroundArcColor = typeArray.getColor(R.styleable.ThreeArcView_ts_bgArcColor, 0xFFFFFFFF); // 圆环色彩 mOutsideArcColor = typeArray.getColor(R.styleable.ThreeArcView_ts_outsideBgColor, 0xFFFFFFFF); mMiddleArcColor = typeArray.getColor(R.styleable.ThreeArcView_ts_middleBgColor, 0xFFFFFFFF); mInsideArcColor = typeArray.getColor(R.styleable.ThreeArcView_ts_insideBgColor, 0xFFFFFFFF); // 圆进度 mOutsideProgress = typeArray.getFloat(R.styleable.ThreeArcView_ts_outsideProgress, 0f); mMiddleProgress = typeArray.getFloat(R.styleable.ThreeArcView_ts_middleProgress, 0f); mInsideProgress = typeArray.getFloat(R.styleable.ThreeArcView_ts_insideProgress, 0f); // 圆环偏移值 mArcOffset = typeArray.getDimension(R.styleable.ThreeArcView_ts_radiusOffset, dp2px(context, 20)); typeArray.recycle(); // 偏移值不能小于画笔宽度的一半,否则会产生笼罩 if (mArcOffset < mArcStrokeWidth / 2){ mArcOffset = mArcStrokeWidth / 2; } } private void initVariable() { //背景圆弧画笔设置 mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mArcStrokeWidth); mPaint.setStrokeCap(Paint.Cap.ROUND);//开启显示边缘为圆形 } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 别离获取冀望的宽度和高度,并取其中较小的尺寸作为该控件的宽和高 int measureWidth = MeasureSpec.getSize(widthMeasureSpec); int measureHeight = MeasureSpec.getSize(heightMeasureSpec); //裁剪出一个 (宽:高) = (2:1) 的矩形 int finalWidth = measureWidth; int finalHeight = measureHeight; if (measureWidth >= measureHeight * 2) { finalWidth = measureHeight * 2; } else { finalHeight = measureWidth / 2; } setMeasuredDimension(finalWidth, finalHeight); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); // 初始半径 float originalRadius = getWidth() * .5f; // 画笔半宽 float halfArcStokeWidth = mArcStrokeWidth * .5f; // 外圆环半径=初始半径-画笔半宽 float outSideArcRadius = originalRadius - halfArcStokeWidth; drawArc(canvas, mOutsideProgress, mOutsideArcColor, outSideArcRadius); // 中圆环半径=外圆的半径-圆环偏移值-画笔半宽 float middleArcRadius = outSideArcRadius - mArcOffset - halfArcStokeWidth; drawArc(canvas, mMiddleProgress, mMiddleArcColor, middleArcRadius); // 内圆环半径=中圆的半径-圆环偏移值-画笔半宽 float insideArcRadius = middleArcRadius - mArcOffset - halfArcStokeWidth; drawArc(canvas, mInsideProgress, mInsideArcColor, insideArcRadius); } /** * 绘制圆弧 * @param canvas * @param progress 进度 * @param color 进度色彩 * @param radius 圆弧半径 */ private void drawArc(Canvas canvas, float progress, int color, float radius){ // 圆心 mXCenter = getWidth() / 2; mYCenter = getHeight() ; mPaint.setColor(mBackgroundArcColor); // 结构边界矩形 RectF oval = new RectF(); oval.left = (mXCenter - radius); oval.top = (mYCenter - radius); oval.right = mXCenter + radius; oval.bottom = radius * 2 + (mYCenter - radius); //绘制圆弧背景 canvas.drawArc(oval, -180, 180, false, mPaint); //绘制圆弧进度 float showDegree = progress / 100 * 180; mPaint.setColor(color); canvas.drawArc(oval, -180, showDegree, false, mPaint); } private void setOutSideProgress(float progress){ this.mOutsideProgress = progress; postInvalidate(); } private void setMiddleProgress(float progress){ this.mMiddleProgress = progress; postInvalidate(); } private void setInsideProgress(float progress){ this.mInsideProgress = progress; postInvalidate(); } public void setProgress(float outSideProgress, float middleProgress, float insideProgress) { mOutsideProgress = outSideProgress; mMiddleProgress = middleProgress; mInsideProgress = insideProgress; postInvalidate(); } public int dp2px(Context context, float dipValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dipValue * scale + 0.5f); } public int sp2px(Context context, float spValue) { final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (spValue * fontScale + 0.5f); } public int px2sp(Context context, float pxValue) { final float fontScale = context.getResources().getDisplayMetrics().scaledDensity; return (int) (pxValue / fontScale + 0.5f); }}styes.xml<declare-styleable name="ThreeArcView"> <!-- 画笔宽度 --> <attr name="ts_strokeWidth" format="dimension" /> <!-- 圆弧背景色 --> <attr name="ts_bgArcColor" format="color" /> <!-- 外圆进度色彩 --> <attr name="ts_outsideBgColor" format="color" /> <!-- 中圆进度色彩 --> <attr name="ts_middleBgColor" format="color" /> <!-- 内圆进度色彩 --> <attr name="ts_insideBgColor" format="color" /> <!-- 外圆进度 --> <attr name="ts_outsideProgress" format="float" /> <!-- 中圆进度 --> <attr name="ts_middleProgress" format="float" /> <!-- 内圆进度 --> <attr name="ts_insideProgress" format="float" /> <!-- 圆偏移值 --> <attr name="ts_radiusOffset" format="dimension" /></declare-styleable>OK,本文到此结束,若发现问题,欢送一起留言一起探讨,感激~ ...

December 14, 2021 · 4 min · jiezi

关于android:Android就业市场究竟怎么样还能不能坚持下去

我的状况自己一般本科毕业,计算机专业,从事Android开发也有3年了。2020年因为疫情的起因,整个互联网行业都进入了低谷期,许多公司都进行了裁员,而我所在的公司也不例外。就这样,我来到了工作了3年多的公司。开启了2020年的面试旅程。 当初抉择从事Android开发,前景怎么样呢?当初想想,挪动互联网的倒退人不知;鬼不觉曾经十多年了,Mobile First 也曾经变成了 AI First。换句话说,咱们曾经不再是“风口上的猪”。挪动开发的光环和溢价开始缓缓隐没,并且正在向 AI、区块链等新的畛域转移。挪动开发的新鲜血液也曾经变少,最显著的是国内应届生都纷纷涌向了 AI 方向。 能够说,国内挪动互联网的红利期曾经过来了,当初是增量降落、存量厮杀,从抢夺用户到抢夺时长。比拟显著的是手机厂商纷纷互联网化,与传统互联网企业间接竞争。另外一方面,过来渠道的打法失灵,小程序、快利用等新兴渠道崛起,无论是手机厂商,还是各大 App 都把出海摆到了策略的地位。 这一年里我也遇到很多艰难,也踩了很多坑,同时我本人也摸索了很多的学习办法,总结了很多心得体会,并且,我对招聘也做了一些钻研和相应的筹备。在往年的秋季招聘完结当前,参考了阿里P7,腾讯T12等大佬的面试总结经验做成专题,以便分享给更多将来将要加入招聘的敌人。 面试专题合集目录1.如何对 Android 利用进行性能剖析 2.什么状况下会导致内存泄露 3.如何防止 OOM 异样 4.Android 中如何捕捉未捕捉的异样 5.ANR 是什么?怎么防止和解决 ANR(重要) 6.Android 线程间通信有哪几种形式 7.Devik 过程,linux 过程,线程的区别 8.形容一下 android 的零碎架构 9.android 利用对内存是如何限度的?咱们应该如何正当应用内存? 简述 android 应用程序构造是哪些11.请解释下 Android 程序运行时权限与文件系统权限的区别 12.Framework 工作形式及原理,Activity 是如何生成一个 view 的,机制是什么 13.多线程间通信和多过程之间通信有什么不同,别离怎么实现 14.Android 屏幕适配 15.什么是 AIDL 以及如何应用 16.Handler 机制 17.事件散发机制 18.子线程发消息到主线程进行更新 UI,除了 handler 和 AsyncTask,还有什么 19.子线程中能不能 new handler?为什么 20.Android 中的动画有哪几类,它们的特点和区别是什么 21.如何批改 Activity 进入和退出动画 22.SurfaceView & View 的区别 ...

December 14, 2021 · 3 min · jiezi

关于android:2022-Android面试题含答案

此面试题合集分为9个局部:Java根底、Android根底、UI控件篇、网络通信篇、架构设计篇、性能优化篇、源码流程篇、新技术篇、面试篇,共1932页,从知识点到面试题到新技术都进行了全面以及具体的解析! 残缺目录如下: 如果感觉此合集对你有用的话,能够间接群文件支付! 局部内容截图第一章 Java根底动态外部类和非动态外部类的比拟多态的了解与利用java办法的多态性了解java中接口和继承的区别线程池的益处,详解,单例(相对好记)线程池的长处及其原理线程池的长处(重点)为什么不举荐通过Executors间接创立线程池不怕难之BlockingQueue及其实现深刻了解ReentrantLock与ConditionJava多线程:线程间通信之LockSynchronized 关键字原理ReentrantLock原理HashMap中的Hash抵触解决和扩容机制JVM常见面试题JVM内存构造类加载机制/双亲委托 第二章 Android根底Activity知识点(必问)Fragment知识点Service知识点Intent知识点 第三章 UI控件篇屏幕适配次要控件优化事件散发与嵌套滚动动态化页面构建计划第四章 网络通信篇网络协议第五章 架构设计篇MVP架构设计组件化架构 第六章 性能优化篇启动优化内存优化绘制优化安装包优化第七章 源码流程篇开源库源码剖析Glide源码剖析Android面试题:Glideday 20 面试题:Glide面试题聊一聊对于Glide在面试中的那些事面试官:简历上如果写Glide,请留神以下几点...OkHttp源码剖析okhttp连接池复用机制okhttp 流程和优化的实现一篇让你受用的okhttp剖析OkHttp面试之--OkHttp的整个异步申请流程OkHttp面试之--HttpEngine中的sendRequest办法详解OkHttp解析大总结Okhttp工作队列工作原理Android高频面试专题 - 架构篇(二)okhttp面试必知必会Android 网络优化,应用 HTTPDNS 优化 DNS,从原理到 OkHttp 集成Retrofit源码剖析RxJava源码剖析 第八章 新技术篇实战问题篇第九章 面试篇开源文档面试文献 篇幅无限,具体内容就不作齐全展现了,须要此面试题合集的群文件支付~ 最初祝大家都能把握住每一次的晋升机会,成为更高级的开发人员。

December 13, 2021 · 1 min · jiezi

关于android:零代码快速集成AGC崩溃服务xamarin框架iOS

华为AGC的解体服务反对跨平台,依照文档整顿了个Xamarin插件集成的文档,有须要的开发者能够参考。 环境配置和我的项目设置1.装置Xamarin环境 次要是先装置visual studio for MAC,而后装置Mobile development with .NET,具体能够参考Xamarin环境搭建。 2.AGC创立我的项目工程,并且开明华为剖析服务。 这部分是基本操作,能够参见创立我的项目和开明华为剖析 3.集成AGC Xamarin NuGet包 点击创立的我的项目工程,右键抉择”Manage NuGet Packages” 抉择对应的包后装置: 持续增加HA包,留神须要抉择1.2.0.300版本: 4.增加Json文件到我的项目目录下 5.将“Build Action”设置为“BundleResource”。 6.设置利用包名。 7.配置收费预配证书 如果没有申请付费证书,能够应用收费证书,具体参见: https://developer.huawei.com/... 集成实现1.布局界面设计 双击main.storyboard拉起Xcode创立3个按键“MakeCrash”,” CatchException”,” CustomReport”。 2.代码调用 编辑 ViewController.cs 文件, 调用 AGCCrash.GetSharedInstance.TestIt 制作一次解体事件,调用 AGCCrash.GetSharedInstance.SetUserId 自定义用户标识,调用 AGCCrash.GetSharedInstance.SetCustomKey 自定义键值对,调用 AGCCrash.GetSharedInstance.Log 自定义日志级别,调用 AGCCrash.GetSharedInstance. RecordException 产生并记录一次非重大异样。 using System;using UIKit;using Huawei.Agconnect.Crash;using Foundation;  namespace crashios0512{    public partial class ViewController : UIViewController    {        public ViewController(IntPtr handle) : base(handle)        {        }         public override void ViewDidLoad()        {            base.ViewDidLoad();            // Perform any additional setup after loading the view, typically from a nib.        }         public override void DidReceiveMemoryWarning()        {            base.DidReceiveMemoryWarning();            // Release any cached data, images, etc that aren't in use.        }          partial void MakeCrash(UIKit.UIButton sender)        {            AGCCrash.GetSharedInstance().TestIt();         }         partial void CatchException(UIKit.UIButton sender)        {            AGCCrash.GetSharedInstance().RecordError(new Foundation.NSError());        }         partial void CustomReport(UIKit.UIButton sender)        {            AGCCrash.GetSharedInstance().SetUserId("testuser");            AGCCrash.GetSharedInstance().Log("default info level");            AGCCrash.GetSharedInstance().SetCustomValue(new NSString("test"), "this is string value");            AGCCrash.GetSharedInstance().LogWithLevel(AGCCrashLogLevel.Warning, "this is warning log level");            AGCCrash.GetSharedInstance().SetCustomValue(new NSNumber(123), "this is number");                 }    }}解体报告查看集成完后点击按键制作解体和非重大异样,并产生自定义报告,能够在AGC页面查看 1.解体概览 2.问题概览 3.查看解体详情堆栈 4.查看自定义键值对 5.查看自定义日志级别 6.查看自定义用户标识 欲了解更多详情,请参见: 1、华为AGC 解体服务文档:https://developer.huawei.com/... ...

December 13, 2021 · 1 min · jiezi

关于android:Android-App秒开的奥秘

什么是秒开Android App秒开,广义的讲是指你的App的Activity从启动到显示所破费的工夫在1秒以内,狭义的讲是指这个过程所破费的工夫越少越好。这个工夫越短,你的App给用户的感觉就是响应越快,应用越晦涩,用户体验更好。秒开是Android App的一个很重要的性能指标。须要咱们继续的给予关注和优化。 如何优化秒开Google提供了很多性能优化的倡议和官网的工具,网上也有十分多的对于Android App性能优化的文章和工具,能够帮忙你解决大部分卡顿的问题。然而事实却可能是即便你付出了很多精力去做优化,你的App还是在启动新Activity的时候破费过多的工夫。特地是随着需要的一直增长,你的App会变得复杂而宏大,要做优化首先要定位须要优化的点,而这会变得愈发艰难。同时大型App在启动新Activity的工夫破费过多状况呈现的可能性反而会越来越大。 在泛滥的优化倡议中,有一条比拟根本的准则是尽量避免在主线程(或者说UI线程)中进行耗时操作。例如文件读写操作、网络申请、大量计算、循环等等。直观的了解是因为启动新Activity须要在主线程执行很多代码,例如onCreate()等生命周期的回调。如果此时有耗时操作的代码在主线程被执行,到新Activity展现进去所须要的工夫就会缩短。要优化秒开,首先要能监测主线程的运行状态,那么问题来了,主线程到底是怎么在运行呢?你的代码又是什么时候,如何在主线程被执行的呢? 深刻主线程要理解主线程的工作过程,首先要理解Android的音讯机制。 音讯机制先看一下现实生活中的一个例子,尽管当初都是挪动领取了,但置信大家都去银行取过钱。当你达到银行的时候,如果你是第一个,那祝贺你,你能够马上到柜员那里办理你的业务;如果你后面还有人,那就比拟惨了,你须要排队,得等到你后面的人都办完业务才会轮到你;更可怕的是如果你后面有几位须要办理的业务破费的工夫比拟长,那你须要等更长的工夫;前面来的人则会按程序排在你身后,和你一样不耐烦的推敲什么时候能力轮到本人。 形象一下,音讯机制其实和这个例子非常相似。每个人都看做是个音讯,什么时候到的银行是不确定的。柜员能够看做一个音讯处理器,他帮你办业务就相当于在解决你的音讯;而人们依照先后顺序排起来的队伍能够看做是个音讯队列。所以这个过程能够形象为有个音讯处理器,他有个音讯队列,随机来到的音讯依照肯定顺序排列在这个队列里,音讯处理器不停的从队列头部获取音讯而后解决之,周而复始的循环反复这个过程。如下图所示: 那么Android是怎么实现这个音讯机制的呢? Android的音讯机制音讯机制首先得有音讯,在Android中就是Message。怎么能确定一个音讯呢?音讯要有起源或者指标,也就是target;音讯要表明本人要做什么,也就是what或者callback;音讯要表明本人心愿在什么时候执行,也就是when。有了这几个因素,基本上这个音讯就是个齐备的音讯了,能够被退出到音讯队列中了。Android中的音讯队列是MessageQueue。音讯解决循环是Looper。Looper是个死循环,不停的从MessageQueue中获取音讯而后解决之,具体的执行是在Handler外面进行的。另外音讯退出音讯队列也须要Handler来操作。Message,MessageQueue,Looper,Handler组合在一起,就形成了整个Android的音讯机制。 Android的主线程就运行着这样一个音讯机制。 Android的主线程主线程是在ActivityThread中创立的,能够看到在main函数中 public static void main(String[] args) { ... Looper.prepareMainLooper(); ... Looper.loop(); }主线程实现了一个音讯机制。所以Android的主线程就是个音讯解决的循环。它所做的工作就是在不停的从音讯队列获取音讯,解决音讯,周而复始。你的App所有的在UI上的操作,例如点击事件的解决、页面动画、显示更新页面、View绘制、启动新Activity等操作都是在给主线程发消息,主线程而后挨个解决这些音讯。 主线程如何影响秒开咱们理解了主线程的工作机制后,就要看看主线程中的音讯解决是如何影响Activity秒开的。当咱们要启动一个新的Activity的时候,从调用startActivity开始到新Activity显示进去,Android零碎会发送一系列的音讯给主线程。这一系列的音讯解决所破费的总工夫会影响页面的秒开,如果执行工夫过长,用户就会有响应十分慢的感觉。此外,除了Android零碎会给主线程发消息,App本身也会给主线程发消息,如果在启动新Activity的过程中,这些App本人的音讯正好插入这一系列的Android零碎音讯中,那也会导致总的解决工夫缩短,造成不能秒开。 上图代表了启动新Activity的主线程的三种状况,每个矩形代表主线程解决一个音讯所花的工夫,越宽代表解决的工夫越长。绿色填充的代表这是一个Android零碎发过来的音讯;蓝色填充的代表这是一个App本人发过来的音讯。最下方的向右箭头代表工夫,终点是startActivity被调用的时刻。 第一种情况代表失常的情景,主线程中只有和startActivity相干的零碎音讯被解决,而且解决每个音讯所破费的工夫都在正当范畴内。所以这个页面能够满足秒开。第二种状况代表一个异样的情景,尽管主线程解决的音讯都是零碎音讯,然而某一个或某几个音讯的解决工夫超出了正当值,导致页面不能秒开。第三种状况代表另一种异样的情景,在零碎音讯中混入了App本人的音讯,主线程不仅要解决零碎音讯,还要解决App本人的音讯,后果就是总的启动工夫要额定加上App音讯的解决工夫,导致页面不能秒开。理论状况中还有可能会呈现既有零碎音讯解决工夫过长同时也混有App本人的音讯的情景。秒开优化理解了影响秒开的因素之后,咱们只有有方法能监测主线程中每个音讯解决工夫,咱们就能定位到造成页面卡慢的起因,而后再做优化。幸好Android工程师为咱们在Looper中预留了打log的地位。 public static void loop() { final Looper me = myLooper(); ... final MessageQueue queue = me.mQueue; ... for (;;) { Message msg = queue.next(); // might block ... Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } ... msg.recycleUnchecked(); } }public void setMessageLogging(@Nullable Printer printer) { mLogging = printer; }可见在音讯被解决的开始和解决完结之后都会打印log。你只须要在代码中调用Looper.setMessageLogging()设置一下就好。 ...

December 13, 2021 · 1 min · jiezi

关于android:Android性能优化笔记一启动优化

利用启动类型冷启动场景:开机后第一次启动利用 或者 利用被杀死后再次启动生命周期:Process.start->Application创立->attachBaseContext->onCreate->onStart->onResume->Activity生命周期启动速度:在几种启动类型中最慢,也是咱们优化启动速度最大的拦路虎温启动场景:利用曾经启动,返回键退出生命周期:onCreate->onStart->onResume->Activity生命周期启动速度:较快热启动场景:Home键最小化利用生命周期:onResume->Activity生命周期启动速度:快从下面的总结能够看出,在利用的启动过程中,冷启动是最慢最耗时的,零碎以及利用自身都有大量的工作须要解决,所以,冷启动对于利用的启动速度是最具挑战以及最有必要进行优化的。 冷启动流程冷启动指的是应用程序从过程在零碎不存在,到零碎创立利用运行过程空间的过程。冷启动通常会产生在一下两种状况: 设施启动以来首次启动应用程序零碎杀死应用程序之后再次启动应用程序在冷启动的最开始,零碎须要负责做三件事: 加载以及启动appapp启动之后立即显示一个空白的预览窗口创立app过程一旦零碎实现创立app过程后,app过程将要接着负责实现上面的工作: 创立Application对象创立并且启动主线程ActivityThread创立启动第一个ActivityInflating views布局屏幕执行第一次绘制一旦app过程完实现了第一次绘制工作,零碎过程就会用main activity替换后面显示的预览窗口,这个时候,用户就能够正式开始与app进行交互了。 ![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2018/12/26/167e9f92508dd4ec~tplv-t2oaga2asx-watermark.image) 从冷启动的流程看,咱们无奈干涉app过程创立等零碎操作,咱们可能干涉的有: 预览窗口Application生命周期回调Activity生命周期回调优化剖析测量工具对研发人员来说,启动速度是咱们的“门面”,它清清楚楚能够被所有人看到,咱们都心愿本人利用的启动速度能够秒杀所有竞争对手。 “工欲善其事必先利其器”,咱们须要先找到一款适宜做启动优化剖析的工具或者形式。 adb shell am start -W [packageName]/[ packageName. AppstartActivity]在统计 app 启动工夫时,零碎为咱们提供了 adb 命令,能够输入启动工夫。零碎在绘制实现后,ActivityManagerService 会回调该办法,然而可能不便咱们通过脚本屡次启动测量 TotalTime,比照版本间启动工夫差别。然而统计工夫不如 Systrace 精确。 代码埋点通过代码埋点来精确获取记录每个办法的执行工夫,晓得哪些地方耗时,而后再有针对性地优化。例如通过在 app 启动生命周期中,要害地位退出工夫点记录,达到测量目标;又例如能够在 Application 的 attachBaseContext办法中记录开始工夫,而后在启动的第一个 Activity 的 onWindowFocusChanged办法记录完结工夫。 然而从用户点击 app Icon 到 Application 被创立,再到 Activity 的渲染,两头还是有很多步骤的,比方冷启动的过程创立过程,而这个工夫用此版本是没方法统计了,必须得接受这点数据的不准确性。 NanoscopeNanoscope 十分实在,不过临时只反对 Nexus 6 和 x86 模拟器。 SimpleperfSimpleperf 的火焰图并不适宜做启动流程剖析。 TraceView通过 TraceView 次要能够失去两种数据:单次执行耗时的办法 以及 执行次数多的办法。然而TraceView 性能耗损太大,不能比拟正确反映真实情况。 SystraceSystrace 可能追踪要害零碎调用的耗时状况,如零碎的 IO 操作、内核工作队列、CPU 负载、Surface 渲染、GC 事件以及 Android 各个子系统的运行状况等。然而不反对利用程序代码的耗时剖析。 ...

December 13, 2021 · 3 min · jiezi

关于android:快速入门Python基础知识

读完本篇文章后,可对 Python 语言个性、编码格调有肯定理解,并可写出简略的 Python 程序。 一、装置与运行各个系统的 Python 装置教程请自行查阅材料,这里不再赘述。 查看 Python 版本,在命令行输出 python 即可,同时会进入命令行交互模式,能够在这里执行 python 命令。 如果电脑中装置了 python2.x 和 python3.x 两个版本,输出 python 运行的是 2.x 版本。想运行 3.x,则需输出 python3。 在命令行输出 python : Solo-mac:~ solo$ pythonPython 2.7.10 (default, Aug 17 2018, 19:45:58)[GCC 4.2.1 Compatible Apple LLVM 10.0.0 (clang-1000.0.42)] on darwinType "help", "copyright", "credits" or "license" for more information.>>>在命令行输出 python3 : Solo-mac:~ solo$ python3Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 26 2018, 23:26:24)[Clang 6.0 (clang-600.0.57)] on darwinType "help", "copyright", "credits" or "license" for more information.>>>输出 exit() 即可退出命令行模式。 ...

December 13, 2021 · 10 min · jiezi

关于android:记一个-Compose-版华容道你值得拥有

基本思路游戏逻辑比较简单,所以没有应用 MVI 之类的框架,然而整体依然听从数据驱动UI的设计思维: 定义游戏的状态基于状态的UI绘制用户输出触发状态变动 1. 定义游戏状态游戏的状态很简略,即以后各棋子(Chees)的摆放地位,所以能够将一个棋子的 List 作为承载 State 的数据结构 1.1 棋子定义先来看一下单个棋子的定义 data class Chess(    val name: String, //角色名称    val drawable: Int //角色图片    val w: Int, //棋子宽度    val h: Int, //棋子长度    val offset: IntOffset = IntOffset(0, 0) //偏移量)通过 w,h 能够确定棋子的形态,offset 确定在棋牌中的以后地位 1.2 开局棋子摆放接下来咱们定义各个角色的棋子,并依照开局的状态摆放这些棋子 val zhang = Chess("张飞", R.drawable.zhangfei, 1, 2)val cao = Chess("曹操", R.drawable.caocao, 2, 2)val huang = Chess("黄忠", R.drawable.huangzhong, 1, 2)val zhao = Chess("赵云", R.drawable.zhaoyun, 1, 2)val ma = Chess("马超", R.drawable.machao, 1, 2)val guan = Chess("关羽", R.drawable.guanyu, 2, 1)val zu = buildList {  repeat(4) { add(Chess("卒$it", R.drawable.zu, 1, 1)) } }各角色的定义中明确棋子形态,比方“张飞”的长宽比是 2:1,“曹操” 的长宽比是2:2。 接下来定义一个游戏开局: val gameOpening: List<Triple<Chess, Int, Int>> = buildList {    add(Triple(zhang, 0, 0)); add(Triple(cao,   1, 0))    add(Triple(zhao,  3, 0)); add(Triple(huang, 0, 2))    add(Triple(ma,    3, 2)); add(Triple(guan,  1, 2))    add(Triple(zu[0], 0, 4)); add(Triple(zu[1], 1, 3))    add(Triple(zu[2], 2, 3)); add(Triple(zu[3], 3, 4))}Triple 的三个成员别离示意棋子以及其在棋盘中的偏移,例如  Triple(cao, 1, 0) 示意曹操开局处于(1,0)坐标。 最初通过上面代码,将 gameOpening 转化为咱们所需的 State, 即一个 List<Chess>: const val boardGridPx = 200 //棋子单位尺寸fun ChessOpening.toList() =    map { (chess, x, y) ->        chess.moveBy(IntOffset(x * boardGridPx, y * boardGridPx))    }2. UI渲染,绘制棋局有了 List<Chess> 之后,顺次绘制棋子,从而实现整个棋局的绘制。 @Composablefun ChessBoard (chessList: List<Chess>) {    Box(        Modifier            .width(boardWidth.toDp())            .height(boardHeight.toDp())    ) {        chessList.forEach { chess ->             Image( //棋子图片                    Modifier                        .offset { chess.offset } //偏移地位                        .width(chess.width.toDp()) //棋子宽度                        .height(chess.height.toDp())) //棋子高度                    painter = painterResource(id = chess.drawable),                    contentDescription = chess.name             )        }    }}Box 确定棋盘的范畴,Image 绘制棋子,并通过 Modifier.offset{ } 将其摆放到正确的地位。 到此为止,咱们应用 Compose 绘制了一个动态的开局,接下来就是让棋子追随手指动起来,这就波及到 Compose Gesture 的应用了 3. 拖拽棋子,触发状态变动Compose 的事件处理也是通过 Modifier 设置的, 例如 Modifier.draggable(), Modifier.swipeable() 等能够做到开箱即用。华容道的游戏场景中,能够应用 draggable 监听拖拽 3.1 监听手势1) 应用 draggable 监听手势棋子能够x轴、y轴两个方向进行拖拽,所以咱们别离设置两个 draggable : @Composablefun ChessBoard (    chessList: List<Chess>,    onMove: (chess: String, x: Int, y: Int) -> Unit) {    Image(        modifier = Modifier           ...           .draggable(//监听程度拖拽                 orientation = Orientation.Horizontal,                 state = rememberDraggableState(onDelta = {                     onMove(chess.name, it.roundToInt(), 0)                 })            )            .draggable(//监听垂直拖拽                 orientation = Orientation.Vertical,                 state = rememberDraggableState(onDelta = {                     onMove(chess.name, 0, it.roundToInt())                 })            ),            ...    )}orientation 用来指定监听什么方向的手势:程度或垂直。rememberDraggableState保留拖动状态,onDelta 指定手势的回调。咱们通过自定义的 onMove 将拖拽手势的位移信息抛出。 此时有人会问了,draggable 只能监听或者程度或者垂直的拖拽,那如果想监听任意方向的拖拽呢,此时能够应用 detectDragGestures 2) 应用 pointerInput 监听手势draggable , swipeable 等,其外部都是通过调用 Modifier.pointerInput() 实现的,基于 pointerInput 能够实现更简单的自定义手势: fun Modifier.pointerInput(    key1: Any?,    block: suspend PointerInputScope.() -> Unit) : Modifier = composed (...) {    ...}pointerInput 提供了 PointerInputScope,在其中能够应用suspend函数对各种手势进行监听。例如,能够应用 detectDragGestures 监听任意方向的拖拽: suspend fun PointerInputScope.detectDragGestures(    onDragStart: (Offset) -> Unit = { },    onDragEnd: () -> Unit = { },    onDragCancel: () -> Unit = { },    onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit)detectDragGestures 也提供了程度、垂直版本供选择,所以在华容道的场景中,也能够应用以下形式进行程度和垂直方向的监听: @Composablefun ChessBoard (    chessList: List<Chess>,    onMove: (chess: String, x: Int, y: Int) -> Unit) {    Image(        modifier = Modifier            ...            .pointerInput(Unit) {                scope.launch {//监听程度拖拽                    detectHorizontalDragGestures { change, dragAmount ->                        change.consumeAllChanges()                        onMove(chess.name, 0, dragAmount.roundToInt())                    }                }                scope.launch {//监听垂直拖拽                    detectVerticalDragGestures { change, dragAmount ->                        change.consumeAllChanges()                        onMove(chess.name, 0, dragAmount.roundToInt())                    }                }            },            ...    )}须要留神 detectHorizontalDragGestures 和  detectVerticalDragGestures 是挂起函数,所以须要别离启动协程进行监听,能够类比成多个 flow 的 collect。 3.2 棋子的碰撞检测获取了棋子拖拽的位移信息后,能够更新棋局状态并最终刷新UI。然而在更新状态之前须要对棋子的碰撞进行检测,棋子的拖拽是有边界的。 碰撞检测的准则很简略:棋子不能越过以后挪动方向上的其余棋子。 1) 绝对地位断定首先,须要确定棋子之间的绝对地位。能够应用上面办法,断定棋子A在棋子B的上方: val Chess.left get() = offset.xval Chess.right get() = left + widthinfix fun Chess.isAboveOf(other: Chess) =    (bottom <= other.top) && ((left until right) intersect (other.left until other.right)).isNotEmpty()拆解上述条件表达式,即 棋子A的下边界位于棋子B上边界之上 且 在程度方向上棋子A与棋子B的区域有交加: 比方下面的棋局中,能够失去如下断定后果: 曹操 位于 关羽 之上关羽 位于 卒1 黄忠 之上卒1 位于 卒2 卒3 之上尽管地位上 关羽位于卒2的上方,然而从碰撞检测的角度看,关羽 和 卒2 在x轴方向没有交加,因而 关羽 在y轴方向上的挪动不会碰撞到 卒2, guan.isAboveOf(zu1) == false同理,其余几种地位关系如下: infix fun Chess.isToRightOf(other: Chess) =    (left >= other.right) && ((top until bottom) intersect (other.top until other.bottom)).isNotEmpty()infix fun Chess.isToLeftOf(other: Chess) =    (right <= other.left) && ((top until bottom) intersect (other.top until other.bottom)).isNotEmpty()infix fun Chess.isBelowOf(other: Chess) =    (top >= other.bottom) && ((left until right) intersect (other.left until other.right)).isNotEmpty()2) 越界检测接下来,判断棋子挪动时是否越界,即是否越过了其挪动方向上的其余棋子或者出界 ...

December 13, 2021 · 1 min · jiezi

关于android:Android笔记apk嵌套

a.apk-主利用 b.apk-被启动利用次要思维:把b.apk放到assets目录下,因为有大小限度(1M),所以改名成b.mp3(因为mp3,jpg,png,mp4等不会查看,不会限度大小),而后在用的时候再改回来 1.具体实现:public void intallApp(Context context) {try {String path = context.getFilesDir().getAbsolutePath()+ "/b.apk"; //从assets中解压到这个目录File f = new File(path);if (!f.exists()) {f.createNewFile();}InputStream is = context.getAssets().open("b.mp3");//assets里的文件在利用装置后依然存在于apk文件中inputStreamToFile(is, f);String cmd = "chmod 777 " + f.getAbsolutePath();Runtime.getRuntime().exec(cmd);cmd = "chmod 777 " + f.getParent();Runtime.getRuntime().exec(cmd);// 尝试晋升上2级的父文件夹权限,在浏览插件下载到手机存储时,刚好用到了2级目录// /data/data/packagename/files/这个目录上面所有的层级目录都须要晋升权限,才可装置apk,弹出装置界面cmd = "chmod 777 " + new File(f.getParent()).getParent();Runtime.getRuntime().exec(cmd);Intent intent = new Intent();intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);intent.setAction(android.content.Intent.ACTION_VIEW);String type = "application/vnd.android.package-archive";/* 设置intent的file与MimeType */intent.setDataAndType(Uri.fromFile(f), type);context.startActivity(intent);} catch (ActivityNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}public void inputStreamToFile(InputStream inputStream,File file){///InputStream inputStream = null;OutputStream outputStream = null;try {// read this file into InputStream//inputStream = new FileInputStream("test.txt"); // write the inputStream to a FileOutputStreamoutputStream = new FileOutputStream(file); int read = 0;byte[] bytes = new byte[1024]; while ((read = inputStream.read(bytes)) != -1) {outputStream.write(bytes, 0, read);} System.out.println("Done!"); } catch (IOException e) {e.printStackTrace();} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}if (outputStream != null) {try {// outputStream.flush();outputStream.close();} catch (IOException e) {e.printStackTrace();} }}}2.如果是启动已装置的利用,实现如下:public boolean startApp(Context context, String packageName) {//String packageName = "XXX";Intent intent = new Intent(Intent.ACTION_MAIN, null);intent.addCategory(Intent.CATEGORY_LAUNCHER);PackageManager pm = context.getPackageManager();List<ResolveInfo> listInfos = pm.queryIntentActivities(intent, 0);String className = null;for (ResolveInfo info : listInfos) {if (packageName.equals(info.activityInfo.packageName)) {className = info.activityInfo.name;break;}}if (className != null && className.length() > 0) {intent.setComponent(new ComponentName(packageName, className));intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);context.startActivity(intent);return true;}return false;}如果你晓得包名,还晓得作为启动的那activity的类名,就更简略了,就能够省掉下面查找的过程,间接启动。 ...

December 13, 2021 · 1 min · jiezi

关于android:华为云函数中使用云数据库的JavaScript-SDK基础入门

背景介绍应用云数据库Server端的SDK,此处我以华为提供的官网Demo为例,他们的Demo也曾经开源放在了GitHub上,大家须要的能够自行下载。 https://github.com/AppGallery... 下载完Demo的我的项目代码后,还须要依照以下步骤操作: 后期在调研华为云函数服务的时候,发现能够在云函数中配置云数据库的触发器,能够通过云数据库的插入删除批改等事件,用来触发云函数中代码的执行。当初华为AGC的云数据库服务全网公布了,并且还提供了Server端的JavaScript SDK,刚好能够运行在云函数反对的Node.js环境中。话不多说,先来尝试一下如何在云函数中应用云数据库提供的JavaScript SDK,进行最简略的数据的插入和查问吧。 开明服务这里波及开明两个服务: 首先须要应用华为账号登录AGC网站 ,依照以下步骤进行操作: 1.开明云函数服务在AGC内抉择 "我的我的项目"–>抉择对应的我的项目->"构建"->"云函数",界面上点击 "立刻开明" , 开明当前点击右侧的创立函数即可。 2.开明云数据库服务在AGC内抉择 "我的我的项目"–>抉择对应的我的项目->"构建"->"云数据库",界面上点击“立刻开明”,开明当前,还须要创立对应的对象类型和存储区。 此处对应类型和存储区的创立,我都是依照数据库文档中的应用入门来创立的。 包含创立一个 BookInfo 的对象类型, 还有一个QuickStartDemo 的存储区。 云数据库JavaScript代码开发应用云数据库Server端的SDK,此处我以华为提供的官网Demo为例,他们的Demo也曾经开源放在了GitHub上,大家须要的能够自行下载。 https://github.com/AppGallery... 下载完Demo的我的项目代码后,还须要依照以下步骤操作: 1.导出对象类型文件 在云数据库的界面中,对曾经创立的对象类型BookInfo,导出成js的ServerSDK的格局,将其放到demo的model目录下。 2.下载认证凭据。 AGC的界面中,抉择我的项目设置,在ServerSDK页签下,点击 “下载认证凭据” 进行下载,并且同样放到demo的model目录下。 3、批改credentialPath门路。 因为我的认证凭据下载当前放在model目录下,因而还须要同步批改代码中credentialPath门路,在CloudDBZoneWrapper.js文件中,批改初始化的代码,具体如下: (留神对于云函数环境,获取门路下的文件,要应用_dirname办法) let api_client_name = "agc-apiclient-testDB.json";let path = require('path');let api_client_path = path.join(__dirname,api_client_name);agconnect.AGCClient.initialize(agconnect.CredentialParser.toCredential(api_client_path));4.配置函数入口。 我这没有应用示例代码中默认的Start.js的接口,而是本人创立了一个inde.js的文件作为函数的入口,对应的代码如下 const CloudDBZoneWrapper = require("./model/CloudDBZoneWrapper.js");let myHandler = async function(event, context, callback, logger) {logger.info(JSON.stringify(event));logger.info("event start");const cloudDBZoneWrapper = new CloudDBZoneWrapper();const bookInfo = cloudDBZoneWrapper.getSingleBook();// upsert a list of booksawait cloudDBZoneWrapper.upsertBookInfos(bookInfo);let result = {"message":"Run Success"}callback(result);}module.exports.myHandler = myHandler;打包上传与配置触发器1.如需将本地我的项目运行到云函数中,须要将我的项目整体打包上传,而后应用事件进行测试。留神函数入口须要在根目录下 ...

December 13, 2021 · 1 min · jiezi

关于android:Android入门教程-Android-MediaPlayer-播放音频

MediaPlayer的应用形式创立 MediaPlayer 能够间接 new MediaPlayer,也能够用 MediaPlayer 提供的 create 办法创立。 mediaPlayer = new MediaPlayer();应用 create 办法创立胜利后,mediaPlayer 处于 Prepared 状态。能够间接start播放。 mediaPlayer = MediaPlayer.create(getApplicationContext(), Uri.fromFile(file)); mediaPlayer.start();设置音源 - setDataSource 通过调用 setDataSource 来设置音源。setDataSource 有多个重载办法,咱们来看罕用的几种。 例如设置应用 assets 里的资源。理论状况可能须要 try catch。 AssetFileDescriptor fd = null; MediaPlayer mediaPlayer = new MediaPlayer(); fd = context.getApplicationContext().getAssets().openFd(name); mediaPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());本地文件,须要文件的绝对路径。 mediaPlayer.setDataSource(file.getAbsolutePath());或者获取文件的 Uri 来创立 mediaPlayer。 mediaPlayer = MediaPlayer.create(getApplicationContext(), Uri.fromFile(file));设置网络音频,也是用 setDataSource 办法,设置url。 mediaPlayer.setDataSource("https://demo.com/sample.mp3"));播放网络音频时,如果应用的是http,有可能会报错 java.io.IOException: Cleartext HTTP traffic to demo.com not permitted能够简略地设置一下manifest,设置usesCleartextTraffic="true" ...

December 13, 2021 · 3 min · jiezi

关于android:Android开发不会ANR这里有ANR全解析和各种案例包教包会

前言相比于产生应用程序解体,产生ANR更加让人头大,次要起因是解体产生的时候会在Logcat中打印出产生异样的地位,开发人员很容易就能定位到解体并解决,显然ANR没那么轻松;然而咱们大可不必这么难过,因为有问题就会有解决办法,解决不了,只是因为没有用对办法 总体思路导出ANR日志信息,依据日志信息,判断确认产生ANR的包名类名,过程号,产生工夫,导致ANR起因类型等。关注系统资源信息,包含ANR产生前后的CPU,内存,IO等系统资源的应用状况。查看主线程状态,关注主线程是否存在耗时、死锁、等锁等问题,判断该ANR是App导致还是零碎导致的。联合利用日志,代码或源码等,剖析ANR问题产生前,利用是否有异样,其中具体问题具体分析。导出ANR日志ANR问题产生时,零碎会收集ANR相干的日志信息,CPU应用状况,trace日志也就是各线程执行状况等信息,生成一个traces.txt的文件并且放在/data/anr/门路下。 留神:每一次新的ANR问题的产生,会把之前的ANR信息笼罩掉。 咱们能够通过adb命令将traces文件导出到本地。 adb rootadb shell ls /data/anradb pull /data/anr/<filename>读取要害日志信息1)在log中找到ANR产生信息: Traces文件中的关键字,例如: 09-24 15:20:20.211 1001 1543 1570 XXXXXXX: ANR in xxxxxx09-24 15:20:20.211 1001 1543 1570 XXXXXXX: PID: xxxxx09-24 15:20:20.211 1001 1543 1570 XXXXXXX: Reason: xxxxxx其中: ANR in中,包含导致ANR的包名,类名PID 中,为产生ANR的过程PIDReason 中,为导致ANR的起因,例如keyDispatchingTimedOut2)找到CPU Usage信息 09-24 15:20:20.211 1001 1543 1570 XXXXXX: CPUusage from xxx to xxx ago xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx09-24 15:20:20.211 1001 1543 1570 XXXXXX: CPUusage from xxx to xxx later xxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxx其中 ago 示意ANR产生前的CPU的应用状况later示意ANR产生后的CPU的应用状况重点关注xxx%TOTAL: xxx% user + xxx% kernel + xxx% iowait,可通过这几项理解到CPU的占用状况。具体分析剖析CPU usage当前,如若还是无奈找出问题起因,则须要进一步剖析trace文件。traces文件中具体记录了产生ANR前后该过程的各个线程的Stack,个别从主线程的stack动手剖析,查看剖析ANR问题产生前,利用是否有异样。 ...

December 13, 2021 · 1 min · jiezi

关于android:Android-实现抖音传送带特效

一、实现成果1.1 首先来看抖音的传送带特效 从上图能够看到,抖音的传送带特效有如下特点 屏幕左半边局部是失常预览视频屏幕右半边局部像传送带个别,将画面一直地像左边运送依据此特效的特点,咱们能够制作出各种乏味的视频 1.2 笔者实现传送带特效 从上图来看,笔者实现的成果基本上和抖音实现的统一 那么,对于该特效,咱们应该如何去实现呢? 其实在介绍抖音蓝线挑战特效那一章曾经将到一个外围知识点Fbo,对,没错,过后做蓝线挑战特效用到的就是Fbo,接下来传送带特效也须要应用Fbo的保留上一帧性能 接下来,咱们就来进行特效剖析和具体实现 二、特效剖析首先,依据下面的效果图,咱们能够简略画出示意图,如下图所示(小格子的数量越多,画面越精密) 咱们以横向进行剖析 在OpenGLES中,纹理坐标程度方向的起始地位在左方(精确的说是在左上角,这里只是剖析横向的成果,故图上标点0.0随便标在左方,便于剖析) 依据下面的效果图,理解到,该特效有两个特点 屏幕左半边局部是失常预览视频屏幕右半边局部像传送带个别,将画面一直地像左边运送这里,我用了运送一词,那么,咱们得首先晓得,它运送的是什么 2.1 运送什么?通过剖析特效图,咱们晓得,图像右半局部是一直地向左边挪动,而左半局部是失常预览的,看起来就如同是从左半局部的边缘处一直挪动到左边,那么从这里能够得出一个小论断 它运送的是左半局部的边缘区域,依据上图,精确的说是中线右边0区域的画面那么,晓得了这点,咱们就高深莫测了 2.2 它是如何运送的?后面,咱们晓得了它运送的是0区域的画面,那么接下来就来剖析下,它是如何运送的 在预览时,相机画面个别都是失常显示,0区域的画面当然也是失常一帧帧刷新当0区域显示第一帧(简称f1,前面以f开后,数字为帧序)时,将其挪动到1区域当0区域显示f2时,将1区域的f1挪动到2区域,将0区域的f2挪动到1区域顺次类推,就能够将0区域的画面源源不断地运送到左边2.3 Fbo其实,在晓得了它是运送什么,且如何运送后,咱们还是无奈得悉如何实现这一特效 此刻,就该Fbo退场了,后面蓝线挑战特效的篇章曾经对其做了详细描述,当初简略介绍下 能够将Oes纹理转换成2D纹理能够将纹理数据不显示在屏幕上,并保留下来这里,咱们要实现该特效,就要应用它的保留帧数据的性能 2.4 特效实现在下面,咱们曾经晓得了该特效是如何运送数据,那么通过下图,咱们来理解如何应用Fbo实现 从下面的剖析可知,该特效运送的是左半局部的边缘区域,所有有如何下实现步骤: 首先假如每个小格的步长为0.1,那么左半局部的边缘区域就是0.4 ~ 0.5这个区域Fbo能够保留上一帧,那么在渲染时,咱们将上一帧的数据保留下来在渲染的时候,会有两个纹理,一个是相机的失常预览纹理,另一个是保留的上一帧,此时,咱们在着色器里就要进行判断当纹理坐标x小于0.5时,显示相机的失常预览画面当纹理坐标x大于0.5时,显示保留的上一帧画面,不过这里要留神,并不是对应坐标的上一帧数据,即,不是0.5 ~ 1.0区域的数据,而是0.4 ~ 0.9区域的数据,大家能够思考下这是为什么,前面具体实现的时候会有解答这样,当相机一直产生预览数据时,右半局部将一直地将左半局部的边缘区域向左边运送 三、具体实现后面咱们剖析了该特效的整个实现流程,接下来就是具体的实现 首先,先上大家最关怀的着色器代码 3.1 着色器顶点着色器 attribute vec4 aPos;attribute vec2 aCoordinate;varying vec2 vCoordinate;void main(){    vCoordinate = aCoordinate;    gl_Position = aPos;}对于顶点着色器,并没有做任何非凡解决 片元着色器 precision mediump float;uniform sampler2D uSampler;uniform sampler2D uSampler2;varying vec2 vCoordinate;uniform float uOffset;void main(){    if (vCoordinate.x < 0.5) {        gl_FragColor = texture2D(uSampler, vCoordinate);    } else {        gl_FragColor = texture2D(uSampler2, vCoordinate - vec2(uOffset, 0.0));    }}对于片元着色器,要害就在于main()函数外面的if判断,后面也有提到,会对纹理坐标进行一个判断 当x小于0.5时,显示相机预览画面当x大于0.5时,显示上一帧的数据,且取的是对应坐标往左偏移的数据(uOffset是偏移量,能够了解成小格子的宽度)那么对于为什么要偏移呢? 这是因为通过下面,咱们能够晓得,该特效是从左半局部的边缘区域开始运送的,那么如果咱们从对应坐标取,那么不就得不到左半局部区域的坐标了吗,所有得偏移一个小格子的宽度,从而失去对应的数据 这样,每帧渲染时,都取0.4 ~ 0.9区域数据显示到0.5 ~ 1.0区域,从而就实现了该传送带特效 在晓得了如何实现该特效后,咱们还能够实现纵向的传送带特效,只须要将片元着色器里的x改为y即可 precision mediump float;uniform sampler2D uSampler;uniform sampler2D uSampler2;varying vec2 vCoordinate;uniform float uOffset;void main(){    if (vCoordinate.y < 0.5) {        gl_FragColor = texture2D(uSampler, vCoordinate);    } else {        gl_FragColor = texture2D(uSampler2, vCoordinate - vec2(0.0, uOffset));    }}3.2 Java代码实现局部上面是Java代码实现局部 这外面应用了一个lastRender保留上一帧数据,从而在下一次渲染时可能应用 public class ConveyorBeltHFilter extends BaseFilter {    private final BaseRender lastRender;    private int uSampler2Location;    private int uOffsetLocation;    private int lastTextureId = -1;    private float offset = 0.01f;    public ConveyorBeltHFilter(Context context) {        super(                context,                "render/filter/conveyor_belt_h/vertex.frag",                "render/filter/conveyor_belt_h/frag.frag"        );        lastRender = new BaseRender(context);        lastRender.setBindFbo(true);    }    @Override    public void onCreate() {        super.onCreate();        lastRender.onCreate();    }    @Override    public void onChange(int width, int height) {        super.onChange(width, height);        lastRender.onChange(width, height);    }    @Override    public void onDraw(int textureId) {        super.onDraw(textureId);        lastRender.onDraw(getFboTextureId());        lastTextureId = lastRender.getFboTextureId();    }    @Override    public void onInitLocation() {        super.onInitLocation();        uSampler2Location = GLES20.glGetUniformLocation(getProgram(), "uSampler2");        uOffsetLocation = GLES20.glGetUniformLocation(getProgram(), "uOffset");    }    @Override    public void onActiveTexture(int textureId) {        super.onActiveTexture(textureId);        GLES20.glActiveTexture(GLES20.GL_TEXTURE1);        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, lastTextureId);        GLES20.glUniform1i(uSampler2Location, 1);    }    @Override    public void onSetOtherData() {        super.onSetOtherData();        GLES20.glUniform1f(uOffsetLocation, offset);    }}以上就是抖音传送带特效的实现全过程,心愿大家喜爱!!! 四、GitHubgithub地址:https://github.com/JYangkai/M... ConveyorBeltHFilter.javaConveyorBeltVFilter.java原文链接:https://juejin.cn/post/699849... 文末您的点赞珍藏就是对我最大的激励!欢送关注我,分享Android干货,交换Android技术。对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!

December 13, 2021 · 1 min · jiezi

关于android:APK的更新安装隐藏解除隐藏

一、用户装置的apk产生更新 public void registerReceiver(Context context) { Slog.d("PMSdddd", "systemReady1"); IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REPLACED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addDataScheme("package"); BroadcastReceiver packgeMonitorReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); final String packageName =intent.getData().getSchemeSpecificPart(); final boolean replacing =intent.getBooleanExtra(Intent.EXTRA_REPLACING, false); final boolean dataRemoved =intent.getBooleanExtra(Intent.EXTRA_DATA_REMOVED, false); Slog.d("PMSdddd", "action: " + action + " packageName:"+ packageName + " replacing:" + replacing + " dataRemoved:" + dataRemoved); } }; context.registerReceiver(packgeMonitorReceiver, filter); }第1步,新装置apk ...

December 13, 2021 · 5 min · jiezi

关于android:华为云函数中使用云数据库的JavaScript-SDK基础入门

背景介绍=========应用云数据库Server端的SDK,此处我以华为提供的官网Demo为例,他们的Demo也曾经开源放在了GitHub上,大家须要的能够自行下载。 https://github.com/AppGallery... 下载完Demo的我的项目代码后,还须要依照以下步骤操作: 后期在调研华为云函数服务的时候,发现能够在云函数中配置云数据库的触发器,能够通过云数据库的插入删除批改等事件,用来触发云函数中代码的执行。当初华为AGC的云数据库服务全网公布了,并且还提供了Server端的JavaScript SDK,刚好能够运行在云函数反对的Node.js环境中。话不多说,先来尝试一下如何在云函数中应用云数据库提供的JavaScript SDK,进行最简略的数据的插入和查问吧。 开明服务这里波及开明两个服务: 首先须要应用华为账号登录AGC网站 ,依照以下步骤进行操作: 1.开明云函数服务在AGC内抉择 "我的我的项目"–>抉择对应的我的项目->"构建"->"云函数",界面上点击 "立刻开明" , 开明当前点击右侧的创立函数即可。 2.开明云数据库服务在AGC内抉择 "我的我的项目"–>抉择对应的我的项目->"构建"->"云数据库",界面上点击“立刻开明”,开明当前,还须要创立对应的对象类型和存储区。 此处对应类型和存储区的创立,我都是依照数据库文档中的应用入门来创立的。 包含创立一个 BookInfo 的对象类型, 还有一个QuickStartDemo 的存储区。 云数据库JavaScript代码开发应用云数据库Server端的SDK,此处我以华为提供的官网Demo为例,他们的Demo也曾经开源放在了GitHub上,大家须要的能够自行下载。 https://github.com/AppGallery... 下载完Demo的我的项目代码后,还须要依照以下步骤操作: 1.导出对象类型文件在云数据库的界面中,对曾经创立的对象类型BookInfo,导出成js的ServerSDK的格局,将其放到demo的model目录下。 2.下载认证凭据。AGC的界面中,抉择我的项目设置,在ServerSDK页签下,点击 “下载认证凭据” 进行下载,并且同样放到demo的model目录下。 3、批改credentialPath门路。因为我的认证凭据下载当前放在model目录下,因而还须要同步批改代码中credentialPath门路,在CloudDBZoneWrapper.js文件中,批改初始化的代码,具体如下: (留神对于云函数环境,获取门路下的文件,要应用_dirname办法) let api_client_name = "agc-apiclient-testDB.json" ; let path = require('path'); let api_client_path = path.join(__dirname,api_client_name); agconnect.AGCClient.initialize(agconnect.CredentialParser.toCredential(api_client_path)); 4.配置函数入口。我这没有应用示例代码中默认的Start.js的接口,而是本人创立了一个inde.js的文件作为函数的入口,对应的代码如下 const CloudDBZoneWrapper = require("./model/CloudDBZoneWrapper.js"); logger.info(JSON.stringify(event));``````logger.info("event start");``````const cloudDBZoneWrapper = new CloudDBZoneWrapper();``````const bookInfo = cloudDBZoneWrapper.getSingleBook();``````// upsert a list of books``````await cloudDBZoneWrapper.upsertBookInfos(bookInfo);``````let result = {"message":"Run Success"}```}` ...

December 13, 2021 · 1 min · jiezi

关于android:Android-Studio查看第三方库依赖树

我的项目的开发过程中,咱们或多或少都会引入第三方库,引入的库越多,越容易产生库之间的依赖抵触。 上面就拿我遇到的问题还原一下: 之前接人容联客服零碎的时候,集成实现后进入客服页面产生闪退,咱们回顾一下错误信息: 咱们要害看一下报错代码: java.lang.NoSuchMethodError: No virtual method into (Landroid/widget/ImageView;)Lcom/bumptech/glide/request/target/Target; in class Lcom/a/a/i; or its super classes (declaration of 'com.a.a.i' appears in/data/app/com.sami91sami.h5-1/base.apk)咱们能够依据报错,跳到报错的中央: 该报错的意思就是:没有 into(Landroid/widget/ImageView)的办法,代码能编译通过,阐明我的项目中必定是增加依赖了,那怎么还会报这个谬误呢?还没增加依赖之前,我的项目中也是应用的Glide进行图片的加载,会不会是我的项目中的Glide与容联Demo中的Glide有抵触呢。 咱们能够依据报错的中央into办法,点进入看源码: 能够看到容联Demo应用的Glide版本是3.7.0。 再来看看我的项目中Glide应用的版本: 能够看到我的项目中应用的Glide版本是4.5.0。 这时就想到真的很大概率是两者的Glide版本有抵触了。 果然将容联Demo中的Glide版本改成4.5.0之后,编译运行进入客服界面后,没有报错了,完满解决。 这就是我之前遇到的库抵触的问题,这个问题有错误信息能够定位到是Glide库依赖的问题,要是遇到其它错误信息没那么显著的,那是不是就头疼了呢。 过后遇到这个问题,我并没有应用查看依赖树的形式,而是间接查看了源码,因为过后我并不知道还能这么干,侥幸的是很快就定位到了问题所在,所以当咱们降级第三方库或者引入新的第三方库时,库与库之间依赖抵触,咱们须要晓得每个第三方依赖库的依赖树,晓得依赖树就分明哪里抵触啦。 上面就记录下几种查看依赖树的形式: 计划一: Gradle task工具查看 1、点击Android studio面板右上角“Gradle”,如图所示: 2、依照如图目录找到dependencise双击,会在Run控制台输入打印,如图所示: 3、打印如图所示: 计划二:应用Gradle View插件 1、快捷键Ctrl+Alt+s,关上settings,而后点击按钮Plugins 2、搜寻 Gradle View,而后装置,并重启Android Studio,我这是曾经装置胜利后的截图 3、点击菜单栏上View -> Tool Windows -> Gradle View,而后期待一会,就能够查看了。 如图所示: 计划三:Terminal控制台查看 在windows上Android studio Terminal中应用这个命令: ...

December 13, 2021 · 1 min · jiezi

关于android:Android-SplashPage实现应用秒开3步

Android 利用冷启动时,须要从Application开始启动,加载工夫就会比拟长,这段时间里,用户所能看到的就是”白屏“(这是因为默认的AppTheme的 android:windowBackground 默认是设置成红色的),因而我认为真正的启动页就应该是让用户点开利用时看到的不是”白屏“,而是咱们创立的一个页面,能够是一张图片、一段文字。 就会让人感觉到,这个利用能够秒开。 1.首先在 drawable 目录下新建一个 splash\_screen.xml 文件<?xml version="1.0" encoding="utf-8"?><layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque"> <item android:drawable="@color/colorPrimary"/> <item> <bitmap android:src="@drawable/ic_logo" android:gravity="center"/> </item></layer-list>咱们应用 layer-list 标签创立一个图层列表,理论就是一个 LayerDrawable ,设置一个背景,而后放上利用图标,这是我想展现的启动页,能够依据本人的须要自行定义。 2.而后在 style.xml 文件中定义一个 SplashTheme<resources> ... <style name="SplashTheme" parent="AppTheme"> <item name="android:windowBackground">@drawable/splash_screen</item> </style></resources>这里只须要将窗口背景设置为咱们方才定义的 LayerDrawable。 3.而后须要在 AndroidMenifest.xml 文件中将咱们的主页面,我这里是 MainActivity 的 android:theme 设置成咱们定义的SplashTheme<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" ... > ... <application ... > <activity android:name=".activity.MainActivity" android:launchMode="singleTask" android:theme="@style/SplashTheme"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> ... </application></manifest>是不是很简略这样就能够了 Android零根底系列教程:【Android根底课程】 本文转自 https://juejin.cn/post/6963874611960217631,如有侵权,请分割删除。

December 13, 2021 · 1 min · jiezi

关于android:强烈推荐移动端音视频从零到上手

概述随着整个互联网的崛起,数据传递的模式也在一直降级变动,总的风行趋势如下: 纯文本的短信,QQ -> 空间,微博,朋友圈的图片文字联合 -> 微信语音 -> 各大直播软件 -> 抖音快手短视频音视频的倒退正在向各个行业一直扩大,从教育的近程授课,交通的人脸识别,医疗的近程就医等等,音视频方向曾经占据一个相当重要的地位,而音视频真正入门的文章又少之甚少,一个刚毕业小白可能很难切入了解,因为音视频中波及大量理论知识,而代码的书写须要联合这些实践,所以搞懂音视频,编解码等理论知识至关重要.自己也是从实习开始接触音视频我的项目,看过很多人的文章,在这里总结一个通俗易懂的文章,让更多筹备学习音视频的同学更快入门。 划重点本文中理论知识来自于各种音视频文章的演绎音视频编码基本原理汇总,其中也会有一些我本人总结减少的局部.若有谬误可评论,查看后会更正. 为了避免大家了解过于空洞,作者花了三个月工夫将最罕用,最重要的一些性能的理论知识及实战Demo亲自写进去,配合文章浏览成果更佳.每一部分的文章能够在上面每章章节开始的深刻学习中点击链接查看, 链接中文章均有Github地址,每个Demo都亲测能够通过,能够下载Demo运行. 如果喜爱,请帮忙点赞并反对转载,转载请附原文链接. 原理采集 无论是iOS平台,还是安卓平台,咱们都是须要借助官网的API实现一系列相干性能.首先咱们要明确咱们想要什么,最开始咱们须要一部手机,智能手机中摄像头是不可短少的一部分,所以咱们通过一些零碎API获取就要能够获取物理摄像头将采集到的视频数据与麦克风采集到的音频数据. 解决 音频和视频原始数据实质都是一大段数据,零碎将其包装进自定义的构造体中,通常都以回调函数模式提供给咱们,拿到音视频数据后,能够依据各自我的项目需要做一系列非凡解决,如: 视频的旋转,缩放,滤镜,美颜,裁剪等等性能, 音频的单声道降噪,打消回声,静音等等性能. 编码 原始数据做完自定义解决后就能够进行传输,像直播这样的性能就是把采集好的视频数据发送给服务器,以在网页端供所有粉丝观看,而传输因为自身就是基于网络环境,宏大的原始数据就必须压缩后能力带走,能够了解为咱们搬家要将物品都打包到行李箱这样了解. 传输 编码后的音视频数据通常以RTMP协定进行传输,这是一种专门用于传输音视频的协定,因为各种各样的视频数据格式无奈对立,所以须要有一个规范作为传输的规定.协定就起到这样的作用. 解码 服务端接管到咱们送过来的编码数据后,须要对其解码成原始数据,因为编码的数据间接送给物理硬件的设施是不能间接播放的,只有解码为原始数据能力应用. 音视频同步 解码后的每帧音视频中都含有最开始录制时候设置的工夫戳,咱们须要依据工夫戳将它们正确的播放进去,然而在网络传输中可能会失落一些数据,或者是延时获取,这时咱们就须要肯定的策略去实现音视频的同步,大体分为几种策略:缓存肯定视频数据,视频追音频等等. 推流,拉流流程推流: 将手机采集到的视频数据传给后盾播放端进行展现,播放端能够是windows, linux, web端,即手机充当采集的性能,将手机摄像头采集到视频和麦克风采集到的音频合成编码后传给对应平台的播放端。拉流: 将播放端传来的视频数据在手机上播放,推流的逆过程,行将windows, linux, web端传来的视频数据进行解码后传给对应音视频硬件,最终将视频渲染在手机界面上播放.推流如下: 拉流如下: 具体分析推流,拉流理论为互逆过程,这里依照从采集开始介绍. 1. 采集采集是推流的第一个环节,是原始的音视频数据的起源.采集的原始数据类型为音频数据PCM,视频数据YUV,RGB...。 1.1. 音频采集深入研究 iOS Core Audio简介iOS Audio Session治理音频上下文iOS Audio Queue采集播放音频数据iOS Audio Queue采集音频数据实战iOS Audio Unit采集音频数据iOS Audio Unit采集音频数据实战采集起源 内置麦克风外置具备麦克风性能的设施(相机,话筒...)零碎自带相册音频主要参数 采样率(samplerate): 模拟信号数字化的过程,每秒钟采集的数据量,采样频率越高,数据量越大,音质越好。声道数(channels): 即单声道或双声道 (iPhone无奈间接采集双声道,但能够模仿,即复制一份采集到的单声道数据.安卓局部机型能够)位宽: 每个采样点的大小,位数越多,示意越精密,音质越好,个别是8bit或16bit.数据格式: iOS端设施采集的原始数据为线性PCM类型音频数据其余: 还能够设置采样值的精度,每个数据包有几帧数据,每帧数据占多少字节等等.音频帧 音频与视频不同,视频每一帧就是一张图片,音频是流式,自身没有明确的帧的概念,理论中为了不便,取2.5ms~60ms为单位的数据为一帧音频. 计算 数据量(字节 / 秒)=(采样频率(Hz)* 采样位数(bit)* 声道数)/ 8 ...

December 12, 2021 · 2 min · jiezi

关于android:手写自定义View流式布局

视频:价值100w+Android我的项目实战大全:手把手实战,自定义View原文: https://juejin.cn/post/6969132819855441934View的生命周期先onMeasure()测量 、 再onLayout()布局 、最初onDraw()绘制。 在onMeasure()度量时,会先去度量儿子(先走儿子的生命周期),想确认儿子的大小能力确认本人的大小。 自定义View分成两类: 自定义View个别继承自View,SurfaceView或其余的View。 onMeasure()--> onDraw()都会执行,onLayout()看需要 自定义ViewGroup继承自ViewGroup或各种Layout onMeasure()--> onLayout()都会执行, onDraw()看需要 自定义View蕴含什么布局: onlayout onmeausre/ Layout:viewGroup 显示: onDraw :view: canvas paint matrix clip rect animation path(贝塞尔) line text绘制 frameWork: 交互: onTouchEvent :组合的viewGroup LayoutParams与MeasureSpecLayoutParamsLayoutParams(布局参数),也就是xml里定义的 获取 MeasureSpecMeasureSpec是一个(32位)的int值,高两位示意父容器对 view 的布局上的限度(Mode),低30位示意view的Size 1.unspecified--无限度 2.exactly -- 确定的大小 3.at\_most -- 最大不超过 实现流式布局1.继承ViewGroup 2.自定义ViewGroup须要实现onMeasure()度量和onLayout() 3.规范开局,要度量本人的大小得先度量儿子的大小,所以遍历儿子View,去view.measure() 4.儿子的宽高是依据父亲给他的MeasureSpec的Mode + 上本人的LayoutParams算进去的,也就是下面的那个表格。如父亲给的是exactly(确定的大小),儿子又是android:layout\_width="10dp",两个都是确定的,那就是10dp 5.取到儿子的宽高 6.儿子的宽高拿到了,那就是计算每行的宽度(并且取最大行宽度)。计算总的高度 7.for循环跑完了,这样每个孩子都度量完了,也晓得了最大的宽高,就能够度量本人的宽高了 8.然而本人的宽高并不只受儿子的影响,还跟父亲给的Mode相干 到此onMeasure()度量完了,接下来开始布局onLayout() 9.因为度量的时候,曾经确定了每一行存哪几个View,把他存到数组里,这样布局的时候简略很多 ...

December 12, 2021 · 1 min · jiezi

关于android:Android性能优化这些绘制优化你一定不能忽略

前言本文次要解说Android性能优化中的绘制优化 适度绘制的优化准则尽可能地管制 适度绘制的次数 = 2 次(绿色)以下,蓝色最现实尽可能防止 适度绘制的粉色 & 红色状况不容许 3 次以上的优化计划移除默认的 Window 背景移除 控件中不必要的背景缩小布局文件的层级(嵌套)自定义控件View优化:应用 clipRect() 、 quickReject()优化计划1: 移除默认的 Window 背景背景 个别应用程序 默认 继承的主题 = windowBackground ,如默认的 Light 主题: <style name="Theme.Light"> <item name="isLightTheme">true</item> <item name="windowBackground">@drawable/screen\_background\_selector\_light</item> ... </style>问题 个别状况下,该默认的 Window 背景根本用不上:因背景都自定义设置 若不移除,则导致所有界面都多 1 次绘制解决方案 移除默认的 Window 背景 形式1:在利用的主题中增加如下的一行属性<item name="android:windowBackground">@android:color/transparent</item> <!-- 或者 --> <item name="android:windowBackground">@null</item>形式2:在 BaseActivity 的 onCreate() 办法中应用上面的代码移除 getWindow().setBackgroundDrawable(null); <!-- 或者 --> getWindow().setBackgroundDrawableResource(android.R.color.transparent);优化计划2:移除 控件中不必要的背景如2个常见场景: 场景1:ListView 与 Item 列表页(ListView) 与 其内子控件(Item)的背景雷同 = 红色,故可移除子控件(Item)布局中的背景 场景2:ViewPager 与 Fragment 对于1个ViewPager + 多个 Fragment 组成的首页界面,若每个 Fragment 都设有背景色,即 ViewPager 则无必要设置,可移除 ...

December 12, 2021 · 2 min · jiezi

关于android:Android笔记Android之ContentProvider总结

1.实用场景 ContentProvider为存储和读取数据提供了对立的接口 应用ContentProvider,应用程序能够实现数据共享 android内置的许多数据都是应用ContentProvider模式,供开发者调用的(如视频,音频,图片,通讯录等) 2.相干概念介绍 1)ContentProvider简介当利用继承ContentProvider类,并重写该类用于提供数据和存储数据的办法,就能够向其余利用共享其数据。尽管应用其余办法也能够对外共享数据,但数据拜访形式会因数据存储的形式而不同,如:采纳文件形式对外共享数据,须要进行文件操作读写数据;采纳sharedpreferences共享数据,须要应用sharedpreferences API读写数据。而应用ContentProvider共享数据的益处是对立了数据拜访形式。 2)Uri类简介Uri uri = Uri.parse("content://com.changcheng.provider.contactprovider/contact")在Content Provider中应用的查问字符串有别于规范的SQL查问。很多诸如select, add, delete, modify等操作咱们都应用一种非凡的URI来进行,这种URI由3个局部组成, “content://”, 代表数据的门路,和一个可选的标识数据的ID。以下是一些示例URI: content://media/internal/images 这个URI将返回设施上存储的所有图片content://contacts/people/ 这个URI将返回设施上的所有联系人信息content://contacts/people/45 这个URI返回单个后果(联系人信息中ID为45的联系人记录) 只管这种查问字符串格局很常见,然而它看起来还是有点令人蛊惑。为此,Android提供一系列的帮忙类(在android.provider包下),外面蕴含了很多以类变量模式给出的查问字符串,这种形式更容易让咱们了解一点,因而,如下面content://contacts/people/45这个URI就能够写成如下模式: Uri person = ContentUris.withAppendedId(People.CONTENT_URI, 45); 而后执行数据查问: Cursor cur = managedQuery(person, null, null, null); 这个查问返回一个蕴含所有数据字段的游标,咱们能够通过迭代这个游标来获取所有的数据: package com.wissen.testApp;public class ContentProviderDemo extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); displayRecords(); } private void displayRecords() { //该数组中蕴含了所有要返回的字段 String columns[] = new String[] { People.NAME, People.NUMBER }; Uri mContacts = People.CONTENT_URI; Cursor cur = managedQuery( mContacts, columns, // 要返回的数据字段 null, // WHERE子句 null, // WHERE 子句的参数 null // Order-by子句 ); if (cur.moveToFirst()) { String name = null; String phoneNo = null; do { // 获取字段的值 name = cur.getString(cur.getColumnIndex(People.NAME)); phoneNo = cur.getString(cur.getColumnIndex(People.NUMBER)); Toast.makeText(this, name + ” ” + phoneNo, Toast.LENGTH_LONG).show(); } while (cur.moveToNext()); } }}上例示范了一个如何顺次读取联系人信息表中的指定数据列name和number。 ...

December 12, 2021 · 3 min · jiezi

关于android:Android-多线程IntentService详解

IntentService一、IntentService概述上一篇咱们聊到了HandlerThread,本篇咱们就来看看HandlerThread在IntentService中的利用,看本篇前倡议先看看上篇的HandlerThread,有助于咱们更好把握IntentService。同样地,咱们先来看看IntentService的特点: 它实质是一种非凡的Service,继承自Service并且自身就是一个抽象类它能够用于在后盾执行耗时的异步工作,当工作实现后会主动进行它领有较高的优先级,不易被零碎杀死(继承自Service的缘故),因而比拟适宜执行一些高优先级的异步工作它外部通过HandlerThread和Handler实现异步操作创立IntentService时,只需实现onHandleIntent和构造方法,onHandleIntent为异步办法,能够执行耗时操作二、IntentService的惯例应用套路大略理解了IntentService的特点后,咱们就来理解一下它的应用形式,先看个案例:IntentService实现类如下: package com.zejian.handlerlooper; import android.app.IntentService;import android.content.Intent;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.IBinder;import android.os.Message; import com.zejian.handlerlooper.util.LogUtils; import java.io.BufferedInputStream;import java.io.IOException;import java.net.HttpURLConnection;import java.net.URL; /** Created by zejianTime 16/9/3.Description: */public class MyIntentService extends IntentService { public static final String DOWNLOAD_URL="download_url";public static final String INDEX_FLAG="index_flag";public static UpdateUI updateUI;public static void setUpdateUI(UpdateUI updateUIInterface){ updateUI=updateUIInterface;}public MyIntentService(){ super("MyIntentService");}/** * 实现异步工作的办法 * @param intent Activity传递过去的Intent,数据封装在intent中 */@Overrideprotected void onHandleIntent(Intent intent) { //在子线程中进行网络申请 Bitmap bitmap=downloadUrlBitmap(intent.getStringExtra(DOWNLOAD_URL)); Message msg1 = new Message(); msg1.what = intent.getIntExtra(INDEX_FLAG,0); msg1.obj =bitmap; //告诉主线程去更新UI if(updateUI!=null){ updateUI.updateUI(msg1); } //mUIHandler.sendMessageDelayed(msg1,1000); LogUtils.e("onHandleIntent");}//----------------------重写一下办法仅为测试------------------------------------------@Overridepublic void onCreate() { LogUtils.e("onCreate"); super.onCreate();}@Overridepublic void onStart(Intent intent, int startId) { super.onStart(intent, startId); LogUtils.e("onStart");}@Overridepublic int onStartCommand(Intent intent, int flags, int startId) { LogUtils.e("onStartCommand"); return super.onStartCommand(intent, flags, startId);}@Overridepublic void onDestroy() { LogUtils.e("onDestroy"); super.onDestroy();}@Overridepublic IBinder onBind(Intent intent) { LogUtils.e("onBind"); return super.onBind(intent);}public interface UpdateUI{ void updateUI(Message message);}private Bitmap downloadUrlBitmap(String urlString) { HttpURLConnection urlConnection = null; BufferedInputStream in = null; Bitmap bitmap=null; try { final URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024); bitmap= BitmapFactory.decodeStream(in); } catch (final IOException e) { e.printStackTrace(); } finally { if (urlConnection != null) { urlConnection.disconnect(); } try { if (in != null) { in.close(); } } catch (final IOException e) { e.printStackTrace(); } } return bitmap;}}通过代码能够看出,咱们继承了IntentService,这里有两个办法是必须实现的,一个是构造方法,必须传递一个线程名称的字符串,另外一个就是进行异步解决的办法onHandleIntent(Intent intent) 办法,其参数intent能够附带从activity传递过去的数据。这里咱们的案例次要利用onHandleIntent实现异步下载图片,而后通过回调监听的办法把下载完的bitmap放在message中回调给Activity(当然也能够应用播送实现),最初通过Handler去更新UI。上面再来看看Acitvity的代码: ...

December 12, 2021 · 6 min · jiezi

关于android:Android笔记Jetpack-Compose

留神,Jetpack Compose中的控件被定义成一个一个的可组合函数,官网称这些控件为Composable,翻译成中文是“可组合项”,当强调它作为一个界面的一部分呈现时,我会应用“控件”或“元素”之类的术语,要留神这三者之间的差异,我不晓得有没有更好的词,所以我只能用这两个。当仅仅强调它是一个可组合项时,我会失常应用“可组合项”这个术语。Layout零碎 根本准则元素须要通过一些束缚来测量本人,这限度了一个元素的最大和最小的宽高。如果一个元素有子元素,那么它会测量每一个子元素来帮忙决定本人的大小,每当一个元素向父元素报告了它本人的大小时,那么它就失去了绝对于本身来搁置本人的子元素的机会。 compose不容许屡次测量,和Flutter一样,起因就是反复测量作用于UI这种树形构造的是时候会带来指数级的性能降落。当然有很多时候你须要反复获取子元素的一些信息,这会有其它的方法。 自定义 layout modifier在compose中,Modifier提供了一系列函数,应用它们能够提供很多布局上的参数,比方padding等信息,通过自定义modifier来看下它是怎么工作的。 通过扩大函数来扩大Modifier中的办法,因为modifier是链式调用的,咱们扩大的办法也应该合乎链式调用规定,Modifier.then办法用来辅助实现链式调用,它承受一个Modifier,返回一个与这个Modifier联合后的Modifier。 fun Modifier.firstBaselineToTop( firstBaseLineToTop: Dp) = this.then( layout { measurable, constraints -> // do something... })外面的这个layout也是一个Modifier中的办法,它承受一个参数,这个参数是一个lambda,一会再说,这个lambda外面就是咱们进行测量和摆放子控件的中央。 measurable:被摆放的子控件constraints:子控件的最大和最小宽高限度上面实现一个这个成果,能够通过咱们扩大的firstBaselineToTop办法,设置文字的FristBaseline与顶部的间距。 第一步,咱们须要测量这个子控件,取得一个Placeable对象,咱们能够通过这个Placeable对象,绝对于父控件的地位来摆放这个子控件。 layout { measurable, constraints -> val placeable = measurable.measure(constraints = constraints)}这里能够将给定的constraints限度间接传入,也能够本人结构。 当初这个子控件曾经依据给定的限度被测量好,下一步,咱们就须要计算它离顶部的高度,这里应该应用用户传入的高度减去FirstBaseline的地位,失去的就是这个控件应该离顶部的高度。 // 检测子元素是否有FirstBaseLinecheck(placeable[FirstBaseline] != AlignmentLine.Unspecified)val firstBaseLine = placeable[FirstBaseline]// 计算元素该被搁置到的Y坐标,并减少元素的高度val placeableY = firstBaseLineToTop.roundToPx() - firstBaseLineval height = placeable.height + placeableY万事俱备,该摆放这个控件了。 应用MeasureScope.layout办法向内部报告大小,并摆放本人,这个办法会返回一个MeasureResult,正好是内部整个lambda表达式所要求的返回值。 layout(placeable.width,height) { placeable.placeRelative(0,placeableY)}残缺代码: fun Modifier.firstBaselineToTop( firstBaseLineToTop: Dp) = this.then( layout { measurable, constraints -> val placeable = measurable.measure(constraints = constraints) check(placeable[FirstBaseline] != AlignmentLine.Unspecified) val firstBaseLine = placeable[FirstBaseline] val placeableY = firstBaseLineToTop.roundToPx() - firstBaseLine val height = placeable.height + placeableY layout(placeable.width,height) { placeable.placeRelative(0,placeableY) } })@Preview@Composablefun useFirstBaselineToTop() { Column { Text("Hi,there", modifier = Modifier.firstBaselineToTop(24.dp)) }}@Preview@Composablefun usePadding() { Column { Text("Hi,there",modifier = Modifier.padding(top = 24.dp)) }}自定义Layout上面是自定义的一个简略的Column布局。因为和自定义Modifier差不多,不多说了。 ...

December 12, 2021 · 1 min · jiezi

关于android:如何做好性能优化字节大佬历时3个月为你整理出这份Android性能优化实战全解析

前言面试造火箭,工作拧螺丝,近些年有数开发者都对面试官疾恶如仇。尤其是在性能优化方面,各大厂的面试官根本都会发动夺命连环炮: 面试官: 性能优化你理解么? 我: 有。 面试官: 你都做过哪方面的性能优化呢?  我: 启动速度、电量、页面、内存… 面试官: 看来你教训还是比拟丰盛的,我想问一下,你个别如何缩小APP启动工夫? 我: …  面试官: ok,方才你提到了内存优化,说一下你对内存泄露的了解。  我: …  面试官: … 这个局面预计让很多开发者都痛不欲生,在各大厂的面试中,性能优化的问题或者会早退,但必定不会缺席。这也能够看出,目前各大厂都尤为关注开发者在性能优化局部的能力边界。一款产品的从开发到面世,凝聚了有数的心血,但如果最初因为卡顿、闪退等问题影响用户体验,导致用户散失,那么所有的致力都将付诸东流。 而且随着Android开发越来越趋于欠缺,工程师的开发程度以及用户对产品的要求也日益增长,所以对于开发品质的要求,甚至有点不近人情的刻薄。内存优化、UI卡顿优化、App监控解体等性能调优,也逐步成为了中高级开发者的必备技能。所以大厂面试官,会抽丝剥茧到极致,直到探到你的能力边界为止。 很多五年教训左右的Android工程师,对于性能优化相干内容都还不够相熟,很多人都只是偶然应用过,甚至是据说过。为了帮忙大家更好地把握性能优化技能,早日胜利拥抱高薪,在这里给大家分享一份字节大佬历时三个月整理出来的《Android性能优化-实战全解析》,从ANR,内存优化,耗电优化,网络优化等板块,给大家带来全方位源码实操解说! 第一章.ANR问题解析1.Android ANR:原理剖析及解决办法 ANR阐明和起因ANR剖析方法造成ANR的起因以及解决办法ANR源码剖析Android ANR的信息采集2.卡顿监控-ANR底层机制源码剖析前言四大组件启动超时ANRInput响应超时ANR... 第二章.crash监控计划1.线程监控-死锁。存活周期与CPU占用率 前言监控死锁监控存活周期监控CPU占用率总结... 第三章.启动速度与执法效率优化我的项目实站1.Android卡顿检测及优化 卡顿帧率卡顿起因卡顿检测卡顿优化2.微信越滑越卡背景卡顿的起因剖析FlingRunnable沉积的起因代码剖析ontouchdownmflingRunnable.flywheeltouch... 第四章.内存优化1.Android内存优化工具 topdumpaya meminfomemory profilerLeak canaryMAT内存问题高效分析方法参考资料2.Android内存透露剖析及检测工具LeakCanary简介背景什么是内存透露如何检测内存透露profilerLeakcanary... 纸上得来终觉浅,绝知此事要躬行,心愿大家都能早日增强性能优化技能,这份《Android性能优化-实战全解析》肯定能够给到大家帮忙,让大家早日成为真正的高级Android开发者,材料内容细节比拟多因为文章篇幅无限,须要完整版的敌人能够点击这里收费支付! 最初明天的文章就到这里,感谢您的浏览,有问题能够在评论区留言探讨,期待与大家共同进步。喜爱的话不要忘了三连。大家的反对和认可,是我分享的最大能源。

December 12, 2021 · 1 min · jiezi

关于android:感受一波Android自定义view实现超萌动感小炸弹

老规矩,咱们先来看看效果图! 再来看android的实现成果。 上面咱们和自定义view实现超萌动感天气小太阳一样,开始解析动画!(没看过天气小太阳的敌人能够先去看天气小太阳,有些天气小太阳讲过的套路将不再讲,同时须要把握path、camera、贝塞尔曲线等,不然局部代码可能会引起不适)。 咱们先把动态view绘制进去,而后再实现动画,Let’s go。 1.地板 能够看到地板其实就是一条直线。而后两头两个缺口。这要个么实现呢?看到小太阳的小伙伴可能都会说,这很简略。只有画一线直线而后笼罩两个白的区间就能够了。确实这能够实现,然而仔细观察能够发现下方的缺口是两个半圆加矩形实现的,这样的话就有点麻烦,而且不不便缺口地位的挪动。那有什么简略的办法呢?有,那就是应用Path进行绘画一条直线,而后通过设置圆笔头,再设置DashPathEffect(实现虚线,一段画,一段不画的成果,能够自在管制各段长度)来实现距离(本view的缺口都是应用此个性实现的,不相熟的小伙伴能够去看一下),代码如下: float[] groundEffectFloat=new float[]  {        bombLineWidth/4,bombLineWidth/2+bombLineWidth,bombLineWidth*2,bombLineWidth/3*2+bombLineWidth,getMeasuredWidth(),0};//设置画与不画所占长度        groundDashPathEffect=new DashPathEffect(groundEffectFloat,0);        mPaint.setStyle(Paint.Style.STROKE);        mPaint.setColor(bombLineColor);        mPaint.setPathEffect(groundDashPathEffect);//设置虚线成果        mPath.reset();        mPath.moveTo(bombLineWidth/2,getMeasuredHeight()-bombLineWidth/2);        mPath.lineTo(getMeasuredWidth()-bombLineWidth/2,getMeasuredHeight()-          bombLineWidth/2);        canvas.drawPath(mPath,mPaint);2.身材的边框 认真一看!聪慧的你肯定会说太简略了,这不就是一个圆而后再用DashPathEffect实现缺口不就能够了!!嗯,对,就是这样的。间接放代码: mPaint.setPathEffect(bodyDashPathEffect);        mPaint.setColor(bombLineColor);        mPaint.setStyle(Paint.Style.STROKE);        mPath.reset();        mPath.addCircle(bombCenterX,bombCenterY,bodyRadius,          Path.Direction.CW);        canvas.drawPath(mPath,mPaint);        canvas.restore();简略!简略的不能再简略了,上面看身材 ...

December 12, 2021 · 2 min · jiezi

关于android:Android入门教程-MediaPlayer-多媒体播放器

MediaPlayer 根底简介简略介绍 MediaPlayer 的基本概念,状态,罕用的办法与监听器。 什么是 MediaPlayerMediaPlayer 类能够用来播放音视频文件,或者是音频流。开发者能够用它来播放本地音频,或者是网络在线音频。 MediaPlayer 属于 android.media 包。 MediaPlayer 的状态播放管制由状态机管制。在日常生活中,咱们常见的音频状态有播放中,暂停,进行,缓冲等等。 MediaPlayer 的状态有如下几种: - Idle - End - Error - Initialized - Preparing - Prepared - Started - Stopped - Paused - PlaybackCompleted 状态的切换参考官网图例。 这里略微解释一下状态转换图片。椭圆代表 MediaPlayer 可能停留的状态。椭圆之间的箭头示意办法调用,状态切换的方向。单箭头示意办法同步调用,双箭头示意异步调用。 从图中咱们能够看出状态切换的门路和波及到的办法。 Idle 与 End 状态 当 new 一个 MediaPlayer 或者调用了reset 办法,以后 MediaPlayer 会处于 Idle 状态。调用release 后,会处于 End 状态。在这 2 个状态之间的状态能够看做是 MediaPlayer 对象的生命周期。 在新创建 MediaPlayer 和调用 reset 的 MediaPlayer 之间有一些轻微的差异。 这两种状况都处于 Idle 状态,调用 getCurrentPosition(), getDuration(), getVideoHeight(), getVideoWidth(), setAudioAttributes(android.media.AudioAttributes), setLooping(boolean), setVolume(float, float), pause(), start(), stop(), seekTo(long, int), prepare() 或 prepareAsync() 办法都会抛出谬误,如果是新实例化的 MediaPlayer,不会回调 OnErrorListener.onError();但如果是 reset 后的 MediaPlayer,会回调 OnErrorListener.onError() 并且转换到 Error 状态。 ...

December 12, 2021 · 3 min · jiezi

关于android:Android-类加载器

类的生命周期 加载阶段加载阶段能够细分如下 加载类的二进制流数据结构转换,将二进制流所代表的动态存储构造转化成办法区的运行时的数据结构生成java.lang.Class对象,作为办法区这个类的各种数据的拜访入口加载类的二进制流的办法 从zip包中读取。咱们常见的JAR、AAR依赖运行时动静生成。咱们常见的动静代理技术,在java.reflect.Proxy中就是用ProxyGenerateProxyClass来为特定的接口生成代理的二进制流验证验证是连贯阶段的第一步,这一阶段的目标是为了确保 Class 文件的字节流中蕴含的信息合乎以后虚拟机的要求,并且不会危害虚拟机本身的平安。 文件格式验证:如是否以魔数 0xCAFEBABE 结尾、主、次版本号是否在以后虚拟机解决范畴之内、常量合理性验证等。 此阶段保障输出的字节流能正确地解析并存储于办法区之内,格局上合乎形容一个 Java类型信息的要求。元数据验证:是否存在父类,父类的继承链是否正确,抽象类是否实现了其父类或接口之中要求实现的所有办法,字段、办法是否与父类产生矛盾等。 第二阶段,保障不存在不合乎 Java 语言标准的元数据信息。字节码验证:通过数据流和控制流剖析,确定程序语义是非法的、合乎逻辑的。例如保障跳转指令不会跳转到办法体以外的字节码指令上。符号援用验证:在解析阶段中产生,保障能够将符号援用转化为间接援用。能够思考应用 -Xverify:none 参数来敞开大部分的类验证措施,以缩短虚拟机类加载的工夫。 筹备为类变量分配内存并设置类变量初始值,这些变量所应用的内存都将在办法区中进行调配。 解析虚拟机将常量池内的符号援用替换为间接援用的过程。 解析动作次要针对类或接口、字段、类办法、接口办法、办法类型、办法句柄和调用点限定符 7 类符号援用进行 初始化到初始化阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行 <clinit>() 办法的过程。 类加载的机会虚拟机标准规定了有且只有 5 种状况必须立刻对类进行“初始化”(而加载、验证、筹备天然须要在此之前开始) 遇到new、getstatic 和 putstatic 或 invokestatic 这4条字节码指令时,如果类没有进行过初始化,则须要先触发其初始化。对应场景是:应用 new 实例化对象、读取或设置一个类的动态字段(被 final 润饰、已在编译期把后果放入常量池的动态字段除外)、以及调用一个类的静态方法。对类进行反射调用的时候,如果类没有进行过初始化,则须要先触发其初始化。当初始化类的父类还没有进行过初始化,则须要先触发其父类的初始化。(而一个接口在初始化时,并不要求其父接口全副都实现了初始化)虚拟机启动时,用户须要指定一个要执行的主类(蕴含 main() 办法的那个类), 虚构机会先初始化这个主类。当应用 JDK 1.7 的动静语言反对时,如果一个 java.lang.invoke.MethodHandle 实例最初的解析后果 REF\_getStatic、REF\_putStatic、REF\_invokeStatic 的办法句柄,并且这个办法句柄所对应的类没有进行过初始化,则须要先触发其初始化。留神: 通过子类援用父类的动态字段,不会导致子类初始化。通过数组定义来援用类,不会触发此类的初始化。MyClass[] cs = new MyClass[10];常量在编译阶段会存入调用类的常量池中,实质上并没有间接援用到定义常量的类,因而不会触发定义常量的类的初始化。类加载器把实现类加载阶段中的“通过一个类的全限定名来获取形容此类的二进制字节流”这个动作的代码模块称为“类加载器”。 将 class 文件二进制数据放入办法区内,而后在堆内(heap)创立一个 java.lang.Class 对象,Class 对象封装了类在办法区内的数据结构,并且向开发者提供了拜访办法区内的数据结构的接口。 类的唯一性对于任意一个类,都须要由加载它的类加载器和这个类自身一起确立其在Java虚拟机中的唯一性。 即便两个类来源于同一个 Class 文件,被同一个虚拟机加载,只有加载它们的类加载器不同,那这两个类也不相等。 这里所指的“相等”,包含代表类的 Class 对象的 equals() 办法、 isAssignableFrom() 办法、isInstance() 办法的返回后果,也包含应用 instanceof 关键字做对象所属关系断定等状况 ...

December 12, 2021 · 3 min · jiezi

关于android:Android实现优雅快速的网络请求

指标简略调用、少写反复代码不依赖第三方库(只含Retrofit+Okhttp+协程)齐全不懂协程也能立马上手(模板代码)用Kotlin的形式写Kotlin代码,什么意思呢?比照一下上面2个代码就晓得了: mViewModel.wxArticleLiveData.observe(this, object : IStateObserver<List<WxArticleBean>>() {    override fun onSuccess(data: List<WxArticleBean>?) {    }    override fun onError() {    }})mViewModel.wxArticleLiveData.observeState(this) {    onSuccess { data: List<WxArticleBean>? ->    }    onError {    }}既然是用Kotlin了,就不要用Java的形式写接口回掉了,DSL表达式不香么? 提供两种形式实现: 形式一代码量更少,网络申请自带Loading,不须要手动调用Loading形式二解耦更彻底两种形式设计思路在解耦这一块存在差别,看具体需要,没有谁好谁差,按照本人的我的项目,哪个更不便用哪个。 基于官网架构的封装: 一、封装一核心思想是:通过一个LiveData 贯通整个流程,借用网上一张图: Activity中的代码示例 点击申请网络mViewModel.getArticleData()设置监听,只监听胜利的后果,应用默认异样解决 mViewModel.wxArticleLiveData.observeState(this) {    onSuccess { data ->        Log.i("wutao","网络申请的后果是:$data")    }}如果须要独自解决每一个回调 这些回调都是可选的,不须要可不实现 mViewModel.wxArticleLiveData.observeState(this) {    onSuccess { data ->        Log.i("wutao","网络申请的后果是:$data")    }    onEmpty{        Log.i("wutao", "返回的数据是空,展现空布局")    }    onFailed {        Log.i("wutao", "后盾返回的errorCode: $it")    }    onException { e ->        Log.i("wutao","这是非后盾返回的异样回调")    }    onShowLoading {         Log.i("wutao","自定义单个申请的Loading")    }    onComplete {        Log.i("wutao","网络申请完结")    }}申请自带Loading很多网络申请都须要Loading,不想每次都写onShowLoading{}办法,也so easy。 mViewModel.wxArticleLoadingLiveData.observeState(this, this) {    onSuccess { data ->  Log.i("wutao","网络申请的后果是:$data")    }}observeState()第二个办法传入ui的援用就可,这样单个网络申请之前会主动加载Loading,胜利或者失败主动勾销Loading。 下面代码都是Activity中,咱们来看下ViewModel中。 ViewModel中代码示例 class MainViewModel{    private val repository by lazy { WxArticleRepository() }    val wxArticleLiveData = StateLiveData<List<WxArticleBean>>()    fun requestNet() {        viewModelScope.launch {            repository.fetchWxArticle(wxArticleLiveData)        }    }}很简略,引入对应的数据仓库Repo,而后应用协程执行网络申请办法。来看下Repo中的代码。 Repository中代码示例 class WxArticleRepository : BaseRepository() {    private val mService by lazy { RetrofitClient.service }    suspend fun fetchWxArticle(stateLiveData: StateLiveData<List<WxArticleBean>>) {        executeResp(stateLiveData, mService::getWxArticle)    }  }interface ApiService {    @GET("wxarticle/chapters/json")    suspend fun getWxArticle(): BaseResponse<List<WxArticleBean>>}获取一个Retrofit实例,而后调用ApiService接口办法。 封装一的劣势代码很简洁,不须要手写线程切换代码,没有很多的接口回调。自带Loading状态,不须要手动启用Loading和敞开Loading。数据驱动ui,以LiveData为载体,将页面状态和网络后果通过在LiveData返回给ui。我的项目地址见: https://github.com/ldlywt/Fas...  (分支名字是:withLoading) 封装一的有余 *封装一的核心思想是:一个LiveData贯通整个网络申请链。这是它的劣势,也是它的劣势。 解耦不彻底,违反了"在利用的各个模块之间设定明确定义的职责界线"的思维LiveData监听时,如果须要Loading,BaseActivity都须要实现带有Loading办法接口。obserState()办法第二个参数中传入了UI援用。不能达到"看办法如其意",如果是刚接触,会有很多疑难:为什么须要一个livedata作为办法的参数。网络申请的返回值去哪了?封装一还有一个最大的缺点:对于是多数据源,封装一就展现了很不敌对的一面。Repository是做一个数据仓库,我的项目中获取数据的形式都在这里批准治理,网络获取数据只是其中一个形式而已。 如果想加一个从数据库或者缓存中获取数据,封装一想改都不好改,如果强制改就毁坏了封装,侵入性很大。 针对封装一的有余,优化出了封装二。 二、封装二思路想要解决下面的有余,不能以LiveData为载体贯通整个网络申请。Observe()办法中去掉ui援用,不要小看一个ui援用,这个援用代表着具体的Activity跟Observe耦合起来了,并且Activity还要实现IUiView接口。网络申请跟Loading状态离开了,须要手动管制Loading。Repository中的办法都有返回值,会返回后果,也不须要用livedata作为办法参数。LiveData只存在于ViewModel中,LiveData不会贯通整个申请链。Repository中也不须要LiveData的援用,Repository的代码就是单纯的获取数据。针对多数据源,也十分好解决。跟ui没任何关系,能够齐全作为一个独立的Lib应用。Activity中代码 // 申请网络mViewModel.login("username", "password")// 注册监听mViewModel.userLiveData.observeState(this) {    onSuccess {data ->        mBinding.tvContent.text = data.toString()    }    onComplete {        dismissLoading()    }}observeState()中不再须要一个ui援用了。 ViewModel中 class MainViewModel {    val userLiveData = StateLiveData<User?>()    fun login(username: String, password: String) {        viewModelScope.launch {            userLiveData.value = repository.login(username, password)        }    }}通过livedata的setValue或者postValue办法将数据发送进来。 Repository中 suspend fun login(username: String, password: String): ApiResponse<User?> {    return executeHttp {        mService.login(username, password)    }}Repository中的办法都返回申请后果,并且办法参数不须要livedata。Repository齐全能够独立进去了。 针对多数据源// WxArticleRepositoryclass WxArticleRepository : BaseRepository() {    private val mService by lazy {        RetrofitClient.service    }    suspend fun fetchWxArticleFromNet(): ApiResponse<List<WxArticleBean>> {        return executeHttp {            mService.getWxArticle()        }    }    suspend fun fetchWxArticleFromDb(): ApiResponse<List<WxArticleBean>> {        return getWxArticleFromDatabase()    }}// MainViewModel.kt  private val dbLiveData = StateLiveData<List<WxArticleBean>>()private val apiLiveData = StateLiveData<List<WxArticleBean>>()val mediatorLiveDataLiveData = MediatorLiveData<ApiResponse<List<WxArticleBean>>>().apply {    this.addSource(apiLiveData) {        this.value = it    }    this.addSource(dbLiveData) {        this.value = it    }}能够看到,封装二更合乎职责繁多准则,Repository单纯的获取数据,ViewModel对数据进行解决和发送。 三、实现原理数据来源于鸿洋大神的玩Android 凋谢API 回数据结构定义: {    "data": ...,    "errorCode": 0,    "errorMsg": ""}封装一和封装二的代码差距很小,次要看封装二。 定义数据返回类 open class ApiResponse<T>(        open val data: T? = null,        open val errorCode: Int? = null,        open val errorMsg: String? = null,        open val error: Throwable? = null,) : Serializable {    val isSuccess: Boolean        get() = errorCode == 0}data class ApiSuccessResponse<T>(val response: T) : ApiResponse<T>(data = response)class ApiEmptyResponse<T> : ApiResponse<T>()data class ApiFailedResponse<T>(override val errorCode: Int?, override val errorMsg: String?) : ApiResponse<T>(errorCode = errorCode, errorMsg = errorMsg)data class ApiErrorResponse<T>(val throwable: Throwable) : ApiResponse<T>(error = throwable)基于后盾返回的基类,依据不同的后果,定义不同的状态数据类。 网络申请对立解决:BaseRepository open class BaseRepository {    suspend fun <T> executeHttp(block: suspend () -> ApiResponse<T>): ApiResponse<T> {        runCatching {            block.invoke()        }.onSuccess { data: ApiResponse<T> ->            return handleHttpOk(data)        }.onFailure { e ->            return handleHttpError(e)        }        return ApiEmptyResponse()    }    /**     * 非后盾返回谬误,捕捉到的异样     */    private fun <T> handleHttpError(e: Throwable): ApiErrorResponse<T> {        if (BuildConfig.DEBUG) e.printStackTrace()        handlingExceptions(e)        return ApiErrorResponse(e)    }    /**     * 返回200,然而还要判断isSuccess     */    private fun <T> handleHttpOk(data: ApiResponse<T>): ApiResponse<T> {        return if (data.isSuccess) {            getHttpSuccessResponse(data)        } else {            handlingApiExceptions(data.errorCode, data.errorMsg)            ApiFailedResponse(data.errorCode, data.errorMsg)        }    }    /**     * 胜利和数据为空的解决     */    private fun <T> getHttpSuccessResponse(response: ApiResponse<T>): ApiResponse<T> {        return if (response.data == null || response.data is List<*> && (response.data as List<*>).isEmpty()) {            ApiEmptyResponse()        } else {            ApiSuccessResponse(response.data!!)        }    }}Retrofit协程的错误码解决是通过异样抛出来的,所以通过try...catch来捕获非200的错误码。包装成不同的数据类对象返回。 扩大LiveData和Observer在LiveData的Observer()来判断是哪种数据类,进行相应的回调解决: abstract class IStateObserver<T> : Observer<ApiResponse<T>> {    override fun onChanged(apiResponse: ApiResponse<T>) {        when (apiResponse) {            is ApiSuccessResponse -> onSuccess(apiResponse.response)            is ApiEmptyResponse -> onDataEmpty()            is ApiFailedResponse -> onFailed(apiResponse.errorCode, apiResponse.errorMsg)            is ApiErrorResponse -> onError(apiResponse.throwable)        }        onComplete()    }再扩大LiveData,通过kotlin的DSL表达式替换java的callback回调,简写代码。 class StateLiveData<T> : MutableLiveData<ApiResponse<T>>() {    fun observeState(owner: LifecycleOwner, listenerBuilder: ListenerBuilder.() -> Unit) {        val listener = ListenerBuilder().also(listenerBuilder)        val value = object : IStateObserver<T>() {            override fun onSuccess(data: T) {                listener.mSuccessListenerAction?.invoke(data)            }            override fun onError(e: Throwable) {                listener.mErrorListenerAction?.invoke(e) ?: toast("Http Error")            }            override fun onDataEmpty() {                listener.mEmptyListenerAction?.invoke()            }            override fun onComplete() {                listener.mCompleteListenerAction?.invoke()            }            override fun onFailed(errorCode: Int?, errorMsg: String?) {                listener.mFailedListenerAction?.invoke(errorCode, errorMsg)            }        }        super.observe(owner, value)    }}四、总结封装一:代码量更少,能够依据我的项目须要封装一些具体的ui相干,开发起来更疾速,用起来更爽。 封装二:解耦更彻底,能够独立于ui模块运行。 集体认为,框架设计次要还是服务于本人的我的项目需要(开源我的项目除外),合乎设计模式和设计准则更好,然而不满足也没关系,适宜本人我的项目需要,能节俭本人的工夫,就是好的。 咱们本人我的项目中应用,怎么轻便,怎么疾速,怎么写的爽就怎么来。 原文链接:https://juejin.cn/post/699329... 文末您的点赞珍藏就是对我最大的激励!欢送关注我,分享Android干货,交换Android技术。对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!

December 12, 2021 · 1 min · jiezi

关于android:密码学-02-Base64

1. 简述:Base64是一种用64个字符示意任意二进制数据的办法。它是一种编码,而非加密A-Z a-z 0-9 + / = 2. 改编在url 传输应用的码表中, + / 被 - _ 代替。 因为后端接管到 + ,会成为 空字符串。相似Hexbin编码,通过批改码表,能够生成变种base64 3. Base64的利用RSA密钥、加密后的密文、图片等数据中,会有一些不可见字符。间接转成文本传输的话,会有乱码、数据谬误、数据失落等状况呈现,就能够应用Base64编码 4. Base64的代码实现和码表java public static void main(String[] args) { String name = "横笛"; byte[] bytes = name.getBytes(StandardCharsets.UTF_8); String encode = Base64.getEncoder().encodeToString(bytes); byte[] encode1 = Base64.getEncoder().encode(bytes); System.out.println(encode); System.out.println(new String(encode1));}Android String name1 = "横笛";// okio.Base64 encodeByteString byteString1 = ByteString.of(name1.getBytes(StandardCharsets.UTF_8));String encode1 = byteString1.base64();System.out.println("okhttp3:" + encode1);// java.util.Base64// 这里是因为对Android版本有要求 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { String string2 = Base64.getEncoder().encodeToString(name1.getBytes(StandardCharsets.UTF_8)); byte[] string3 = Base64.getEncoder().encode(name1.getBytes(StandardCharsets.UTF_8)); System.out.println("java- utils:"+string2); System.out.println("java- utils:"+ Arrays.toString(string3)); }// android.util.Base64String string4 = android.util.Base64.encodeToString(name1.getBytes(StandardCharsets.UTF_8),0);byte[] string5 = android.util.Base64.encode(name1.getBytes(StandardCharsets.UTF_8),0);System.out.println("android.util.Base64:"+string4);System.out.println("android.util.Base64:"+new String(string5));5. Base64编码细节每个Base64字符代表原数据中的6bitBase64编码后的字符数,是4的倍数编码的字节数是3的倍数时,不须要填充 ...

December 11, 2021 · 1 min · jiezi

关于android:密码学-01-HEX

1. 简介:Hex编码是一种用16个字符示意任意二进制数据的办法。它是一种编码,而非加密 码表为 "0123456789ABCDEF" 这16个字符。能够自行批改为改编版本。 字符编码 URL编码 2. Hex编码的代码实现和码表java 中: public static void main(String[] args) { String name = "小肩膀"; byte[] bytes = name.getBytes(StandardCharsets.UTF_8); System.out.println(bytes.length); System.out.println(Arrays.toString(bytes)); String encode = HexBin.encode(bytes); String encode1= encode(bytes); System.out.println(encode); System.out.println(encode1); }Android 中: 配置: app-> build.gradle--> dependencies 中减少如下 api "com.squareup.okhttp3:okhttp:3.10.0"代码 String name = "小肩膀";byte[] bytes = name.getBytes(StandardCharsets.UTF_8);ByteString byteString = ByteString.of(bytes);System.out.println(byteString.hex());Hex编码特点a) 用0-9 a-f 16个字符示意。b) 每个十六进制字符代表4bit, 也就是2个十六进制字符代表一个字节。c) 在理论利用中,比方密钥初始化,肯定要分分明传进去的密钥是哪种编码的,采纳对应 形式解析,能力失去正确的后果d) 编程中很多问题,须要从字节甚至二进制位的角度去思考,能力明确

December 11, 2021 · 1 min · jiezi

关于android:码上来战探索智感生活HMS-Core线上Codelabs挑战赛第4期开始

HMS Core线上Codelabs挑战赛第4期正式开始!咱们向所有实际力超强、创新力满满的开发者收回邀请用你的超级“码”力,解锁更多利用价值!生存里,咱们被手机“秒懂”的时刻越来越多: 出行游览,目的地天气提前预报,玩法攻略一秒到手; 商场逛吃,优惠券码点亮手机,超值买买买不在话下; 晨练健身,插入耳机,适宜静止的音乐歌单就位响应…… 手机里的利用成了一个个术业有专攻的贴身管家,总能第一工夫洞悉需要,预判动机,再将适配的服务适时推出。这些细致入微的服务都得益于挪动利用开发中一系列感知能力的使用,以动静的形式、精细化治理用户生存,驱动生存更智能化和品质化。 HMS Core凋谢的情景感知服务提供了让利用“更懂用户”的一系列感知能力,助力打造诸多便捷优质体验。 例如:①在游览出行APP中,调用工夫、地位和天气感知能力;②在静止衰弱APP中,获取用户耳机状态及静止状态…… 本期挑战赛围绕HMS Core 情景感知服务开展,通过对情景感知服务提供工夫、地位、流动、信标、音频设备、环境光和天气感知等能力的组合使用,解锁更多的体验场景,构建具备实用价值和创新力的性能利用,让生存“智慧”起来。【示例参考】 示例场景阐明:事实领取场景中,领取时关上领取码往往操作繁琐,借助情景感知服务的环境光感知能力,联合手机的重力传感器。当手机屏幕向下且环境光强度大于肯定阈值时,手机主动调取付款二维码,实现智感领取。→参考示例demo源码。 “智”感生存:应用情景感知服务的系列感知能力,构建场景化利用demo。 #### 第1步:应用HMS Core情景感知服务中的各个能力及其示例代码,构建你的智慧利用性能。实现构建,进行场景化利用性能演示。 √ 挑战者能够在本人开发的利用中,基于理论用户应用场景,联合情景感知服务的能力,实现"智"感生存的场景化性能演示。 √ 同时,HMS Core的开源社区也提供了一些App Demo,开发者也可基于这些利用构建本人的创意场景。 (1)新闻垂域demo (2)电商垂域demo (3)音频播放demo (4)静止衰弱demo 学习参考:情景感知服务精品实战课第2步:提交作品 √ 将demo源码上传至指定gitee仓:https://gitee.com/hms-core/co... √ 论坛发帖展现: ① 在【论坛-HMS Core板块】以图文+gif演示模式投稿,全面展现作品性能。 ② 发帖题目:前带【HMS Core挑战赛第4期】 ③ 发帖要求:内容原创,语句通顺,排版整洁。 ④ HMS Core论坛首发,著作权归发帖人所有,华为有收费使用权。 ⑤ 发帖内容合乎法律法规要求,不得进犯第三方合法权益,否则责任自负。 挑战赛专家评委将基于以下几个方面对作品进行打分: 实用性:性能正当,操作稳固,满足实在场景下较广泛的应用需要;创新性:性能独特新鲜,构思奇妙,具备较强商业转化价值更佳;完成度:作品无效调用了情景感知能力的1至多个能力,实现价值性能;代码好看度:代码整洁利落,清晰明了。 即日起至12月27日 扫描下方二维码,退出HMS Core 技术交换群,获取挑战赛最新信息,探讨技术,专家答疑。 (已退出挑战赛群的小伙伴无需反复加群哦~) 帮忙改良 >> 你的体验反馈,能帮忙咱们一直打磨、以更好的产品发明更大价值。你可在华为开发者联盟官方论坛-HMS Core板块,提出在挑战赛过程中遇到的问题及改良倡议。 更多精彩内容,请见华为开发者官方论坛→https://developer.huawei.com/...

December 10, 2021 · 1 min · jiezi

关于android:Android入门教程-Camera-相机二

应用 Camera API 进行视频的采集,别离应用 SurfaceView、TextureView 来预览 Camera 数据,取到NV21 的数据回调 筹备应用相机权限 <uses-permission android:name="android.permission.CAMERA" />camera 预览回调中默认应用 NV21格局。查看手机是否反对摄像头。 UI筹备 <!-- 全屏显示 --><style name="FullScreenTheme" parent="AppTheme"> <item name="windowNoTitle">true</item> <item name="android:windowFullscreen">true</item></style>承载预览图像 <FrameLayout android:id="@+id/camera_preview" android:layout_width="match_parent" android:layout_height="match_parent" />应用 SurfaceView 预览 Camera,取到 NV21 数据自定义 CameraPreview 继承 SurfaceView,实现 SurfaceHolder.Callback 接口 获取 NV21 数据,Camera.setPreviewCallback() 要放在 Camera.startPreview() 之前。 应用Camera.PreviewCallback 获取预览数据回调。默认是NV21格局。 surfaceChanged 中,camera 启动预览前能够进行设置,例如设置尺寸,调整方向 /** * camera预览视图 * Created by Rust on 2018/2/26. */public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private static final String TAG = "rustApp"; private SurfaceHolder mHolder; private Camera mCamera; private int mFrameCount = 0; public CameraPreview(Context context) { super(context); } public CameraPreview(Context context, Camera camera) { super(context); mCamera = camera; mHolder = getHolder(); mHolder.addCallback(this); mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } public void setCamera(Camera c) { this.mCamera = c; } @Override public void surfaceCreated(SurfaceHolder holder) { // 开启预览 try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); } catch (IOException e) { Log.d(TAG, "Error setting camera preview: " + e.getMessage()); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { // 可在此开释camera } @Override public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { // 若须要旋转、更改大小或从新设置,请确保障已进行预览 if (mHolder.getSurface() == null) { return; } try { mCamera.stopPreview(); } catch (Exception e) { // ignore: tried to stop a non-existent preview } Camera.Parameters parameters = mCamera.getParameters(); // ImageFormat.NV21 == 17 Log.d(TAG, "parameters.getPreviewFormat(): " + parameters.getPreviewFormat()); if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) { mCamera.setDisplayOrientation(90); } else { mCamera.setDisplayOrientation(0); } try { mCamera.setPreviewDisplay(mHolder); mCamera.setPreviewCallback(mCameraPreviewCallback); // 回调要放在 startPreview() 之前 mCamera.startPreview(); } catch (Exception e) { Log.d(TAG, "Error starting camera preview: " + e.getMessage()); } } private Camera.PreviewCallback mCameraPreviewCallback = new Camera.PreviewCallback() { @Override public void onPreviewFrame(byte[] data, Camera camera) { mFrameCount++; Log.d(TAG, "onPreviewFrame: data.length=" + data.length + ", frameCount=" + mFrameCount); } };}为了避免阻塞 UI 线程,在子线程中关上 camera。camera 常放在 try catch 中应用。 ...

December 10, 2021 · 4 min · jiezi

关于android:Android性能全面分析与优化方案研究教科书级总结

前言对于现在的Android开发者来说,性能优化是开发者们肯定要把握的开发技能。晦涩度和应用体验很大水平上影响着产品在市场体现中的命根子。而且当初招聘要求上,各大厂也是对精通性能优化的开发者求贤若渴。 一、为什么要学性能优化? 咱们身为Android开发者,而这是一篇对于Android高级架构师的招聘,从上图咱们能够看进去性能优化不论是对咱们本身也好还是对Android待业还是十分重要的。我发现很多人对于 Android性能优化 常识的把握大多浮于外表,对一些技术点只停留在“应用据说过”甚至是“不理解”的阶段,这其中甚至不乏一些工作 5 年以上的 Android 工程师。 随着 Android 开发越来越标准,国内工程师的素质,以及用户对产品的要求也越来越高。这也间接导致咱们对研发我的项目的品质要求到了近乎刻薄的境地,内存优化、UI 卡顿优化、App 解体监控等性能调优也逐步成了人手必备的技能。然而,还是有很多小伙伴在入门性能优化或者说学习性能优化上总是不足系统地、办法级别的指引,导致本人不足思路! 二、怎么去学Android性能优化?在这整顿收集的对于Android性能优化的常识脑图总结和学习手册文档!既可能夯实底层原理、性能调优等核心技术点,又可能把握一般开发者,难以涉及的架构设计那你在工作中、团队里、面试时,也就领有了同行难以复制的外围竞争力。 深刻摸索Android稳定性优化深刻摸索Android启动速度优化深刻摸索Android内存优化Android性能优化—实战解析 思维导图纲要正确认识Crash优化ANR优化挪动端业务高可用计划建设...... —、启动优化的意义二、利用启动流程三、启动耗时检测四、启动优化惯例计划启动过程中的常见问题...... 重识内存优化中常见工具抉择Android内存管理机制回顾内存抖动内存优化体系化搭建......对字符串匹配算法的一点了解安卓APP解体捕捉计划——xCrash深刻了解Gradle框架之一: Plugin, Extension, buildSrcAndroid H5首屏优化实际任意URL跳转破绽修复与JDK中getHost()办法之间的坑......因为文章篇幅无限,文档资料内容较多,本能够提供链接下载,但无奈容易被谐和,所以全副存档,须要这些文档这里的敌人,能够点击我的【Gitee】,同时也给大家提供一个技术交换探讨平台,心愿可能共同进步,共勉! 总结性能优化不是更新一两个版本就能够解决的,是持续性的需要,继续集成迭代反馈。在理论的我的项目中,在我的项目刚开始的时候,因为人力和我的项目实现工夫限度,性能优化的优先级比拟低,等进入我的项目投入使用阶段,就须要把优先级进步,但在我的项目初期,在设计架构计划时,性能优化的点也须要提前思考进去,这就体现出一个程序员的技术功底了。什么时候开始有性能优化的需要,往往都是从发现问题开始,而后剖析问题起因及背景,进而寻找最优解决方案,最终解决问题,这也是日常工作中常会用到的解决形式。

December 10, 2021 · 1 min · jiezi

关于android:Android开发Jetpack-Compose-ButtonIconButton等各种Button的讲解

前言本文会解说Button,IconButton, ExtendedFloatingActionButton, FloatingActionButton,IconToggleButton,OutlinedButton,RadioButton,TextButton这几个Button的用法详解,感兴趣的请往下看 一:Button的用法先来看看Button的源码(OutlinedButton跟Button的属性一样只是两个按钮的形态不太一样) @Composablefun Button( onClick: () -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, elevation: ButtonElevation? = ButtonDefaults.elevation(), shape: Shape = MaterialTheme.shapes.small, border: BorderStroke? = null, colors: ButtonColors = ButtonDefaults.buttonColors(), contentPadding: PaddingValues = ButtonDefaults.ContentPadding, content: @Composable RowScope.() -> Unit) { ...}content 是示意Button的内容。比方外面能够是一个TextonClick点击的回调 @Preview()@Composablefun buttonTest(){ val context = LocalContext.current Column(modifier = Modifier.padding(10.dp,10.dp)) { Button( onClick = { Toast.makeText(context,"点击了登录",Toast.LENGTH_SHORT).show() } ){ Text(text = stringResource(id = R.string.login)) } }}modifier 修饰符enabled 是否能够 (不可用默认是灰色,可用默认是蓝色) ...

December 10, 2021 · 6 min · jiezi

关于android:Android-PDF开发androidpdfview

Android PDF开发:android-pdfview android-pdfview应用比较简单,要害的中央是PDFView,将PDFView作为像Android的ImageView或者TextView一样写进xml布局文件: <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" > <com.joanzapata.pdfview.PDFView android:id="@+id/pdfView" android:layout_width="match_parent" android:layout_height="match_parent" /></FrameLayout>而后在Java下层代码间接加载pdf文件资源装载进去即可: package zhangphil.pdfview;import com.joanzapata.pdfview.PDFView;import com.joanzapata.pdfview.listener.OnPageChangeListener;import android.app.Activity;import android.os.Bundle;import android.widget.Toast;public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); PDFView pdfView = (PDFView) findViewById(R.id.pdfView); // 在我这个测试例子中,当时筹备一个叫做sample.pdf的pdf大文件放到assets目录下。 // 从assets文件目录下读取名为 sample.pdf的文件,缺省把该pdf定位到第一页。 pdfView.fromAsset("sample.pdf").defaultPage(1).onPageChange(new OnPageChangeListener() { @Override public void onPageChanged(int page, int pageCount) { // 当用户在翻页时候将回调。 Toast.makeText(getApplicationContext(), page + " / " + pageCount, Toast.LENGTH_SHORT).show(); } }).load(); }}

December 10, 2021 · 1 min · jiezi

关于android:领导谁再用定时任务实现关闭订单立马滚蛋

在电商、领取等畛域,往往会有这样的场景,用户下单后放弃领取了,那这笔订单会在指定的时间段后进行敞开操作,仔细的你肯定发现了像某宝、某东都有这样的逻辑,而且工夫很精确,误差在1s内;那他们是怎么实现的呢? 个别的做法有如下几种 定时工作敞开订单rocketmq提早队列rabbitmq死信队列工夫轮算法redis过期监听一、定时工作敞开订单(最low)个别状况下,最不举荐的形式就是关单形式就是定时工作形式,起因咱们能够看上面的图来阐明 咱们假如,关单工夫为下单后10分钟,定时工作距离也是10分钟;通过上图咱们看出,如果在第1分钟下单,在第20分钟的时候能力被扫描到执行关单操作,这样误差达到10分钟,这在很多场景下是不可承受的,另外须要频繁扫描主订单号造成网络IO和磁盘IO的耗费,对实时交易造成肯定的冲击,所以PASS 二、rocketmq提早队列形式提早音讯 生产者把音讯发送到音讯服务器后,并不心愿被立刻生产,而是期待指定工夫后才能够被消费者生产,这类音讯通常被称为提早音讯。 在RocketMQ开源版本中,反对提早音讯,然而不反对任意工夫精度的提早音讯,只反对特定级别的提早音讯。 音讯提早级别别离为1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,共18个级别。 发送提早音讯(生产者) /** * 推送提早音讯 * @param topic * @param body * @param producerGroup * @return boolean */ public boolean sendMessage(String topic, String body, String producerGroup) { try { Message recordMsg = new Message(topic, body.getBytes()); producer.setProducerGroup(producerGroup); //设置音讯提早级别,我这里设置14,对应就是延时10分钟 // "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h" recordMsg.setDelayTimeLevel(14); // 发送音讯到一个Broker SendResult sendResult = producer.send(recordMsg); // 通过sendResult返回音讯是否胜利送达 log.info("发送提早音讯后果:======sendResult:{}", sendResult); DateFormat format =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); log.info("发送工夫:{}", format.format(new Date())); return true; } catch (Exception e) { e.printStackTrace(); log.error("提早音讯队列推送音讯异样:{},推送内容:{}", e.getMessage(), body); } return false; }生产提早音讯(消费者) ...

December 10, 2021 · 4 min · jiezi

关于android:大厂迎来了寒冬

前言12月1日,爱奇艺员工张章透漏出大规模裁员的音讯,裁员比例20%~40%,多位爱奇艺员工也证实了裁员的真实情况。 优胜劣汰本是常事,但裁员的不只爱奇艺一家,字节跳动的教育、游戏、本地生存业务,腾讯CSIG事业群下的儿童启蒙教育业务,快手、金山云、58近期也开始裁员,并且裁员规模都不小。 互联网人开始人人自危,兴许是早就人人自危了。 ”往年是10年以来最差的一年,也将是将来10年最好的一年““老话”常谈,王兴在2019年说了一句话:2019年是互联网10年以来最差的一年,也将是将来10年最好的一年。当初看来,这句话每年都实用。 已经进入大厂是一个人实力的体现、是有高薪资的保障、是集体职业路线的富丽终点,但互联网曾经过了高速发展期,时常曝出一些大规模的裁员,无论是应届生、基层员工还是高薪员工,无一例外可能呈现在裁员名单下面。 当初进大厂仍旧是实力、高薪、富丽终点的体现,但也同样意味着能够在任何时候被优化、被取代。 如何度过寒冬?被裁与否,生存和工作都要持续,在各行各业要平安度过寒冬,唯有晋升集体实力,对于Android开发来说,也是如此。 在这里给大家分享一份Android架构开发手册,年后跳槽、想进大厂或者想晋升本人的都能够看一看。 手册手册次要介绍:Android框架的初始化过程、次要组件的工作原理。 间接剖析和整顿了:Android框架的次要源代码。 并具体解说了:了解框架工作原理所需的各种基础知识和形成理论Android平台骨干的服务框架。 次要内容包含: Android Jetpack实战教程Android框架的原理和源码解析(MVC/MVP/MVVM)大厂的架构演进,包含抖音、美团、安居客、携程、微信、淘宝等(互联网寒冬之下,大厂更值得冲一把了,把握大厂架构很有必要)内容概览以及局部截图: 第一章 Android Jetpack实战和教程 1.Android Jetpack - Navigation 2.Android Jetpack - Data Binding 3.Android Jetpack - ViewModel & LiveData 4.Android Jetpack - Room 5.Android Jetpack - Paging 6.Android Jetpack - WorkManger 7.Android Jetpack - Paging 3 第二章 MVC/MVP/MVVM 1.MVC框架-导言 2.MVC框架-ASP.NET窗体 3.MVC框架-第一应用程序 4.MVC框架-文件夹 5.MVC框架-模型 6.MVC框架-控制器 7.MVC框架-视图 8.MVC框架-布局 9.MVC框架-路由引擎 10.MVC框架-动作过滤器 11.MVC框架-高级示例 12.MVC框架-Ajax反对 13.MVC框架-捆绑 14.MVC框架-异样解决 15.MVP架构设计:Google官网MVP思维解读 16.开源MVP框架 ...

December 10, 2021 · 1 min · jiezi

关于android:全面解析-钥匙环服务的应用场景商业价值

在互联互通的场景驱动下,同一开发者旗下经常领有多款利用或者多个利用状态,用户在同一设施的不同利用或端口登录时,即使应用同一帐号,仍须要反复输出明码进行验证,操作简单,间接影响到用户的应用体验,而华为钥匙环服务的呈现则无效地解决了这一问题。 钥匙环服务性能盘点 华为钥匙环服务(Keyring)为开发者提供全生命周期的凭据治理能力,包含对凭据进行获取、加密存储、受权共享、查问读取、删除等等,保障开发者的业务流畅性。 1.本地加密存储应用钥匙环服务所获取的凭据在可信执行环境(TEE)中能够随机生成密钥,而后再进行加密,每个设施的密钥均不雷同,而且密钥只能在TEE内应用,无奈来到设施,华为也不把握密钥的内容,反对凭据在本地平安存储。保留凭据时,开发者还能够设置读取此凭据的内容时是否通过锁屏明码或生物特色认证用户的身份。 1.凭据共享受权钥匙环服务可能提供同一团队开发的App之间受权共享凭据的能力,被受权应用凭据的利用能够是Android利用、快利用或者Web利用。 用户在下次进行登录操作时,利用在钥匙环服务中查找可用的凭据。查找到的凭据可能是本利用存储的凭据,也可能是其它利用受权给本利用应用的凭据。 1.保障数据安全除此之外,应用钥匙环服务,共享凭据过程中的安全性也可能失去保障。钥匙环服务通过验证安卓利用的APK包名、快利用的包名和证书哈希,或者获取以后网页的实在URL作为身份信息等形式,认证读写凭据的APK或网站的实在身份,避免凭据被仿冒的程序或网站盗用。 1.凭据删除或更新钥匙环服务向开发者提供删除和更新凭据的API。如果用户须要退出利用帐户,相应的认证凭据也将从钥匙环服务中删除。 利用场景 跨状态登录同一利用App在挪动端的入口出现多样化的散布趋势,用户在不同的利用状态之间来回切换,重复登录,导致体验割裂。而钥匙环服务对于不同的利用状态提供了不同的接口,包含钥匙环服务SDK、快利用API和Web API,反对跨利用状态共享用户认证凭据。 以电商购物类利用为例,节假日促销短信推送是咱们最罕用的营销形式之一,也广泛被用户所承受。用户在安卓利用登录之后,点开促销短信中的链接,在华为浏览器中关上Web利用,间接处于登录状态,无需反复登录即可实现下单。 跨利用登录在业务倒退过程中,同一个团队往往会开发多个App,新利用上架之后,取得用户并非易事。针对全新业务场景的利用,钥匙环服务会为其提供一条畅通无阻、高效便捷的桥梁,将流量从原有的利用当中引进来,用户无需反复输出帐号密码,只有一键受权,就能够实现无缝登录。 也就是说,用户在利用A已登录,装置同一开发者旗下的新利用B,无需输出帐号密码,能应用登录利用A的帐号,实现间接登录利用B。 帐号易切换钥匙环服务还能够存储多个凭据,如果用户在某一利用登录过多个帐号,利用能够提供确认页面,让用户自主抉择某个帐号登录,操作便捷。当然了,作为开发者,您能够抉择在用户登录之前,验证用户的生物特色或者锁屏明码,无效躲避帐号被冒用等安全隐患。 商业价值 钥匙环服务的跨利用登录场景将帮忙同一开发者旗下的关联利用之间共享流量,助力孵化新产品。同时实现流量在安卓利用、快利用、Web利用之间的双向流转。通过发明一处登录,处处登录的无缝登录体验,钥匙环服务帮忙开发者缩短用户的交互转化门路,升高登录操作的复杂度,进一步晋升导流转化率。总之,钥匙环服务是助力开发者取得商业胜利的利器。 总体来看,华为钥匙环服务具备便捷登录、平安无忧、流量共享等外围劣势,能够帮忙开发者疾速满足用户在购物、出行、社交、浏览等多个应用场景下的平安需要。 之后,华为钥匙环服务还会推出其余个性,HMS Core也将在平安畛域凋谢新的能力,为开发者们带来更优质的服务和体验。

December 10, 2021 · 1 min · jiezi

关于android:做Android开发怎么才能不被淘汰看完这篇你就知道

前言真正淘汰你的不是因为技术提高太快,是你之于企业集体价值感的丢失。或者说,你没有致力跟上当初倒退的潮流。在中国岗位性质个别分为两类:业余技术深刻型的专家与综合倒退管理层人员。这就是深度和宽度的比拼。 大环境都这样,那咱们该朝哪方面倒退呢? 对于程序员,就是要么始终学习常识跟上时代倒退,成为能超过他人疾速实现需求的人。要么就是走向负责整个我的项目、或者转行为产品经理等职位的管理层。但专家数才占总比例的5%,同时,工资也是因物以稀为贵而水涨船高。如果只会写代码,那就不是不可代替的。 因而,大多数人都会往治理岗位倒退,也就是复合技能型人才倒退,所以什么是复合型人才呢?来源于技术、跨界、认知格局。但往这方面倒退须要学什么呢?其实多看看招聘的岗位要求就能明确。 以京东招聘Android高级工程师为例,相比以前须要理解的常识更多,并且还要分明和工程师相干岗位的工作内容,并且有相应的理解。 咱们能够通过这则招聘理解当初企业、大环境须要什么样的人才。 多学一项技能,可能就会成为你升职加薪的利器。常常混迹于各简单业务线的人,能力跳出反复工作、一直踩坑的怪圈。而一个成熟的码农在于技术过关后,更突出其余技能对业余技术的附加值。 毋须讳言的是,35岁当前你的一线coding能力肯定是降落的。到时候敲代码能力就显得没那么重要了,因为编程只是你整个武器库当中的一种,你的教训,你的视线,你的架构能力,你的治理能力,你剖析和解决问题的能力曾经远远不局限于技术这个畛域。 不可替代性是决定咱们价值的惟一起因 不可替代性也是程序员不被淘汰,并且能瀑布逆行的基本。机会经常昙花一现,一不小心就错过一个时代,比方苹果安卓时代、java时代、微信时代、抖音时代。 当初Android技术更新的太快了,每年甚至每个月都有新货色。作为程序猿的咱们,肯定要花费肯定的精力和工夫去学习。如果在你最迷茫,而又不晓得怎么做的时候,最好的形式,就是进阶本人。加油吧,小伙伴们,没有谁是天生都会的,只有本人真正的口头。 Android学习之路任重而道远,咱们也都在奋斗的路上。上面是我整顿的最新的学习材料,心愿能帮到想在Android这条路上一路走到黑的敌人。 1.Jetpack架构组件从入门到精通 Android Jetpack - NavigationAndroid Jetpack - Data BindingAndroid Jetpack - ViewModel & LiveDataAndroid Jetpack - RoomAndroid Jetpack - PagingAndroid Jetpack - WorkMangerAndroid Jetpack架构组件之LifecycleAndroid Jetpack Compose 最全上手指南2.Framework精编内核解析 次要内容蕴含: 深刻解析Binder深刻解析HandlerDalvik VM 过程零碎深刻解析 WMSPackagerManagerService 3.Kotlin强化实战(附Demo) 第一章 Kotlin入门教程第二章 Kotlin 实战避坑指南第三章 我的项目实战《Kotlin Jetpack 实战》从一个膜拜大神的 Demo 开始Kotlin 写 Gradle 脚本是一种什么体验?Kotlin 编程的三重境界Kotlin 高阶函数Kotlin 泛型Kotlin 扩大Kotlin 委托协程“鲜为人知”的调试技巧图解协程:suspend 4.Android设计思维解读开源框架 热修复插件化组件化框架设计图片加载框架网络申请框架RXJava 响应式编程框架设计IOC 架构设计Android架构组件Jetpack 5.NDK模块开发 NDK 模块开发JNI 模块Native 开发工具Linux 编程最初一线互联网Android面试题含详解(高级到高级专题)这些题目是往年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整顿了答案,相熟这些知识点会大大增加通过前两轮技术面试的几率 ...

December 10, 2021 · 1 min · jiezi

关于android:Android笔记android-Toast

1.默认成果: Toast.makeText(getApplicationContext(), "默认Toast款式", Toast.LENGTH_SHORT).show(); 2.自定义显示地位成果 Toast toast = new Toast(Class.this); toast = Toast.makeText(getApplicationContext(), "自定义地位Toast", Toast.LENGTH_LONG); toast.setGravity(Gravity.CENTER, 0, 0); toast.show(); 3.带图片成果 Toast toast = new Toast(Class.this); toast = Toast.makeText(getApplicationContext(), "带图片的Toast", Toast.LENGTH_LONG); toast.setGravity(Gravity.CENTER, 0, 0); LinearLayout toastView = (LinearLayout) toast.getView(); ImageView p_w_picpathCodeProject = new ImageView(getApplicationContext()); p_w_picpathCodeProject.setImageResource(R.drawable.icon); toastView.addView(p_w_picpathCodeProject, 0); toast.show(); 4.齐全自定义成果 LayoutInflater inflater = getLayoutInflater(); View layout = inflater.inflate(R.layout.custom, (ViewGroup) findViewById(R.id.llToast)); ImageView p_w_picpath = (ImageView) layout .findViewById(R.id.tvImageToast); p_w_picpath.setImageResource(R.drawable.icon); TextView title = (TextView) layout.findViewById(R.id.tvTitleToast); title.setText("Attention"); TextView text = (TextView) layout.findViewById(R.id.tvTextToast); text.setText("齐全自定义Toast"); toast = new Toast(getApplicationContext()); toast.setGravity(Gravity.RIGHT | Gravity.TOP, 12, 40); toast.setDuration(Toast.LENGTH_LONG); toast.setView(layout); toast.show(); 5.其余线程 ...

December 10, 2021 · 1 min · jiezi

关于android:Android高仿京东2020版首页联动效果

本篇效果图: 新增成果(不同于本篇成果的另一种成果,蕴含在本我的项目中): 第一张图 通过RecyclerView+Vlayout多布局实现;第二张具备实战性质的效果图 通过CoordinatorLayout+RecyclerView实现; 第一版得布局结构图: 起初思考到TabLayout和RecyclerView(ViewPager中)能够一起滑动,所以很容易想到的方法就是用Scrollview将两者嵌套进去,成果是实现了,然而Scrollview嵌套Viewpager的弊病不言而喻! 而第二版即本篇博客并不是为了解决Scrollview嵌套Viewpager的问题,而是换一种思路去实现! 布局结构图,很简略,就两层: <?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="#f7f7f7"    android:focusable="true"    android:focusableInTouchMode="true">    <LinearLayout        android:id="@+id/ll_content"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="vertical">        <net.lucode.hackware.magicindicator.MagicIndicator            android:id="@+id/magicIndicator"            android:layout_width="match_parent"            android:layout_height="35dp"            android:background="#acddee"            android:visibility="gone" />        <com.byl.jdrefresh.v1.CustomViewPager            android:id="@+id/customViewPager"            android:layout_width="match_parent"            android:layout_height="match_parent" />    </LinearLayout>    <RelativeLayout 搜寻栏.../></RelativeLayout>就是将第一版中的第一层和第二层(自定义JdScrollVIew)放在了Tab1的fragment中: <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <com.byl.jdrefresh.v2.JdScrollView2        android:id="@+id/jdScrollView"        android:layout_width="match_parent"        android:layout_height="match_parent" /></LinearLayout>JdScrollView布局 仅须要将原来布局中的ViewPager换成RecyclerView即可,具体可参考源码! 但这样做如同并没有解决TabLayout和列表一起滑动的成果啊?! 其实,这里取了一个巧,MainActivity中的有一个TabLayout,而tab1也就是首页中的Fragment也蕴含了一个一摸一样的TabLayout(NestedScrollview嵌套TabLayout+RecyclerView),当viewpager的position==0时,MainActivity中的TabLayout暗藏,其它页面时显示,所有的成果操作由MainActivity转移到了Tab1Fragment中,这样也就防止了应用ScrollView嵌套Viewpager这种模式! <?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/rl_content"    android:layout_width="match_parent"    android:layout_height="match_parent">    <View        android:id="@+id/view"        android:layout_width="match_parent"        android:layout_height="180dp"        android:background="#acddee" />    <ImageView        android:id="@+id/iv_ad"        android:layout_width="match_parent"        android:layout_height="1000dp"        android:layout_marginTop="-820dp"        android:scaleType="centerCrop"        android:src="@mipmap/bg_ad" />    <LinearLayout        android:id="@+id/ll_content"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:orientation="vertical">        <TextView            android:id="@+id/tv_refresh_state"            android:layout_width="match_parent"            android:layout_height="40dp"            android:gravity="center"            android:text="下拉刷新"            android:textColor="#dddddd" />        <net.lucode.hackware.magicindicator.MagicIndicator            android:id="@+id/magicIndicator"            android:layout_width="match_parent"            android:layout_height="35dp" />        <com.byl.jdrefresh.v2.CustomRecyclerView            android:id="@+id/recyclerView"            android:layout_width="match_parent"            android:layout_height="match_parent"            android:layout_marginHorizontal="15dp"            android:nestedScrollingEnabled="false" />    </LinearLayout></RelativeLayout>另外,本篇在原来的根底上多加了一个性能,能够参考京东app,即下拉超过肯定间隔后,背景会主动向下全屏开展,而后主动进入到广告页面: 实现计划,就是在手势抬起(ACTION_UP)时,判断以后下拉的间隔,超过某一设定值时,则主动在肯定工夫内让图片及整体布局处于全屏状态,其实就是依附ValueAnimator,一直的设置背景图的marginTop以及内容的paddingTop: case MotionEvent.ACTION_UP:                if (adScrollDistance > 500) {                    isInterceptTouch = true;                    AnimUtils.start(-(int) (marginTop + adScrollDistance), 500, new AnimUtils.OnAnimListener() {                        @Override                        public void onUpdate(int value) {                            layoutAd(marginTop + adScrollDistance + value);                            ll_content.setPadding(0, (int) (paddingTop + adScrollDistance + AD_START_SCROLL_DISTANCE) + value, 0, 0);                        }                        @Override                        public void onEnd() {                            context.startActivity(new Intent(context, AdActivity.class));                            new Handler().postDelayed(() -> {                                tv_refresh_state.setText("下拉刷新");                                isInterceptTouch = false;                                recyclerView.setRefreshing(false);                                isInterceptScroll = false;                                REFRESH_STATUS = REFRESH_DONE;                                layoutAd(marginTop);                                iv_ad.setImageAlpha(0);                                if (onPullListener != null) onPullListener.onPull(255);                                ll_content.setPadding(0, paddingTop, 0, 0);                                reset();                            }, 300);                        }                    });                    return true;                }                ......有一点须要留神的是,背景图片的高度,并不是屏幕高度,而是屏幕的高度加上 这一部分的高度: screenHeight = SysUtils.getScreenHeight(context);topRemainHeight = SysUtils.Dp2Px(context, imageShowHeight) - StatusBarUtil.getStatusBarHeight(context) - SysUtils.Dp2Px(context, 40);//40是搜寻栏高度,是多少就写多少RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, screenHeight + topRemainHeight);marginTop = -(screenHeight + topRemainHeight - SysUtils.Dp2Px(context, imageShowHeight));layoutParams.topMargin = marginTop;iv_ad.setLayoutParams(layoutParams);这样做的起因是,如果只把背景图设为屏幕高度,则背景图通过一直设置marginTop直至为0齐全开展时,红框局部会正好卡在底部,并不会齐全暗藏掉,起因其实很简略,如图: 图片达到底部时,因为红框与图片底部是持平的,所以正好漏在了里面,因而,这就须要下面所说的办法,将图片高度在屏幕高度根底上再+红框局部高度,这样在背景图片全屏时,可见内容区就移至了屏幕外,整个屏幕就只有背景图片可见了! Github地址:https://github.com/baiyuliang... 原文地址:https://juejin.cn/post/702168... 文末您的点赞珍藏就是对我最大的激励! 欢送关注我,分享Android干货,交换Android技术。 对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!

December 10, 2021 · 1 min · jiezi

关于android:Android-studio-点击按钮-跳转界面

问题形容首先,咱们有两个Java文件和与之绑定的xml文件。此处以HistoryActivity.java,activity\_history.xml 和 EventDetail.java,activity\_event\_detail.xml为例子。咱们要实现在HistoryActivity界面中增加一个按钮,并且点击跳转到EventDetail界面。 为HistoryActivity界面增加按钮在其对应的activity\_history.xml 中: <?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".HistoryActivity"> <Button android:id="@+id/History" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Historical Event" android:layout_alignParentLeft="true" android:layout_alignParentStart="true"/></android.support.constraint.ConstraintLayout>咱们通过android:id="@+id/History"语句讲button的id设置为History,在之后设置点击事件时应用。 为History按钮增加点击事件 在HistoryActivity.java中: package com.example.xff.tm;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.content.Intent;import android.widget.Button;import android.widget.*;public class HistoryActivity extends AppCompatActivity { Button button = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_history); button = (Button)findViewById(R.id.History); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(); intent.setClass(HistoryActivity.this,EventDetail.class); startActivity(intent); } }); }}通过之前定义的button的id来找到对应button,为之设置点击监听。当产生点击事件时,通过Intent进行跳转。 在manifests->AndroidManifest.xml中增加activity(这个步骤通常是增加点击事件之后零碎主动生成,能够进行查看) ...

December 10, 2021 · 1 min · jiezi

关于android:在fragment中使用viewpager嵌套fragment

步骤:1、在Activity布局文件中定义framelayout用于增加Fragment2、创立两个Fragment用于切换3、获取Fragment管理器,并开启事物FragmentTransaction4、通FragmentTransaction.add(resource id, fragment)将fragment增加到布局上,提交事物commit5、通FragmentTransaction. replace(resource id, fragment)切换显示的fragment,提交事物commit acvitity_dynamic.xml <Button android:id="@+id/btn_change" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="切换" /> <FrameLayout android:id="@+id/fl_fragment" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/holo_green_light"></FrameLayout>DynamicAcvitity.javapublic class DynamicActivity extends AppCompatActivity { private Button btn_change; private BlankFragment blank; private SecFragment sec; private Fragment fragment;//用该变量示意当初展现的是哪一个fragment private FragmentManager manager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_dynamic); blank = new BlankFragment(); sec = new SecFragment(); fragment=blank; manager=getSupportFragmentManager(); //开始一个事物 FragmentTransaction transaction=manager.beginTransaction(); transaction.add(R.id.fl_fragment,blank); transaction.commit();//事物要提交过后才会无效 //实例化按钮 btn_change= (Button) findViewById(R.id.btn_change); btn_change.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //一个事物提交后完结不能再用,要新建一个事物 FragmentTransaction transaction1=manager.beginTransaction(); if(fragment instanceof BlankFragment){ fragment=sec; }else { fragment=blank; } transaction1.replace(R.id.fl_fragment,fragment); transaction1.commit(); } });Fragment中嵌套fragment问题在fragment中应用viewpager嵌套fragment,获取fragmentManager的时候应用getChildFragmentManager替换getFragmentManager. ...

December 10, 2021 · 1 min · jiezi

关于android:今日份分享Flutter自定义之旋转木马

先上图,带你回到童年时光: 成果剖析子布局依照圆形程序搁置且平分角度子布局旋转、反对手势滑动旋转、疾速滑动抬手持续旋转、主动旋转反对X轴旋转反对前后缩放子布局(起始角度为前,绝对地位为后,最后面最大,反而越小)多个布局叠加时后面遮挡前面成果难点问题Flutter如何实现控件布局达到3D成果?Flutter如何实现子控件旋转、主动旋转、手势滑动时关联子控件旋转滚动?疾速滑动抬手持续旋转滚动?Flutter如何实现多个布局叠加时后面遮挡前面?1.子布局依照圆形程序搁置且平分角度如上图所示: 如上图所示(参考系:最下方为0度,逆时针旋转角度减少) 第一个点解:依据已知条件列方程式x2=width/2+sin(a)*Ry2=height/2+cos(a)*R    第二个点解:依据已知条件列方程式①① x=width/2-sin(b)*R    y=height/2-cos(b)*R因为b=a-180,所以带入①方程得:② x=width/2-sin(a-180)*R   y=height/2-cos(a-180)*R 又因为sin(k*360+a)=sin(a),所以②形式能够批改为:③ x=width/2-sin(180+a)*R   y=height/2-cos(180+a)*R又又因为 sin(180+a)=-sin(a),cos(180+a)=-cosa 带入③方程式得:④ x=width/2+sin(a)*R   y=height/2+cos(a)*R 由下面2点计算得,每个子布局的中心点坐标公式对立为:x=width/2+sin(a)*R y=height/2+cos(a)*R以上所用三角函数公式表: 通过下面计算得出子控件的地位公式后,开始咱们的代码。 实现子控件依照圆形布局及平分角度代码如下: //所有子控件的地位数据//count:子控件数量;  //startAngle:开始角度默认为0;  //rotateAngle:偏转角度默认为0;List<Point> _childPointList({Size size = Size.zero}) {    List<Point> childPointList = [];    double averageAngle = 360 / count;    double radius = size.width / 2 - childWidth / 2;       for (int i = 0; i < count; i++) {       /********************子布局角度*****************/      double angle = startAngle + averageAngle * i + rotateAngle;      //子布局中心点坐标      var centerX = size.width / 2 + sin(radian(angle)) * radius;      var centerY = size.height / 2 + cos(radian(angle)) * radius;      childPointList.add(Point(        centerX,        centerY,        childWidth,        childHeight,        centerX - childWidth / 2,        centerY - childHeight / 2,        centerX + childWidth / 2,        centerY + childHeight / 2,        1,        angle,        i,      ));    }    return childPointList;  }///角度转弧度///弧度 =度数 * ( / 180)///度数 =弧度 * (180 / )double radian(double angle) {    return angle * pi / 180;}2.子布局如何旋转?主动旋转?反对手势滑动旋转?疾速滑动抬手持续旋转?子布局如何旋转 所谓的旋转就是所有的子布局绕着圆形挪动,布局一旦挪动就代表两头地位扭转,依据下面咱们计算的子布局地位的公式来看: 中心点坐标x=width/2+sin(a)*R y=height/2+cos(a)*R因为width和R都是已知并且定下来的尺寸,所以说,想要扭转中心点坐标,只需批改 角度a就能够了。要想达到旋转成果的话就是让所有的子布局都同时挪动雷同的角度即可。 子布局原始角度值:double angle = startAngle + averageAngle * i; 咱们能够在此基础上加上一个可变的角度值,通过扭转这个值,所有的子布局都会同时加上此值同时挪动了地位。如下:double angle = startAngle + averageAngle * i + rotateAngle; 其中 rotateAngle 就是可变的值。扭转这个值就能让布局动起来主动旋转同理,咱们只有搞个定时器,周期性批改这个rotateAngle值,并setState(() {})刷新下,看起来就主动旋转了。 反对手势滑动旋转大家曾经晓得通过批改rotateAngle值去实现旋转,那么反对手势滑动旋转顾名思义就是通过手势批改这个rotateAngle值就OK,那么手势解决Flutter提供了GestureDetector组件,这个组件性能很弱小,这外面咱们应用了他的几个回调办法。 本次实现间接应用程度滑动监听,大家如果想兼容竖直滑动能够本人尝试批改就能够。 GestureDetector(        ///程度滑动按下        onHorizontalDragDown: (DragDownDetails details) {...},        ///程度滑动开始        onHorizontalDragStart: (DragStartDetails details) {          //记录拖动开始时以后的抉择角度值          downAngle = rotateAngle;          //记录拖动开始时的x坐标          downX = details.globalPosition.dx;        },        ///程度滑动中        onHorizontalDragUpdate: (DragUpdateDetails details) {           //滑动中X坐标值          var updateX = details.globalPosition.dx;          //计算以后旋转角度值并刷新          rotateAngle = (downX - updateX) * slipRatio + downAngle;          if (mounted) setState(() {});        },        ///程度滑动完结        onHorizontalDragEnd: (DragEndDetails details) {...},        ///滑动勾销        onHorizontalDragCancel: () {...},        behavior: HitTestBehavior.opaque,//deferToChild   translucent        child: xxx,);疾速滑动抬手持续旋转抬手还能持续旋转,也就是当咱们疾速滑动抬手的时候只有持续批改旋转角度值rotateAngle就能够达到持续旋转的成果。当咱们抬手的时候咱们能够拿到什么呢? 例如:当咱们骑着小黄单车在大路上疾速的蹬着脚蹬子而后进行蹬,你的小黄已过后的速度飞驰在这个大路上,因为高空的摩擦力的影响,速度会越来越小,最初进行。 ///程度滑动完结onHorizontalDragEnd: (DragEndDetails details) {          //x方向上每秒速度的像素数          velocityX = details.velocity.pixelsPerSecond.dx;           _controller.reset();          _controller.forward(); },  //动画设置rotateAngle   _controller = AnimationController(      vsync: this,      duration: Duration(milliseconds: 1000),    );    animation = CurvedAnimation(      parent: _controller,      curve: Curves.linearToEaseOut,    );    animation = new Tween<double>(begin: 1, end: 0).animate(animation)      ..addListener(() {        //以后速度        var velocity = animation.value * -velocityX;        var offsetX = radius != 0 ? velocity * 5 / (2 * pi * radius) : velocity;        rotateAngle += offsetX;        setState(() => {});      })      ..addStatusListener((status) {        if (status == AnimationStatus.completed) {          rotateAngle = rotateAngle % 360;          _startRotateTimer();        }      });3.反对X轴旋转 上图是X轴方向查看旋转切面图,依照x轴旋转所有的x坐标都是雷同的,y值从上往下一直减少。因为绕着X轴旋转时,X坐标是不变的,Y坐标值扭转,当旋转了a角度时,当初的Y坐标如图所示为 Y坐标旋转后=height/2+y*cos(a)     y值咱们曾经在下面计算过了,y=cos(a)*R 所以最终的计算公式是:Y坐标值=height/2+cos(a)*R*cos(a)cos(a)在a=[0,90]区间时对应的值是1-0   即是 a=0度时cos(a)=1,就是原始状态(与Y轴平行),a=90度时cos(a)=0,就是与Y轴垂直准状态。所以咱们能够设置a在0-90之间即可。4.反对前后缩放子布局(起始角度为前,绝对地位为后,最后面最大,反而越小) 上图为cos余弦曲线图。0度和360度最大 ,180度最小,刚好与咱们设计的初始值从0开始,而后逆时针绕一圈角度从0-360度。 所以缩放比scale计算公式能够写为: var scale = (1 - minScale) / 2 * (1 + cos(radian(angle - startAngle))) + minScale;minScale为最小缩放比,为了让缩放有个极限值,即 scale范畴为:(minScale,1) 5.多个布局叠加时后面遮挡前面从视觉感触,凑近后面的布局应该遮挡前面的布局,在Android当中bringToFront()办法能够让布局置于后面,Flutter没有提供此办法,咱们该如何解决这种状况呢? Flutter提供一个Stack布局,也叫层叠式布局,当咱们增加子布局到Stack布局中时,前面增加的会遮住后面增加的,所以只有咱们在增加子布局的时候依照由后到前来增加即可。话说怎么晓得是前是后呢? 晓得实现思路当初要解决的问题是: 如何辨别前与后?有什么条件能够辨别? 思考中...1、依据坐标值?Y坐标小就是前面,Y坐标大就是后面?答案是不肯定;因为当我启动角度不是0的时候,比方是90度,那么最右面是后面,最右边是前面,这个时候是X坐标的大小辨别前后关系,所以说独自应用坐标值的大小来决定前后关系是不对的。 2、依据前大后小准则?依据缩放值排序来增加子布局?答案是可行;因为咱们曾经实现了后面的布局缩放值是1,前面的缩放值越来越小,而且咱们曾经解决了启动角度问题,所以依据缩放值来实现是可行的。 ///通过缩放值进行排序,从小到大childPointList.sort((a, b) {  return a.scale.compareTo(b.scale);});///遍历增加子布局Stack(  children: childPointList.map(              (Point point) {                return Positioned(                    width: point.width,                    left: point.left,                    top: point.top,                    child: this.widget.children[point.index]);              },            ).toList(),   ),通过下面形式即可实现前后遮挡成果了。 小知识点Flutter 之Stack 组件Stack一个能够叠加子控件的布局,这里次要讲一下 Positioned,其余应用形式能够看下官网阐明。 Positioned({  Key key,  this.left,  this.top,  this.right,  this.bottom,  this.width,  this.height,  @required Widget child,})应用Positioned管制Widget的地位,通过Positioned能够随便摆放一个组件,有点像相对布局。其中left、top 、right、 bottom别离代表离Stack左、上、右、底四边的间隔。 Flutter之LayoutBuilder 组件有时咱们心愿依据组件的大小确认组件的外观,比方竖屏的时候高低展现,横屏的时候左右展现,通过LayoutBuilder组件能够获取父组件的束缚尺寸。 附:github链接:https://github.com/yixiaolunh... 原文链接:https://www.jianshu.com/p/451... 文末您的点赞珍藏就是对我最大的激励!欢送关注我,分享Android干货,交换Android技术。对文章有何见解,或者有何技术问题,欢送在评论区一起留言探讨!

December 10, 2021 · 1 min · jiezi

关于android:全面解析-钥匙环服务的应用场景商业价值

在互联互通的场景驱动下,同一开发者旗下经常领有多款利用或者多个利用状态,用户在同一设施的不同利用或端口登录时,即使应用同一帐号,仍须要反复输出明码进行验证,操作简单,间接影响到用户的应用体验,而华为钥匙环服务的呈现则无效地解决了这一问题。 钥匙环服务性能盘点 华为钥匙环服务(Keyring)为开发者提供全生命周期的凭据治理能力,包含对凭据进行获取、加密存储、受权共享、查问读取、删除等等,保障开发者的业务流畅性。 1.本地加密存储应用钥匙环服务所获取的凭据在可信执行环境(TEE)中能够随机生成密钥,而后再进行加密,每个设施的密钥均不雷同,而且密钥只能在TEE内应用,无奈来到设施,华为也不把握密钥的内容,反对凭据在本地平安存储。保留凭据时,开发者还能够设置读取此凭据的内容时是否通过锁屏明码或生物特色认证用户的身份。 1.凭据共享受权钥匙环服务可能提供同一团队开发的App之间受权共享凭据的能力,被受权应用凭据的利用能够是Android利用、快利用或者Web利用。 用户在下次进行登录操作时,利用在钥匙环服务中查找可用的凭据。查找到的凭据可能是本利用存储的凭据,也可能是其它利用受权给本利用应用的凭据。 1.保障数据安全除此之外,应用钥匙环服务,共享凭据过程中的安全性也可能失去保障。钥匙环服务通过验证安卓利用的APK包名、快利用的包名和证书哈希,或者获取以后网页的实在URL作为身份信息等形式,认证读写凭据的APK或网站的实在身份,避免凭据被仿冒的程序或网站盗用。 1.凭据删除或更新钥匙环服务向开发者提供删除和更新凭据的API。如果用户须要退出利用帐户,相应的认证凭据也将从钥匙环服务中删除。 利用场景 跨状态登录同一利用App在挪动端的入口出现多样化的散布趋势,用户在不同的利用状态之间来回切换,重复登录,导致体验割裂。而钥匙环服务对于不同的利用状态提供了不同的接口,包含钥匙环服务SDK、快利用API和Web API,反对跨利用状态共享用户认证凭据。 以电商购物类利用为例,节假日促销短信推送是咱们最罕用的营销形式之一,也广泛被用户所承受。用户在安卓利用登录之后,点开促销短信中的链接,在华为浏览器中关上Web利用,间接处于登录状态,无需反复登录即可实现下单。 跨利用登录在业务倒退过程中,同一个团队往往会开发多个App,新利用上架之后,取得用户并非易事。针对全新业务场景的利用,钥匙环服务会为其提供一条畅通无阻、高效便捷的桥梁,将流量从原有的利用当中引进来,用户无需反复输出帐号密码,只有一键受权,就能够实现无缝登录。 也就是说,用户在利用A已登录,装置同一开发者旗下的新利用B,无需输出帐号密码,能应用登录利用A的帐号,实现间接登录利用B。 帐号易切换钥匙环服务还能够存储多个凭据,如果用户在某一利用登录过多个帐号,利用能够提供确认页面,让用户自主抉择某个帐号登录,操作便捷。当然了,作为开发者,您能够抉择在用户登录之前,验证用户的生物特色或者锁屏明码,无效躲避帐号被冒用等安全隐患。 商业价值 钥匙环服务的跨利用登录场景将帮忙同一开发者旗下的关联利用之间共享流量,助力孵化新产品。同时实现流量在安卓利用、快利用、Web利用之间的双向流转。通过发明一处登录,处处登录的无缝登录体验,钥匙环服务帮忙开发者缩短用户的交互转化门路,升高登录操作的复杂度,进一步晋升导流转化率。总之,钥匙环服务是助力开发者取得商业胜利的利器。 总体来看,华为钥匙环服务具备便捷登录、平安无忧、流量共享等外围劣势,能够帮忙开发者疾速满足用户在购物、出行、社交、浏览等多个应用场景下的平安需要。 之后,华为钥匙环服务还会推出其余个性,HMS Core也将在平安畛域凋谢新的能力,为开发者们带来更优质的服务和体验。 更多精彩内容,请见华为开发者官方论坛→https://developer.huawei.com/...

December 10, 2021 · 1 min · jiezi

关于android:全面解析-钥匙环服务的应用场景商业价值

在互联互通的场景驱动下,同一开发者旗下经常领有多款利用或者多个利用状态,用户在同一设施的不同利用或端口登录时,即使应用同一帐号,仍须要反复输出明码进行验证,操作简单,间接影响到用户的应用体验,而华为钥匙环服务的呈现则无效地解决了这一问题。 钥匙环服务性能盘点 华为钥匙环服务(Keyring)为开发者提供全生命周期的凭据治理能力,包含对凭据进行获取、加密存储、受权共享、查问读取、删除等等,保障开发者的业务流畅性。 1.本地加密存储应用钥匙环服务所获取的凭据在可信执行环境(TEE)中能够随机生成密钥,而后再进行加密,每个设施的密钥均不雷同,而且密钥只能在TEE内应用,无奈来到设施,华为也不把握密钥的内容,反对凭据在本地平安存储。保留凭据时,开发者还能够设置读取此凭据的内容时是否通过锁屏明码或生物特色认证用户的身份。 1.凭据共享受权钥匙环服务可能提供同一团队开发的App之间受权共享凭据的能力,被受权应用凭据的利用能够是Android利用、快利用或者Web利用。 用户在下次进行登录操作时,利用在钥匙环服务中查找可用的凭据。查找到的凭据可能是本利用存储的凭据,也可能是其它利用受权给本利用应用的凭据。 1.保障数据安全除此之外,应用钥匙环服务,共享凭据过程中的安全性也可能失去保障。钥匙环服务通过验证安卓利用的APK包名、快利用的包名和证书哈希,或者获取以后网页的实在URL作为身份信息等形式,认证读写凭据的APK或网站的实在身份,避免凭据被仿冒的程序或网站盗用。 1.凭据删除或更新钥匙环服务向开发者提供删除和更新凭据的API。如果用户须要退出利用帐户,相应的认证凭据也将从钥匙环服务中删除。 利用场景 跨状态登录同一利用App在挪动端的入口出现多样化的散布趋势,用户在不同的利用状态之间来回切换,重复登录,导致体验割裂。而钥匙环服务对于不同的利用状态提供了不同的接口,包含钥匙环服务SDK、快利用API和Web API,反对跨利用状态共享用户认证凭据。 以电商购物类利用为例,节假日促销短信推送是咱们最罕用的营销形式之一,也广泛被用户所承受。用户在安卓利用登录之后,点开促销短信中的链接,在华为浏览器中关上Web利用,间接处于登录状态,无需反复登录即可实现下单。 跨利用登录在业务倒退过程中,同一个团队往往会开发多个App,新利用上架之后,取得用户并非易事。针对全新业务场景的利用,钥匙环服务会为其提供一条畅通无阻、高效便捷的桥梁,将流量从原有的利用当中引进来,用户无需反复输出帐号密码,只有一键受权,就能够实现无缝登录。 也就是说,用户在利用A已登录,装置同一开发者旗下的新利用B,无需输出帐号密码,能应用登录利用A的帐号,实现间接登录利用B。 帐号易切换钥匙环服务还能够存储多个凭据,如果用户在某一利用登录过多个帐号,利用能够提供确认页面,让用户自主抉择某个帐号登录,操作便捷。当然了,作为开发者,您能够抉择在用户登录之前,验证用户的生物特色或者锁屏明码,无效躲避帐号被冒用等安全隐患。 商业价值 钥匙环服务的跨利用登录场景将帮忙同一开发者旗下的关联利用之间共享流量,助力孵化新产品。同时实现流量在安卓利用、快利用、Web利用之间的双向流转。通过发明一处登录,处处登录的无缝登录体验,钥匙环服务帮忙开发者缩短用户的交互转化门路,升高登录操作的复杂度,进一步晋升导流转化率。总之,钥匙环服务是助力开发者取得商业胜利的利器。 总体来看,华为钥匙环服务具备便捷登录、平安无忧、流量共享等外围劣势,能够帮忙开发者疾速满足用户在购物、出行、社交、浏览等多个应用场景下的平安需要。 之后,华为钥匙环服务还会推出其余个性,HMS Core也将在平安畛域凋谢新的能力,为开发者们带来更优质的服务和体验。 更多精彩内容,请见华为开发者官方论坛→https://developer.huawei.com/...

December 10, 2021 · 1 min · jiezi

关于android:安卓逆向-3840-编译-release版本的app

release版本须要生成签名秘钥 1. 配置生成秘钥证书信息随便写而后勾销 2. 配置配置签名信息 点击ok。 3. 抉择build变量 4. 利用签名配置信息 5. 从新生成apkbuild --> Generate Signed Bundle or APK --> 始终抉择下一步,直到finish.最初release版本的apk会生成app/release/app-release.apk

December 9, 2021 · 1 min · jiezi

关于android:Android入门教程-Camera-相机一

概述Android 应用 Android Camera API 实现音视频的采集、编码、封包成 mp4 输入。 基于android.hardware.Camera,创立一个横屏利用,实时预览摄像头图像,实现录像并输入MP4的性能。 这里不应用Camera2。 申请权限<!-- 须要录制音视频权限和写内部存储权限 --><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.RECORD_AUDIO" /><uses-permission android:name="android.permission.CAMERA" /><uses-feature android:name="android.hardware.camera" />在 activity 中动静申请权限 private static final String[] VIDEO_PERMISSIONS = { Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE};实现摄像头预览性能应用SurfaceView来预览。新建CameraPreview类继承自SurfaceView并实现SurfaceHolder.Callback; camera相干操作都放在这个View里。 surfaceCreated中获取Camera实例,启动预览;设置预览相干参数surfaceDestroyed开释Camerapublic class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private static final String TAG = "rustAppCameraPreview"; private SurfaceHolder mHolder; private Camera mCamera; public static final int MEDIA_TYPE_IMAGE = 1; public static final int MEDIA_TYPE_VIDEO = 2; private static int mOptVideoWidth = 1920; // 默认视频帧宽度 private static int mOptVideoHeight = 1080; private Uri outputMediaFileUri; private String outputMediaFileType; public CameraPreview(Context context) { super(context); mHolder = getHolder(); mHolder.addCallback(this); } private static Camera getCameraInstance() { Camera c = null; try { c = Camera.open(); } catch (Exception e) { Log.d(TAG, "camera is not available"); } return c; } @Override public void surfaceCreated(SurfaceHolder holder) { mCamera = getCameraInstance(); try { mCamera.setPreviewDisplay(holder); mCamera.startPreview(); getCameraOptimalVideoSize(); // 找到最合适的分辨率 } catch (IOException e) { Log.d(TAG, "Error setting camera preview: " + e.getMessage()); } } private void getCameraOptimalVideoSize() { try { Camera.Parameters parameters = mCamera.getParameters(); List<Camera.Size> mSupportedPreviewSizes = parameters.getSupportedPreviewSizes(); List<Camera.Size> mSupportedVideoSizes = parameters.getSupportedVideoSizes(); Camera.Size optimalSize = CameraHelper.getOptimalVideoSize(mSupportedVideoSizes, mSupportedPreviewSizes, getWidth(), getHeight()); mOptVideoWidth = optimalSize.width; mOptVideoHeight = optimalSize.height; Log.d(TAG, "prepareVideoRecorder: optimalSize:" + mOptVideoWidth + ", " + mOptVideoHeight); } catch (Exception e) { Log.e(TAG, "getCameraOptimalVideoSize: ", e); } } @Override public void surfaceDestroyed(SurfaceHolder holder) { mHolder.removeCallback(this); mCamera.setPreviewCallback(null); mCamera.stopPreview(); mCamera.release(); mCamera = null; } @Override public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { }}在 Fragment 中显示摄像头预览 ...

December 9, 2021 · 3 min · jiezi

关于android:总结UI原理和高级的UI优化方式

不晓得UI原理如何做UI优化? 本文内容分为三个局部,UI原理、LayoutInflater原理、UI优化,篇幅有点长,能够抉择本人喜爱的章节进行浏览,每一个局部最初都有小结。 置信大家多多少少看过一些Activity启动源码剖析的文章,也能大略说出Activity启动流程,例如这种答复: AMS负责管理系统所有Activity,所以利用startActivity 最终会通过Binder调用到AMS的startActivity办法,AMS启动一个Activity之前会做一些查看,例如权限、是否在清单文件注册等,而后就能够启动了,AMS是一个零碎服务,在独自过程,所以要将生命周期通知利用,又波及到跨过程调用,这个跨过程同样采纳Binder,媒介是通过ActivityThread的外部类ApplicationThread,AMS将生命周期跨过程传到ApplicationThread,而后ApplicationThread 再分发给ActivityThread外部的Handler,这时候生命周期曾经回调到利用主线程了,回调Activity的各个生命周期办法。还能够细分,比方Activity、Window、DecorView之间的关系,这个其实也应该难度不大,又忽然想到,setContentView为什么要放在onCreate中?,放在其它办法里行不行,能不能放在onAttachBaseContext办法里?其实,这些问题能够在源码中找到答案。 本文参考Android 9.0源码,API 28。 注释写一个Activity,咱们个别都是通过在onCreate办法中调用setContentView办法设置咱们的布局,有没有想过这个问题,设置完布局,界面就会开始绘制吗? 一、从生命周期源码剖析UI原理Activity启动流程大抵如下: Context -> startActivityAMS -> startActivity过程不存在则告诉Zygote启动过程,启动完过程,执行ActivityThread的main办法,进入loop循环,通过Handler散发音讯。ApplicationThread -> scheduleLaunchActivityActivityThread -> handleLaunchActivity其它生命周期回调从第4点开始剖析 1.1 ActivityThread1.1.1 外部类 ApplicationThreadAMS暂且先不剖析,AMS启动Activity会通过ApplicationThread告诉到ActivityThread,启动Activity从ApplicationThread开始说起,看下 scheduleLaunchActivity 办法 1.1.2 ApplicationThread#scheduleLaunchActivitypublic final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Configuration curConfig, Configuration overrideConfig, CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state, PersistableBundle persistentState, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents, boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) { ActivityClientRecord r = new ActivityClientRecord(); r.token = token; r.ident = ident; r.intent = intent; ... sendMessage(H.LAUNCH_ACTIVITY, r); }sendMessage 最终封装一个ActivityClientRecord对象到msg,调用mH.sendMessage(msg);,mH 是一个Handler,间接看解决局部吧, ...

December 9, 2021 · 21 min · jiezi

关于android:BinderHwBinder-和-VndBinder

碰到钻研过 Android 过程间通信的敌人,我通常喜爱求教 Binder 相干的问题。然而,太细节的问题有点求全责备了,对于实质的了解是我冀望的。题目的问题通常是我最喜爱问的一个。Android 8.0 当前的版本中,Binder有哪几种?它们都是怎么应用的?IPC 域阐明/dev/binder框架/利用过程之间的 IPC,应用 AIDL 接口/dev/hwbinder框架/供应商过程之间的 IPC,应用 HIDL 接口 供应商过程之间的 IPC,应用 HIDL 接口/dev/vndbinder供应商/供应商过程之间的 IPC,应用 AIDL 接口为什么会存在这三种 Binder?Android 8.0 从新设计了 Android 零碎框架,引入 Treble 机制。在新的架构中,引入了 HAL 接口定义语言(HIDL),提供了独立的供应商分区(vendor),以及供应商原生开发套件 (VNDK)。通过这些新技术,能够将零碎框架与供应商实现分隔开来,使得用户能够独立替换分区镜像,以便制造商可能更轻松、更疾速地更新 Android 零碎。 Treble 的引进,使得 system 和 vendor 分区间无奈间接拜访,导致原有的 Binder 机制不能持续应用。因而将 Binder 拆分为 Binder、HwBinder 和 VndBinder,用于在 system/system、system/vendor 和 vendor/vendor 之间进行过程间通信。三种 Binder 的应用如下图所示, ] 三种 Binder 应用的资源有什么不同?这个问题的答案曾经在上图中,能够归结为以下几点: Device nodeBinder LibraryServiceInterface LanguageBinder/dev/binderlibbinderservicemanagerAIDLHwBinder/dev/hwbinderlibhwbinderhwservicemanagerHIDLVndBinder/dev/vndbinderlibbindervndservicemanagerAIDL为什么会引入 HwBinder?HwBinder 引入的实质还是 Treble 机制的应用,这使得 system 和 vendor 分区互相隔离。在 Android 8.0 之前,Android HAL 与零碎框架是紧耦合的,它们打包在一个镜像里。HAL只是一个个的so库,framework 通过关上动静库来调用 HAL。 为了适配 HwBinder,Android 8.0 同时引入了 HIDL,用于建设 framework 和 HAL 间的通信。 ...

December 9, 2021 · 1 min · jiezi

关于android:全面解析-钥匙环服务的应用场景商业价值

在互联互通的场景驱动下,同一开发者旗下经常领有多款利用或者多个利用状态,用户在同一设施的不同利用或端口登录时,即使应用同一帐号,仍须要反复输出明码进行验证,操作简单,间接影响到用户的应用体验,而华为钥匙环服务的呈现则无效地解决了这一问题。 钥匙环服务性能盘点 华为钥匙环服务(Keyring)为开发者提供全生命周期的凭据治理能力,包含对凭据进行获取、加密存储、受权共享、查问读取、删除等等,保障开发者的业务流畅性。 1.本地加密存储应用钥匙环服务所获取的凭据在可信执行环境(TEE)中能够随机生成密钥,而后再进行加密,每个设施的密钥均不雷同,而且密钥只能在TEE内应用,无奈来到设施,华为也不把握密钥的内容,反对凭据在本地平安存储。保留凭据时,开发者还能够设置读取此凭据的内容时是否通过锁屏明码或生物特色认证用户的身份。 1.凭据共享受权钥匙环服务可能提供同一团队开发的App之间受权共享凭据的能力,被受权应用凭据的利用能够是Android利用、快利用或者Web利用。 用户在下次进行登录操作时,利用在钥匙环服务中查找可用的凭据。查找到的凭据可能是本利用存储的凭据,也可能是其它利用受权给本利用应用的凭据。 1.保障数据安全除此之外,应用钥匙环服务,共享凭据过程中的安全性也可能失去保障。钥匙环服务通过验证安卓利用的APK包名、快利用的包名和证书哈希,或者获取以后网页的实在URL作为身份信息等形式,认证读写凭据的APK或网站的实在身份,避免凭据被仿冒的程序或网站盗用。 1.凭据删除或更新钥匙环服务向开发者提供删除和更新凭据的API。如果用户须要退出利用帐户,相应的认证凭据也将从钥匙环服务中删除。 利用场景 跨状态登录同一利用App在挪动端的入口出现多样化的散布趋势,用户在不同的利用状态之间来回切换,重复登录,导致体验割裂。而钥匙环服务对于不同的利用状态提供了不同的接口,包含钥匙环服务SDK、快利用API和Web API,反对跨利用状态共享用户认证凭据。 以电商购物类利用为例,节假日促销短信推送是咱们最罕用的营销形式之一,也广泛被用户所承受。用户在安卓利用登录之后,点开促销短信中的链接,在华为浏览器中关上Web利用,间接处于登录状态,无需反复登录即可实现下单。 跨利用登录在业务倒退过程中,同一个团队往往会开发多个App,新利用上架之后,取得用户并非易事。针对全新业务场景的利用,钥匙环服务会为其提供一条畅通无阻、高效便捷的桥梁,将流量从原有的利用当中引进来,用户无需反复输出帐号密码,只有一键受权,就能够实现无缝登录。 也就是说,用户在利用A已登录,装置同一开发者旗下的新利用B,无需输出帐号密码,能应用登录利用A的帐号,实现间接登录利用B。 帐号易切换钥匙环服务还能够存储多个凭据,如果用户在某一利用登录过多个帐号,利用能够提供确认页面,让用户自主抉择某个帐号登录,操作便捷。当然了,作为开发者,您能够抉择在用户登录之前,验证用户的生物特色或者锁屏明码,无效躲避帐号被冒用等安全隐患。 商业价值 钥匙环服务的跨利用登录场景将帮忙同一开发者旗下的关联利用之间共享流量,助力孵化新产品。同时实现流量在安卓利用、快利用、Web利用之间的双向流转。通过发明一处登录,处处登录的无缝登录体验,钥匙环服务帮忙开发者缩短用户的交互转化门路,升高登录操作的复杂度,进一步晋升导流转化率。总之,钥匙环服务是助力开发者取得商业胜利的利器。 总体来看,华为钥匙环服务具备便捷登录、平安无忧、流量共享等外围劣势,能够帮忙开发者疾速满足用户在购物、出行、社交、浏览等多个应用场景下的平安需要。 之后,华为钥匙环服务还会推出其余个性,HMS Core也将在平安畛域凋谢新的能力,为开发者们带来更优质的服务和体验。 更多精彩内容,请见华为开发者官方论坛→https://developer.huawei.com/...

December 9, 2021 · 1 min · jiezi

关于android:全面解析-钥匙环服务的应用场景商业价值

在互联互通的场景驱动下,同一开发者旗下经常领有多款利用或者多个利用状态,用户在同一设施的不同利用或端口登录时,即使应用同一帐号,仍须要反复输出明码进行验证,操作简单,间接影响到用户的应用体验,而华为钥匙环服务的呈现则无效地解决了这一问题。 钥匙环服务性能盘点 华为钥匙环服务(Keyring)为开发者提供全生命周期的凭据治理能力,包含对凭据进行获取、加密存储、受权共享、查问读取、删除等等,保障开发者的业务流畅性。 1.本地加密存储应用钥匙环服务所获取的凭据在可信执行环境(TEE)中能够随机生成密钥,而后再进行加密,每个设施的密钥均不雷同,而且密钥只能在TEE内应用,无奈来到设施,华为也不把握密钥的内容,反对凭据在本地平安存储。保留凭据时,开发者还能够设置读取此凭据的内容时是否通过锁屏明码或生物特色认证用户的身份。 1.凭据共享受权钥匙环服务可能提供同一团队开发的App之间受权共享凭据的能力,被受权应用凭据的利用能够是Android利用、快利用或者Web利用。 用户在下次进行登录操作时,利用在钥匙环服务中查找可用的凭据。查找到的凭据可能是本利用存储的凭据,也可能是其它利用受权给本利用应用的凭据。 1.保障数据安全除此之外,应用钥匙环服务,共享凭据过程中的安全性也可能失去保障。钥匙环服务通过验证安卓利用的APK包名、快利用的包名和证书哈希,或者获取以后网页的实在URL作为身份信息等形式,认证读写凭据的APK或网站的实在身份,避免凭据被仿冒的程序或网站盗用。 1.凭据删除或更新钥匙环服务向开发者提供删除和更新凭据的API。如果用户须要退出利用帐户,相应的认证凭据也将从钥匙环服务中删除。 利用场景 跨状态登录同一利用App在挪动端的入口出现多样化的散布趋势,用户在不同的利用状态之间来回切换,重复登录,导致体验割裂。而钥匙环服务对于不同的利用状态提供了不同的接口,包含钥匙环服务SDK、快利用API和Web API,反对跨利用状态共享用户认证凭据。 以电商购物类利用为例,节假日促销短信推送是咱们最罕用的营销形式之一,也广泛被用户所承受。用户在安卓利用登录之后,点开促销短信中的链接,在华为浏览器中关上Web利用,间接处于登录状态,无需反复登录即可实现下单。 跨利用登录在业务倒退过程中,同一个团队往往会开发多个App,新利用上架之后,取得用户并非易事。针对全新业务场景的利用,钥匙环服务会为其提供一条畅通无阻、高效便捷的桥梁,将流量从原有的利用当中引进来,用户无需反复输出帐号密码,只有一键受权,就能够实现无缝登录。 也就是说,用户在利用A已登录,装置同一开发者旗下的新利用B,无需输出帐号密码,能应用登录利用A的帐号,实现间接登录利用B。 帐号易切换钥匙环服务还能够存储多个凭据,如果用户在某一利用登录过多个帐号,利用能够提供确认页面,让用户自主抉择某个帐号登录,操作便捷。当然了,作为开发者,您能够抉择在用户登录之前,验证用户的生物特色或者锁屏明码,无效躲避帐号被冒用等安全隐患。 商业价值 钥匙环服务的跨利用登录场景将帮忙同一开发者旗下的关联利用之间共享流量,助力孵化新产品。同时实现流量在安卓利用、快利用、Web利用之间的双向流转。通过发明一处登录,处处登录的无缝登录体验,钥匙环服务帮忙开发者缩短用户的交互转化门路,升高登录操作的复杂度,进一步晋升导流转化率。总之,钥匙环服务是助力开发者取得商业胜利的利器。 总体来看,华为钥匙环服务具备便捷登录、平安无忧、流量共享等外围劣势,能够帮忙开发者疾速满足用户在购物、出行、社交、浏览等多个应用场景下的平安需要。 之后,华为钥匙环服务还会推出其余个性,HMS Core也将在平安畛域凋谢新的能力,为开发者们带来更优质的服务和体验。 更多精彩内容,请见华为开发者官方论坛→https://developer.huawei.com/...

December 9, 2021 · 1 min · jiezi

关于android:性能优化Android-开发进阶必经之路

前言性能优化,是 Android 程序员进阶的必经之路。不论是在我的项目开发过程中,还是在面试的时候,咱们都会遇到对于性能优化的问题。 性能优化是一个重点同时也是一个难点。很多 Android 学习者和开发者都会在性能优化上面临着很多的困扰和解决不了的难题。 上面来看看 Android 性能优化蕴含的知识点有哪些? 360°全方位性能调优 程序性能优化: OOM 问题原理解析、ANR 问题解析、Crash 监控计划、启动速度与执行效率优化我的项目实战、布局检测与优化、内存优化、耗电优化、网络传输与数据存储优化、APK大小优化、屏幕适配 开发效率优化: 分布式版本控制系统Git、自动化构建零碎Gradle 性能优化常见问题内存问题: 耗内存:耗内存、内存泄露会影响整机的性能;OOM问题:OOM会影响产品的稳定性;程序切换到后盾后占用内存无奈开释:占用内存多预示着留给其它利用的残余内存空间小;功耗问题:发烫(耗电); 晦涩度问题:启动慢、页面显示须要长时间转圈加载、页面切换卡顿、黑白屏; 针对下面一系列的性能问题,谷歌官网提供了各种各样的工具来针对性的解决各个方面的问题:内存问题: 提供了 Android Studio 的动态代码检测性能、Android Monitor;第三方内存泄露剖析工具 Leakcanary、MAT; 功耗问题: 提供了 GPU 出现模式、battery-historian、Android Monitor; 晦涩度问题: 提供了 Android Studio 的动态代码检测性能、Android Monitor、HierarchyViewer、StrictMode、过渡绘制检测工具、TraceView 等; 性能优化总结360度全方位性能调优: Android 的性能优化牵扯的知识点很多,除了罕用解决方案,底层原理也值得咱们深入探讨。这里给大家分享一份722页超全的性能优化常识手册:《360度全方位性能调优》,帮忙大家更好地学习 Android 性能优化。 设计思维与代码品质优化程序性能优化开发效率优化APP 性能优化实际 Android性能优化——实战解析: 除了实践方面,更重要的就是我的项目实战。这边同时还给大家分享一份各个大厂(腾讯、爱奇艺、字节跳动、百度、京东、支付宝......)的 Android 性能优化实战案例。 因为篇幅无限,只能以截图的模式展现目录和局部内容,须要 Android 性能优化材料 的敌人能够点击这里收费支付完整版PDF文档。

December 9, 2021 · 1 min · jiezi

关于android:全面解析-钥匙环服务的应用场景商业价值

在互联互通的场景驱动下,同一开发者旗下经常领有多款利用或者多个利用状态,用户在同一设施的不同利用或端口登录时,即使应用同一帐号,仍须要反复输出明码进行验证,操作简单,间接影响到用户的应用体验,而华为钥匙环服务的呈现则无效地解决了这一问题。 钥匙环服务性能盘点 华为钥匙环服务(Keyring)为开发者提供全生命周期的凭据治理能力,包含对凭据进行获取、加密存储、受权共享、查问读取、删除等等,保障开发者的业务流畅性。 1.本地加密存储应用钥匙环服务所获取的凭据在可信执行环境(TEE)中能够随机生成密钥,而后再进行加密,每个设施的密钥均不雷同,而且密钥只能在TEE内应用,无奈来到设施,华为也不把握密钥的内容,反对凭据在本地平安存储。保留凭据时,开发者还能够设置读取此凭据的内容时是否通过锁屏明码或生物特色认证用户的身份。 1.凭据共享受权钥匙环服务可能提供同一团队开发的App之间受权共享凭据的能力,被受权应用凭据的利用能够是Android利用、快利用或者Web利用。 用户在下次进行登录操作时,利用在钥匙环服务中查找可用的凭据。查找到的凭据可能是本利用存储的凭据,也可能是其它利用受权给本利用应用的凭据。 1.保障数据安全除此之外,应用钥匙环服务,共享凭据过程中的安全性也可能失去保障。钥匙环服务通过验证安卓利用的APK包名、快利用的包名和证书哈希,或者获取以后网页的实在URL作为身份信息等形式,认证读写凭据的APK或网站的实在身份,避免凭据被仿冒的程序或网站盗用。 1.凭据删除或更新钥匙环服务向开发者提供删除和更新凭据的API。如果用户须要退出利用帐户,相应的认证凭据也将从钥匙环服务中删除。 利用场景 跨状态登录同一利用App在挪动端的入口出现多样化的散布趋势,用户在不同的利用状态之间来回切换,重复登录,导致体验割裂。而钥匙环服务对于不同的利用状态提供了不同的接口,包含钥匙环服务SDK、快利用API和Web API,反对跨利用状态共享用户认证凭据。 以电商购物类利用为例,节假日促销短信推送是咱们最罕用的营销形式之一,也广泛被用户所承受。用户在安卓利用登录之后,点开促销短信中的链接,在华为浏览器中关上Web利用,间接处于登录状态,无需反复登录即可实现下单。 跨利用登录在业务倒退过程中,同一个团队往往会开发多个App,新利用上架之后,取得用户并非易事。针对全新业务场景的利用,钥匙环服务会为其提供一条畅通无阻、高效便捷的桥梁,将流量从原有的利用当中引进来,用户无需反复输出帐号密码,只有一键受权,就能够实现无缝登录。 也就是说,用户在利用A已登录,装置同一开发者旗下的新利用B,无需输出帐号密码,能应用登录利用A的帐号,实现间接登录利用B。 帐号易切换钥匙环服务还能够存储多个凭据,如果用户在某一利用登录过多个帐号,利用能够提供确认页面,让用户自主抉择某个帐号登录,操作便捷。当然了,作为开发者,您能够抉择在用户登录之前,验证用户的生物特色或者锁屏明码,无效躲避帐号被冒用等安全隐患。 商业价值 钥匙环服务的跨利用登录场景将帮忙同一开发者旗下的关联利用之间共享流量,助力孵化新产品。同时实现流量在安卓利用、快利用、Web利用之间的双向流转。通过发明一处登录,处处登录的无缝登录体验,钥匙环服务帮忙开发者缩短用户的交互转化门路,升高登录操作的复杂度,进一步晋升导流转化率。总之,钥匙环服务是助力开发者取得商业胜利的利器。 总体来看,华为钥匙环服务具备便捷登录、平安无忧、流量共享等外围劣势,能够帮忙开发者疾速满足用户在购物、出行、社交、浏览等多个应用场景下的平安需要。 之后,华为钥匙环服务还会推出其余个性,HMS Core也将在平安畛域凋谢新的能力,为开发者们带来更优质的服务和体验。 更多精彩内容,请见华为开发者官方论坛→https://developer.huawei.com/...

December 9, 2021 · 1 min · jiezi

关于android:全面解析-钥匙环服务的应用场景商业价值

在互联互通的场景驱动下,同一开发者旗下经常领有多款利用或者多个利用状态,用户在同一设施的不同利用或端口登录时,即使应用同一帐号,仍须要反复输出明码进行验证,操作简单,间接影响到用户的应用体验,而华为钥匙环服务的呈现则无效地解决了这一问题。 钥匙环服务性能盘点 华为钥匙环服务(Keyring)为开发者提供全生命周期的凭据治理能力,包含对凭据进行获取、加密存储、受权共享、查问读取、删除等等,保障开发者的业务流畅性。 1.本地加密存储应用钥匙环服务所获取的凭据在可信执行环境(TEE)中能够随机生成密钥,而后再进行加密,每个设施的密钥均不雷同,而且密钥只能在TEE内应用,无奈来到设施,华为也不把握密钥的内容,反对凭据在本地平安存储。保留凭据时,开发者还能够设置读取此凭据的内容时是否通过锁屏明码或生物特色认证用户的身份。 1.凭据共享受权钥匙环服务可能提供同一团队开发的App之间受权共享凭据的能力,被受权应用凭据的利用能够是Android利用、快利用或者Web利用。 用户在下次进行登录操作时,利用在钥匙环服务中查找可用的凭据。查找到的凭据可能是本利用存储的凭据,也可能是其它利用受权给本利用应用的凭据。 1.保障数据安全除此之外,应用钥匙环服务,共享凭据过程中的安全性也可能失去保障。钥匙环服务通过验证安卓利用的APK包名、快利用的包名和证书哈希,或者获取以后网页的实在URL作为身份信息等形式,认证读写凭据的APK或网站的实在身份,避免凭据被仿冒的程序或网站盗用。 1.凭据删除或更新钥匙环服务向开发者提供删除和更新凭据的API。如果用户须要退出利用帐户,相应的认证凭据也将从钥匙环服务中删除。 利用场景 跨状态登录同一利用App在挪动端的入口出现多样化的散布趋势,用户在不同的利用状态之间来回切换,重复登录,导致体验割裂。而钥匙环服务对于不同的利用状态提供了不同的接口,包含钥匙环服务SDK、快利用API和Web API,反对跨利用状态共享用户认证凭据。 以电商购物类利用为例,节假日促销短信推送是咱们最罕用的营销形式之一,也广泛被用户所承受。用户在安卓利用登录之后,点开促销短信中的链接,在华为浏览器中关上Web利用,间接处于登录状态,无需反复登录即可实现下单。 跨利用登录在业务倒退过程中,同一个团队往往会开发多个App,新利用上架之后,取得用户并非易事。针对全新业务场景的利用,钥匙环服务会为其提供一条畅通无阻、高效便捷的桥梁,将流量从原有的利用当中引进来,用户无需反复输出帐号密码,只有一键受权,就能够实现无缝登录。 也就是说,用户在利用A已登录,装置同一开发者旗下的新利用B,无需输出帐号密码,能应用登录利用A的帐号,实现间接登录利用B。 帐号易切换钥匙环服务还能够存储多个凭据,如果用户在某一利用登录过多个帐号,利用能够提供确认页面,让用户自主抉择某个帐号登录,操作便捷。当然了,作为开发者,您能够抉择在用户登录之前,验证用户的生物特色或者锁屏明码,无效躲避帐号被冒用等安全隐患。 商业价值 钥匙环服务的跨利用登录场景将帮忙同一开发者旗下的关联利用之间共享流量,助力孵化新产品。同时实现流量在安卓利用、快利用、Web利用之间的双向流转。通过发明一处登录,处处登录的无缝登录体验,钥匙环服务帮忙开发者缩短用户的交互转化门路,升高登录操作的复杂度,进一步晋升导流转化率。总之,钥匙环服务是助力开发者取得商业胜利的利器。 总体来看,华为钥匙环服务具备便捷登录、平安无忧、流量共享等外围劣势,能够帮忙开发者疾速满足用户在购物、出行、社交、浏览等多个应用场景下的平安需要。 之后,华为钥匙环服务还会推出其余个性,HMS Core也将在平安畛域凋谢新的能力,为开发者们带来更优质的服务和体验。 更多精彩内容,请见华为开发者官方论坛→https://developer.huawei.com/...

December 9, 2021 · 1 min · jiezi

关于android:全面解析-钥匙环服务的应用场景商业价值

在互联互通的场景驱动下,同一开发者旗下经常领有多款利用或者多个利用状态,用户在同一设施的不同利用或端口登录时,即使应用同一帐号,仍须要反复输出明码进行验证,操作简单,间接影响到用户的应用体验,而华为钥匙环服务的呈现则无效地解决了这一问题。 钥匙环服务性能盘点 华为钥匙环服务(Keyring)为开发者提供全生命周期的凭据治理能力,包含对凭据进行获取、加密存储、受权共享、查问读取、删除等等,保障开发者的业务流畅性。 1.本地加密存储应用钥匙环服务所获取的凭据在可信执行环境(TEE)中能够随机生成密钥,而后再进行加密,每个设施的密钥均不雷同,而且密钥只能在TEE内应用,无奈来到设施,华为也不把握密钥的内容,反对凭据在本地平安存储。保留凭据时,开发者还能够设置读取此凭据的内容时是否通过锁屏明码或生物特色认证用户的身份。 1.凭据共享受权钥匙环服务可能提供同一团队开发的App之间受权共享凭据的能力,被受权应用凭据的利用能够是Android利用、快利用或者Web利用。 用户在下次进行登录操作时,利用在钥匙环服务中查找可用的凭据。查找到的凭据可能是本利用存储的凭据,也可能是其它利用受权给本利用应用的凭据。 1.保障数据安全除此之外,应用钥匙环服务,共享凭据过程中的安全性也可能失去保障。钥匙环服务通过验证安卓利用的APK包名、快利用的包名和证书哈希,或者获取以后网页的实在URL作为身份信息等形式,认证读写凭据的APK或网站的实在身份,避免凭据被仿冒的程序或网站盗用。 1.凭据删除或更新钥匙环服务向开发者提供删除和更新凭据的API。如果用户须要退出利用帐户,相应的认证凭据也将从钥匙环服务中删除。 利用场景 跨状态登录同一利用App在挪动端的入口出现多样化的散布趋势,用户在不同的利用状态之间来回切换,重复登录,导致体验割裂。而钥匙环服务对于不同的利用状态提供了不同的接口,包含钥匙环服务SDK、快利用API和Web API,反对跨利用状态共享用户认证凭据。 以电商购物类利用为例,节假日促销短信推送是咱们最罕用的营销形式之一,也广泛被用户所承受。用户在安卓利用登录之后,点开促销短信中的链接,在华为浏览器中关上Web利用,间接处于登录状态,无需反复登录即可实现下单。 跨利用登录在业务倒退过程中,同一个团队往往会开发多个App,新利用上架之后,取得用户并非易事。针对全新业务场景的利用,钥匙环服务会为其提供一条畅通无阻、高效便捷的桥梁,将流量从原有的利用当中引进来,用户无需反复输出帐号密码,只有一键受权,就能够实现无缝登录。 也就是说,用户在利用A已登录,装置同一开发者旗下的新利用B,无需输出帐号密码,能应用登录利用A的帐号,实现间接登录利用B。 帐号易切换钥匙环服务还能够存储多个凭据,如果用户在某一利用登录过多个帐号,利用能够提供确认页面,让用户自主抉择某个帐号登录,操作便捷。当然了,作为开发者,您能够抉择在用户登录之前,验证用户的生物特色或者锁屏明码,无效躲避帐号被冒用等安全隐患。 商业价值 钥匙环服务的跨利用登录场景将帮忙同一开发者旗下的关联利用之间共享流量,助力孵化新产品。同时实现流量在安卓利用、快利用、Web利用之间的双向流转。通过发明一处登录,处处登录的无缝登录体验,钥匙环服务帮忙开发者缩短用户的交互转化门路,升高登录操作的复杂度,进一步晋升导流转化率。总之,钥匙环服务是助力开发者取得商业胜利的利器。 总体来看,华为钥匙环服务具备便捷登录、平安无忧、流量共享等外围劣势,能够帮忙开发者疾速满足用户在购物、出行、社交、浏览等多个应用场景下的平安需要。 之后,华为钥匙环服务还会推出其余个性,HMS Core也将在平安畛域凋谢新的能力,为开发者们带来更优质的服务和体验。 更多精彩内容,请见华为开发者官方论坛→https://developer.huawei.com/...

December 9, 2021 · 1 min · jiezi

关于android:Android笔记Android-Service-服务

一、 Service简介Service是android 零碎中的四大组件之一(Activity、Service、BroadcastReceiver、ContentProvider),它跟Activity的级别差不多,但不能自己运行只能后盾运行,并且能够和其余组件进行交互。service能够在很多场合的利用中应用,比方播放多媒体的时候用户启动了其余Activity这个时候程序要在后盾持续播放,比方检测SD卡上文件的变动,再或者在后盾记录你地理信息地位的扭转等等,总之服务总是藏在后盾的。 Service的启动有两种形式:context.startService() 和 context.bindService() 二、 Service启动流程context.startService() 启动流程: context.startService() -> onCreate() -> onStart() -> Service running -> context.stopService() -> onDestroy() -> Service stop 如果Service还没有运行,则android先调用onCreate(),而后调用onStart(); 如果Service曾经运行,则只调用onStart(),所以一个Service的onStart办法可能会反复调用屡次。 如果stopService的时候会间接onDestroy,如果是调用者本人间接退出而没有调用stopService的话,Service会始终在后盾运行,该Service的调用者再启动起来后能够通过stopService敞开Service。 所以调用startService的生命周期为: onCreate --> onStart (可屡次调用) --> onDestroy context.bindService()启动流程: context.bindService() -> onCreate() -> onBind() -> Service running -> onUnbind() -> onDestroy() -> Service stop onBind()将返回给客户端一个IBind接口实例,IBind容许客户端回调服务的办法,比方失去Service的实例、运行状态或其余操作。这个时候把调用者(Context,例如Activity)会和Service绑定在一起,Context退出了,Srevice就会调用onUnbind->onDestroy相应退出。 所以调用bindService的生命周期为:onCreate --> onBind(只一次,不可屡次绑定) --> onUnbind --> onDestory。 在Service每一次的开启敞开过程中,只有onStart可被屡次调用(通过屡次startService调用),其余onCreate,onBind,onUnbind,onDestory在一个生命周期中只能被调用一次。 三、 Service生命周期Service的生命周期并不像Activity那么简单,它只继承了onCreate()、onStart()、onDestroy()三个办法 当咱们第一次启动Service时,先后调用了onCreate()、onStart()这两个办法;当进行Service时,则执行onDestroy()办法。 这里须要留神的是,如果Service曾经启动了,当咱们再次启动Service时,不会在执行onCreate()办法,而是间接执行onStart()办法。 它能够通过Service.stopSelf()办法或者Service.stopSelfResult()办法来进行本人,只有调用一次stopService()办法便能够进行服务,无论调用了多少次的启动服务办法。 四、 Service示例上面我做了一个简略的音乐播放的利用,别离应用startService和bindService来启动本地的服务。 Activity public class PlayMusicService extends Activity implements OnClickListener { private Button playBtn; private Button stopBtn; private Button pauseBtn; private Button exitBtn; private Button closeBtn; private Intent intent; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.music_service); playBtn = (Button) findViewById(R.id.play); stopBtn = (Button) findViewById(R.id.stop); pauseBtn = (Button) findViewById(R.id.pause); exitBtn = (Button) findViewById(R.id.exit); closeBtn = (Button) findViewById(R.id.close); playBtn.setOnClickListener(this); stopBtn.setOnClickListener(this); pauseBtn.setOnClickListener(this); exitBtn.setOnClickListener(this); closeBtn.setOnClickListener(this); } @Override public void onClick(View v) { int op = -1; intent = new Intent("com.homer.service.musicService"); switch (v.getId()) { case R.id.play: // play music op = 1; break; case R.id.stop: // stop music op = 2; break; case R.id.pause: // pause music op = 3; break; case R.id.close: // close activity this.finish(); break; case R.id.exit: // stopService op = 4; stopService(intent); this.finish(); break; } Bundle bundle = new Bundle(); bundle.putInt("op", op); intent.putExtras(bundle); startService(intent); // startService } @Override public void onDestroy(){ super.onDestroy(); if(intent != null){ stopService(intent); } } } Service ...

December 9, 2021 · 3 min · jiezi

关于android:干货分享研效优化实践AI算法助力深层BUG挖掘

导语随着产品在线上的继续经营,产品在线上的规模越来越大,性能也越来越简单。产品体量的增长对品质要求越来越高。为了达到更高的品质要求,必然须要想方法减少测试的强度,但用传统的手工写用例自动化回归的形式老本过高。近年来,AI技术在越来越多的畛域施展了越来越重要的作用。在腾讯外部,咱们也始终放弃着对新技术的好奇心,踊跃学习并利用于日常工作中。本文作者是腾讯安全部零碎测试高级工程师林军克,他领有16年的软件测试教训,对AI技术在测试畛域的落地颇有钻研。 本文以平安防护产品举例子,但此方法论实用于波及多因素组合导致的BUG的深度开掘。上面所示的图为典型的流量攻打的防护流程:黑客在互联网上对业务服务器发动攻打,咱们有检测流量的设施检测攻打,检测到攻打后主动启动防护,将被攻打IP的流量导流到进攻设施,在进攻设施上对流量进行荡涤后将失常流量从新转发到业务服务器。 01平安产品测试的痛点剖析平安防护产品特点:1,黑产攻打手法多样且疾速翻新,须要产品能疾速响应现网的新攻打手法,但绝不能误杀失常用户,所以产品防护策略十分多。上面的表格显示次要策略文件中配置项的数量,加起来达到两三百个,且数量还在快速增长中。每迭代一个版本又会减少大量新配置项,解决逻辑非常复杂。次要的策略配置文件 策略项置项数量anti_*.conf 50anti_*.conf 147.conf 11.conf 11.conf 10即使开发十分小心,实际上仍无奈确保每个性能都是高内聚低耦合的,有时候仍难免会呈现本来不相干的配置项间相互影响的状况。如果本不相互影响的开关间存在不应有的影响,可能导致切换策略后防护不可控。咱们外部已经呈现过一个非预期的影响导致故障的例子,过后的故障是:一个防护UDP流量的配置项影响了HTTPS流量的防护性能,然而这两个配置本来没有任何关系。因而咱们须要测试在各种组合的策略下,产品性能都可能稳固牢靠。 2,对于特定的流量,最终绝大部份流量会被某个特定的防护模块防爱护。利用这个特点能够简化模型,咱们能够抓住次要特色进行建模,其它的防护细节能够临时不关注。 业界解决这种参数组合导致的问题次要利用全对偶算法来对参数进行两两组合。生成的测试集能够用起码的组合数笼罩任意两个变量的所有取值组合。在实践上,该用例集可能裸露所有由两个变量独特作用而引发的缺点。尽管这种算法生成的组合数起码,但如果新增了新的参数从新生成组合,新的组合跟之前的组合齐全没有关联。所以当参数较少时,咱们常常用它来缩减用例数,同时放弃较好的测试笼罩。然而一旦参数量较多时,每次都生成全新的组合,每次都要依据组合从新计算预期后果,整个过程就将变得十分复杂。难以解决“数百个开关在不同的配置下对于特定流量的防护手法”的问题。 咱们项目组内目前都是用手工减少用例,自动化执行用例。在这种形式下,每次新增配置项都要放弃全对偶其实很难。例如,假如目前己有的用例都是全对偶的,当初新增一个配置项,这个配置项只能取0和1两个值。为了保征所有参数都组合一遍,那么必须在原来所有用例的根底上新增配置项取0时测一遍,取1时再测一遍。每减少一个配置项用例数翻一翻,用例数十分宏大。如果每次都全新生成组合的话,150个配置开关在全新生成全对偶组合的状况下只有约130种组合。而增量形式可达2^150种组合。 02业界是如何自动化生成用例的那业界有没有既可能全新生成组合数少又不须要从新人工计算预期后果的计划呢?答案是有的。UML建模技术就是随被测版本更新保护模型,每次测试均从新整体兼顾生成全新用例进行测试。这项技术最外围价值在于:自动化生成用例,用起码的用例数达到最大化性能笼罩,最终更快更全地测试版本。这项技术的劣势是:模型保护简单,对于设计缺点难以发现(用例只是机械地遍历),没有从用户的角度设计用例。 03AI在前端页面测试畛域的利用近年来,AI技术的倒退十分地快,AI技术也有跟UML同样的特点:喜爱建模型。所以是否通过AI技术绕过简单的建模?整体兼顾用例,用起码的用例数达到最大的笼罩。同时防止人工计算预期后果。 为了摸索新技术利用于测试畛域,我疾速扫了一下AI的盲,再进行更深刻的学习时发现,其实AI利用于测试畛域的将来已至。业界己经有不少工具在利用AI做自动化测试了,连用例都是自动化设计的。对于前端的页面,甚至有工具号称只有给定URL链接,测试人员只需坐等测试后果。相似的软件有:eggplant、appvance IQ、Sauce Labs等等。 通过剖析发现这些技术次要利用AI的计算机视觉技术在页面上辨认所有的按纽,依据每一页上的按纽生成遍历树,再依据遍历树主动遍历可能经验的门路(user journey)。从而达到自动化设计用例,自动化测试的目标。   腾讯的共事之前出版过一本《AI自动化测试》的书,外面具体介绍了AI在图像类游戏和数据类游戏上的测试。业界已有的这些技术都很优良,但次要利用于前端页面的测试,后盾的测试还没有相应的技术。所以咱们开始钻研如何将AI技术利用于后盾测试,通过多种尝试,并联合AI的特点,咱们产生了一个大胆的想法:没有人工的参加,机器不可能了解人工设计的业务逻辑,而像UML那样构建模型又太过于重型,但AI是十分善于解决做数据分类的,既然算不进去预期后果是否不计算?测试套只记录流量如何解决的,记录后由AI依据流量及防护后果分类。分完类后再按各类剖析出此类的典型配置?而后人工审查典型配置下的流量处理形式是否正当。 04摸索AI在后盾测试中的利用依据这些想法,咱们很快就制订了实施方案。咱们的指标:用最小的代价晋升多种因素组合的笼罩,深度开掘深层次的BUG。计划施行胜利的实践根底是:基于测试实践,用起码的用例数来笼罩最多的场景。利用AI对各种场景下的响应进行归类、洞察。这两块串起来是可行的。 打算施行步骤如下:Step1:每次新增了新配置项都从新基于全对偶算法生成配置。Step2:对每种配置用典型的攻打手法并记录被测端的防护形式。Setp3:通过AI剖析各种防护跟配置间的关联。找出各种防护形式最次要的配置项。Step4:查看各种防护形式最相干的N种配置是否合乎预期设计? 第一部份基于测试实践生成全对偶的组合非常简单。我花了半天工夫就施行了。为了对多个配置文件中的配置项做组合,我设计了用配置项名@文件名的形式对配置项命名。应用pairwise工具生成。组合之后再用脚本转成配置文件。 基于全对偶算法一共生成了250种组合。选取27种典型特色的流量别离发动'GET','POST','PUT','DELETE','HEAD','OPTIONS','TRACE','CONNECT'申请。流量种数有278=216种,在250种配置下别离过这216种流量并记录下防护手法,失去250216=54000种场景的防护记录。记录下来的后果是这样的:一共分3部份,第一部份为配置项组合数据,第二部份是所发流量的名字,最初一列是被测端所用的防护手法。 数据有了,能够交给AI了。然而团队只有测试专家,没有AI专家。咱们就在腾讯外部找AI专家求教,AI专家理解咱们的需要后认为可行,但具体的落地依然让咱们非常困扰。因为AI畛域的常识跟测试畛域的常识差别太大了,从头开始学习这些常识好像浏览天书个别。不过只有肯动脑多学习,办法总比艰难多。我找到了一个AI小白也很容易上手的data mining工具,通过重复学习和实际,我认为这几个构件在咱们的计划中能够利用。我建设的模型如下:PCA全称叫主成因剖析构件,能够帮咱们找出对后果影响很大的N个配置项,配置项对后果影响大小排序,输入是一个一维的列表。开发设计的配置开关解决程序必定是个网状的,这个后果参考一下就好。分类树对配置项影响定量分析。集体认为这个构件输入的信息比拟有价值。 PCA的剖析后果是这样的。在咱们的案例中,这条曲线挺平滑的,阐明没有影响特地大的配置项。AI应用RANK构件剖析进去的配置项对后果影响大小,跟开发的设计流程图对了下程序,大抵是能够对得上。初步印证了计划还是有点靠谱的。下图是通过分类树对运行后果分类后的展现:咱们以一个典型的例子阐明一下,如何依据AI的提引找到问题:AI对数据处理后失去了一张很大的分类树图,对数据中每一种后果都会用一种色彩标记,如图中所示黄、紫、白绿别离是4种后果相干的数据展现。其中黄色区域的根节点上示意防护手法为dropos_*的数据共74条。该后果最相干配置项:drop_@anti_.conf。右边叶子节点示意:当drop_@anti_.conf配置为android、ios、linux时。防护手法为:dropos_*。左边叶子节点示意:当drop_@anti_.con+f配置为0时。防护手法为:**_trans。 经合被测系统的防护逻辑,我看到这个中央是的确存在问题。这个性能是一个对特定OS指纹作抛弃的性能,因为我跑用例时只用linux零碎发了流量,性能失常的状况下应只有linux下会抛弃。AI却剖析到当drop_@anti_.conf配置为android、ios、win、linux会抛弃,也就是说在配置为android、ios、win时有OS辨认不精确的问题。咱们先下记下这个点。 方框最下方的配置项是跟后果相干的次相干的配置项,持续察看其叶子结点,咱们特地关注各叶子结点的比例,这个例子中这个配置项配置为不同值时,比例靠近,后果倾向性也很显著,这是耦合性低的信号。 依照分类树展现的信息关上原始表格,暗藏掉不相干的列并把相关联的配置项放在一起,这个时候就能够看出问题所在。按有问题场景对应的行号找出相应配置在环境上重现问题,重现问题如图。重现问题后配置如下:预期:流量在linux下发的,不应匹配上策略,预期应被转发。实测发现流量因为os_**被drop了:这个例子阐明在AI的指引下胜利发现特定场景下OS指纹性能的确存在误辨认的可能,也证实了用AI剖析数据的办法是牢靠的。我认为AI对于测试的外围价值在于把简单的数据以可视化的形式出现,使剖析变得更加容易。 综上所述,本办法能够解决“目前多个参数互相耦合导致的深层次BUG有但不多,但要解决这些问题须要做参数组合测试,解决的代价很大”的痛点。用较小的代价验证多个因素间的耦合性。自动化生成了54000个场景的测试用例,耗时3.5天跑完,AI剖析跑出的后果后,己跟开发确认了其中2个BUG。这54000个场景如果人工写用例,按目前每人天30个用例算,节假日无休也须要4.9年能力实现。应用此办法后,生成组合只需几分钟,3.5天跑完,目前摸索阶段预计10天也能够剖析完,大大提高了测试效率。 对于腾讯WeTest腾讯WeTest是由腾讯官网推出的一站式品质开放平台。十余年品质治理教训,致力于质量标准建设、产品质量晋升。腾讯WeTest为挪动开发者提供兼容性测试、云真机、性能测试、平安防护等优良研发工具,为百余行业提供解决方案,笼罩产品在研发、经营各阶段的测试需要,历经千款产品磨砺。金牌专家团队,通过5大维度,41项指标,360度保障您的产品质量。 关注腾讯WeTest,理解更多测试干货常识WeTest腾讯品质开放平台-专一游戏 晋升品质

December 9, 2021 · 1 min · jiezi

关于android:android多线程AsyncTask二

 上篇剖析AsyncTask的一些根本用法以及不同android版本下的区别,接着本篇咱们就来全面分析一下AsyncTask的工作原理。在开始之前咱们先来理解一个多线程的知识点——Callable<V> 、Future<V>和FutureTask类 一、了解Callable<V> 、Future<V>以及FutureTask类Callable<V>Callable的接口定义如下: public interface Callable<V> { V call() throws Exception; } Callable接口申明了一个名称为call()的办法,该办法能够有返回值V,也能够抛出异样。Callable也是一个线程接口,它与Runnable的次要区别就是Callable在线程执行实现后能够有返回值而Runnable没有返回值,Runnable接口申明如下: public interface Runnable { public abstract void run();} 那么Callable接口如何应用呢,Callable须要和ExcutorService联合应用,其中ExecutorService也是一个线程池对象继承自Executor接口,这里就不深刻了,接着看看ExecutorService提供了那些办法供咱们应用: <T> Future<T> submit(Callable<T> task);<T> Future<T> submit(Runnable task, T result);Future<?> submit(Runnable task);submit(Callable task),传递一个实现Callable接口的工作,并且返回封装了异步计算结果的Future。submit(Runnable task, T result),传递一个实现Runnable接口的工作,并且指定了在调用Future的get办法时返回的result对象。submit(Runnable task),传递一个实现Runnable接口的工作,并且返回封装了异步计算结果的Future。 因而咱们只有创立好咱们的线程对象(实现Callable接口或者Runnable接口),而后通过下面3个办法提交给线程池去执行即可。Callable接口介绍就先到这,再来看看Future时什么鬼。 Future<V> Future接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象工作执行的后果进行获取(get()),勾销(cancel()),判断是否实现等操作。其办法如下: public interface Future<V> { //勾销工作 boolean cancel(boolean mayInterruptIfRunning); //如果工作实现前被勾销,则返回true。 boolean isCancelled(); //如果工作执行完结,无论是失常完结或是中途勾销还是产生异样,都返回true。 boolean isDone(); //获取异步执行的后果,如果没有后果可用,此办法会阻塞直到异步计算实现。 V get() throws InterruptedException, ExecutionException; // 获取异步执行后果,如果没有后果可用,此办法会阻塞,然而会有工夫限度, //如果阻塞工夫超过设定的timeout工夫,该办法将返回null。 V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;}总得来说Future有以下3点作用: ...

December 9, 2021 · 4 min · jiezi