Android开发高手课NOTE

最近学习了极客时间的《Android开发高手课》颇有收获,记录总结一下。
欢迎学习老师的专栏:Android开发高手课前端

内存优化
卡顿的缘由
频繁 GC 形成卡顿、物理内存不足时系统会触发 low memory killer 机制,系统负载太高是形成卡顿的俩个缘由。linux

除了频繁 GC 形成卡顿以外,物理内存不足时系统会触发 low memory killer 机制,系统负载太高是形成卡顿的另一个缘由。“用时分配,及时释放”android

Android 3.0~Android 7.0 将 Bitmap 对象和像素数据统一放到 Java 堆中,这样就算咱们不调用 recycle,Bitmap 内存也会随着对象一块儿被回收。不过 Bitmap 是内存消耗的大户,把它的内存放到 Java 堆中彷佛不是那么美妙。即便是最新的华为 Mate 20,最大的 Java 堆限制也才到 512MB,可能个人物理内存还有 5GB,可是应用仍是会由于 Java 堆内存不足致使 OOM。Bitmap 放到 Java 堆的另一个问题会引发大量的 GC,对系统内存也没有彻底利用起来。
将 Bitmap 内存放到 Native 中,也能够作到和对象一块儿快速释放,同时 GC 的时候也能考虑这些内存防止被滥用。NativeAllocationRegistry 能够一次知足你这三个要求,Android 8.0 正是使用这个辅助回收 Native 内存的机制,来实现像素数据放到 Native 内存中。Android 8.0 还新增了硬件位图 Hardware Bitmap,它能够减小图片内存并提高绘制效率。git

// 步骤一:申请一张空的 Native Bitmap
Bitmap nativeBitmap = nativeCreateBitmap(dstWidth, dstHeight, nativeConfig, 22);

// 步骤二:申请一张普通的 Java Bitmap
Bitmap srcBitmap = BitmapFactory.decodeResource(res, id);

// 步骤三:使用 Java Bitmap 将内容绘制到 Native Bitmap 中
mNativeCanvas.setBitmap(nativeBitmap);
mNativeCanvas.drawBitmap(srcBitmap, mSrcRect, mDstRect, mPaint);

// 步骤四:释放 Java Bitmap 内存
srcBitmap.recycle();
srcBitmap = null;

sampling模式和instrumentation模式的区别
二者的区别:程序员

  • 在sampling模式中,profiler以固定的间隔对运行中的程序进行采样,根据采样结果统计出程序中各个部分的开销。
  • 在instrumentation模式中,profiler对运行中的程序所执行的每个指令都进行记录,最后根据这份记录生成程序中各个部分的开销。

在实际使用中:github

  • sampling模式速度快,记录产生的数据量小,可是统计结果并不十分精确,适合于对程序全局性能进行初步的分析,找出程序瓶颈大体的“区间”。
  • instrumentation模式能精确记录程序各个部分的开销,可是速度慢,记录产生的数据量大,适合于对程序局部进行精细分析,精肯定位瓶颈位置。

捕获堆转储
使用:点击 Dump Java heap 
堆转储显示在您捕获堆转储时您的应用中哪些对象正在使用内存。 特别是在长时间的用户会话后,堆转储会显示您认为不该再位于内存中却仍在内存中的对象,从而帮助识别内存泄漏。 在捕获堆转储后,您能够查看如下信息:web

  • 您的应用已分配哪些类型的对象,以及每一个类型分配多少。、
  • 每一个对象正在使用多少内存。
  • 在代码中的何处仍在引用每一个对象。
  • 对象所分配到的调用堆栈(目前,若是您在记录分配时捕获堆转储,则只有在 Android 7.1 及更低版本中,堆转储才能使用调用堆栈)

在您的堆转储中,请注意由下列任意状况引发的内存泄漏:面试

  • 长时间引用 Activity、Context、View、Drawable 和其余对象,可能会保持对 Activity 或 Context 容器的引用。
  • 能够保持 Activity 实例的非静态内部类,如 Runnable。
  • 对象保持时间超出所需时间的缓存。

分析内存的技巧
使用 Memory Profiler 时,您应对应用代码施加压力并尝试强制内存泄漏。 在应用中引起内存泄漏的一种方式是,先让其运行一段时间,而后再检查堆。 泄漏在堆中可能逐渐汇聚到分配顶部。 不过,泄漏越小,您越须要运行更长时间的应用才能看到泄漏。
您还能够经过如下方式之一触发内存泄漏:json

  • 将设备从纵向旋转为横向,而后在不一样的 Activity 状态下反复操做屡次。 旋转设备常常会致使应用泄漏 Activity、Context 或 View 对象,由于系统会从新建立 Activity,而若是您的应用在其余地方保持对这些对象之一的引用,系统将没法对其进行垃圾回收。
  • 处于不一样的 Activity 状态时,在您的应用与另外一个应用之间切换(导航到主屏幕,而后返回到您的应用)。

ANR后端

  • 个人经验是,先看看主线程的堆栈,是不是由于锁等待致使。接着看看 ANR 日志中 iowait、CPU、GC、system server 等信息,进一步肯定是 I/O 问题,或是 CPU 竞争问题,仍是因为大量 GC 致使卡死.从 Logcat 中咱们能够看到当时系统的一些行为跟手机的状态,例如出现 ANR 时,会有“am_anr”;App 被杀时,会有“am_kill”。
  • 查找共性,机型、系统、ROM、厂商、ABI,这些采集到的系统信息均可以做为维度聚合,在文中我提到 Hprof 文件裁剪和重复图片监控,这是不少应用目前都没有作的,而这两个功能也是微信的 APM 框架 Matrix 中内存监控的一部分。Matrix 是我一年多前在微信负责的最后一个项目,也付出了很多心血,最近据说终于准备开源了。那今天咱们就先来练练手,尝试使用 HAHA 库快速判断内存中是否存在重复的图片,而且将这些重复图片的 PNG、堆栈等信息输出

SharedPreferences的问题

  1. 跨进程不安全。SharedPreferences 在跨进程频繁读写有可能致使数据所有丢失。根据线上统计,SP 大约会有万分之一的损坏率。
  2. 加载缓慢。SharedPreferences 文件的加载使用了异步线程,并且加载线程并无设置线程优先级,若是这个时候主线程读取数据就须要等待文件加载线程的结束。
    这就致使出现主线程等待低优先级线程锁的问题,好比一个 100KB 的 SP 文件读取等待时间大约须要 50~100ms,我建议提早用异步线程预加载启动过程用到的 SP 文件
  3. 全量写入。不管是调用 commit() 仍是 apply(),即便咱们只改动其中的一个条目,都会把整个内容所有写到文件。并且即便咱们屡次写入同一个文件,SP 也没有将屡次修改合并为一次,这也是性能差的重要缘由之一。
  4. 卡顿。因为提供了异步落盘的 apply 机制,在崩溃或者其余一些异常状况可能会致使数据丢失。因此当应用收到系统广播,或者被调用 onPause 等一些时机,系统会强制把全部的 SharedPreferences 对象数据落地到磁盘。若是没有落地完成,这时候主线程会被一直阻塞。

使用mmkv

mmap将一个文件或者其它对象映射进内存。(linux上的东西)
常规文件操做须要从磁盘到页缓存再到用户主存的两次数据拷贝。(从磁盘拷贝到页缓存中,因为页缓存处在内核空间,不能被用户进程直接寻址,因此还须要将页缓存中数据页再次拷贝到内存对应的用户空间中)
而mmap操控文件,只须要从磁盘到用户主存的一次数据拷贝过程。说白了,mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不一样数据不通的繁琐过程。所以mmap效率更高。

Serializable

  • 整个序列化过程使用了大量的反射和临时变量,并且在序列化对象的时候,不只会序列化当前对象自己,还须要递归序列化对象引用的其余对象。
  • 整个过程计算很是复杂,并且由于存在大量反射和 GC 的影响,序列化的性能会比较差。另一方面由于序列化文件须要包含的信息很是多,致使它的大小比 Class 文件自己还要大不少,这样又会致使 I/O 读写上的性能问题
  • Parcel 序列化和 Java 的 Serializable 序列化差异仍是比较大的,Parcelable 只会在内存中进行序列化操做,并不会将数据存储到磁盘里。经过取巧的方法能够实现 Parcelable 的永久存储,可是它也存在两个问题。

Serial的优势

  1. 相比起传统的反射序列化方案更加高效(没有使用反射)
  2. 性能相比传统方案提高了3倍 (序列化的速度提高了5倍,反序列化提高了2.5倍)
  3. 序列化生成的数据量(byte[])大约是以前的1/5
  4. 开发者对于序列化过程的控制较强,可定义哪些object、field须要被序列化
  5. 有很强的debug能力,能够调试序列化的过程(详见:调试)

数据的序列化
Serial 性能看起来还不错,可是对象的序列化要记录的信息仍是比较多,在操做比较频繁的时候,对应用的影响仍是很多的,这个时候咱们能够选择使用数据的序列化。
json:原生、gosn、fastjson(数据量大了的时候最快)

文件遍历在 API level 26 以后建议使用FileVisitor,替代 ListFiles,总体的性能会好不少。

Mars的好处就是跨平台、长连接,看状况

网络数据压缩

电量

  • Android 是基于 Linux 内核,而 Linux 大部分使用在服务器中,它对功耗并无作很是严格苛刻的优化。特别是国内会有各类各样的“保活黑科技”,大量的应用在后台活动简直就是“电量黑洞”。

  • 耗电量这块, 由于要维持推送的实时到达, 只能追求黑科技, 要否则人家就会问,为啥苹果能够收到推送,android就不行~ 可是保活就会加大耗电

  • 耗电优化的第一个方向是优化应用的后台耗电。由于用户最容易感知这个,我明明没有怎么打开,为何耗这么多?在后台不要作这些:长时间获取 WakeLock(及时释放)、WiFi 和蓝牙的扫描、GPS、video、audio

WakeLock 用来阻止 CPU、屏幕甚至是键盘的休眠。相似 Alarm、JobService 也会申请 WakeLock 来完成后台 CPU 操做.
Alarm 用来作一些定时的重复任务

经过 Hook,咱们能够在申请资源的时候将堆栈信息保存起来。当咱们触发某个规则上报问题的时候,能够将收集到的堆栈信息、电池是否充电、CPU 信息、应用先后台时间等辅助信息也一块儿带上。

UI优化

autosize是头条方案,经过反射修改系统的density值

对于硬件绘制,咱们经过调用 OpenGL ES 接口利用 GPU 完成绘制。opengl是一个跨平台的图形 API,它为 2D/3D 图形处理硬件指定了标准软件接口。而 OpenGL ES 是 OpenGL 的子集,专为嵌入式设备设计。

使用 XML 进行 UI 编写能够说是十分方便,能够在 Android Studio 中实时预览到界面。若是咱们要对一个界面进行极致优化,就可使用代码进行编写界面。

xml缺点
读取xml很耗时
递归解析xml较耗时
反射生成对象的耗时是new的3倍以上
x2c:在编译的时候,经过注解的方式,将xml转换成Java代码

利用卡顿优化中的traceview或者systrace定位是最高效的

measure/layout 优化

  • 减小布局的嵌套(viewstub、merge、include)
  • 尽可能不使用 RelativeLayout 或者基于 weighted LinearLayout,它们 layout 的开销很是巨大。这里我推荐使用 ConstraintLayout (约束布局,只有一个层级)替代 RelativeLayout 或者 weighted LinearLayout。
  • 减小多余的background
  • PrecomputedText(研究下),异步的textview。
    Litho (研究下)如我前面提到的 PrecomputedText 同样,把 measure 和 layout 都放到了后台线程,只留下了必需要在主线程完成的 draw,这大大下降了 UI 线程的负载。
    若是你没有计划彻底迁移到 Litho,我建议能够优先使用 Litho 中的 RecyclerCollectionComponent 和 Sections 来优化本身的 RecyelerView 的性能。

减小apk体积

  • Android Studio 3.0 推出了新 Dex 编译器 D8 与新混淆工具 R8,目前 D8 已经正式 Release,大约能够减小 3% 的 Dex 体积。可是计划用于取代 ProGuard 的依然处于实验室阶段,期待它在将来能有更好的表现。
  • MultiDex.install(this);分多个dex包
  • 使用andresguard,路径变成了r/d/a,还有Android 编译过程当中,下面这些格式的文件会指定不压缩;在 AndResGuard 中,咱们支持针对 resources.arsc、PNG、JPG 以及 GIF 等文件的强制压缩。
  • 移出无用的资源
android {
    ...
    buildTypes {
        release {
            shrinkResources true
            minifyEnabled true
        }
    }
}

加快编译速度

  • 你能够把编译简单理解为,将高级语言转化为机器或者虚拟机所能识别的低级语言的过程。对于 Android 来讲,这个过程就是把 Java 或者 Kotlin 转变为 Android 虚拟机运行的Dalvik 字节码
  • 关闭 JITandroid:vmSafeMode=“true”,关闭虚拟机的 JIT 优化
  • r8/d8
  • 使用tinker,能够回退

线上问题排查

  • 日志打点怕打太多也怕太少,担忧出现问题没有足够丰富的信息去定位分析问题。应该打多少日志,如何去打日志并无一个很是严格的准则,这须要整个团队在长期实践中慢慢去摸索。在最开始的时候,可能你们都不重视也不肯意去增长关键代码的日志,可是当咱们经过日志平台解决了一些疑难问题之后,团队内部的成功案例愈来愈多的时候,这种习惯也就慢慢创建起来了。
  • 使用Mars的xlog,Java实现写日志,GC频繁,而C实现并不会出现这种状况,由于它不会占用Java的堆内存。
  • 俩种方式上报日志:push上报,主动上报(在用户出现奔溃,反馈问题时主动上报日志(能够重启了上报))
  • 正由于反复“痛过”,才会有了微信的用户日志和点击流平台,才会有美团的 LoganHomles(看看) 统一日志系统。所谓团队的“提质增效”,就是寻找团队中这些痛点,思考如何去改进。不管是流程的自动化,仍是开发新的工具、新的平台,都是朝着这个目标前进。

h5优化
分为前端优化和本地优化
前端优化

本地:

  • webview预建立。提早建立和初始化 WebView,以及实现 WebView 的复用,这块大约能够节省 100~200 毫秒。
  • 缓存。提早把网页须要的资源请求下来。

React Native 和 Weex 性能差。 JS 是解释性的动态语言,它的执行效率相比 AOT 编译后的 Java,性能依然会在几倍以上的差距。

音视频
对于咱们来讲,最多见的视频格式就是MP4格式,这是一个通用的容器格式。所谓容器格式,就意味内部要有对应的数据流用来承载内容。并且既然是一个视频,那必然有音轨和视轨,而音轨、视轨自己也有对应的格式。常见的音轨、视轨格式包括:

视轨:其中,目前大部分 Android 手机都支持 H.264 格式的直接硬件编码和解码;对于 H.265 来讲,Android 5.0 以上的机器就支持直接硬件解码了,可是对于硬件编码,目前只有一部分高端芯片能够支持,例如高通的 8xx 系列、华为的 98x 系列。对于视轨编码来讲,分辨率越大性能消耗也就越大,编码所需的时间就越长。
音轨:AAC

同一个压缩格式下,码率越高质量也就越好。
咱们分别从摄像头 / 录音设备采集数据,将数据送入编码器,分别编码出视轨 / 音轨以后,再送入合成器(MediaRemuxer 或者相似 mp4v二、FFmpeg 之类的处理库),最终输出 MP4 文件。
对于目前的视频类 App 来讲,还有各类各样的滤镜和美颜效果,实际上均可以基于 OpenGL 来实现。

播放的视频多是做为视频编辑的一部分,在剪辑时须要实时预览视频特效。咱们能够简单配置播放视频的 View 为一个 GLSurfaceView,有了 OpenGL 的环境,咱们就能够在这上实现各类特效、滤镜的效果了。而对于视频编辑常见的快进、倒放之类的播放配置,MediaPlayer 也有直接的接口能够设置。
MediaPlayer没法精准的seek


关于学习

  • 年轻人千万不要碰的东西之一,即是能得到短时间快感的软件。它们会在不知不觉中偷走你的时间,消磨你的意志力,摧毁你向上的勇气。
  • 须要看产出,而是否是工做时长
  • 天天咱们应该须要有一段时间真正的静下心来工做,并且每过一段时间也要从新审视一下本身的工做,有哪些地方作的不够好?有没有什么事情是本身或者团队的人正在反复而低效在作的,是否能够优化。
  • 人的精力是有限的,天天可能也就有2个多小时能高效的产出,必定要把握好这个时间,留给最重要的事情。天天早上通勤的路上或者到公司的前10分钟能够好好规划一下当天要作的事情。
  • 所谓的“T”无非就是横向和纵向两个维度。纵向解决的是深度问题,横向解决的是广度问题。
  • 若是你在大厂,就应该从客户端到后端,尽量全面深刻研究你参与的模块,多想一想如何把你所作的模块优化到极致,而且在巨大的用户量面前依然可以稳定运行。若是你在初创团队,在业余时间也要坚持学习,持续探索本身的技术深度。这样在未来,不管是初创团队内部的晋升,仍是跳到大厂,这样努力的经验均可以成为将来无数次面试、加薪的一大亮点。
  • 我建议你应该至少先在一个技术领域付出大量的精力,深刻钻研透彻,而后再去思考广度的问题。这是由于经验丰富的程序员学新的东西都很是快,由于如今已经不那么容易出现太多全新的技术,所谓的新技术其实都是旧技术的从新组合和微创新。
  • 如今好像有个观点说“Android 开发没人要”,你们都想转去作大前端开发,是否是真的是这样呢?事实上,不管咱们使用哪种跨平台方案,它们最终都要运行在 Android 平台上。崩溃、内存、卡顿、耗电这些问题依然存在,并且可能会更加复杂。并且从 H5 极致体验优化的例子来看,不少优化是须要深刻研究平台特性和系统底层机制,咱们在“高质量开发”中学到的底层和系统相关的知识依然很重要。
  • 这一系列涉及的内容就很是复杂,但每一项单拆出来去看,一层层的去学习和补充,就会感受容易不少。这一点其实在业务开发上也有体现,咱们刚接手一个复杂的业务,代码庞大,注释和文档都不多,但在一段时间后你仍是会对整个业务有或多或少的认识,在接到新的业务的时候也没有以为难到无从下手,顶可能是以为复杂。底层的系统、框架也是如此,这是一个由点到面的过程。
  • 在平时零散的时间里咱们看到一篇技术文章,并非阅读收藏后就结束了,这样你可能会在很短的时间里就忘掉了文章的内容。他将阅读一篇文章分红如下几个步骤:提取这篇文章要解决的问题;而后归纳一下涉及的技术点;提取重点内容,好比问题发生的原因、有哪几种解决方法。整体来讲,这个方法是为了在短期内提取出重点内容,而后记录下来后面再进行复习。因此咱们都须要多记录、多复习,能够培养使用一些工具来帮助本身养成习惯
  • 惟有学习,不可辜负
  • 确认严重程度。解决崩溃也要看性价比,咱们优先解决 Top 崩溃或者对业务有重大影响,例如启动、支付过程的崩溃。我曾经有一次辛苦了几天解决了一个大的崩溃,但下个版本产品就把整个功能都删除了,这令我很崩溃
相关文章
相关标签/搜索