Android性能优化总结
以下从几个方面来总结一下Android的性能优化:
1:界面卡顿优化
2:内存优化
3:App启动优化
1:界面卡顿优化
Android的界面为每秒60帧,即必须在16ms内完成1帧的绘制,如果某个方法耗时过程,导致16ms内无法完成绘制,会导致丢帧,丢帧的多了,直观上感受就是界面卡顿。
60帧是人眼观看动画比较合适的频率,如果每秒的帧数过少,即频繁的出现丢帧,就会感觉界面的卡顿。
1:通过Traceview找出卡主主线程的地方
卡住主线程说明函数在主线程被调用的时长比较长,包括:
1)单个函数调用的时长长
2)函数被调用的次数比较多
2:使用方法:
1)Terminal打开DDMS,输入指令:Monitor
2)点击start method profiling
3)操作APP可能有问题的界面
4)再次点击stop method profiling,生成表格:
3:具体案例分析:recyclerView的onBindViewHolder,当复用屏幕外的数据是,是脏数据,会进行重新绑定,调用onBindViewHolder
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { Log.d(TAG, "onBindViewHolder--->" + position); 。。。。。。
。。。。。。。
SystemClock.sleep(7); }
SystemClock.sleep(10)模拟RecyclerView滚动过程中的耗时操作,操作Recyclerview,得到以下表格:
Real Time/Call表示一个函数被调用的时长, Calls+Recursion + call totals表示一个函数被调用和被递归调用的次数
2:内存优化:
可达性分析:凡是被GC Root对象引用的对象,都不可回收
可以作为GC Root 引用点的是:
- JavaStack中的引用的对象。
- 方法区中静态引用指向的对象。
- 方法区中常量引用指向的对象。
- Native方法中JNI引用的对象。
1:Memory Monitor:
Memory Monitor只能看个大概,可以查看内存抖动,或者内存增长的趋势,具体的小的泄漏,还得通过Heap Viewer查看
内存抖动:短时间内发生了多次内存的涨跌,意味着很有可能发生率内存抖动。
内存抖动带来的问题:短时间内的内存飙升,系统需要频繁的进行GC(在GC线程上),而GC是需要主线程停下来(不单单是主线程,而是把所有的线程挂起来),并且占用CPU资源的,会导致界面卡顿。
例子1:
public void click(View view) { for (int i = 0; i < 100; i++) { byte[] b = new byte[2000]; } }
例子2:字符串String拼接导致,会创建大量的StringBuilder临时对象,
在循环中频繁执行字符串拼接操作时。这是因为字符串通常是不可变的,每次进行字符串拼接操作时,都会创建一个新的字符串对象
String result = ""; for (int i = 0; i < 10000; i++) { result += " " + i; }
例子3:
private int nums[][] = new int[250][250]; //内存抖动案例:
//短时间内创建大量的临时变量 private void init() { Random random = new Random(); for (int i = 0; i < nums.length; i++) { for(int j=0; j<nums[i].length; j++){ nums[i][j] = random.nextInt(1000); } } } private void printNums(){ String totalNums = ""; for (int i = 0; i < nums.length; i++) { for(int j=0; j<nums[i].length; j++){ totalNums += nums[i][j]; } } }
//优化:
private void printNums2(){
StringBuffer totalNums = new StringBuffer();
for (int i = 0; i < nums.length; i++) {
for(int j=0; j<nums[i].length; j++){
totalNums.append(nums[i][j]);
}
}
}
例子4:
自定义View的时候,调用invalidate,会导致不断调用onDraw,如果在onDraw里面new对象,就会导致内存不断升高。
要注意在onDraw
中的对象创建和资源管理,确保不频繁地调用invalidate
把创建的对象放在构造函数
使用Monitor监控:
避免内存抖动的方法:
1)尽量避免在循环体内创建对象,应该把对象创建移到循环体外
2)自定义view的onDraw会被频繁调用,避免在这个函数里面new一个新的对象
2:Heap Viewer:
监控:能够实时观测内存的变动(短时间内通过Memory Monitor是看不出来的,曲线坡度太小,内存变动值很小,定位不到具体的代码)
Heap Viewer具体定位到哪个位置内存泄漏。
内存泄漏例子handler:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //test1(); init(); handler.sendEmptyMessageDelayed(0, 100000); }
//模拟当MainActivity跳转到MainActivity2的时候, // 延迟发送消息导致的内存泄漏问题 private Handler handler = new Handler(){ @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); } }; public void click(){ Intent intent = new Intent(this, MainActivity2.class); startActivity(intent); finish(); }
首先运行应用,处在MainActivity,点击Dump Java Heap,获取当前内存的快照:
得到以下表格:
修改为根据包名查看更方便快速定位到我们自己的代码:
Leaks为0说明当前没有发生内存泄漏
MainActivity的depth为3,说明MainActivity有被引用
点击MainActivity的click,跳转到MainActivity2,MainActivity.finish(),模拟MainActivty发生内存泄漏的场景(Handler引用MainActivity导致内存泄漏),然后点击GC,再截取内存快照
Dump Java Heap:
黄色的Leaks为2说明发生了内存泄漏,点击Leaks,通过以下References可以找到MainActivity被引用的路径。
3:LeakCanary
来源: 极客熊猫
作者: Mikyou
链接: http://www.youkmi.cn/2020/01/01/android-xing-neng-you-hua-zhi-leakcanary-nei-cun-yuan-li-fen-xi/
本文章著作权归作者所有,任何形式的转载都请注明出处。
来源: 极客熊猫
作者: Mikyou
链接: http://www.youkmi.cn/2020/01/01/android-xing-neng-you-hua-zhi-leakcanary-nei-cun-yuan-li-fen-xi/
本文章著作权归作者所有,任何形式的转载都请注明出处。
来源: 极客熊猫
作者: Mikyou
链接: http://www.youkmi.cn/2020/01/01/android-xing-neng-you-hua-zhi-leakcanary-nei-cun-yuan-li-fen-xi/
本文章著作权归作者所有,任何形式的转载都请注明出处。
来源: 极客熊猫
作者: Mikyou
链接: http://www.youkmi.cn/2020/01/01/android-xing-neng-you-hua-zhi-leakcanary-nei-cun-yuan-li-fen-xi/
本文章著作权归作者所有,任何形式的转载都请注明出处。
分为两个步骤:
1)通过虚引用的ReferenceQueue,判断一个对象是否被回收:
虚引用:对于对象来说,是无感的,如果只存在虚引用,GC的时候会直接被回收。虚引用的目的是为了追踪一个对象被回收的时机。如果一个定义了虚引用的对象GC之后被回收了,这个对象会被放入RefereceQueue中,LeakCanary就是在GC之后去检测该队列中是否有该对象判断该对象是否已经被回收。
2)初步判定有内存泄漏之后,通过开源库Haha分析dump之后的heap内存,从而定位到具体的内存泄露的对象的引用链条。
4:内存泄露举例:
1)bindservice导致的内存泄露:非静态内部类默认持有外部类的引用,这里的ServiceConnection持有外部类
SubAppDelegate的引用,如果serviceConnection没有被解绑,就会有一直持有外部类,导致外部类无法被回收,造成内存泄露。
解决方案:
应该在不再需要服务绑定的时候,确保解除ServiceConnection
对象的引用。如果 ServiceConnection
对象必须在较长的时间内存活,您可以考虑使用弱引用(WeakReference)来持有它,这样可以使其更容易被垃圾回收。
内存泄露有哪些导致:
1)单利模式
2)静态变量
3)handler
4)匿名内部类
5)资源的额释放,数据库,文件,流,bitmap
6)注册广播、eventBUs,onDestroy里面要反注册unRegister
7)context,取短不取长,传activity的context
8)arrayList和Linkhashmap,在onDestroy里面,进行数据的clear
3:APP应用启动优化:
1:冷启动和热启动
1)冷启动:app没有启动过或者进程被杀死,系统不存在该app进程,此时启动为冷启动。冷启动流程就是app启动流程全过程,包括创建app进程、加载资源、启动MainThread、初始化SplashActivity并加载布局等。
2)热启动:app暂时退到了后台,热启动将它从后台重新带到前台,展示给客户。点击home键退出,再进去,执行onResume。
我们要做的优化就是针对热启动
3):冷启动的函数调用过程:
Zygote Fork Proccess
-> Application:attachBaseContext()
-> Application:onCreate()
-> MainActiviity:onCreate()
2:用TraceView获取App的启动耗时,查找具体的耗时的函数进行优化:
public class MyApplication extends Application { @Override public void onCreate() { Debug.startMethodTracing("startApp"); super.onCreate(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Debug.stopMethodTracing(); } }
在/sdcard/目录下得到一个startApp.trace文件
通过DDMS打开该文件:可以看到Application里面的onCreate里的哪些代码进行了耗时操作:
3:解决方案:
1)白屏问题:
将启动页主题背景设置成闪屏页图片,这么做的目的主要是为了消除启动时的黑白屏,给用户一种秒响应的感觉,但是并不会真正减少用户启动时间,仅属于视觉优化。
为很么需要一个splashActivity来显示启动的背景图,而不是直接将背景图设置在MainActivity:
因为被启动的时候,不一定是到MainActivty,也可能是其他界面,所以需要一个splashActivity界面来统一处理外部调用时的白屏问题,然后再由splashActivity来进行分发到哪个需要启动的界面。
2)第三方工具的初始化一般都在Application的onCreate里面执行,会造成大量的耗时,解决方案:
a:放在子线程中加载不影响业务的情况,则优先选择放在子线程中加载
b:第三方工具的懒加载初始化,即用到的时候再进行初始化
4:APK瘦身:
1:使用 lint 工具来检测res/中是否有没有使用到的资源;
2:使用 WebP 来代替JPG和PNG图片。WebP 保留了JPG和PNG优点的同时,能提供更好的压缩,达到更小的体积
3:避免使用枚举类型
5:Parcel:
1:Serializable和Parcelable哪个性能高,为什么?
针对Android设备,Parcelable的性能高。Serializable是针对IO流,而Parcelable是针对共享内存。
public class Student implements Parcelable { private String name; private String address; private int age; protected Student(Parcel in) { name = in.readString(); address = in.readString(); age = in.readInt(); } public static final Creator<Student> CREATOR = new Creator<Student>() { @Override public Student createFromParcel(Parcel in) { return new Student(in); } @Override public Student[] newArray(int size) { return new Student[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); dest.writeString(address); dest.writeInt(age); } }