美文网首页性能优化一些收藏
Android性能优化[基于资料]

Android性能优化[基于资料]

作者: 纵横Top | 来源:发表于2021-08-28 14:11 被阅读0次

    一个质量较高的性能分析专栏
    我们先根据这个专栏出发,验证其各种方案的可行性。

    性能优化大纲

    大神博客

    1.性能优化目的

    image.png

    性能优化的目的是:提高应用流畅性、稳定性、节省资源。
    ①流畅性:运行的更加流畅、不卡顿
    ②稳定性:稳定运行,使用过程中不出现引用崩溃和无响应(ANR)问题
    ③节省资源:节省耗费的资源,包括安装包大小、内存占用、耗电量、网络资源(手机流量)等

    2.性能指标

    流畅性、稳定性、资源节省性

    具体操作

    优化操作一:流畅性

    主要针对三个方向优化:
    启动速度、页面显示速度、响应速度
    优化方案:
    采用异步加载、分部加载、延期加载策略,减少主线程中的操作时间。

    image.png
    具体操作:
    1.启动速度

    1.减少onCreate的时间。
    可以将onCreate的代码放到onResume中执行,不过为了防止重复初始化,需要加标志位。
    2.优化布局文件
    优化布局层次结构:使用include、merge、ViewStub、ConstraintLayout等。
    merge使用主要是的目的是在优化布局,减少布局嵌套,一般与include结合使用
    观察布局的工具:Hierarchy Viewer
    优化布局的工具:Layoutopt

    3.提高Adapter、ViewHolder的效率
    4.减少主线程的耗时时间
    将耗时过长的操作放入后台线程中执行,只在需要修改UI时通知主线程修改。

    2.页面显示速度

    优化方案:布局优化 、绘制优化
    Android性能优化:手把手带你全面了解 绘制优化
    Android性能优化:布局优化 详细解析
    主要优化方向:
    ①降低View.onDraw的复杂度
    ②避免过度绘制(Overdraw)

    具体操作就是:
    第一点:不要在我们自定义的View中创建新的局部变量。
    避免onDraw中执行大量或者耗时的操作。
    第二点:移除默认的Window背景、移除控件中不必要的背景、减少布局文件的嵌套、自定义空间View优化(使用#clipRect、#quickReject)

    可以在开发者选项中打开<调试GPU过度绘制>开关,比如蓝色是一层、绿色两层、红色四层等,尽量在两层以内,也就是绿色。(微信整个是红色的...)
    第二点实际操作:
    ①移除默认的Window背景:
    在应用程序默认继承的主题中,也就是清单文件中application项中的theme中:

    image.png
    添加一行:
    <item name="android:windowBackground">@null</item>
    也就是设置windowBackground为null。
    ②③可以使用AS自带的Layout Inspector来查看页面布局嵌套情况。(Hierarchy View已被废弃)
    ④自定义控件View优化:使用clipRect()/quickReject()
    Canvas#clipRect:
    给Canvas设置一个裁剪区域,只有该区域内才会被绘制,区域之外的都不绘制。
    比如抽屉布局可以使用这个。
    quickReject:
    判断和某个矩形相交,若判断与矩形相交,则可跳过相交的区域,减少过渡绘制。

    其他优化方案:
    使用OpenGL绘图。(Android最高级的绘图机制,没用过..)
    Profile GPU Rendering工具:开发者选项中可打开此开关,用于在屏幕上实时显示GPU渲染每一帧图像花费的时间。
    参考资料

    总结:


    image.png
    3.响应速度

    优化原因
    应用程序出现ANR情况,从而导致应用程序响应速度慢。

    image.png
    ANR原因
    ①应用在5s内未响应用户的输入事件
    ②广播接收器在10s内未完成相关的逻辑
    ③Service在20s内无法处理完成(不是15s哦)
    【检测主线程中是否有耗时行为】
    【开启StrictMode模式】
    在Application的onCreate中开启:
      public void onCreate() {
            if (DEVELOPER_MODE) {
                StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                        .detectDiskReads()
                        .detectDiskWrites()
                        .detectNetwork()   // or .detectAll() for all detectable problems
                        .penaltyLog()
                        .build());
                StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                        .detectLeakedSqlLiteObjects()
                        .detectLeakedClosableObjects()
                        .penaltyLog()
                        .penaltyDeath()
                        .build());
            }
            super.onCreate();
      }
    然后过滤信息:
    `adb logcat | grep StrictMode`
    

    StrictMode详解

    博客详解
    StrictMode可以用于捕获应用主线程中发生的磁盘I/O、网络访问违规等问题。(并不会检测到耗时任务,比如Thread.sleep)
    主要检测两大问题:线程策略(ThreadPolicy)和VM策略(VmPolicy)。

    ThreadPolicy线程策略:
    1.自定义的耗时调用:使用detectCustomSlowCalls()开启(检测不到耗时操作,需要自己定义)
    2.磁盘读取、写入(I/O操作):detectDiskReads()、detectDiskWrites()
    3.网络操作:detectNetwork()

    VmPolicy虚拟机策略:
    1.Activity泄露,使用detectActivityLeaks()开启(都不用LeakCanary)
    2.未关闭的Closable对象泄露,使用detectLeadekClosedObjects()开启;
    3.泄露的Sqlite对象,对象detectLeakedSqliteObjects
    4.网络操作,使用detectNetwork
    过滤日志:StrictMode

    优化方案
    使用多线程,实际开发中,当一个进程发生了ANR后,系统会在/data/anr目录下创建一个traces.txt文件,通过该文件可定位出ANR的原因。
    总结

    image.png
    4.稳定性

    大神博客
    主要是:应用崩溃(Crash)、应用无响应(ANR)
    ANR上面已经说了,主要看应用崩溃。
    应用崩溃Crash很多情况是因为内存溢出,即OOM,故需要避免OOM现象。

    image.png
    因为内存优化很重要,所以我们来

    内存优化

    1.Android内存管理机制

    image.png
    从上图可以看到,Android内存分配主要就两个工作:
    内存分配+内存回收(释放)
    内存管理的对象:①进程②对象③变量
    Android系统分为3个层次:
    Application Framework、ART虚拟机、Linux内核
    其中,负责进程内存的角色:Linux内核 、Application Framework
    负责对象、变量内存的角色:ART虚拟机

    针对进程的内存策略

    ActivityManagerService集中管理所有进程的内存分配。
    内存回收策略,分两步:
    1.由Application Framework决定回收的进程类型
    Android中的进程是托管的;有进程空间紧张时,会按进程优先级从低到高的顺序自动回收进程。
    Android进程分为5个优先级:
    前台进程、可见进程、服务进程、后台进程、空进程
    当系统需回收进程时,最先回收空进程、最后回收前台进程
    2.Linux内核真正回收具体进程
    1.ActivityManagerService对所有进程进行评分(评分存放在变量adj中)
    2.更新评分到Linux内核
    3.由Linux内核完成真正的内存回收

    内存分配策略

    image.png

    JMM(Java内存模型)中,将内存区域分为五个部分(图片中只有三个,并不太规范):
    程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区
    ①程序计数器:线程私有,记录线程上下文(多线程是通过轮流切换和分配处理器时间片实现的),唯一没有OutOfMomery
    ②Java虚拟机栈(Stack):线程私有,与线程生命周期相同,线程创建的时候创建。主要存储方法的内容:局部变量、参数、返回值以及运算的中间结果等。
    ③本地方法栈:存储Native方法内容
    Java堆:线程共享,运行时内存区域,占用内存最大的地方,几乎所有的对象实例都存在这里。分为三个部分:年轻代(Eden、Survivor)、老年代
    ⑤方法区:线程共享,主要存储已经被java虚拟机加载的类的信息,包括运行时常量池、字段、方法信息、静态变量等。在编译时就已经分配好,存在与程序整个运行期间,不需要回收。
    垃圾回收算法:
    1.标记-清除算法 2.复制算法 3.标记-整理算法 4.分代收集算法

    常见的内存问题

    1.内存泄漏
    2.内存抖动
    3.图片Bitmap相关
    4.代码质量&数量
    5.日常不正确使用

    1.内存泄漏

    发生内存泄漏的本质原因:
    本该回收的对象因为某些原因而不能被回收、从而继续停留在堆内存中
    常见内存泄漏原因:
    ①集合类
    ②Static变量修饰的成员变量
    ③非静态内部类/匿名类
    ④资源对象使用后未关闭

    具体了解

    1.集合类导致内存泄漏:

    集合类添加元素后,仍引用着集合元素对象,导致该集合中的元素对象无法被回收,从而导致内存泄漏。
    Android内存泄漏之集合类

    // 通过 循环申请Object 对象 & 将申请的对象逐个放入到集合List
    List<Object> objectList = new ArrayList<>();        
           for (int i = 0; i < 10; i++) {
                Object o = new Object();
                objectList.add(o);
                o = null;
            }
    // 虽释放了集合元素引用的本身:o=null)
    // 但集合List 仍然引用该对象,故垃圾回收器GC 依然不可回收该对象
    
    

    解决方案:集合类添加集合元素对象后,在使用后必须从集合中删除(Android Studio检测内存泄漏是检测不到的!!!!!,所以我们必须手动释放)
    TODO 验证!!

    2.static关键字修饰的成员变量

    这我想无需多言,被static关键字修饰的成员变量的生命周期 = 应用生命周期
    常见的就是单例、静态context

    3.非静态内部类的实例为静态的情况下

    如果非静态内部类创建了静态的实例(其生命周期 = 应用生命周期),会因费静态内部类持有外部类的引用而导致外部类无法释放,最终造成内存泄漏。也就是
    外部类中持有非静态内部类的静态对象
    demo:

    // 背景:
       a. 在启动频繁的Activity中,为了避免重复创建相同的数据资源,会在Activity内部创建一个非静态内部类的单例
       b. 每次启动Activity时都会使用该单例的数据
    
    public class TestActivity extends AppCompatActivity {  
        
        // 非静态内部类的实例的引用
        // 注:设置为静态  
        public static InnerClass innerClass = null; 
       
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {        
            super.onCreate(savedInstanceState);   
    
            // 保证非静态内部类的实例只有1个
            if (innerClass == null)
                innerClass = new InnerClass();
        }
    
        // 非静态内部类的定义    
        private class InnerClass {        
            //...
        }
    }
    
    // 造成内存泄露的原因:
        // a. 当TestActivity销毁时,因非静态内部类单例的引用(innerClass)的生命周期 = 应用App的生命周期、持有外部类TestActivity的引用
        // b. 故 TestActivity无法被GC回收,从而导致内存泄漏
    
    

    解决方案:
    将非静态内部类设置为:静态内部类(静态内部类默认不持有外部类的引用)
    该内部类抽取出来封装成一个单例
    尽量 避免 非静态内部类所创建的实例 = 静态

    4.多线程导致的内存泄漏

    多线程泄漏条件:当多线程使用为非静态内部类或者匿名类的时候可能导致内存泄漏。
    这里的多线程包括:AsyncTask/实现Runnable接口/继承Thread类
    泄漏原因:
    当工作线程正在处理任务且外部类需销毁时,由于工作线程实例持有外部类引用,将使得外部类无法被垃圾回收器GC回收,从而造成内存泄漏。
    demo:

       /** 
         * 方式1:新建Thread子类(内部类)
         */  
            public class MainActivity extends AppCompatActivity {
    
            public static final String TAG = "carson:";
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
    
                // 通过创建的内部类 实现多线程
                new MyThread().start();
    
            }
            // 自定义的Thread子类
            private class MyThread extends Thread{
                @Override
                public void run() {
                    try {
                        Thread.sleep(5000);
                        Log.d(TAG, "执行了多线程");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
       /** 
         * 方式2:匿名Thread内部类
         */ 
         public class MainActivity extends AppCompatActivity {
    
        public static final String TAG = "carson:";
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // 通过匿名内部类 实现多线程
            new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(5000);
                        Log.d(TAG, "执行了多线程");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                }
            }.start();
        }
    }
    
    
    /** 
      * 分析:内存泄露原因
      */ 
      // 工作线程Thread类属于非静态内部类 / 匿名内部类,运行时默认持有外部类的引用
      // 当工作线程运行时,若外部类MainActivity需销毁
      // 由于此时工作线程类实例持有外部类的引用,将使得外部类无法被垃圾回收器(GC)回收,从而造成 内存泄露
    

    从上面可以看出,造成内存泄漏的原因有2个关键条件:
    1.存在 工作线程持有外部类的引用关系
    2.工作线程实例的生命周期>外部类的生命周期,即工作线程还在运行,而外部类已经销毁了。

    我们只需要破坏其中一条条件即可解决内存泄漏。
    解决方案也有两种:
    1.将内部线程类声明为静态内部类。
    2.在onDestroy里调用Thread.stop()方法。

    // 共有2个解决方案:静态内部类 & 当外部类结束生命周期时,强制结束线程
    // 具体描述如下
    
       /** 
         * 解决方式1:静态内部类
         * 原理:静态内部类 不默认持有外部类的引用,从而使得 “工作线程实例 持有 外部类引用” 的引用关系 不复存在
         * 具体实现:将Thread的子类设置成 静态内部类
         */  
            public class MainActivity extends AppCompatActivity {
    
            public static final String TAG = "carson:";
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
    
                // 通过创建的内部类 实现多线程
                new MyThread().start();
    
            }
            // 分析1:自定义Thread子类
            // 设置为:静态内部类
            private static class MyThread extends Thread{
                @Override
                public void run() {
                    try {
                        Thread.sleep(5000);
                        Log.d(TAG, "执行了多线程");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
       /** 
         * 解决方案2:当外部类结束生命周期时,强制结束线程
         * 原理:使得 工作线程实例的生命周期 与 外部类的生命周期 同步
         * 具体实现:当 外部类(此处以Activity为例) 结束生命周期时(此时系统会调用onDestroy()),强制结束线程(调用stop())
         */ 
         @Override
        protected void onDestroy() {
            super.onDestroy();
            Thread.stop();
            // 外部类Activity生命周期结束时,强制结束线程
        }
    
    Handler内存泄漏原因

    Handler内存泄漏
    Handler造成内存泄漏有两个关键条件:
    1.存在"未被处理/正在处理的消息,持有非静态内部类Handler的实例(Handler又会持有外部类)"
    2.Handler的生命周期 > 外部类的生命周期

    即Handler消息队列中还有未处理的消息/正在处理消息 而 外部类又需要销毁。
    解决思路:
    只需要上面任一条件不成立即可。
    ①Handler声明为静态内部类,使用WeakReference持有外部类
    ②当外部类结束生命周期时,清空Handler内消息队列:
    在onDestroy里清除Handler消息队列里的所有消息(调用removeCallbacksAndMessages(null))

    5.资源对象使用后未关闭

    泄漏原因:
    对于资源的使用,如:
    ①广播BroadcastReceiver
    ②文件流File
    ③数据库游标Cursor
    ④图片资源Bitmap等
    解决方案:
    在Activity销毁时及时关闭/注销资源
    ①对于 广播BraodcastReceiver:注销注册
    unregisterReceiver()
    ② 对于 文件流File:关闭流
    InputStream / OutputStream.close()
    ③ 对于数据库游标cursor:使用后关闭游标
    cursor.close()
    ④对于 图片资源Bitmap:Android分配给图片的内存只有8M,若1个Bitmap对象占内存较多,当它不再被使用时,应调用recycle()回收此对象的像素所占用的内存;最后再赋为null
    Bitmap.recycle();
    Bitmap = null;
    ⑤对于动画(属性动画)
    将动画设置成无限循环播放repeatCount = “infinite”后
    在Activity退出时记得停止动画

    6.其他泄漏原因

    image.png

    总结

    image.png

    辅助分析内存泄漏的工具

    1.MAT(Memory Analysis Tools)
    2.Heap Viewer
    3.Allocation Tracker
    4.Android Studio 的Profiler
    5.LeakCanary
    其实后两者是最常用的,Android Studio自带的Profiler一直在迭代更新,4.1之后还会显示内存泄露的点,当然可能有些不全,但相信不久的将来,在Google工程师的努力下,会适配绝大多数场景。
    LeakCanary 会将所有的内存泄露以通知栏的方式告知,很方便。

    MAT的话,之前用过,还需要手动dump下来hprof文件,转成MAT需要的格式,操作起来比较麻烦。
    HeapViewer 没用过,但是看使用文档,其具有的功能Android Profiler都有
    Heap Viewer使用

    Allocation Tracker:Profiler里检测内存的就叫Allocation Tracker...

    Bitmap优化

    在点三中有做过总结,概不赘述。

    【其他小技巧】

    1.获取当前可使用的内存大小
    调用ActivityManager.getMemoryClass()方法可获取当前应用可用的内存大小(单位=兆)
    2.【获取当前的内存使用情况】
    在应用生命周期的任何阶段,调用onTrimMemory()获取应用程序当前内存使用情况,可根据该方法返回的内存紧张级别参数来释放内存。

    image.png
    onTrimMemory
    ① OnTrimMemory的主要作用就是指导应用程序在不同的情况下进行自身的内存释放,以避免被系统直接杀掉,提高应用程序的用户体验.
    ②哪些组件可以实现onTrimMemory回调:
    Application.onTrimMemory()
    Activity.onTrimMemory()
    Fragment.OnTrimMemory()
    Service.onTrimMemory()
    ContentProvider.OnTrimMemory()

    ③onTrimMemory和onLowMemory的关系
    onLowMemory是api <14的机器使用的
    ④onTirmMemory会传递不同的等级,等级值有很多,最常用的是:
    TRIM_MEMORY_UI_HIDDEN 表示应用程序的所有UI界面被隐藏了,即用户点击了Home键或者Back键导致应用的UI界面不可见.这时候应该释放一些资源
    onTrimMemory的TRIM_MEMORY_UI_HIDDEN 等级是在onStop方法之前调用的
    我们项目中是在Application的onTirmMemory中释放掉Fresco的图片缓存。同时调用了System.gc(),手动GC一下。

    onTrimMemory典型的使用场景
    1.退出的时候,动态生成的View、图片缓存、Fragment可以及时释放
    2.后台Service,用户退出UI界面后,可以及时释放部分UI和Cache

    包体积优化

    安装包组成结构:
    ①assets文件夹。存放一些配置文件、资源文件,assets不会自动生成对应的 ID,而是通过 AssetManager 类的接口获取。
    ②res。res 是 resource 的缩写,这个目录存放资源文件,会自动生成对应的 ID 并映射到 .R 文件中,访问直接使用资源 ID。
    ③META-INF。保存应用的签名信息,签名信息可以验证 APK 文件的完整性。
    AndroidManifest.xml。这个文件用来描述 Android 应用的配置信息,一些组件的注册信息、可使用权限等。
    ④classes.dex。Dalvik 字节码程序,让 Dalvik 虚拟机可执行,一般情况下,Android 应用在打包时通过 Android SDK 中的 dx 工具将 Java 字节码转换为 Dalvik 字节码。
    ⑤resources.arsc。记录着资源文件和资源 ID 之间的映射关系,用来根据资源 ID 寻找资源。

    image.png

    ①使用Android Lint删除冗余资源
    Android Analyze
    具体使用就是运行 Analyze -> Run Inspection by name

    image.png
    直接输入 unused ...就可以过滤各种没有使用的资源,比如:
    unused resources
    image.png
    ②使用TinyPNG压缩图片,一般体积至少能压百分之五十
    原理:把相似像素的24bit位用8bit位来表示,并且移除了不必要的元数据,是有损压缩,不过肉眼很难看出来。
    ③不要引入过大的库
    ④代码混淆工具,如proGuard,含:压缩、优化、混淆等

    其他

    LeakCanary原理

    监测机制利用了Java的WeakReference和ReferenceQueue,通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果被回收,该WeakReference引用会被放到ReferenceQueue中,通过监测ReferenceQueue里面的内容就能检查到Activity是否能够被回收(在ReferenceQueue中说明可以被回收,不存在泄漏;否则,可能存在泄漏,LeakCanary是执行一遍GC,若还未在ReferenceQueue中,就会认定为泄漏)。

    如果Activity被认定为泄露了,就抓取内存dump文件(Debug.dumpHprofData);之后通过HeapAnalyzerService.runAnalysis进行分析内存文件分析;接着通过HeapAnalyzer (checkForLeak—findLeakingReference---findLeakTrace)来进行内存泄漏分析。最后通过DisplayLeakService进行内存泄漏的展示。

    冷启动优化

    冷启优化

    相关文章

      网友评论

        本文标题:Android性能优化[基于资料]

        本文链接:https://www.haomeiwen.com/subject/jyrvdltx.html