APP性能优化总结

1: ---------课程导学与学习指南【提供免费简历指导机会&内推大公司机会】 ---------

2: --------App性能概览与平台化实践---------

3: --------App启动优化---------

4: --------App内存优化---------

1: 学习问题自助手册

http://coding.imooc.com/lesson/308.html#mid=22071

2: 内存优化介绍及工具选择

1 : 内存问题

  • 内存抖动 : 锯齿状 , GC导致卡顿
  • 内存泄漏 : 可用内存减少 , 频繁GC
  • 内存溢出 : OOM , 程序异常

内存一旦出现问题,最终暴露出来的位置并非是真正的原因,可能由于长时间内存问题集中暴露出来的结果。
三大原因:
1、内存抖动:频繁的GC导致
2、内存泄露:不再使用的内存GC未能成功回收
3、内存溢出:长时间的内存泄露、申请内存过大最终可导致内存溢出,程序崩溃

2 : 工具选择

  • 1、MemoryProfiler(AndroidStudio集成的检测工具 , 内存查询工具:Android Studio -> Memory Profiler)
    • 1 : 实时图表展示内存使用量
    • 2 : 识别内存泄漏 , 抖动等
    • 3 : 提供捕获堆转储 , 强制GC以及跟踪内存分配的能力
  • 2、MemoryAnalyzer (MAT)
    • 1 : 强大的Java Heap分析工具 , 查找内存泄漏及内存占用
    • 2 : 生成整体报告 , 分析问题等
    • 3 : 线下深入使用
  • 3、LeakCanary
    • 1 : 自动内存泄露监测,不适合线上使用

3 : Android内存管理机制

1 : Java的内存管理机制

  • Java内存分配
    • 方法区: 类信息 常量 静态变量等 比如:public , static , final,多有线程都共享
    • 虚拟机栈: 局部变量表等,栈引用
    • 本地方法栈: native方法
    • 堆:
    • 程序计数器: 比如当前线程执行的方法执行到第几行
  • Java回收算法
  • 1、标记-清除算法:
    标记出所有需要回收的对象,统一回收所有被标记的对象
    优缺点:标记清除效率不高,产生大量不连续的内存碎片


    20211212004014.jpg

白色 : 空闲内存
蓝色 : 正在使用的内存
灰色 : 可回收内存

  • 2、复制算法:
    将内存划分为大小相等的两块 , 一块内存用完之后复制存活对象到另一块空闲内存 , 清理另一块内存。
    腾出一半空间,将对象分配到另一半空间;回收时,将存活对象全部拷贝到空闲的那一半空间(内存空间连续),然后清理另一半空间。
    优缺点:运行比标记清理算法高效,浪费一半空间。


    20211212005454.jpg
  • 3、标记-整理算法:
    标记过程与“标记-清除”算法一样,存活对象往一端移动,清理其余内存。
    优缺点:避免标记-清除导致的内存碎片(也就是内存不连续)和避免复制算法的空间浪费。


    20211212005915.jpg
  • 4、分代收集算法:
    结合多种收集算法优势,新生代对象由于存活率低 用复制算法(用于复制的空闲内存自定义,如30%),老年代对象存活率高 用标记-整理算法。

2 : Android的内存管理机制

  • 内存弹性分配 , 分配值与最大值受具体设备影响
  • OOM场景 : 内存真正不足 , 可用内存不足
  • Dalvik和Art(android5.0以后)针对垃圾回收的区别:
    • 1.Dalvik仅固定一种回收算法
    • 2.Art回收算法可运行期选择(前台用标记-清除,后台用标记-整理)
    • 3.Art具备内存整理能力,减少内存空洞
  • Low Memory Killer 机制,针对所有进程
    • 进程分类:前台进程,可见进程,服务进程,后台进程和空进程等;优先级从前到后;
      优先级低的会被先回收。
    • 回收收益: 回收的时候,也会考虑回收内存的大小,是30m,还是300m的效果更好。

4 : 内存抖动解决实战

定义 : 内存频繁分配和回收导致内存不稳定

表现 : 频繁GC , 内存曲线呈锯齿状

危害 : 导致卡顿 , OOM

原因1 : 频繁创建对象, 导致内存不足及碎片(不连续)

原因2 : 不连续的内存片无法被分配 , 导致OOM

实战 :

使用Memory Profiler进行初步排查
使用Memory Profiler或CPU Profiler结合代码排查

解决技巧 : 找循环或者频繁调用的地方

5 : 内存泄漏解决实战

定义 : 内存中存在已经没有用的对象

表现 : 内存抖动 , 可用内存逐渐变少

危害 : 内存不足, GC频繁, OOM

MAT ( Memory Analyzer )

https://www.eclipse.org/mat/downloads.php
androidstudio 生成的 .hprof 文件在这里需要进行转换一下格式。
转换 : hprof-conv 1.hprof(原文件路径) 2.hprof(转换后文件路径)

6 : 全面理解MAT

with outgoing references:引用了哪些类
with incoming references:被哪些类引用
Shallow Size是对象本身占据的内存的大小,不包含其引用的对象。
Retained Size=当前对象大小+当前对象可直接或间接引用到的对象的大小总和。

dominator_tree
percentage : 占内存百分比

Histogram:基于类的角度分析
dominator_tree:基于实例的角度分析

7 : ARTHook优雅检测不合理图片

Bitmap内存模型

  • API10之前Bitmap自身在Dalvik Heap中,像素在Native
  • API10之后像素也被放在Dalvik Heap中
  • API26之后像素又放回native,但是增加了及时通知回收的机制

https://mp.weixin.qq.com/s/iMVLxsrQ2Um4ToFJGyqkpQ

获取Bitmap占用内存

  • 获取Bitmap占用内存
    • getByteCount
    • 一像素占用内存

常规方式

  • 背景: 图片对内存优化至关重要, 图片宽高大于控件宽高, 其实就造成了内存浪费,控件用不了这么大
  • 实现: 继承ImageView,覆写实现计算大小,在onDraw时判断控件宽高及对应的Bitmap宽高,如果比例超过了一定的限度,则给一个警告(这种方式侵入性比较强,通用性不强)

ARTHook介绍 : 运行时插桩 + 性能分析

挂钩,将额外的代码钩住原有方法,修改执行逻辑

  • java层hook库:epic
20211212125847.jpg

20211212130008.jpg

20211212131909.jpg

8 : 线上内存监控方案

20211212140229.jpg

20211212140356.jpg

20211212140528.jpg

20211212140622.jpg

20211212140745.jpg

20211212141104.jpg

20211212141323.jpg

20211212141506.jpg

9 : 内存优化技巧总结

  • 优化大方向

    • 内存泄漏
    • 内存抖动
    • Bitmap
  • 优化细节

    • LargeHeap属性
    • onTrimMemory
    • 使用优化过的集合: SparseArray
    • 谨慎使用SharedPreference
    • 谨慎使用外部库
    • 业务架构设计合理
  • 1.largeHeap可以增大系统分配的内存

  • 2.onTrimMemory和onLowMemory可以监听程序进入低内存

  • 3.使用优化后的集合:SparseArray

  • 4.谨慎使用SharedPreference:因为SharedPreference第一次加载所有数据到内存中

  • 5.谨慎使用使用量不大 没有经过大量验证的外部库,防止其有内存性能缺陷

  • 6.数据量很大时,使用局部加载数据(比如城市,可以按省加载城市,而不是一次加载全国甚至全球的城市)

10 : 内存优化模拟面试

11 : LeakCanary

20211212155638.jpg

https://zhuanlan.zhihu.com/p/433541771

5: --------App布局优化--------

6: -------App卡顿优化---------

7: -------App线程优化---------

1: 学习问题自助手册

http://coding.imooc.com/lesson/308.html#mid=22074

2: Android线程调度原理剖析

  • 线程的调度原理

    • 任意时刻 , 只有一个线程占用CPU , 处于运行状态
    • 多线程并发 : 轮流获取CPU使用权
    • JVM负责线程调度 : 按照特定机制分配CPU使用权
  • 线程调度模型

    • 分时调度模型 : 轮流获取 , 均分CPU时间
    • 抢占式调度模型 : 优先级高的获取 , JVM采用
  • Android线程调度

    • nice值 : Process中定义 -> 值越小 , 优先级越高 -> 默认是THREAD_PRIORITY_DEFAULT , 0
    • cgroup
      • 更严格的群组调度策略
      • 保证前台线程可以获取到更多的CPU
  • 注意点

    • 线程过多会导致CPU频繁切换 , 降低线程运行效率
    • 正确认识任务重要性决定那种优先级
    • 优先级具有继承性

3: Android异步方式汇总

  • Thread

    • 不易复用 , 频繁创建及销毁开销大
    • 复杂场景不易使用
  • HandlerThread

    • 自带消息循环的线程
      • 串行执行
      • 适合长时间运行 , 不断从队列中获取任务
  • IntentService

    • 继承自Service在内部创建HandlerThread
      • 异步 , 不占用主线程
      • 优先级较高 , 不易被系统Kill
  • AsyncTask

    • Android提供的工具类
      • 无需自己处理线程切换
      • 需注意版本不一致问题
  • 线程池

    • Java提供的线程池
      • 易复用 , 减少频繁创建 , 销毁的时间
      • 功能强大 : 定时 , 任务队列 , 并发数控制等
  • RXJava

    • 由强大的Scheduler集合提供
      • 不同类型的区分 : IO , Computation

4: Android线程优化实战

线程使用准则:
  • 1.严禁使用new Thread
  • 2.提供基础线程池供各个业务线使用,避免各个业务线各自维护一套线程池导致线程过多
  • 3.根据任务类型选择合适的异步方式
    ①优先级低长时间执行,HandlerThread
    ②有个任务定时执行,使用线程池
  • 4.创建线程必须命名;方便定位线程归属;运行期Thread.currentThread().setName修改名字
  • 5.关键异步任务监控;异步不等于不耗时;可以通过AOP方式监控
  • 6.重视优先级的设置;Process.setThreadPriority();可以设置多次

5: 如何锁定线程创建者

        DexposedBridge.hookAllConstructors(Thread.class, new XC_MethodHook() {
            @Override
            protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                super.afterHookedMethod(param);
                Thread thread = (Thread) param.thisObject;
                LogUtils.i(thread.getName()+" stack "+Log.getStackTraceString(new Throwable()));
            }
        });

6: 线程收敛优雅实践初步

  • 基础库优雅使用线程

    • 基础库内部暴露API :setExecutor
    • 初始化的时候注入统一的线程库
  • 统一线程库

    • 区分任务类型 : IO , CPU密集型
    • IO密集型任务不消耗CPU , 核心池可以很大
    • CPU密集型任务 : 核心池大小和CPU核心数相关
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
public static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 5));
private static final int MAXIMUM_POOL_SIZE = CORE_POOL_SIZE;
private static final int KEEP_ALIVE_SECONDS = 5;

7: 线程优化模拟面试

8: 如何设定线程池参数

描述:
面试官问:如何设定线程池参数,你如何回答?
线程池我们在开发中经常会用到,课程中我们也说到了滥用线程的危害,那请问大家如何设定线程池的参数?

思路点拨:

滥用线程会出现哪些问题?
不同的问题就决定了我们一定要采用多个线程池,是哪些线程池呢?
对于不同类型的线程池,我们如何设定其参数呢,需要考虑哪些方面的问题?

8: -------App网络优化-------

1: 网络优化有哪些维度展开?

2: 网络优化工具选择

  • a: NetWork Profiler

    • 显示实时网络活动 : 发送 , 接收数据及连接数
    • 显示启动高级分析
    • 只支持HttpURLConnection和OkHttp网络库
  • b: 抓包工具

    • Charles

      • 断点功能
      • Map Local
      • 弱网环境模拟
    • Fiddler

    • Wireshark

    • TcpDump

  • c: Stetho

    • 概念

      • 强大的应用调试桥 , 连接Android和Chrome
      • 网络监控 , 视图查看 , 数据库查看 , 命令行扩展等
    • Stetho使用

      • com.facebook.stetho:stetho-okhttp3:1.5.0
      • Stetho.initializeWithDefaults(this);
      • addNetworkInterceptor
      • Chrome浏览器 : chrome://inspect
  • d: 总结

    • Network Profiler , 抓包工具 , Stetho介绍及使用实战
    • 针对网络 : 最广泛使用的是抓包工具

3: 精准获取流量消耗实战

20211218113819.jpg
20211218114108.jpg
20211218114305.jpg
20211218114447.jpg
20211218114550.jpg
/**
 * 获取某个时间段流量的使用
 */
public class NetUtils {

    private static volatile String sSubscriberId = null;
    private static volatile int sUId = -1;

    @TargetApi(Build.VERSION_CODES.M)
    public static long getNetStats(@NonNull Context context, long startTime, long endTime) {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
            long netUsedByWifi = getNetStats(context, startTime, endTime, NetworkCapabilities.TRANSPORT_WIFI);
            long netUsedByCellular = getNetStats(context, startTime, endTime, NetworkCapabilities.TRANSPORT_CELLULAR);
            return netUsedByWifi + netUsedByCellular;
        } else {
            return 0;
        }
    }

    /**
     * Given the start time and end time, then you can get the traffic usage during this time.
     *
     * @param startTime Start of period. Defined in terms of "Unix time", see
     *                  {@link System#currentTimeMillis}.
     * @param endTime   End of period. Defined in terms of "Unix time", see
     *                  {@link System#currentTimeMillis}.
     * @param netType   the netWorkType you want to query
     * @return Number of bytes.
     */
    @RequiresApi(api = Build.VERSION_CODES.M)
    public static long getNetStats(@NonNull Context context, long startTime, long endTime, int netType) {
        long netDataReceive = 0;
        long netDataSend = 0;
        TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        String subId = telephonyManager.getSubscriberId();
        NetworkStatsManager manager = (NetworkStatsManager) context.getApplicationContext().
                getSystemService(Context.NETWORK_STATS_SERVICE);

        if (manager == null) {
            return 0;
        }
        NetworkStats networkStats = null;
        NetworkStats.Bucket bucket = new NetworkStats.Bucket();
        try {
            networkStats = manager.querySummary(netType, subId, startTime, endTime);
        } catch (Exception e) {
            e.printStackTrace();
        }

        while (networkStats != null && networkStats.hasNextBucket()) {
            networkStats.getNextBucket(bucket);
            int uid = bucket.getUid();
            if (getAppUid(context) == uid) {
                netDataReceive += bucket.getRxBytes();
                netDataSend += bucket.getTxBytes();
            }
        }
        return (netDataReceive + netDataSend);
    }

    private static int getAppUid(@NonNull Context context) {
        if (sUId == -1) {
            PackageManager packageManager = context.getApplicationContext().getPackageManager();
            try {
                PackageInfo packageInfo = packageManager.getPackageInfo(context.getApplicationContext().getPackageName(), PackageManager.GET_META_DATA);
                if (packageInfo != null) {
                    sUId = packageInfo.applicationInfo.uid;
                }
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
        }
        return sUId;
    }
}
20211218120345.jpg
20211218120540.jpg
20211218121208.jpg
20211218121306.jpg

4: 网络请求流量优化实战

  • 使用网络的场景概述

    • 数据: Api , 资源包( 升级包 , H5 , RN ) , 配置信息
    • 图片: 下载 , 上传
    • 监控: APM相关 , 单点问题相关
  • 数据缓存

    • 服务端返回加上过期时间 , 避免每次重新获取
    • 节约流量且大幅提高数据访问速度 , 更好的用户体验
    • OkHttp , Volley都有较好实践
  • 增量数据更新

    • 加上版本的概念 , 只传输有变化的数据
    • 配置信息 , 省市区县等更新
  • 数据压缩

    • Post请求Body使用GZip压缩
    • 请求头压缩
    • 图片上传之前必须压缩
  • 优化发送频率和时机

    • 合并网络请求 , 减少请求次数
    • 性能日志上报 : 批量+特定场景上报
  • 图片相关

    • 图片使用策略细化 : 优化缩略图
    • 使用WebP格式图片
  • 总结

    • 数据缓存 , 增量更新 , 压缩 , 图片相关等
    • 需要结合实际情况进行选择

5: 网络请求质量优化

质量指标:

  • 网络请求成功率
  • 网络请求速度

Http请求过程:

  • 1:请求到达运营商的DNS服务器并解析成对应的IP地址
  • 2:创建连接,走TCP的三次握手,然后根据IP地址找到相应的服务器,发送一个请求
  • 3:服务器找到对应的资源原路返回访问的用户

DNS相关:

  • 问题:DNS(域名到IP地址这个过程)被劫持,DNS解析慢
  • 方案:使用HttpDNS,绕开运营商域名解析过程

HttpDNS不是使用传统的DNS协议向DNS服务器的53端口发送请求,而是使用Http协议向DNS服务器的80端口发送请求

  • 优势:防止DNS被劫持,绕开运营商域名解析过程,降低平均访问时长,提高连接成功率

阿里云Http的DNS解析服务:

compile ('com.aliyun.ams:alicloud-android-httpdns:1.1.7@aar') {
transitive true
}

协议版本升级:

  • 1.0 : 版本TCP连接不复用
  • 1.1 : 版本引入持久连接,但数据通信按次序进行
  • 2 : 版本多工,客户端,服务器双向实时通信

网络请求质量监控:

  • 1:接口请求耗时,成功率,错误码
  • 2:图片加载的每一步耗时

网络容灾机制:

  • 1:备用服务器分流
  • 2:多次失败后一定时间内不进行请求,避免雪崩效应

其他优化:

  • 1:CDN加速,提高带宽,动静资源分离(更新后清理缓存)
  • 2:减少传输量,注意请求时机及频率
  • 3:okHttp的请求池

6: 网络体系化方案建设

  • 1:线下测试相关

    1:只抓单独APP
    2:侧重点:请求有误,多余,网络切换,弱网,无网测试
  • 2:线上监控相关

    1:服务端监控
    • 请求耗时(区分地域,时间段,版本,机型)
    • 失败率(业务失败与请求失败)
    • Top失败接口,异常接口
    2:客户端监控
    • 接口的每一步详细信息(DNS,连接,请求等)
    • 请求次数,网络包大小,失败原因
    • 图片监控
    3:异常监控体系
    • 服务器防刷 : 超限拒绝访问
    • 客户端 : 大文件预警,异常兜底策略
    • 单点问题追查

7: 网络优化模拟面试

  • 1:在网络方面你们做了哪些监控,建立了哪些指标?

    演进过程,优化背景
    质量 : 请求成功率,每步耗时,状态码
    流量 : 精确统计,前后台
  • 2:如何有效的降低用户流量消耗?

    数据 : 缓存,增量更新
    上传 : 压缩(body,图片)
    图片 : 缩略图,webp
  • 3:用户反馈消耗流量多这种问题怎么查?

    精准流量获取能力
    所有请求大小及次数的监控
    主动预警能力

9: -------App电量优化-------

电量优化介绍及方案选择

方案一:设置--电量排行

直观,但没有详细数据,对解决问题没有太多帮助
找特定场景专项测试

方案二:发送电量相关的广播:获取电池电量,充电状态,电池状态等信息

缺点:
价值不大:针对手机整体的耗电量,而非特定APP
实时性差,精度较低,被动通知

 IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_BATTERY_CHANGED);
        Intent intent = registerReceiver(null, filter);
        LogUtils.i("battery " + intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1));

方案三:Battery Historian

Google推出的一款Android系统电量分析工具,支持5.0(API 21)及以上系统的电量分析
优缺点: 1:功能强大, 推荐使用。2: 可视化展示指标:耗电比例,执行时间,次数。3:适合线下使用
测试点相关:1:耗电场景测试:复杂运算,视频播放。2:传感器相关:使用时长,耗电量,发热。3: 后台静默测试。

Battery Historian实战分析

安装一:https://github.com/google/battery-historian
安装二:安装Docker
docker -- run -p <port>:9999 gcr.io/android-battery-historian/stable:3.0 --port 9999
导出电量信息:
adb shell dumpsys batterystats --reset (重置手机电量)
adb shell dumpsys batterystats --enable full-wake-history (获取电量)
adb bugreport bugreport.zip (生成文件)
上传分析:
http://localhost:9999
上传bugreport文件即可
备用: https://bathist.ef.lc/

电量辅助监控实战

运行时能耗获取:
adb pull /system/framework/framework-res.apk
在反编译 xml ---> power_profile

运行时获取使用时长:
AOP辅助统计: 次数,时间,以WakeLock为例

电量优化套路总结

CPU时间片: 1: 获取运行过程线程CPU消耗,定位CPU占有率异常方法。2: 减少后台应用的主动运行。
网络相关: 1: 请求时机及次数限制。2: 数据压缩, 减少时间。3: 禁止使用轮询功能。
定位相关:1: 根据场景谨慎选择定位模式。2: 考虑网络定位代替GPS。3: 使用后务必及时关闭,减少更新频率。
界面相关:1:离开界面后停止相关活动。2: 耗电操作判断前后台。
WakeLock相关:1: 注意成对出现:acquire与release。2: 使用带参数的acquire。3: finally确保一定会被释放。4: 常亮场景使用KeepScreenOn即可。
JobScheduler: 1: 在符合某些条件时创建执行在后台的任务。2: 把不紧急的任务放在更合适的时机批量处理。

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class JobSchedulerService extends JobService {

    @Override
    public boolean onStartJob(JobParameters params) {
        // 此处执行在主线程
        // 模拟一些处理:批量网络请求,APM日志上报
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
}

清单文件:

<service android:name=".net.JobSchedulerService"
            android:permission="android.permission.BIND_JOB_SERVICE"/>
    /**
     * 演示JobScheduler的使用
     */
    private void startJobScheduler() {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
            JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
            JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), JobSchedulerService.class.getName()));
            builder.setRequiresCharging(true)//需要连接电源
                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);//网络状态 wife下
            jobScheduler.schedule(builder.build());
        }
    }

10: -------App瘦身优化--------

  • 1: 瘦身优化及Apk分析方案介绍

Apk组成

  • 代码相关: classes.dex
  • 资源相关: res , asserts , resources.arsc
  • So相关: lib

Apk分析1:

APKTool,反编译工具
网址: https://ibotpeaches.github.io/Apktool/
使用: apktool d xx.apk

20211215122048.jpg

Apk分析2:

20211215122808.jpg

20211215123009.jpg
  • 2:代码瘦身实战

代码混淆( Proguard )

  • 配置minifyEnabled为true,debug下不要配置

  • proguard-rules中配置相应规则

  • 3:资源瘦身实战

  • 4:So瘦身实战

    • So是Android上的动态链接库

    • 七种不同类型的CPU框架

    • 更优方案

      • 完美支持所有类型设备代价太大
      • 都放在armeabi目录,根据CPU类型加载对应架构So
    • 其他方案

      • So动态下载
      • 插件化

so动态下载,预加载并使用
插件化手段,每个功能为一个插件,并且可以从网上下发下来使用
关于插件化可以去看阿里的atelas以及360的replugn

  • 5:APK瘦身问题长效治理

    • 发版之前与上个版本包体积对比,超过阀值则必须优化
    • 推进插件化架构改造

11: -------App稳定性优化-------

1640664832617.jpg
1640665182404.jpg
1640665315828.jpg
1640665412705.jpg
图片.png
图片.png
图片.png
图片.png
图片.png
图片.png
图片.png
图片.png
图片.png
图片.png
图片.png
图片.png

12: -------App专项技术优化-----

  • 1 : 列表卡顿优化

    常规方案

    a : convertView复用,使用ViewHolder
    b : 耗时任务异步处理

    其他方案

    a : 布局相关(布局文件的加载是一个io的过程,同时伴随着反射)

    • 减少布局层级,避免过度绘制
    • 异步inflate或者X2C

    b : 图片相关

    • 避免过大尺寸 : GC频繁,内存抖动
    • 滑动时取消加载

    c : 线程相关

    • 使用线程池收敛线程,降低线程优先级
    • 避免UI线程时间片被抢占

    d : TextView优化
    原因 : 面对复杂文本性能不佳
    绘制原理 : BoringLayout单行,StaticLayout多行,DynamicLayout可编辑.
    方案 : 展示类StaticLayout即可,性能优于DynamicLayout,异步创建StaticLayout,然后在绘制,效率更高.

Executors.newSingleThreadExecutor().execute(new Runnable() {
            @Override
            public void run() {
                mStaticLayout = new StaticLayout(mText, mTextPaint, (int) width, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0, false);
                postInvalidate();
            }
        });

建议使用:facebook/TextLayoutBuilder
e : SysTrace跟踪
f : 注意字符串拼接

  • 2 : 存储优化

常规方案

a : 确保IO操作发生在非主线程
b : Hook或者AOP辅助
c : SharePreferences(xml)相关(缺点)
  • 加载慢 : 初始化加载整个文件(虽然SharePreferences的加载在源码里是在子线程做的,但是获取的方法会等待这个异步线程执行完成,这也就出现了UI线程等待异步线程的情况,所以在attachBaseContext方法中提前异步加载SP文件)
  • 全量写入 : 单次改动都会导致整体写入
  • 卡顿 : 补偿策略导致
d : SharePreferences替代者MMKV
  • mmap和文件锁保证数据完整
  • 增量写入 , Protocol Buffer
  • 支持从SharePreferences迁移
e : 日志存储的优化
  • 大量服务需要日志库支持
  • 对于性能的要求 : 不影响性能 , 日志不丢失 , 安全
  • 常规方案
    • 每产生一个日志,写一遍到磁盘中 : 不丢失 , 性能损耗
    • 开辟一个内存buffer , 先存buffer , 再存文件 : 丢日志
  • 优化方案使用mmap(实现文件磁盘地址 , 和进程虚拟地址 , 空间中一段虚拟地址的一一映射关系,实现了这个映射关系之后,进程就可以采用指针的方式,来读写操作一段内存,而系统会自动的回写到对应的磁盘中,也就是完成了对文件的操作,而不需要自己调用read,write这些系统函数)
    • 内存映射文件
    • 优点 : 高性能 , 不丢失
    • 业界实现 : 微信(XLog) , 美团(Logan)
  • 其他优化
    • 常用数据缓存 , 避免多次读写
    • 合理选择缓冲区Buffer大小 : 4 - 8kb
  • 3 : WebView异常监控

问题 : 性能与适配

腾讯开源框架 : VasSonic

思路

监控屏幕是否白屏 , 白屏则WebView有问题
确认白屏 : 所有像素一样则认为白屏
/**
 * WebView白屏检测
 */
public class BlankDetect {

    /**
     * 判断Bitmap是否都是一个颜色
     * @param bitmap
     * @return
     */
    public static boolean isBlank(View view) {
        Bitmap bitmap = getBitmapFromView(view);
        if (bitmap == null) {
            return true;
        }
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        if (width > 0 && height > 0) {
            int originPix = bitmap.getPixel(0, 0);
            int[] target = new int[width];
            Arrays.fill(target, originPix);
            int[] source = new int[width];
            boolean isWhiteScreen = true;
            for (int col = 0; col < height; col++) {
                bitmap.getPixels(source, 0, width, 0, col, width, 1);
                if (!Arrays.equals(target, source)) {
                    isWhiteScreen = false;
                    break;
                }
            }
            return isWhiteScreen;
        }
        return false;
    }

    /**
     * 从View获取转换到的Bitmap
     * @param view
     * @return
     */
    private static Bitmap getBitmapFromView(View view){
        Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        if (Build.VERSION.SDK_INT >= 11) {
            view.measure(View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(view.getHeight(), View.MeasureSpec.EXACTLY));
            view.layout((int) view.getX(), (int) view.getY(), (int) view.getX() + view.getMeasuredWidth(), (int) view.getY() + view.getMeasuredHeight());
        } else {
            view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
            view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
        }
        view.draw(canvas);
        return bitmap;
    }
}

13: ------课程总结 ---------

Android 基础架构组面试以及面试题
https://zhuanlan.zhihu.com/p/439065104

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容

  • 前述 一个好的app除了有吸引人的功能, 美丽的交互之外,性能也至关重要,作为一个技术人员,在这里当然只能讲技术了...
    唐小鹏阅读 2,483评论 0 23
  • App的性能优化可以说是伴随着整个app的成长,是没有止境的。任何一次需求的修改,都会给项目结构带来变化,都需要我...
    Sunrain16阅读 685评论 0 0
  • 一、首页启动速度 启动过程中做的事情越少越好(尽可能将多个接口合并) 不在UI线程上作耗时的操作(数据的处理在子线...
    一等到天幻阅读 393评论 0 0
  • 【转载】iOS App 性能优化总结 - lanyayue - 博客园 var currentBlogApp = ...
    下页天阅读 449评论 0 2
  • 一、首页启动速度 启动过程中做的事情越少越好(尽可能将多个接口合并) 不在UI线程上作耗时的操作(数据的处理在子线...
    lgvae阅读 298评论 0 0