欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  科技

Android 总结我对性能优化的简单理解

程序员文章站 2022-03-23 11:16:31
性能优化呢,一般有以下五个方面1:内存优化2:UI优化(布局优化和绘制优化)3:速度的优化(线程优化/网络优化)4:电量优化5:启动优化首先我们来讲讲内存优化,内存优化可以说是性能优化中最重要的一个点了,因为内存泄漏可以说是Android中最常见的了,平常写代码一个不注意就导致了内存泄漏了,哈哈,那么内存泄漏是什么呢?我们这边先来说说内存泄漏——内存泄漏如果activity被销毁,但是在这个activity中有一个对象被其它的activity或者fragment引用,导致这个对象在activ...

性能优化呢,我总结以下三个方面

1:内存优化
2:UI优化
3:速度的优化
*

内存优化

首先我们来讲讲内存优化,内存优化可以说是性能优化中最重要的一个点了,因为内存泄漏可以说是Android中最常见的了,平常写代码一个不注意就导致了内存泄漏了,哈哈,那么内存泄漏是什么呢?我们这边先来说说内存泄漏——

内存泄漏
如果activity被销毁,但是在这个activity中有一个对象被其它的activity或者fragment引用,导致这个对象在activity被销毁的时候,无法被gc回收,从而导致了内存泄漏,说白了就是因为使用这块内存,但是没有将其回收,就引起了内存泄漏,
那么常见的内存泄漏都有哪些呢?
1单例引起内存泄漏
由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。
以下是使用单例时防止内存泄漏的写法

// 使用了单例模式
public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context;
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

这样不管传入什么Context最终将使用Application的Context,而单例的生命周期和应用的一样长,这样就防止了内存泄漏

2:非静态内部类在创建静态实例时引起的内存泄漏

  public class MainActivity extends AppCompatActivity {

    private static TestResource mResource = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(mResource == null){
            mResource = new TestResource();
        }
        //...
    }
    
    class TestResource {
    //...
    }
}   

这样在Activity内部创建了一个非静态内部类的单例,每次启动Activity时都会使用该单例的数据。虽然这样避免了资源的重复创建,但是这种写法却会造成内存泄漏。因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,从而导致Activity的内存资源不能被正常回收。
那么我们该如何解决呢?
我们可以直接给内部类加一个静态,因为静态内部类就不会持有外部类的引用。

handler造成的内存泄漏

示例:创建匿名内部类的静态对象

public class MainActivity extends AppCompatActivity {

    private final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // ...
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread(new Runnable() {
            @Override
            public void run() {
                // ...
                handler.sendEmptyMessage(消息);
            }
        });
    }
}

因为当Handler被启动的时候,主线程会自动创建一个Looper对象和与之对应的MessageQueue;当我的主线程创建一个Handler的引用时,它会自动与Looper对象和MessageQueue关联,所以所有发送到messagequeue中的message都会持有handler的引用,looper就会通过Handler的handlermessage方法取出消息,只要Messagequeue中有未处理的消息,looper就会不断的取出消息发送给handler,
这是在站在android角度的handler,站在java角度的handler来说呢,非静态内部类或者匿名内部类都会默认持有外部类的引用,但是静态内部类却不会,所以我按照这两种区别总结出:当我们销毁activity时,因为未处理的消息持有了handler的引用,而handler又持有外部类的引用,所以就会导致外部类也就是activity无法被回收,于是就造成了内存泄漏。
解决方法:将handler给独立出来,或者直接也使用静态内部类这样就可以了。

如何避免内存泄漏,我知道也不太全面,或者说了解的不太多吧,一般和当前activity无关的context我都不会引用当前的context,我会将
context传入applition中,使用全局上下文,这样在当前activity被销毁时有可能避免了context被引用而导致无法被销毁的问题,
还有就是mvp中,(因为我目前使用的mvp框架)p层持有着v层的引用,而activity中又实现了v层,这样一来我activity被销毁的时候,因为p层持有v层引用就会导致v层无法被销毁,于是我用java四大引用中的弱引用来引用v层,弱引用就是当我不使用这个对象的时候将其进行回收,当activity被销毁的时候这个对象也就不会再使用也就被回收掉了。(也就是可以通过四大引用中的知识点来判断何时回收对象)

2还有就是Ui优化

首先Ui优化到底优化什么呢,就是布局和view,就是因为以下的某些原因
1人为在UI线程中做轻微耗时操作,导致UI线程卡顿;
2 布局Layout过于复杂,无法在16ms内完成渲染;
3 同一时间动画执行的次数过多,导致CPU或GPU负载过重;
4 View过度绘制,导致某些像素在同一帧时间内被绘制多次,从而使CPU或GPU负载过重;
5 View频繁的触发measure、layout,导致measure、layout累计耗时过多及整个View频繁的重新渲染;
6 内存频繁触发GC过多(同一帧中频繁创建内存),导致暂时阻塞渲染操作;
7 冗余资源及逻辑等导致加载和执行缓慢;
8 臭名昭著的ANR;

可以看见,上面这些导致卡顿的原因都是我们平时开发中非常常见的。有些人可能会觉得自己的应用用着还蛮OK的,其实那是因为你没进行一些瞬时测试和压力测试,一旦在这种环境下运行你的App你就会发现很多性能问题。

GPU绘制
我们对于UI性能的优化还可以通过开发者选项中的GPU过度绘制工具来进行分析。在设置->开发者选项->调试GPU过度绘制(不同设备可能位置或者叫法不同)中打开调试;
Android 总结我对性能优化的简单理解
我们的目标就是尽量减少红色Overdraw,看到更多的蓝色区域。

可以发现,开启后在我们想要调试的应用界面中可以看到各种颜色的区域,具体含义如下:
这里写图片描述
Android 总结我对性能优化的简单理解

Overdraw有时候是因为你的UI布局存在大量重叠的部分,还有的时候是因为非必须的重叠背景。例如某个Activity有一个背景,然后里面的Layout又有自己的背景,同时子View又分别有自己的背景。仅仅是通过移除非必须的背景图片,这就能够减少大量的红色Overdraw区域,增加蓝色区域的占比。这一措施能够显著提升程序性能。

如果布局中既能采用RealtiveLayout和LinearLayout,那么直接使用LinearLayout,因为Relativelayout的布局比较复杂,绘制的时候需要花费更多的CPU时间。如果需要多个LinearLayout或者Framelayout嵌套,那么可采用Relativelayout。因为多层嵌套导致布局的绘制有大部分是重复的,这会减少程序的性能。

3 速度的优化

影响启动速度的几大因素
高耗时任务
数据库初始化、某些第三方框架初始化、大文件读取、MultiDex加载等,导致CPU阻塞
复杂的View层级
使用的嵌套Layout过多,层级加深,导致View在渲染过程中,递归加深,占用CPU资源,影响Measure、Layout等方法的速度
类过于复杂
Java对象的创建也是需要一定时间的,如果一个类中结构特别复杂,new一个对象将消耗较高的资源,特别是一些单例的初始化,需要特别注意其中的结构
主题及Activity配置
有一些App是带有Splash页的,有的则直接进入主界面,由于主题切换,可能会导致白屏,或者点了Icon,过一会儿才出现主界面
解决方案:
1:**布局抽取:**我们可以将某些相同的属性直接抽取放到资源文件中,因为资源文件它是优先于类加载的,所以在app启动时我们的某些控件属性如宽高,字体大小,字体颜色,控件id等,就是能放到资源文件的绝对不写到布局或者代码中,一是避免重复创建相同的资源,二是加载速度也会变得更快。
2:**少用框架:**有些框架功能虽然很强大,但是不一定都能用到,它会引进来一些我们用不到的方法,所以我们可以去自己写需要的功能,或者是引用一些具有相同功能的轻量级框架,当然这个看个人意愿,我的意思呢也就是尽量不要引用我们很多方法都用不到的框架。
3:View层级:
主要在于首屏/Splash页的Layout布局层次过深,导致View在渲染时,递归加深,消耗过多的CPU和内存资源,阻塞主线程,所以最根本的思路就是解决层级问题,检查一个App的View层级,可以使用Android Studio自带的Layout Inspector工具,如图:
Android 总结我对性能优化的简单理解
在选择了需要检查的进程及Window(Dialog可能会创建新的Window,但显示的Activity是同一个)以后,就可以看到Android Studio自动进行的Capture的内容了
根据左边View层级显示的内容,分析不必要的嵌套布局,通过改造,即可对View层级进行优化
我们可以使用ConstraintLayout(约束布局)
ConstraintLayout采用的约束布局概念,类似于iOS的AutoLayout,但使用起来,远比AutoLayout方便、强大,个人感觉吸取了RelativeLayout的方便、FrameLayout的灵活、LinearLayout的高效等特点,通过控件见相互的约束控制,可以构建出近乎平面的布局,这样就可以减少布局层级,只用ConstraintLayout一层Layout实现复杂的UI布局,非常值得学习和使用!

网络数据哟优化:
连接复用:节省连接建立时间,如开启 keep-alive。
对于Android来说默认情况下HttpURLConnection和HttpClient都开启了keep-alive。只是2.2之前HttpURLConnection存在影响连接池的Bug,具体可见:Android HttpURLConnection及HttpClient选择
请求合并:即将多个请求合并为一个进行请求,比较常见的就是网页中的CSS Image Sprites。如果某个页面内请求过多,也可以考虑做一定的请求合并。
减少请求数据的大小:对于post请求,body可以做gzip压缩的,header也可以做数据压缩。返回数据的body也可以做gzip压缩,body数据体积可以缩小到原来的30%左右。
类过于复杂:
这种情况我们是可以避免的,我们直接利用Aop编程思想将重复的类或者方法抽取出来然后统一调用,这样可以减少类和方法的创建,也许可以避免掉类过于复杂而使人看不懂的情况了。

暂时就总结出来这些优化吧,以后我还会有些补充(因为我总结的并不完整)我对于优化理解的也很片面,所以希望有大佬可以帮忙补充一点,哈哈,总之先这样我会继续补充里面的内容,继续完善我这篇性能优化的文章。

本文地址:https://blog.csdn.net/qq_45490395/article/details/108567225