热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入探索Android内存优化炼狱级别上

前言成为一名优良的Android开发,须要一份齐备的常识体系,在这里,让咱们一起成长为本人所想的那样~。本篇是Android内存优化的进阶篇,难度能够说达到了炼狱级别,倡议对内存优
文章目录[隐藏]
  • 前言
  • 目录
  • 一、重识内存优化
  • 1、手机RAM
  • 2、内存优化的纬度
  • 3、内存问题
  • 二、常见工具抉择
  • 1、Memory Profiler
  • 2、Memory Analyzer
  • 3、LeakCanary
  • 三、Android内存管理机制回顾
  • 1、Java 内存调配
  • 2、Java 内存回收算法
  • 3、Android 内存管理机制
  • 4、小结
  • 四、内存抖动
  • 1、那么,为什么内存抖动会导致 OOM?
  • 2、内存抖动解决实战
  • 3、内存抖动常见案例
  • 五、内存优化体系化搭建
  • 1、MAT回顾
  • 2、搭建体系化的图片优化 / 监控机制
  • 3、建设线上利用内存监控体系
  • 4、建设全局的线程监控组件
  • 5、GC 监控组件搭建
  • 6、建设线上 OOM 监控组件:Probe
  • 7、实现 单机版 的 Profile Memory 自动化内存剖析
  • 8、搭建线下 Native 内存透露监控体系
  • 9、设置内存兜底策略
  • 10、更深刻的内存优化策略

前言

成为一名优良的Android开发,须要一份齐备的常识体系,在这里,让咱们一起成长为本人所想的那样~。

本篇是 Android 内存优化的进阶篇,难度能够说达到了炼狱级别,倡议对内存优化不是十分相熟的认真看看前篇文章: Android性能优化之内存优化,其中详细分析了以下几大模块:

  • 1)、Android的内存管理机制
  • 2)、优化内存的意义
  • 3)、防止内存透露
  • 4)、优化内存空间
  • 5)、图片治理模块的设计与实现

如果你对以上根底内容都比拟理解了,那么咱们便开始 Android 内存优化的探索之旅吧。

本篇文章十分长,倡议珍藏后缓缓享受~

目录

  • 一、重识内存优化
  • 1、手机RAM
  • 2、内存优化的纬度
  • 3、内存问题
  • 二、常见工具抉择
  • 1、Memory Profiler
  • 2、Memory Analyzer
  • 3、LeakCanary
  • 三、Android内存管理机制回顾
  • 1、Java 内存调配
  • 2、Java 内存回收算法
  • 3、Android 内存管理机制
  • 4、小结
  • 四、内存抖动
  • 1、那么,为什么内存抖动会导致 OOM?
  • 2、内存抖动解决实战
  • 3、内存抖动常见案例
  • 五、内存优化体系化搭建
  • 1、MAT回顾
  • 2、搭建体系化的图片优化 / 监控机制
  • 3、建设线上利用内存监控体系
  • 4、建设全局的线程监控组件
  • 5、GC 监控组件搭建
  • 6、建设线上 OOM 监控组件:Probe
  • 7、实现 单机版 的 Profile Memory 自动化内存剖析
  • 8、搭建线下 Native 内存透露监控体系
  • 9、设置内存兜底策略
  • 10、更深刻的内存优化策略
  • 六、内存优化演进
  • 1、自动化测试阶段
  • 2、LeakCanary
  • 3、应用基于 LeakCannary 的改进版 ResourceCanary
  • 七、内存优化工具
  • 1、top
  • 2、dumpsys meminfo
  • 3、LeakInspector
  • 4、JHat
  • 5、ART GC Log
  • 6、Chrome Devtool
  • 八、内存问题总结
  • 1、内类是有危险的编码方式
  • 2、一般 Hanlder 外部类的问题
  • 3、登录界面的内存问题
  • 4、应用零碎服务时产生的内存问题
  • 5、把 WebView 类型的透露装进垃圾桶过程
  • 6、在适当的时候对组件进行登记
  • 7、Handler / FrameLayout 的 postDelyed 办法触发的内存问题
  • 8、图片放错资源目录也会有内存问题
  • 9、列表 item 被回收时留神开释图片的援用
  • 10、应用 ViewStub 进行占位
  • 11、留神定时清理 App 过期的埋点数据
  • 12、针对匿名外部类 Runnable 造成内存透露的解决
  • 九、内存优化常见问题
  • 1、你们内存优化我的项目的过程是怎么做的?
  • 2、你做了内存优化最大的感触是什么?
  • 3、如何检测所有不合理的中央?
  • 十、总结
  • 1、优化大方向
  • 2、优化细节
  • 3、内存优化体系化建设总结

一、重识内存优化

Android给每个利用过程调配的内存都是十分无限的,那么,为什么不能把图片下载下来都放到磁盘中呢?那是因为放在 内存 中,展示会更 “”,快的起因有两点,如下所示:

  • 1)、硬件快:内存自身读取、存入速度快。
  • 2)、复用快:解码成绩无效保留,复用时,间接应用解码后对象,而不是再做一次图像解码。

这里说一下解码的概念。Android零碎要在屏幕上展现图片的时候只认 “像素缓冲”,而这也是大多数操作系统的特色。而咱们 常见的jpg,png等图片格式,都是把 “像素缓冲” 应用不同的伎俩压缩后的后果,所以这些格局的图片,要在设施上 展现,就 必须通过一次解码,它的 执行速度会受图片压缩比、尺寸等因素影响。(官网倡议:把从内存中淘汰的图片,升高压缩比后存储到本地,以备后用,这样能够最大限度地升高当前复用时的解码开销。)

上面,咱们来理解一下内存优化的一些重要概念。

1、手机RAM

手机不应用 PC 的 DDR内存,采纳的是 LPDDR RAM,即 ”低功耗双倍数据速率内存“。其计算规定如下所示:

LPDDR系列的带宽 = 时钟频率 ✖️内存总线位数 / 8
LPDDR4 = 1600MHZ ✖️64 / 8 ✖️双倍速率 = 25.6GB/s。

那么内存占用是否越少越好?

当零碎 内存短缺 的时候,咱们能够 多用 一些取得 更好的性能。当零碎 内存不足 的时候,咱们心愿能够做到 ”用时调配,及时开释“。

2、内存优化的纬度

对于Android内存优化来说又能够细分为如下两个维度,如下所示:

  • 1)、RAM优化
  • 2)、ROM优化

1、RAM优化

次要是 升高运行时内存。它的 目标 有如下三个:

  • 1)、避免利用产生OOM
  • 2)、升高利用因为内存过大被LMK机制杀死的概率
  • 3)、防止不合理应用内存导致GC次数增多,从而导致利用产生卡顿

2、ROM优化

升高利用占ROM的体积,进行APK瘦身。它的 目标 次要是为了 升高利用占用空间,防止因ROM空间有余导致程序无奈装置

3、内存问题

那么,内存问题次要是有哪几类呢?内存问题通常来说,能够细分为如下 三类:

  • 1)、内存抖动
  • 2)、内存透露
  • 3)、内存溢出

上面,咱们来理解下它们。

1、内存抖动

内存稳定图形呈 锯齿张GC导致卡顿

这个问题在 Dalvik虚拟机 上会 更加显著,而 ART虚拟机 在 内存治理跟回收策略 上都做了 大量优化内存调配和GC效率相比晋升了5~10倍,所以 呈现内存抖动的概率会小很多

2、内存透露

Android零碎虚拟机的垃圾回收是通过虚拟机GC机制来实现的。GC会抉择一些还存活的对象作为内存遍历的根节点GC Roots,通过对GC Roots的可达性来判断是否须要回收。内存透露就是 在以后利用周期内不再应用的对象被GC Roots援用,导致不能回收,使理论可应用内存变小。简言之,就是 对象被持有导致无奈开释或不能依照对象失常的生命周期进行开释。一般来说,可用内存缩小、频繁GC,容易导致内存透露

3、内存溢出

即OOM,OOM时会导致程序异样。Android设施出厂当前,java虚拟机对单个利用的最大内存调配就确定下来了,超出这个值就会OOM。单个利用可用的最大内存对应于 /system/build.prop 文件中的 dalvik.vm.heapgrowthlimit

此外,除了因内存透露累积到肯定水平导致OOM的状况以外,也有一次性申请很多内存,比如说 一次创立大的数组或者是载入大的文件如图片的时候会导致OOM。而且,理论状况下 很多OOM就是因图片处理不当 而产生的。

二、常见工具抉择

在 Android性能优化之内存优化中咱们曾经介绍过了相干的优化工具,这里再简略回顾一下。

1、Memory Profiler

作用

  • 1)、实时图表展现利用内存使用量
  • 2)、用于辨认内存透露、抖动等
  • 3)、提供捕捉堆转储、强制GC以及依据内存调配的能力

长处

  • 1)、不便直观
  • 2)、线下应用

2、Memory Analyzer

弱小的 Java Heap 剖析工具,查找 内存透露及内存占用, 生成 整体报告剖析内存问题 等等。倡议 线下深刻应用

3、LeakCanary

自动化 内存透露检测神器。倡议仅用于线下集成

它的 毛病 比拟显著,具体有如下两点:

  • 1)、尽管应用了 idleHandler与多过程,然而 dumphprof 的 SuspendAll Thread 的个性仍然会导致利用卡顿
  • 2)、在三星等手机,零碎会缓存最初一个Activity,此时应该采纳更严格的检测模式

三、Android内存管理机制回顾

ART 和 Dalvik 虚拟机应用 分页和内存映射 来治理内存。上面咱们先从Java的内存调配开始说起。

1、Java 内存调配

Java的 内存调配区域 分为如下 五局部

  • 1)、办法区:次要寄存动态常量
  • 2)、虚拟机栈:Java变量援用
  • 3)、本地办法栈:native变量援用
  • 4)、堆:对象
  • 5)、程序计数器:计算以后线程的以后办法执行到多少行

2、Java 内存回收算法

1、标记-革除算法

流程可简述为 两步

  • 1)、标记所有须要回收的对象
  • 2)、对立回收所有被标记的对象

长处

实现比较简单。

毛病

  • 1)、标记、革除效率不高
  • 2)、产生大量内存碎片

2、复制算法

流程可简述为 三步

  • 1)、将内存划分为大小相等的两块
  • 2)、一块内存用完之后复制存活对象到另一块
  • 3)、清理另一块内存

长处

实现简略,运行高效,每次仅需遍历标记一半的内存区域

毛病

节约一半的空间,代价大。

3、标记-整顿算法

流程可简述为 三步

  • 1)、标记过程与 标记-革除算法 一样
  • 2)、存活对象往一端进行挪动
  • 3)、清理其余内存

长处

  • 1)、防止 标记-革除 导致的内存碎片
  • 2)、防止复制算法的空间节约

4、分代收集算法

当初 支流的虚拟机 个别用的比拟多的还是分代收集算法,它具备如下 特点

  • 1)、联合多种算法劣势
  • 2)、新生代对象存活率低,应用 复制算法
  • 3)、老年代对象存活率高,应用 标记-整顿算法

3、Android 内存管理机制

Android 中的内存是 弹性调配 的,调配值 与 最大值 受具体设施影响

对于 OOM场景 其实能够细分为如下两种:

  • 1)、内存真正有余
  • 2)、可用(被调配的)内存不足

咱们须要着重留神一下这两种的辨别。

4、小结

以Android中虚拟机的角度来说,咱们要分明 Dalvik 与 ART 区别Dalvik 仅固定一种回收算法,而 ART 回收算法可在 运行期按需抉择,并且,ART 具备 内存整理 能力,缩小内存空洞

最初,LMK(Low Memory killer) 机制保障了过程资源的正当利用,它的实现原理次要是 依据过程分类和回收收益来综合决定的一套算法集

四、内存抖动

当 内存频繁调配和回收 导致内存 不稳固,就会呈现内存抖动,它通常体现为 频繁GC、内存曲线呈锯齿状

并且,它的危害也很重大,通常会导致 页面卡顿,甚至造成 OOM

1、那么,为什么内存抖动会导致 OOM?

次要起因有如下两点:

  • 1)、频繁创建对象,导致内存不足及碎片(不间断)
  • 2)、不间断的内存片无奈被调配,导致OOM

2、内存抖动解决实战

这里咱们假如有这样一个场景:点击按钮应用 handler 发送一个空音讯,handler 的 handleMessage 接管到音讯后创立内存抖动,即在 for 循环创立 100个容量为10万 的 strings 数组并在 30ms 后持续发送空音讯。

个别应用 Memory Profiler (体现为 频繁GC、内存曲线呈锯齿状)联合代码排查即可找到内存抖动呈现的中央。

通常的技巧就是着重查看 循环或频繁被调用 的中央。

3、内存抖动常见案例

上面列举一些导致内存抖动的常见案例,如下所示:

1、字符串应用加号拼接

  • 1)、应用StringBuilder代替
  • 2)、初始化时设置容量,缩小StringBuilder的扩容

2、资源复用

  • 1)、应用 全局缓存池,以 重用频繁申请和开释的对象
  • 2)、留神 完结 应用后,须要 手动开释对象池中的对象

3、缩小不合理的对象创立

  • 1)、ondraw、getView 中创立的对象尽量进行复用
  • 2)、防止在循环中一直创立局部变量

4、应用正当的数据结构

应用 SparseArray类族、ArrayMap 来代替 HashMap

五、内存优化体系化搭建

在开始咱们明天正式的主题之前,咱们先来回归一下内存透露的概念与解决技巧。

所谓的内存透露就是 内存中存在曾经没有用的对象。它的 体现 个别为 内存抖动、可用内存逐步缩小。 它的 危害 即会导致 内存不足、GC频繁、OOM

而对于 内存透露的剖析 个别可简述为如下 两步

  • 1)、应用 Memory Profiler 初步察看
  • 2)、通过 Memory Analyzer 联合代码确认

1、MAT回顾

MAT查找内存透露

对于MAT来说,其惯例的查找内存透露的形式能够细分为如下三步:

  • 1)、首先,找到以后 Activity,在 Histogram 中抉择其 List Objects 中的 with incoming reference(哪些援用引向了我)
  • 2)、而后,抉择以后的一个 Path to GC Roots/Merge to GC Roots 的 exclude All 弱软虚援用
  • 3)、最初,找到的透露对象在左下角下会有一个小圆圈

此外,在Android性能优化之内存优化还有几种进阶的应用形式,这里就不一一赘述了,上面,咱们来看看对于 MAT 应用时的一些要害细节。

MAT的要害应用细节

要全面把握MAT的用法,必须要先理解 暗藏在 MAT 应用中的四大细节,如下所示:

  • 1)、长于应用 Regex 查找对应透露类
  • 2)、应用 group by package 查找对应包下的具体类
  • 3)、明确 with outgoing references 和 with incoming references 的区别
  • with outgoing references:它援用了哪些对象
  • with incoming references:哪些对象援用了它
  • 4)、理解 Shallow Heap 和 Retained Heap 的区别
  • Shallow Heap:示意对象本身占用的内存
  • Retained Heap:对象本身占用的内存 + 对象援用的对象所占用的内存

MAT 要害组件回顾

除此之外,MAT 共有 5个要害组件 帮忙咱们去剖析内存方面的问题,别离如下所示:

  • 1)、Dominator_tree
  • 2)、Histogram
  • 3)、thread_overview
  • 4)、Top Consumers
  • 5)、Leak Suspects

上面咱们这里再简略地回顾一下它们。

1、Dominator(支配者):

如果从GC Root达到对象A的门路上必须通过对象B,那么B就是A的支配者。

2、Histogram和dominator_tree的区别:

  • 1)、Histogram 显示 Shallow Heap、Retained Heap、Objects,而 dominator_tree 显示的是 Shallow Heap、Retained Heap、Percentage
  • 2)、Histogram 基于  的角度,dominator_tree是基于 实例 的角度。Histogram 不会具体显示每一个透露的对象,而dominator_tree会

3、thread_overview

查看 线程数量 和 线程的 Shallow Heap、Retained Heap、Context Class Loader 与 is Daemon

4、Top Consumers

通过 图形 的模式列出 占用内存比拟多的对象

在下方的 Biggest Objects 还能够查看其 绝对比拟具体的信息,例如 Shallow Heap、Retained Heap

5、Leak Suspects

列出有内存透露的中央,点击 Details 能够查看其产生内存透露的援用链

2、搭建体系化的图片优化 / 监控机制

在介绍图片监控体系的搭建之前,首先咱们来回顾下 Android Bitmap 内存调配的变动

Android Bitmap 内存调配的变动

在Android 3.0之前

  • 1)、Bitmap 对象寄存在 Java Heap,而像素数据是寄存在 Native 内存中的
  • 2)、如果不手动调用 recycle,Bitmap Native 内存的回收齐全依赖 finalize 函数回调,然而回调机会是不可控的

Android 3.0 ~ Android 7.0

将 Bitmap对象 和 像素数据 对立放到 Java Heap 中,即便不调用 recycle,Bitmap 像素数据也会随着对象一起被回收。

然而,Bitmap 全副放在 Java Heap 中的毛病很显著,大抵有如下两点:

  • 1)、Bitmap是内存耗费的小户,而 Max Java Heap 个别限度为 256、512MB,Bitmap 过大过多容易导致 OOM
  • 2)、容易引起大量 GC,没有充分利用零碎的可用内存

Android 8.0及当前

  • 1)、应用了可能辅助回收 Native 内存的NativeAllocationRegistry,以实现将像素数据放到 Native 内存中,并且能够和 Bitmap 对象一起疾速开释,最初,在 GC 的时候还能够思考到这些 Bitmap 内存以避免被滥用
  • 2)、Android 8.0 为了 解决图片内存占用过多和图像绘制效率过慢 的问题新增了硬件位图Hardware Bitmap

那么,咱们如何将图片内存寄存在 Native 中呢?

将图片内存寄存在Native中的步骤有 四步,如下所示:

  • 1)、调用 libandroid_runtime.so 中的 Bitmap 构造函数,申请一张空的 Native Bitmap。对于不同 Android 版本而言,这里的获取过程都有一些差别须要适配
  • 2)、申请一张一般的 Java Bitmap
  • 3)、将 Java Bitmap 的内容绘制到 Native Bitmap 中
  • 4)、开释 Java Bitmap 内存

咱们都晓得的是,当 零碎内存不足 的时候,LMK 会依据 OOM_adj 开始杀过程,从 后盾、桌面、服务、前台,直到手机重启。并且,如果频繁申请开释 Java Bitmap 也很容易导致内存抖动。对于这种种问题,咱们该 如何评估内存对利用性能的影响 呢?

对此,咱们能够次要从以下 两个方面 进行评估,如下所示:

  • 1)、解体中异样退出和 OOM 的比例
  • 2)、低内存设施更容易呈现内存不足和卡顿,须要查看利用中用户的手机内存在 2GB 以下所占的比例

对于具体的优化策略与伎俩,咱们能够从以下 七个方面 来搭建一套 成体系化的图片优化 / 监控机制

1、对立图片库

在我的项目中,咱们须要 收拢图片的调用,防止应用 Bitmap.createBitmap、BitmapFactory 相干的接口创立 Bitmap,而应该应用本人的图片框架

2、设施分级优化策略

内存优化首先须要依据 设施环境 来综合思考,让高端设施应用更多的内存,做到 针对设施性能的好坏应用不同的内存调配和回收策略

因而,咱们能够应用相似device-year-class的策略对设施进行分级,对于低端机用户能够敞开简单的动画或”重性能“,应用565格局的图片或更小的缓存内存 等等。

业务开发人员须要 思考性能是否对低端机开启,在系统资源不够时被动去做降级解决

3、建设对立的缓存治理组件

建设对立的缓存治理组件,并正当应用 OnTrimMemory / LowMemory 回调,依据零碎不同的状态去开释相应的缓存与内存

在实现过程中,须要 解决应用 static LRUCache 来缓存大尺寸 Bitmap 的问题

并且,在通过理论的测试后,发现 onTrimMemory 的 ComponetnCallbacks2.TRIM_MEMORY_COMPLETE 并不等价于 onLowMemory,因而倡议依然要去监听 onLowMemory 回调

4、低端机防止应用多过程

一个 空过程 也会占用 10MB 内存,低端机应该尽可能减少应用多过程。

针对低端机用户能够推出 4MB 的轻量级版本,现在日头条极速版、Facebook Lite。

5、线下大图片检测

在开发过程中,如果检测到不合规的图片应用(如图片宽度超过View的宽度甚至屏幕宽度),应该立即提醒图片所在的Activity和堆栈,让开发人员更快发现并解决问题。在灰度和线上环境,能够将异样信息上报到后盾,还能够计算超宽率(图片超过屏幕大小所占图片总数的比例)

上面,咱们介绍下如何实现对大图片的检测。

惯例实现

继承 ImageView,重写实现计算图片大小。然而侵入性强,并且不通用。

因而,这里咱们介绍一种更好的计划:ARTHook。

ARTHook优雅检测大图

ARTHook,即 挂钩,用额定的代码勾住原有的办法,以批改执行逻辑,次要能够用于以下四个方面:

  • 1)、AOP编程
  • 2)、运行时插桩
  • 3)、性能剖析
  • 4)、平安审计

具体咱们是应用 Epic 来进行 Hook,Epic 是 一个虚拟机层面,以 Java 办法为粒度的运行时 Hook 框架。简略来说,它就是 ART 上的 Dexposed,并且它目前 反对 Android 4.0~10.0

应用步骤

Epic通常的应用步骤为如下三个步骤:

1、在我的项目 moudle 的 build.gradle 中增加

compile 'me.weishu:epic:0.6.0'

2、继承 XC_MethodHook,实现 Hook 办法前后的逻辑。如 监控Java线程的创立和销毁

class ThreadMethodHook extends XC_MethodHook{
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
        Thread t = (Thread) param.thisObject;
        Log.i(TAG, "thread:" + t + ", started..");
    }

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        Thread t = (Thread) param.thisObject;
        Log.i(TAG, "thread:" + t + ", exit..");
    }
}

3、注入 Hook 好的办法:

DexposedBridge.findAndHookMethod(Thread.class, "run", new ThreadMethodHook());

晓得了 Epic 的根本应用办法之后,咱们便能够利用它来实现大图片的监控报警了。

我的项目实战

以 Awesome-WanAndroid我的项目为例,首先,在 WanAndroidApp 的 onCreate 办法中增加如下代码:

DexposedBridge.hookAllConstructors(ImageView.class, new XC_MethodHook() {
        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            super.afterHookedMethod(param);
        // 1
        DexposedBridge.findAndHookMethod(ImageView.class, "setImageBitmap", Bitmap.class, new ImageHook());
        }
    });

在正文1处,咱们 通过调用 DexposedBridge 的 findAndHookMethod 办法找到所有通过 ImageView 的 setImageBitmap 办法设置的切入点,其中最初一个参数 ImageHook 对象是继承了 XC_MethodHook 类,其目标是为了 重写 afterHookedMethod 办法拿到相应的参数进行监控逻辑的判断

接下来,咱们来实现咱们的 ImageHook 类,代码如下所示:

public class ImageHook extends XC_MethodHook {

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        // 1
        ImageView imageView = (ImageView) param.thisObject;
        checkBitmap(imageView,((ImageView) param.thisObject).getDrawable());
    }

    private static void checkBitmap(Object thiz, Drawable drawable) {
        if (drawable instanceof BitmapDrawable && thiz instanceof View) {
            final Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
            if (bitmap != null) {
                final View view = (View) thiz;
                int width = view.getWidth();
                int height = view.getHeight();
                if (width > 0 && height > 0) {
                    // 2、图标宽高都大于view的2倍以上,则正告
                    if (bitmap.getWidth() >= (width <<1)
                        &&  bitmap.getHeight() >= (height <<1)) {
                    warn(bitmap.getWidth(), bitmap.getHeight(), width, height, new RuntimeException("Bitmap size too large"));
                }
                } else {
                    // 3、当宽高度等于0时,阐明ImageView还没有进行绘制,应用ViewTreeObserver进行大图检测的解决。
                    final Throwable stackTrace = new RuntimeException();
                    view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                        @Override
                        public boolean onPreDraw() {
                            int w = view.getWidth();
                            int h = view.getHeight();
                            if (w > 0 && h > 0) {
                                if (bitmap.getWidth() >= (w <<1)
                                    && bitmap.getHeight() >= (h <<1)) {
                                    warn(bitmap.getWidth(), bitmap.getHeight(), w, h, stackTrace);
                                }
                                view.getViewTreeObserver().removeOnPreDrawListener(this);
                            }
                            return true;
                        }
                    });
                }
            }
        }
    }

    private static void warn(int bitmapWidth, int bitmapHeight, int viewWidth, int viewHeight, Throwable t) {
        String warnInfo = "Bitmap size too large: " +
            "\n real size: (" + bitmapWidth + &#039;,&#039; + bitmapHeight + &#039;)&#039; +
            "\n desired size: (" + viewWidth + &#039;,&#039; + viewHeight + &#039;)&#039; +
            "\n call stack trace: \n" + Log.getStackTraceString(t) + &#039;\n&#039;;

        LogHelper.i(warnInfo);
    }
}

首先,在正文1处,咱们重写了 ImageHook 的 afterHookedMethod 办法,拿到了以后的 ImageView 和要设置的 Bitmap 对象。而后,在正文2处,如果以后 ImageView 的宽高大于0,咱们便进行大图检测的解决:ImageView 的宽高都大于 View 的2倍以上,则正告。接着,在正文3处,如果以后 ImageView 的宽低等于0,则阐明 ImageView 还没有进行绘制,则应用 ImageView 的 ViewTreeObserver 获取其宽高进行大图检测的解决。至此,咱们的大图检测检测组件就曾经实现了。

ARTHook计划实现小结

  • 1)、无侵入性
  • 2)、通用性强
  • 3)、兼容性问题大,开源计划不能带到线上环境

6、线下反复图片检测

首先咱们来理解一下这里的 反复图片 所指的概念: 即 Bitmap 像素数据完全一致,然而有多个不同的对象存在

反复图片检测的原理其实就是 应用内存 Hprof 剖析工具,主动将反复 Bitmap 的图片和援用堆栈输入

应用阐明

应用非常简单,只须要批改 Main 类的 main 办法的第一行代码,如下所示:

// 设置咱们本人 App 中对应的 hprof 文件门路
String dumpFilePath = "//Users//quchao//Documents//heapdump//memory-40.hprof";

而后,咱们执行 main 办法即可在 //Users//quchao//Documents//heapdump 这个门路下看到生成的 images 文件夹,外面保留了我的项目中检测进去的反复的图片。images 目录如下所示:

留神:须要应用 8.0 以下的机器,因为 8.0 及当前 Bitmap 中的 buffer 已保留在 native 内存之中。

实现步骤

具体的实现能够细分为如下三个步骤:

  • 1)、首先,获取 android.graphics.Bitmap 实例对象的 mBuffer 作为 ArrayInstance ,通过 getValues 获取的数据为 Object 类型。因为前面计算 md5 须要为 byte[] 类型,所以通过反射的形式调用 ArrayInstance#asRawByteArray 间接返回 byte[] 数据
  • 2)、而后,依据 mBuffer 的数据生成 png 图片文件。
  • 3)、最初,获取堆栈信息,间接 应用LeakCanary 获取 stack 的办法,应用 leakcanary-analyzer-1.6.2.jar 和 leakcanary-watcher-1.6.2.jar 这两个库文件。并用 反射 的形式调用了 HeapAnalyzer#findLeakTrace 办法。

其中,获取堆栈 的信息也能够间接应用 haha 库来进行获取。这里简略说一下 应用 haha 库获取堆栈的流程,其具体能够细分为八个步骤,如下所示:

  • 1)、首先,准备一个曾经存在反复 bitmap 的 hprof 文件
  • 2)、利用 haha 库上的 MemoryMappedFileBuffer 读取 hrpof 文件 [要害代码 new MemoryMappedFileBuffer(heapDumpFile) ]
  • 3)、解析生成 snapshot,获取 heap,这里我只获取了 app heap [要害代码 snapshot.getHeaps(); heap.getName().equals(“app”) ]
  • 4)、从 snapshot 中依据指定 class 查找出所有的 Bitmap Classes [要害代码snapshot.findClasses(Bitmap.class.getName()) ]
  • 5)、从 heap 中取得所有的 Bitmap 实例 instance [要害代码 clazz.getHeapInstances(heap.getId()) ]
  • 6)、依据 instance 中获取所有的属性信息 Field[],并从 Field[] 查找出咱们须要的 “mWidth” “mHeight” “mBuffer” 信息
  • 7)、通过 “mBuffer” 属性即可获取到他们的 hashcode 来判断是否是反复图片
  • 8)、最初,通过 instance 中 mNextInstanceToGcRoot 获取整个援用链信息并打印

7、建设全局的线上 Bitmap 监控

为了建设全局的 Bitmap 监控,咱们必须 对 Bitmap 的调配和回收 进行追踪。咱们先来看看 Bitmap 有哪些特点:

  • 1)、创立场景比拟繁多:在 Java 层调用 Bitmap.create 或 BitmapFactory 等办法创立,能够封装一层对 Bitmap 创立的接口,留神要 蕴含调用第三方库产生的 Bitmap,这里咱们具体能够应用 ASM 编译插桩 + Gradle Transform 的形式来高效地实现。
  • 2)、创立频率比拟低
  • 3)、和 Java 对象的生命周期一样遵从 GC,能够应用 WeakReference 来追踪 Bitmap 的销毁

依据以上特点,咱们能够建设一套 Bitmap 的高性价比监控组件

  • 1)、首先,在接口层将所有创立进去的 Bitmap 放入一个 WeakHashMap 中,并记录创立 Bitmap 的数据、堆栈等信息。
  • 2)、而后,每隔肯定工夫查看 WeakHashMap 中有哪些 Bitmap 依然存活来判断是否呈现 Bitmap 滥用或透露。
  • 3)、最初,如果产生了 Bitmap 滥用或泄露,则将相干的数据与堆栈等信息打印进去或上报至 APM 后盾。

这个计划的 性能耗费很低,能够在 正式环境 中进行。然而,须要留神的一点是,正式与测试环境须要采纳不同水平的监控。

3、建设线上利用内存监控体系

要建设线上利用的内存监控体系,咱们须要 先获取 App 的 DalvikHeap 与 NativeHeap,它们的获取形式可归结为如下四个步骤:

  • 1、首先,通过 ActivityManager 的 getProcessMemoryInfo => Debug.MemoryInfo 获取内存信息数据
  • 2、而后,通过 hook Debug.MemoryInfo 的 getMemoryStat 办法(os v23 及以上)能够取得 Memory Profiler 中的多项数据,进而取得 细分内存的应用状况
  • 3、接着,通过 Runtime 获取 DalvikHeap
  • 4、最初,通过 Debug.getNativeHeapAllocatedSize 获取 NativeHeap

对于监控场景,咱们须要将其划分为两大类,如下所示:

  • 1)、惯例内存监控
  • 2)、低内存监控

1、惯例内存监控

依据 斐波那契数列 每隔一段时间(max:30min)获取内存的应用状况。惯例内存的监控办法有多种实现形式,上面,咱们依照 我的项目晚期 => 壮大期 => 成熟期 的惯例内存监控形式进行 演进式 解说。

我的项目晚期:针对场景进行线上 Dump 内存的形式

具体应用 Debug.dumpHprofData() 实现。

其实现的流程为如下四个步骤:

  • 1)、超过最大内存的 80%
  • 2)、内存 Dump
  • 3)、回传文件至服务器
  • 4)、MAT 手动剖析

然而,这种形式有如下几个毛病:

  • 1)、Dump文件太大,和对象数正相干,能够进行裁剪
  • 2)、上传失败率高,剖析艰难

壮大期:LeakCanary带到线上的形式

在应用 LeakCanary 的时候咱们须要 预设透露狐疑点,一旦发现透露进行回传。但这种实现形式毛病比拟显著,如下所示:

  • 1)、不适宜所有状况,须要预设狐疑点
  • 2)、剖析比拟耗时,容易导致 OOM

成熟期:定制 LeakCanary 形式

那么,如何定制线上的LeakCanary?

定制 LeakCanary 其实就是对 haha组件 来进行 定制。haha库是 square 出品的一款 主动剖析Android堆栈的java库

对于haha库,它的 根本用法 个别遵循为如下四个步骤:

1、导出堆栈文件

File heapDumpFile = ...
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());

2、依据堆栈文件创建出内存映射文件缓冲区

DataBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);

3、依据文件缓存区创立出对应的快照

Snapshot snapshot = Snapshot.createSnapshot(buffer);

4、从快照中获取指定的类

ClassObj someClass = snapshot.findClass("com.example.SomeClass");

咱们在实现线上版的LeakCanary的时候次要要解决的问题有三个,如下所示:

  • 1)、解决 预设狐疑点 时不精确的问题 => 主动找狐疑点
  • 2)、解决掉将 hprof 文件映射到内存中的时候可能导致内存暴涨甚至产生 OOM 的问题 => 对象裁剪,不全副加载到内存。即对生成的 Hprof 内存快照文件做一些优化:裁剪大部分图片对应的 byte 数据 以缩小文件开销,最初,应用 7zip 压缩,个别可 节俭 90% 大小
  • 3)、剖析透露链路慢而导致剖析工夫过长 => 剖析 Retain size 大的对象

成熟期:实现内存透露监控闭环

在实现了线上版的 LeakCanary 之后,就须要 将线上版的 LeakCanary 与服务器和前端页面联合 起来。具体的 内存透露监控闭环流程 如下所示:

  • 1)、当在线上版 LeakCanary 上发现内存透露时,手机将上传内存快照至服务器
  • 2)、此时服务器剖析 Hprof,如果不是零碎起因导致误报则通过 git 失去该最近批改人
  • 3)、最初将内存透露 bug 单提交给负责人。该负责人通过前端实现的 bug 单零碎即可看到本人新增的bug

此外,在实现 图片内存监控 的过程中,应留神 两个关键点,如下所示:

  • 1)、在线上能够依照 不同的零碎、屏幕分辨率 等纬度去 剖析图片内存的占用状况
  • 2)、在 OOM 解体时,能够将 图片总内存、Top N 图片占用内存 写入 解体日志

2、低内存监控

对于低内存的监控,通常有两种形式,别离如下所示:

  • 1、利用 onTrimMemory / onLowMemory 监听系统回调的物理内存正告
  • 2、在后盾起一个服务定时监控零碎的内存占用,只有超过虚拟内存大小最大限度的 90% 则间接触发内存正告

3、内存监控指标

为了精确掂量内存性能,咱们须要引入一系列的内存监控指标,如下所示:

1)、产生频率

2)、产生时各项内存应用情况

3)、产生时App的以后场景

4)、内存异样率

内存 UV 异样率 = PSS 超过 400MB 的 UV / 采集UV
PSS 获取:调用 Debug.MemoryInfo 的 API 即可

如果呈现 新的内存使用不当或内存透露 的场景,这个指标会有所 上涨

5)、触顶率

内存 UV 触顶率 = Java 堆占用超过最大堆限度的 85% 的 UV / 采集UV

计算触顶率的代码如下所示:

long javaMax = Runtime.maxMemory();
long javaTotal = Runtime.totalMemory();
long javaUsed = javaTotal - runtime.freeMemory();
float proportion = (float) javaUsed / javaMax;

如果超过 85% 最大堆 的限度,GC 会变得更加 频发,容易造成 OOM 和 卡顿

4、小结

在具体实现的时候,客户端 尽量只负责 上报数据,而 指标值的计算 能够由 后盾 来计算。这样便能够通过 版本比照 来监控是否有 新增内存问题。因而,建设线上内存监控的残缺计划 至多须要蕴含以下四点

  • 1)、待机内存、重点模块内存、OOM率
  • 2)、整体及重点模块 GC 次数、GC 工夫
  • 3)、加强的 LeakCanry 自动化内存透露剖析
  • 4)、低内存监控模块的设置

4、建设全局的线程监控组件

每个线程初始化都须要 mmap 肯定的栈大小,在默认状况下初始化一个线程须要 mmap 1MB 左右的内存空间

在 32bit 的利用中有 4g 的 vmsize理论能应用的有 3g+,这样一个过程 最大能创立的线程数 能够达到 3000个,然而,linux 对每个过程可创立的线程数也有肯定的限度(/proc/pid/limits),并且,不同厂商也能批改这个限度,超过该限度就会 OOM。

因而,对线程数量的限度,在肯定水平上能够 无效地防止 OOM 的产生。那么,实现一套 全局的线程监控组件 便是 迫不及待 的了。

全局线程监控组件的实现原理

在线下或灰度的环境下通过一个定时器每隔 10分钟 dump 出利用所有的线程相干信息,当线程数超过以后阈值时,则将以后的线程信息上报并预警

5、GC 监控组件搭建

通过 Debug.startAllocCounting 来监控 GC 状况,留神有肯定 性能影响

在 Android 6.0 之前 能够拿到 内存调配次数和大小以及 GC 次数,其对应的代码如下所示:

long allocCount = Debug.getGlobalAllocCount();
long allocSize = Debug.getGlobalAllocSize();
long gcCount = Debug.getGlobalGcInvocationCount();

并且,在 Android 6.0 及之后 能够拿到 更精准 的 GC 信息:

Debug.getRuntimeStat("art.gc.gc-count");
Debug.getRuntimeStat("art.gc.gc-time");
Debug.getRuntimeStat("art.gc.blocking-gc-count");
Debug.getRuntimeStat("art.gc.blocking-gc-time");

对于 GC 信息的排查,咱们个别关注 阻塞式GC的次数和耗时,因为它会 暂停线程,可能导致利用产生 卡顿。倡议 仅对重度场景应用

6、建设线上 OOM 监控组件:Probe

美团的 Android 内存透露自动化链路剖析组件 Probe 在 OOM 时会生成 Hprof 内存快照,而后,它会通过 独自过程 对这个 文件 做进一步 剖析

Probe 组件的缺点及解决方案

它的毛病比拟多,具体为如下几点:

  • 1、在解体的时候生成内存快照容易导致二次解体
  • 2、局部手机生成 Hprof 快照比拟耗时
  • 3、局部 OOM 是由虚拟内存有余导致

在实现自动化链路剖析组件 Probe 的过程中次要要解决两个问题,如下所示:

1、链路剖析工夫过长

  • 1)、应用链路归并:将具备 雷同层级与构造 的链路进行 合并
  • 2)、应用 自适应扩容法通过一直比拟现有链路和新链路,联合扩容因子,逐步欠缺为残缺的透露链路

2、剖析过程占用内存过大

剖析过程占用的内存 跟 内存快照文件的大小 不成正相干,而跟 内存快照文件的 Instance 数量 呈 正相干。所以在开发过程中咱们应该 尽可能排除不须要的Instance实例

Prope 剖析流程揭秘

Prope 的 总体架构图 如下所示:

而它的整个剖析流程具体能够细分为八个步骤,如下所示:

1、hprof 映射到内存 => 解析成 Snapshot & 计数压缩:

解析后的 Snapshot 中的 Heap 有四种类型,具体为:

  • 1)、DefaultHeap
  • 2)、ImageHeap
  • 3)、App Heap:包含 ClassInstance、ClassObj、ArrayInstance、RootObj
  • 4)、System Heap

解析完 后应用了 计数压缩策略,对 雷同的 Instance 应用 计数,以 缩小占用内存。超过计数阈值的须要计入计数桶(计数桶记录了 抛弃个数 和 每个 Instance 的大小)

2、生成 Dominator Tree

3、计算 RetainSize

4、生成 Reference 链 && 根底数据类型加强:

如果对象是 根底数据类型,会将 本身的 RetainSize 累加到父节点 上,将 怀疑对象 替换为它的 父节点

5、链路归并

6、计数桶弥补 & 根底数据类型和父节点交融

应用计数弥补策略计算 RetainSize,次要是 判断对象是否在计数桶中,如果在的话则将 抛弃的个数和大小弥补到对象上,累积计算RetainSize,最初对 RetainSize 排序以查找可疑对象

7、排序扩容

8、查找泄露链路

7、实现 单机版 的 Profile Memory 自动化内存剖析

在配置的时候要留神两个问题:

  • 1、liballoc-lib.so在构建后工程的 build => intermediates => cmake 目录下。将对应的 cpu abi 目录拷贝到新建的 libs 目录下
  • 2、在 DumpPrinter Java 库的 build.gradle 中的 jar 闭包中须要退出以下代码以辨认源码门路:
    sourceSets.main.java.srcDirs = [‘src’]

应用步骤

具体的应用步骤如下所示:

1、首先,点击 ”开始记录“ 按钮能够看到触发对象调配的记录,阐明对象曾经开始记录对象的调配,log如下所示:

12-26 10:54:03.963 30450-30450/com.dodola.alloctrack I/AllocTracker: ====current alloc count 388=====

2、而后,点击屡次 ”生成1000个对象“ 按钮,当对象达到设置的最大数量的时候触发内存dump,会失去保留数据门路的日志。如下所示:

12-26 10:54:03.963 30450-30450/com.dodola.alloctrack I/AllocTracker: ====current alloc count 388=====
12-26 10:56:45.103 30450-30450/com.dodola.alloctrack I/AllocTracker: saveARTAllocationData write file to /storage/emulated/0/crashDump/1577329005

3、此时,能够看到数据保留在 sdk 下的 crashDump 目录下。

4、接着,通过 gradle task :buildAlloctracker 工作编译出寄存在 tools/DumpPrinter-1.0.jar 的 dump 工具,而后采纳如下命令来将数据解析 到dump_log.txt 文件中。

java -jar tools/DumpPrinter-1.0.jar dump文件门路 > dump_log.txt

5、最初,就能够在 dump_log.txt 文件中看到解析进去的数据,如下所示:

Found 4949 records:
tid=1 byte[] (94208 bytes)
    dalvik.system.VMRuntime.newNonMovableArray (Native method)
    android.graphics.Bitmap.nativeCreate (Native method)
    android.graphics.Bitmap.createBitmap (Bitmap.java:975)
    android.graphics.Bitmap.createBitmap (Bitmap.java:946)
    android.graphics.Bitmap.createBitmap (Bitmap.java:913)
    android.graphics.drawable.RippleDrawable.updateMaskShaderIfNeeded (RippleDrawable.java:776)
    android.graphics.drawable.RippleDrawable.drawBackgroundAndRipples (RippleDrawable.java:860)
    android.graphics.drawable.RippleDrawable.draw (RippleDrawable.java:700)
    android.view.View.getDrawableRenderNode (View.java:17736)
    android.view.View.drawBackground (View.java:17660)
    android.view.View.draw (View.java:17467)
    android.view.View.updateDisplayListIfDirty (View.java:16469)
    android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:3905)
    android.view.ViewGroup.dispatchGetDisplayList (ViewGroup.java:3885)
    android.view.View.updateDisplayListIfDirty (View.java:16429)
    android.view.ViewGroup.recreateChildDisplayList (ViewGroup.java:3905)

8、搭建线下 Native 内存透露监控体系

在 Android 8.0 及之后,能够应用 Address Sanitizer、Malloc 调试和 Malloc 钩子 进行 native 内存剖析

对于线下 Native 内存透露监控的建设,次要针对 是否能重编 so 的状况 来记录调配的内存信息。

针对无奈重编so的状况

  • 1)、首先,应用 PLT Hook 拦挡库的内存调配函数,而后,重定向到咱们本人的实现后去 记录调配的 内存地址、大小、起源so库门路 等信息。
  • 2)、最初,定期 扫描调配与开释 的配对内存块,对于 不配对的调配 输入上述记录的信息

针对可重编的so状况

  • 1)、首先,通过 GCC 的 ”-finstrument-functions“ 参数给 所有函数插桩,而后,在桩中模仿调用栈的入栈与出栈操作
  • 2)、接着,通过 ld 的 ”–warp“ 参数 拦挡内存调配和开释函数,重定向到咱们本人的实现后记录调配的 内存地址、大小、起源so以及插桩调用栈此刻的内容
  • 3)、最初,定期扫描调配与开释是否配对,对于不配对的调配输入咱们记录的信息

9、设置内存兜底策略

设置内存兜底策略的目标,是为了 在用户无感知的状况下,在靠近触发零碎异样前,抉择适合的场景杀死过程并将其重启,从而使得利用内存占用回到失常状况

通常执行内存兜底策略时至多须要满足六个条件,如下所示:

  • 1)、是否在主界面退到后盾且位于后盾工夫超过 30min
  • 2)、以后工夫为早上 2~5 点
  • 3)、不存在前台服务(告诉栏、音乐播放栏等状况)
  • 4)、Java heap 必须大于以后过程最大可调配的85% || native内存大于800MB
  • 5)、vmsize 超过了4G(32bit)的85%
  • 6)、非大量的流量耗费(不超过1M/min) && 过程无大量CPU调度状况

只有在满足了以上条件之后,咱们才会去杀死以后主过程并通过 push 过程从新拉起及初始化

10、更深刻的内存优化策略

除了在 Android性能优化之内存优化 => 优化内存空间中解说过的一些惯例的内存优化策略以外,在上面列举了一些更深刻的内存优化策略。

1、使 bitmap 资源在 native 中调配

对于 Android 2.x 零碎,应用反射将 BitmapFactory.Options 外面暗藏的 inNativeAlloc 关上

对于 Android 4.x 零碎,应用或借鉴 Fresco 将 bitmap 资源在 native 中调配的形式

2、图片加载时的降级解决

应用 Glide、Fresco 等图片加载库,通过定制,在加载 bitmap 时,若产生 OOM,则应用 try catch 将其捕捉,而后革除图片 cache,尝试升高 bitmap format(ARGB8888、RGB565、ARGB4444、ALPHA8)。

须要留神的是,OOM 是能够捕捉的,只有 OOM 是由 try 语句中的对象申明所导致的,那么在 catch 语句中,是能够开释掉这些对象,解决 OOM 的问题的。

3、前台每隔 3 分钟去获取以后利用内存占最大内存的比例,超过设定的危险阈值(如80%)则被动开释利用 cache(Bitmap 为大头),并且显示地除去利用的 memory,以减速内存收集的过程。

计算以后利用内存占最大内存的比例的代码如下:

max = Runtime.getRuntime().maxMemory();
available = Runtime.getRuntime.totalMemory() - Runtime.getFreeMemory();
ratio = available / max;

显示地除去利用的 memory,以减速内存收集过程的代码如下所示:

WindowManagerGlobal.getInstance().startTrimMemory(TRIM_MEMORY_COMPLETE);

4、因为 webview 存在内存零碎透露,还有 图库占用内存过多 的问题,能够采纳独自的过程。

5、当UI暗藏时开释内存

当用户切换到其它利用并且你的利用 UI 不再可见时,应该开释利用 UI 所占用的所有内存资源。这可能显著减少零碎缓存过程的能力,可能晋升用户体验。

在所有 UI 组件都暗藏的时候会接管到 Activity 的 onTrimMemory() 回调并带有参数 TRIM_MEMORY_UI_HIDDEN

6、Activity 的兜底内存回收策略

在 Activity 的 onDestory 中递归开释其援用到的 Bitmap、DrawingCache 等资源,以升高产生内存透露时对利用内存的压力。

7、应用相似 Hack 的形式修复零碎内存透露

LeakCanary 的 AndroidExcludeRefs 列出了一些因为零碎起因导致援用无奈开释的例子,可应用相似 Hack 的形式去修复。

8、当利用应用的Service不再应用时应该销毁它,倡议应用 IntentServcie。

9、审慎应用第三方库,防止为了应用其中一两个性能而导入一个大而全的解决方案。

很感谢您浏览这篇文章,心愿您能将它分享给您的敌人或技术群,这对我意义重大。

文章转自 https://juejin.cn/post/6844904099998089230 如有侵权,请分割删除。

相干视频举荐:

Android 性能优化学习【一】:APK瘦身优化_哔哩哔哩_bilibili

Android 性能优化学习【二】:APP启动速度优化_哔哩哔哩_bilibili

Android 性能优化学习【三】:如何解决OOM问题_哔哩哔哩_bilibili

Android 性能优化学习【四】:UI卡顿优化_哔哩哔哩_bilibili



推荐阅读
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文总结了在开发中使用gulp时的一些技巧,包括如何使用gulp.dest自动创建目录、如何使用gulp.src复制具名路径的文件以及保留文件夹路径的方法等。同时介绍了使用base选项和通配符来保留文件夹路径的技巧,并提到了解决带文件夹的复制问题的方法,即使用gulp-flatten插件。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
  • 突破MIUI14限制,自定义胶囊图标、大图标样式,支持任意APP
    本文介绍了如何突破MIUI14的限制,实现自定义胶囊图标和大图标样式,并支持任意APP。需要一定的动手能力和主题设计师账号权限或者会主题pojie。详细步骤包括应用包名获取、素材制作和封包获取等。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 本文介绍了Android中的assets目录和raw目录的共同点和区别,包括获取资源的方法、目录结构的限制以及列出资源的能力。同时,还解释了raw目录中资源文件生成的ID,并说明了这些目录的使用方法。 ... [详细]
  • 本文介绍了OkHttp3的基本使用和特性,包括支持HTTP/2、连接池、GZIP压缩、缓存等功能。同时还提到了OkHttp3的适用平台和源码阅读计划。文章还介绍了OkHttp3的请求/响应API的设计和使用方式,包括阻塞式的同步请求和带回调的异步请求。 ... [详细]
author-avatar
向着成功一直努力的人
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有