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、标记-清除算法:
标记出所有需要回收的对象,统一回收所有被标记的对象
优缺点:标记清除效率不高,产生大量不连续的内存碎片
白色 : 空闲内存
蓝色 : 正在使用的内存
灰色 : 可回收内存
-
2、复制算法:
将内存划分为大小相等的两块 , 一块内存用完之后复制存活对象到另一块空闲内存 , 清理另一块内存。
腾出一半空间,将对象分配到另一半空间;回收时,将存活对象全部拷贝到空闲的那一半空间(内存空间连续),然后清理另一半空间。
优缺点:运行比标记清理算法高效,浪费一半空间。
-
3、标记-整理算法:
标记过程与“标记-清除”算法一样,存活对象往一端移动,清理其余内存。
优缺点:避免标记-清除导致的内存碎片(也就是内存不连续)和避免复制算法的空间浪费。
- 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
8 : 线上内存监控方案
9 : 内存优化技巧总结
-
优化大方向
- 内存泄漏
- 内存抖动
- Bitmap
-
优化细节
- LargeHeap属性
- onTrimMemory
- 使用优化过的集合: SparseArray
- 谨慎使用SharedPreference
- 谨慎使用外部库
- 业务架构设计合理
1.largeHeap可以增大系统分配的内存
2.onTrimMemory和onLowMemory可以监听程序进入低内存
3.使用优化后的集合:SparseArray
4.谨慎使用SharedPreference:因为SharedPreference第一次加载所有数据到内存中
5.谨慎使用使用量不大 没有经过大量验证的外部库,防止其有内存性能缺陷
6.数据量很大时,使用局部加载数据(比如城市,可以按省加载城市,而不是一次加载全国甚至全球的城市)
10 : 内存优化模拟面试
11 : LeakCanary
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
- 继承自Service在内部创建HandlerThread
-
AsyncTask
- Android提供的工具类
- 无需自己处理线程切换
- 需注意版本不一致问题
- Android提供的工具类
-
线程池
- Java提供的线程池
- 易复用 , 减少频繁创建 , 销毁的时间
- 功能强大 : 定时 , 任务队列 , 并发数控制等
- Java提供的线程池
-
RXJava
- 由强大的Scheduler集合提供
- 不同类型的区分 : IO , Computation
- 由强大的Scheduler集合提供
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: 精准获取流量消耗实战
/**
* 获取某个时间段流量的使用
*/
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;
}
}
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
Apk分析2:
-
2:代码瘦身实战
代码混淆( Proguard )
配置minifyEnabled为true,debug下不要配置
proguard-rules中配置相应规则
-
3:资源瘦身实战
- 清理无用资源: Android studio 右键 , Refactor , Remove Unused Resource
- 图片压缩
- 快速发展期的APP没有相关规范
- https://tinypng.com/及TinyPngPlugin
- 图片格式选择
- 资源混淆
- https://github.com/shwenzhang/AndResGuard : 将资源路径混淆变短
-
4:So瘦身实战
So是Android上的动态链接库
七种不同类型的CPU框架
-
更优方案
- 完美支持所有类型设备代价太大
- 都放在armeabi目录,根据CPU类型加载对应架构So
-
其他方案
- So动态下载
- 插件化
so动态下载,预加载并使用
插件化手段,每个功能为一个插件,并且可以从网上下发下来使用
关于插件化可以去看阿里的atelas以及360的replugn
-
5:APK瘦身问题长效治理
- 发版之前与上个版本包体积对比,超过阀值则必须优化
- 推进插件化架构改造
11: -------App稳定性优化-------
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