还算全面的Android性能内存分析优化实战

日常工作中用到的一些常规的性能优化方式总结。希望对大家有用!欢迎学习交流!

一、App启动优化

App启动简单流程

1 . 加载并启动app
2 . 创建空白窗口,为了让用户感觉秒开(默认为白色)
3 . 创建app进程
4 . 创建主activity
5 . 加载布局、绘制

APP执行时长检测方式

方式一 、 app启动时长检测 – adb 命令检测

 // 启动一次
adb shell am start -W -n com.package/.activity.MyActivity
// 启动多次
adb shell am start -S -R 10 -W com.package/.activity.MyActivity(启动十次)
Activity: com.mediatek.wwtv.tvcenter/.nav.TurnkeyUiMainActivity
ThisTime: 678   (表示一连串启动Activity 的最后一个 Activity 的启动耗时)
TotalTime: 678   (一系列activity启动时间,一般只关注这项)
WaitTime: 915  (总的耗时,包括app冷启动时加载进程的时候)

方式二、 代码执行时长检测 – MethodTracing

第一步、在项目代码里埋下需要追踪的代码块

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
	//在指定目录下生成.trace 文件
    File file = new       File(Environment.getExternalStorageDirectory().getPath(),"filename");
    Debug.startMethodTracing(file.getAbsolutePath());
    //模拟初始化操作
    initSDK();
    loadRes();
    // 获取trace文件命令
    Debug.stopMethodTracing();
}

第二步、把 .trace文件从手机里转存到PC,拖拽文件到studio上自动打开,鼠标移动到对应的方法上就可以观察各个方法执行的时机
在这里插入图片描述

app冷启动解决方案

冷、热、暖启动概念

  • 冷启动:系统未给该应用创建过经过,刚开机、首次安装后、任务管理手动杀死进程。
  • 热启动:用户把应用最小化到后台或者在后台被回收了部分资源。
  • 暖启动:用户按返回键退出该应用,相比热启动稍微多消耗一些资源,它跟热启动最大的区别在于它必须调用oncreate方法重新创建活动。

冷启动未优化造成的问题
在启动app时候我们经常会引入一些第三方的SDK,随着需求、功能不断的迭代开发,需要引用的框架就越来越多,app在启动时候所花费的时机也会增加。

冷启动解决方案

1. 视觉优化

  • 启动用透明背景代替,虽然消失白屏但是桌面的响应时间增多。
  • 启动用logo背景代替,看起来无缝链接,视觉效果不错。

这么做的目的主要是为了消除启动时的黑白屏,给用户一种秒响应的感觉,但是并不会 真正减少用户启动时间,仅属于视觉优化。

2. 代码优化

  • 分析app启动时间(MethodTracing),针对性的优化部分占用时间长初始化操作。
  • Application 和 主 Activity 的 onCreate 中异步初始化某些代码
    因为在主线程上进行资源初始化会降低启动速度,所以可以将不必要的资源初始化延 迟,达到优化的效果。但是这里要注意懒加载集中化的问题,别用户启动时间快了,但 是无法在界面上操作就尴尬了。
  • 项目不及时需要的代码通过异步加载。
  • 将对一些使用率不高的初始化,做懒加载。
  • 将对一些耗时任务通过开启一个 IntentService来处理。
  • 找到更加轻量级的代替方案

3. 主页面布局优化

  • 通过减少冗余或者嵌套布局来降低视图层次结构

  • 用 ViewStub 替代在启动过程中不需要显示的 UI 控件

二、 UI渲染流程及布局优化

CPU和 GPU

CPU作为"中央处理器",除了要负责逻辑计算外,还需要做内存管理,显示操作,因此随着各种复杂App的出现,其实际运算的性能会大打折扣。
设计原由∶为了提高图形显示效率以及复杂的图形,设计出了GPU。 主要功能∶为了帮助CPU分担图形显示

CPU和GPU结构
在这里插入图片描述

  • 蓝色的Control为控制器,用于协调控制整个CPU的运行,包括取出指令,控制其他模块的运行等;
  • 绿色的ALU (Arithmetic Logic Unit)为算术逻辑单元,用于数学以及逻辑运算;
  • 橙色的Cache和DRAM分别为缓存和RAM,用户存储信息;

CPU控制器比较复杂,ALU数量较少。因此CPU擅长各种复杂的逻辑运算,但不擅长数学尤其是浮点运算;

FPS
12fps∶ 画面帧率高于每秒约10-12帧时,眼睛会认为它是连贯的;
24 fps∶ 电影拍摄一般为每秒24帧;

30 fps∶ 游戏一般会在每秒30帧左右;

60fps∶手机交互过程中,需要触摸和反馈,需要60帧才能达到不卡顿的效果;
我们手机绘制画面每一帧要在16毫秒内完成,如果超出了就会感觉到不连贯卡顿,

布局优化目标

1.CPU减少XML换成对象的过程
2.GPU 减少XML重复绘制,
过度绘制概念:GPU每隔16ms画一次,如果CPU传递过来的图形有重复的位置,会造成用户只能看到顶层画面,而底层画面则被遮盖,底层部分的绘制虽然用户无法看到,但同样也占据了计算资源,造成了不必要的浪费;这种情况就叫过度绘制;

过度绘制查看方式
在设置 - > 开发者选项 -> GPU过度绘制调试 - > 打开

选取规则:
1x Overdraw 最好
2x Overdraw 很好
3x Overdraw 可以有
4x Overdraw 不要有
在这里插入图片描述

过度绘制优化方式有哪些
1.减少ViewGroup层级嵌套
2.减少无用的背景色,把相同的背景色提取到最底层父容器上
3.对重叠的自定义view进行裁剪,只显示可见部分
4.利用meger标签减少一层ViewGroup
5. 一个页面比较长的时候,不需要立马显示出来的布局可使用“ViewSub”布局懒加载,进行延时加载
5.终极大法利用ConstraintLayout 进行布局

三、内存优化-垃圾回收机制

强|软||弱|虚引用。

强引用(strong reference)
如:Object object=new Object(),object新建的对象就是一个强引用。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
软引用(SoftReference)
只有内存不够时才回收,常用于缓存;当内存达到一个阀值,GC就会去回收它;如果回收后还内存不足以新对象使用将会抛出OutOfMemoryError异常。
弱引用(WeakReference)
弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
虚引用(PhantomReference)
"虚引用"顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。一般用不到。

Java虚拟机运行时数据区域

在这里插入图片描述
Java运行时数据区域分为几大模块:方法区、java堆、java虚拟栈、本地方法栈 和 程序计数器,其中方法区和java 堆是线程共享的,java虚拟栈、本地方法栈和程序计数器是线程独占的。

方法区:
方法区和java堆一样,是所有线程共享的内存区域。它存储了每一个类的结构信息,例如运行时常量池,字段和方法数据,构造函数和普通方法的字节码内容,还包括一些在类,实例,接口初始化时用到的特殊方法。

Java堆: 
java堆是Java虚拟机所管理的内存中最大的一块,它是被所有线程所共享的内存区域,在虚拟机启动时候创建。java堆是供所有类实例和数组分配内存的区域,也是垃圾收集器管理的主要区域,因此很多时候也被称为“GC堆”。

Java虚拟机栈:
Java虚拟机栈线程私有的,它的生命周期与线程相同(随线程而生,随线程而灭)
;如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常;
;Java虚拟机栈是Java方法执行的内存模型:每个方法执行的同时会创建一个栈帧。
对于我们来说,主要关注的stack栈内存,就是虚拟机栈中局部变量表部分。
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。
每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。

本地方法栈:
本地方法栈和虚拟机栈的作用类似,区别在于java虚拟机栈支持Java方法执行,而本地方法栈则支持native方法执行。

程序计数器:
程序计数器是很小的一块内存区域,可以看做是当前线程所执行字节码的行号指示器。
程序计数器是线程私有的,每一个线程里都分配有一个程序计数器。
Java虚拟机执行线程时是高速切换的,如果同时执行A和B两个线程,执行了B线程就是挂起A线程,那么执行了B线程再回去A线程 cpu会根据程序计数器记录继续执行下一行。

垃圾回收算法分为两大基础类:

1.引用计数算法
(1)给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
(2)优点是简单,高效。 缺点是很难处理循环引用,比如图中相互引用的两个对象则无法释放。
2.可达性分析算法(根搜索算法GC Roots)
(1)为了解决上面的循环引用问题,Java采用了一种新的算法:可达性分析算法。 从GC Roots(每种具体实现对GC Roots有不同的定义)作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。即使循环引用了,只要没有被GC Roots引用了依然会被回收
哪些对象可以作为GC Roots:
(1) Java虚拟机栈正在被引用的对象。
(2) Java本地方法栈(JNI native方法)中正在被引用的对象
(2) 方法区中静态变量 和 常量引用的对象。
(3) 本地方法栈中JNI native方法引用的对象。
可以总结为:生命周期比较长的和正在执行的
在这里插入图片描述

四种垃圾回收算法

1.标记清除算法 (Mark-Sweep)
标记清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。 优点是简单,容易实现。 缺点是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾动作
在这里插入图片描述

2. 复制算法 (Copying)

复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。 优缺点就是,实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。 从算法原理我们可以看出,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。
适用于存活对象很少,回收对象多。
在这里插入图片描述

3. 标记整理算法 (Mark-Compact)

该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。 所以,特别适用于存活对象多,回收对象少的情况下。
在这里插入图片描述

4. 分代收集算法
分代回收算法是根据复制算法和标记整理算法的特点综合而成的,分代手机算法最大的特点就是根据对象生命周期长短划到不同局域存储。
分代回收算法分为了新生代和老年代两个大区(内存比例为:1/3 和 2/3),新生代又分为成Eden、from survivor 、to survivor(内存比例为:8 : 1 : 1)

Eden主要负责存储新建对象,From survivor 和 to survivor 两块空间在GC时会清空掉其中一个区域,为让下次新存活对象腾出空间作准备,老年代负责存储生命周期长、占用空间大的对象。
在这里插入图片描述

模拟分代收集算法的循环过程:

// 如果分配了多个对象
// 放到Eden区
//....
// 如果Eden区满了,发生了GC(新生代GC:Minor GC)
//把Eden区的存活对象复制到 from Survivor 区,然后清空Eden区(本来to Survivor 区也需要清空的,不过本来就是空的)

// 如果又分配了多个对象
// 继续放到Eden区
// Eden区又满了,又发生了GC(新生代GC:Minor GC)
//把Eden区和from Survivor 区的存活对象复制到to Survivor 区,然后清空Eden区和from Survivor 区
//....
// 如果又分配了多个对象
//  继续放到Eden区
// Eden区又满了,又发生了GC(新生代GC:Minor GC)
// 把Eden区和 to Survivor 区的存活对象复制到from Survivor 区,然后清空Eden区和to Survivor区
//....
// 如果对象来回在from Survivor 区或者 to Survivor呆了多次,就被分配到老年代Old区
// 如果新建对象太大,超过了Eden区,直接被分配在Old区
// 如果存活对象,放不下Survivor区,也被分配到Old区
//....
// 在某次Minor GC的过程中突然发现老年代Old区也满了,将调一次大GC(老年代GC:Major GC)
// Old区GC后又有空间了
// 继续Minor GC
//....

四、 内存分析优化— 内存泄漏

内存泄漏和内存溢出概念

  • 内存泄漏∶一个不再被程序使用的对象或变量依旧存活在内存中无法被回收;
  • 内存溢出∶ 当程序申请内存时,没有足够的内存供程序使用;
    比较小的内存泄漏并不会有太大的影响,但内存泄漏多了,占用的内存空间就更大,程序正常需要申请使用的内存则会相应减少;

Profile分析内存泄漏
模拟Handler内存泄漏:ondestory没有remove掉,造成关闭TestActivity不能被回收

public class TestActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        handler.sendEmptyMessageDelayed(0,1000);
    }
    private Handler handler=new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            handler.sendEmptyMessageDelayed(0,1000);
        }
    };
}

模拟打开 -> 关闭 TestActivity 三次
在这里插入图片描述

一般只需要关注下面几点:
① 执行GC
② 抓取GC后的内存详细片段,会自动在底部生成内存详细
③ 选择应用程序分配内存的主堆app heap
④ 选择根据包名筛选Arrange by package
⑤ 在package Name里面找到自己app的包名就能看到每个对象的使用情况
⑥ 右侧的instance view 显示TestActivity在内存有三个实例,刚刚模拟打开关闭三次TestActivity三次,正好有三个对象没有被销毁,很明显这里被某些对象引用了没有得到释放

MAT 查看泄漏对象的引用

(1) 右键profile -> Export 导出已经抓取的灰色内存块到桌面会自动生成 “. hprof”文件
在这里插入图片描述

(2) 利用platform-tools下的hprof-conv.exe工具对android profile生成的“.hprof”文件过滤
命令窗口输入 hprof-conv -z fileName. hprof newFileName-mat . hprof
在这里插入图片描述

(3) 打开MAT工具 -> File -> Open Heap Dump -> 点击底部的Histogram
在这里插入图片描述

(4) 搜索你怀疑的内存泄漏对象
在这里插入图片描述

(5) 右键泄漏对象 -> 选择Merge shortest paths to GC Root - > exclude all phantom/weak/soft etc…(排除软引用和弱引用的对象)
在这里插入图片描述

(6) 查看泄漏引用
①this$ 在android里有美元符号就是内部类了,MessageQueue一看就知道跟handler有关
②剩下的就是进代码里排除handler相关的代码了。
在这里插入图片描述

LeakCanary 线下内存泄漏采集工具

LeakCanary 内存泄漏分析工具使用

五、内存抖动

内存抖动概念
内存抖动通常是指在短时间内发生了多次内存的分配和释放,主要原因则是短时间内频繁的创建对象,为了应对这种情况,虚拟机会频繁的触发GC操作,当GC进行时,其他线程会被挂起等待GC的完成,频繁GC,也就导致比如UI在绘制时会超过16ms—帧,导致画面卡顿等;

内存抖动案例

  1. 大量字符串拼接
String str = "";
for(int i=0; i< 10000; i++) {
		str += i;
}

优化方法:避免使用+或者+=操作,使用StringBuilder来实现字符串的拼接

  1. 自定义组件 onDraw() 里高频率的创建对象、刷新UI

  2. Glide 复用池也是内存抖动很好的案例

六、 图片优化

Bitmap 占用大小计算方式
Bitmap内存占用 ≈ 像素数据总大小 = 横向像素数量 × 纵向像素数量 × 每个像素的字节大小
例:1.22mb ≈ 1,280,000byte = 800 × 400 × 4(ARGB_8888 24位真彩色)

减少图片大小

  1. 转换成Webp图片格式
    图片格式通常有三种:JPG、Png、WebP我们在decodeBitmap一张图片的时候可以选择WEBP,同等质量图片下,webp的大小是jpg的20%。
    WebP是一种支持有损压缩和无损压缩的图片文件 格式,派生自图像编码格式 VP8。根据 Google 的测试,无损压缩后的 WebP 比 PNG 文件少了 45% 的文件大小,即使这些 PNG 文件经过其他压缩工具压缩之 后,WebP 还是可以减少 28%的文件大小。
    在这里插入图片描述

  2. 使用低色彩的解析模式
    如Bitmap.Config.RGB565,减少单个像素的字节大小,大约能减少一半的内存开销。Android默认是使用ARGB8888配置来处理色彩,占用4字节,改用RGB565,将只占用2字节,代价是显示的色彩将相对少,适用于对色彩丰富程度要求不高的场景。
    在这里插入图片描述

  3. 缩放图片
    options.inSampleSize 根据缩放比例缩放图片,适合在列表展示位显示,图片尺寸和质量要求不高的场景下。

  4. 后台相同图片分为两套不同质量图片。
    与后台协商,图片分成不同尺寸,一张图片分为低质量和高质量两张,列表展示位显示低质量,大图显示高质量。

  5. 哈夫曼算法无损压缩图片

巨图加载
加载长图屏幕显示不下可以用BitmapRegionDecoder,根据触摸时间移动只加载显示可见的区域大小。

六、 线程优化

Android 线程调度机制

  • 分时调度∶ 所有的线程轮流获得cpu使用权,平均分配每个线程占用的cpu时间,默认创建的线程与母线程优先级一致。
  • 抢占式调度∶ 优先让可运行池中的优先级高的线程占用 cpu,优先级相同随机选择一个线程

线程优化方式一、抢占式调度 - 线程优先级
Android可以通过androidos.Process.setThreadPriority(int)设置线程优先级,参数范围- 20~24, 数值越小优先级越高,0位默认的优先级。Android系统会根据当前运行的前台可见的程序和不可见的后台程序对线程进行归类,系统为保证前台操作流畅CPU会分配更多的资源进行调度。我们根据功能需求合理的分配优先级已达到优化目的。

线程优化方式二、ThreadPoolExecutor线程池
如果需要大量创建线程来完成任务的需求,应该使用线程池,减少频繁创建和销毁带来的消耗。并且有效控制线程的最大并发数量,防止线程过大导致抢占资源造成系统阻塞,可以对线程进行一定地管理。

七、 其他常规的优化方式

代码优化

1 . 尽量减少使用全局变量,能使用局部变量的就用布局变量。

2 . 经量减少使用静态变量、对象。

3 . 避免使用静态的集合装着大量的数据供其他页面使用,就算不使用了也会常驻内存,静态对象与APP生命周期几乎一致。

4 . 合理的使用设计模式,使用流行的开发模式开发APP:MVP、MVVM开发模式!多人开发中使用MVC进行开发,在复杂逻辑下会Activity和Fragment会非常混乱和臃肿。

5 . 要学会如何抽取公用方法为Util类和抽取出公用的控件(Widget)供所有类使用,减少代码冗余。要抽取公用的BaseFragment 、BaseActivity,BaseXXX供子类继承,在base类里面做公共的初始化。

6 . 使用完IO流、Cursor用完一定要记得关闭,将对象占用的资源释放,减少资源的浪费 !

7 . 做耗时的操作要在子线程里进行,不然容易造成ANR无响应异常。

列表优化(ListView、GridView等)

列表是手机应用中出现几率最大的一个控件,如果处理不当会影响列表滑动的流畅性。

1 . 在Adapter里的getView方法使用convertView复用item。

2 . 使用ViewHolder减少findViewbyId

3 . 使用ImagerLoader等图片框架加载异步加载图片和缓存处理。

4 . 使用RecycleView替换List View,在可定制化和性能上都有少许提升。

5 . 数据量大要采用分页分批加载。

6 . 适当的缓存可减少用户等待时间!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值