Android 常见性能优化总结

2022-08-22  本文已影响0人  一个不掉头发的开发

前言

北京时间2022年8月16日凌晨 Android 13正式版全球也已发布,android 每次发新版本都会围绕“优化”这个词展开,android 13也不例外 个人认为他最大的亮点是UI界面优化,其实经过这么多年更新迭代 Android系统性能也已经非常流畅,可以在体验上完全媲美iOS。但是,到了各大厂商手里,改源码、自定义系统,使得Android原生系统变得鱼龙混杂,同时开发人员技术水平的参差不齐,即使很多手机在跑分软件性能非常高,打开应用依然存在卡顿现象。另外,随着产品内容迭代,功能越来越复杂,UI页面也越来越丰富,也成为流畅运行的一种阻碍。综上所述,对APP进行性能优化已成为开发者该有的一种综合素质,也是开发者能够完成高质量应用程序作品的保证。

优化点

Android的性能优化,主要是从以下几个方面进行优化的:


分类

整体优化方向:


优化总结

优化方案

一:流畅性

1. 响应速度:

多线程的方式 包括:AsyncTask、继承 Thread类、实现 Runnable接口、Handler消息机制、HandlerThread等
注:实际开发中,当一个进程发生了ANR后,系统会在 /data/anr目录下创建一个文件 traces.txt,通过分析该文件可定位出ANR的原因

2. 启动速度:app启动(Application )、页面启动(activity)、h5(wabView)

app启动(Application ):app启动分为冷启动(Cold start)、热启动(Hot start)和温启动(Warm start)三种(概念不再赘述,类似博客挺多挺详细的)。
无论何种启动,我们的优化点都是: Application、Activity创建以及回调等过程
谷歌官方给的建议是:
1、利用提前展示出来的Window,快速展示出来一个界面,给用户快速反馈的体验;
2、避免在启动时做密集沉重的初始化(Heavy app initialization);
3、避免I/O操作、反序列化、网络操作、布局嵌套等。

方案1: 治标不治本的迷惑性做法:主题设置图片资源

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
  <!-- The background color, preferably the same as your normal theme -->
  <item android:drawable="@android:color/white"/>
  <!-- Your product logo - 144dp color version of your app icon -->
  <item>
    <bitmap
      android:src="@drawable/product_logo_144dp"
      android:gravity="center"/>
  </item>
</layer-list>

方案2: 避免在启动时做密集沉重的初始化

application 中init三方SDK耗时优化:
1、业务非必要的可以的异步加载(线程池)。
2、非第一时间需要的可以在主线程做延时启动。当程序已经启动起来之后,在进行初始化。
3、对于图片,网络请求框架必须在主线程里初始化了。
4、启动图+开屏广告(以时间换时间 利用广告加载的时间初始化sdk)
ps:以上操作也仅仅是在应用层能做总有一些能到极限的操作
5、 通过利用多进程、修改类加载过程、dex压缩等方向去实现

3. 页面显示速度

方案1: 页面布局优化

如果父控件有颜色,也是自己需要的颜色,那么就不必在子控件加背景颜色
如果每个自控件的颜色不太一样,而且可以完全覆盖父控件,那么就不需要再父控件上加背景颜色
尽量减少不必要的嵌套
能用LinearLayout和FrameLayout,就不要用RelativeLayout,因为RelativeLayout控件相对比较复杂,测绘也想要耗时。
使用include和merge增加复用,减少层级
ViewStub按需加载,更加轻便
复杂界面可选择ConstraintLayout,可有效减少层级
ps:开发者模式,查看是否存在过度绘制

方案2:绘制优化

onDraw中不要创建新的局部对象、也不要做耗时的任务
ps:我们平时感觉的卡顿问题最主要的原因之一是因为渲染性能,因为越来越复杂的界面交互,其中可能添加了动画,或者图片等等。我们希望创造出越来越炫的交互界面,同时也希望他可以流畅显示,但是往往卡顿就发生在这里。
这个是Android的渲染机制造成的,Android系统每隔16ms发出VSYNC信号,触发对UI进行渲染,但是渲染未必成功,如果成功了那么代表一切顺利,但是失败了可能就要延误时间,或者直接跳过去,给人视觉上的表现,就是要么卡了一会,要么跳帧。
View的绘制频率保证60fps是最佳的,这就要求每帧绘制时间不超过16ms(16ms = 1000/60),虽然程序很难保证16ms这个时间,但是尽量降低onDraw方法中的复杂度总是切实有效的。

方案3:如果页面就是比较复杂,层级再怎么缩减也达不到预取效果该怎么办:

代码动态创建View 通过addview的形式添加

方案4:主线程耗时操作排查

开启strictmode,这样一来,主线程的耗时操作都将以告警的形式呈现到logcat当中
StrictMode,严苛模式,是Android提供的一种运行时检测机制,用于检测代码运行时的一些不规范的操作,最常见的场景是用于发现主线程的IO操作和网络读写等耗时的操作。

方案5:viewStub&merge&ViewStub的使用
方案6:页面刷新

item 刷新局部刷新
复杂Activity、fragment:利用LiveData等数据共享

二:稳定性

1. ANR:

以下四个条件都可以造成ANR发生:
InputDispatching Timeout:5秒内无法响应屏幕触摸事件或键盘输入事件
BroadcastQueue Timeout :在执行前台广播(BroadcastReceiver)的onReceive()函数时10秒没有处理完成,后台为60秒。
Service Timeout :前台服务20秒内,后台服务在200秒内没有执行完毕。
ContentProvider Timeout :ContentProvider的publish在10s内没进行完。
实际开发中,当一个进程发生了ANR后,系统会在 /data/anr目录下创建一个文件 traces.txt,通过分析该文件可定位出ANR的原因
ps:总之记住这一点:尽量避免在主线程(UI线程)中作耗时操作,耗时操作都放到子线程
多注意一些常用库出现ANR的情况:如:SharedPreferences等

1. Crash:

1:频繁GC

内存抖动
1短时间内创建了大量对象同时又被快速释放。比如在一个大循环里去不断创建对象,会导致频繁gc;
内存泄漏
2内存泄漏会导致可用内存逐渐变少,而且内存碎片加多,这也会增多gc次数,甚至可能发生OOM
3一次申请太大内存空间
由于内存碎片的存在,就算内存本身足够,但由于碎片导致无法找到一块大空间,这也会触发gc;

2:在Android平台上常见的OOM有如下几种:

1、使用static修饰Context变量,Context被Hold住了导致Activity无法销毁。比如 Thread
2、Bitmap没有及时回收,调用recycle()函数并不能立即释放Bitmap,读取Bitmap到内存的时候没有做采样率的设置;
3、线程数超限,proc/pid/status中记录的线程数超过proc/sys/kernel/threads-max中规定的最大线程数,场景如随意创建线程,没有使用线程池来管理;
4、文件描述符(fd)超限,proc/pid/fd下文件数目超过proc/pid/limits中的限制,场景如大量的Socket请求或者文件打开操作。
5、Cursor使用问题,使用完之后没有及时关闭。
6、Java堆内存超限,申请的堆内存超过Runtime.getRuntime().maxMemory()。
7、Adapter没使用缓存的convertView;
8、广播注册之后没有反注册;
9、WebView没有销毁,应该调用destroy()函数去销毁;
10、数组内对象过多,没有及时清理。
11、EventBus 等观察者模式的框架忘记手动解除注册
ps:分析工具:LeakCanary、Android Studio自带工具Profilerd等

3:异常日志搜集:三方的奔溃搜集工具很多,不在赘述,android 崩溃日志收集以及上传服务器 -自定义我自己实现的感兴趣的可以看看

三:apk体积

1. 安装包结构:
该图来自网络截图
2. 优化方案:

一:最省(资源)

1. 内存:

部分上面OOM分析中已经提到了,下面做个简单补充
1:SparseArray代替HashMap
2:使用Link优化项目
3:Bitmap压缩:一个比较好的压缩库,Curzibn/Luban: Luban(鲁班)—Image compression with efficiency very close to WeChat Moments/可能是最接近微信朋友圈的图片压缩算法 (github.com)
4:推荐使用match_parent,或者固定尺寸
5:线程优化(线程池)

2. 网络:
该图来自网络

Android 性能优化之网络优化 这篇文章写的挺详细不在赘述
ps:网上有很多写的比较好的网络请求框架的封装可以借鉴一下思路
Retrofit+OkHttp+Kotlin协程 网络请求架构等等

1. 耗电量:

Battery Historian
是由Google提供的Android系统电量分析工具,从手机中导出bugreport文件上传至页面,在网页中生成详细的图表数据来展示手机上各模块电量消耗过程,最后通过App数据的分析制定出相关的电量优化的方法。
谷歌推荐使用JobScheduler,来调整任务优先级等策略来达到降低损耗的目的。JobScheduler可以避免频繁的唤醒硬件模块,造成不必要的电量消耗。避免在不合适的时间(例如低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量。
具体功能:
1、可以推迟的非面向用户的任务(如定期数据库数据更新);
2、当充电时才希望执行的工作(如备份数据);
3、需要访问网络或 Wi-Fi 连接的任务(如向服务器拉取配置数据);
4、零散任务合并到一个批次去定期运行;
5、当设备空闲时启动某些任务;
6、只有当条件得到满足, 系统才会启动计划中的任务(充电、WIFI...);

实现一个通过JobScheduler的简单demo

public class JobSchedulerService extends JobService{
    private String TAG = JobSchedulerService.class.getSimpleName();

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        Log.d(TAG, "onStartJob:" + jobParameters.getJobId());

        if(true) {
            // JobService在主线程运行,如果我们这里需要处理比较耗时的业务逻辑需单独开启一条子线程来处理并返回true,
            // 当给定的任务完成时通过调用jobFinished(JobParameters params, boolean needsRescheduled)告知系统。

            //假设开启一个线程去下载文件
            new DownloadTask().execute(jobParameters);

            return true;

        }else {
            //如果只是在本方法内执行一些简单的逻辑话返回false就可以了
            return false;
        }
    }

    /**
     * 比如我们的服务设定的约束条件为在WIFI状态下运行,结果在任务运行的过程中WIFI断开了系统
     * 就会通过回掉onStopJob()来通知我们停止运行,正常的情况下不会回掉此方法
     *
     * @param jobParameters
     * @return
     */
    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        Log.d(TAG, "onStopJob:" + jobParameters.getJobId());

        //如果需要服务在设定的约定条件再次满足时再次执行服务请返回true,反之false
        return true;
    }

    class DownloadTask extends AsyncTask<JobParameters, Object, Object> {
        JobParameters mJobParameters;

        @Override
        protected Object doInBackground(JobParameters... jobParameterses) {
            mJobParameters = jobParameterses[0];

            //比如说我们这里处理一个下载任务
            //或是处理一些比较复杂的运算逻辑
            //...

            try {
                Thread.sleep(30*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            return null;
        }

        @Override
        protected void onPostExecute(Object o) {
            super.onPostExecute(o);
            //如果在onStartJob()中返回true的话,处理完成逻辑后一定要执行jobFinished()告知系统已完成,
            //如果需要重新安排服务请true,反之false
            jobFinished(mJobParameters, false);
        }
    }
}

public class MainActivity extends Activity{
    private JobScheduler mJobScheduler;
    private final int JOB_ID = 1;

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

        mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE );

        //通过JobInfo.Builder来设定触发服务的约束条件,最少设定一个条件
        JobInfo.Builder jobBuilder = new JobInfo.Builder(JOB_ID, new ComponentName(this, JobSchedulerService.class));

        //循环触发,设置任务每三秒定期运行一次
        jobBuilder.setPeriodic(3000);

        //单次定时触发,设置为三秒以后去触发。这是与setPeriodic(long time)不兼容的,
        // 并且如果同时使用这两个函数将会导致抛出异常。
        jobBuilder.setMinimumLatency(3000);

        //在约定的时间内设置的条件都没有被触发时三秒以后开始触发。类似于setMinimumLatency(long time),
        // 这个函数是与 setPeriodic(long time) 互相排斥的,并且如果同时使用这两个函数,将会导致抛出异常。
        jobBuilder.setOverrideDeadline(3000);

        //在设备重新启动后设置的触发条件是否还有效
        jobBuilder.setPersisted(false);

        // 只有在设备处于一种特定的网络状态时,它才触发。
        // JobInfo.NETWORK_TYPE_NONE,无论是否有网络均可触发,这个是默认值;
        // JobInfo.NETWORK_TYPE_ANY,有网络连接时就触发;
        // JobInfo.NETWORK_TYPE_UNMETERED,非蜂窝网络中触发;
        // JobInfo.NETWORK_TYPE_NOT_ROAMING,非漫游网络时才可触发;
        jobBuilder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);

        //设置手机充电状态下触发
        jobBuilder.setRequiresCharging(true);

        //设置手机处于空闲状态时触发
        jobBuilder.setRequiresDeviceIdle(true);
        
        //得到JobInfo对象
        JobInfo jobInfo = jobBuilder.build();

        //设置开始安排任务,它将返回一个状态码
        //JobScheduler.RESULT_SUCCESS,成功
        //JobScheduler.RESULT_FAILURE,失败
        if (mJobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) {
            //安排任务失败
        }

        //停止指定JobId的工作服务
        mJobScheduler.cancel(JOB_ID);
        //停止全部的工作服务
        mJobScheduler.cancelAll();
    }

最主要的一点

记得在Manifest文件内配置Service <service android:name=".JobSchedulerService" android:permission="android.permission.BIND_JOB_SERVICE"/>

总结

作为一个程序员,性能优化是常有的事情,不管是桌面应用还是web应用,不管是前端还是后端,不管是单点应用还是分布式系统,所以我们应该更加去注重性能优化的一个使用和技术上提升,综上所述,对APP进行性能优化已成为开发者该有的一种综合素质,也是开发者能够完成高质量应用程序作品的保证。
以下文档有关于单向具体优化的操作,感兴趣的童鞋可以看一下
Android 性能优化 | Android 开源项目 | Android Open Source Project (google.cn)
Android深度性能优化--启动优化(一篇就够) - 知乎 (zhihu.com)
(28条消息) Android性能优化常见问题,与详细解决思路方法!_chuhe1989的博客-CSDN博客

上一篇下一篇

猜你喜欢

热点阅读