1APP启动
首页白屏第一种 设置一个图片<br>
<item name="android:windowBackground">@mipmap/ic_launcher</item><br>
第二种设置为透明<br>
<item name="android:windowIsTranslucent">true</item><br>
第三种<br>
<item name="android:windowBackground">@null</item><br>
<item name="android:windowDisablePreview">true</item><br>
代码设置setTheme(R.style.)
TRACE工具使用
File file = new File(Environment.getExternalStorageDirectory(), "app1.trace");
Log.i(TAG, "onCreate: " + file.getAbsolutePath());
//开始
Debug.startMethodTracing(file.getAbsolutePath());
//结束
Debug.stopMethodTracing();
adb pull /storage/emulated/0/app1.trace把文件拉出来分析
总结优化方案:
1.异步加载 没建handler 没操作UI 对异步要求不高(采用CountDownLatch确保异步任务完成后才到下一个阶段)->异步启动器
val cnt = CountDownLatch(2)
val thread = Thread() {
Thread.sleep(1000)
cnt.countDown()//计数器-1
}
thread.start()
cnt.await() //等待任务完成在向下执行
2.懒加载 用到的时候再初始化,如网络,数据库操作
启动主要优化 application调用oncreate->加载 XML->activity 调用oncreate
写一个专门记时的类
使用aop(AspectJx)获取每个类的时间
3 预加载
吧任务放到集合中-> CPU空闲是mIdleHandler便会回调自身的queueIdle方法,在将任务拿出来执行
IdleHandler = new MessageQueue.IdleHandler
Looper.myQueue().addIdleHandler(mIdleHandler);
2 UI绘制
使用的工具Choreographer 可以实时显示fps
AOP Hook 没有侵入性可以知道每个控件的耗时
dp = px / density(密度),
DPI / 160 = density,
刷新机制
垂直同步:每16ms发送一次 VSYNC信号,如果每次16ms内操作没有计算完就会丢帧
双缓存:
如果上一帧还没有显示完,数据就更新了,会有闪烁
UI总是先在一个buffer中绘制,在和另一个交换,就是说当buffer数据准备好了在切换buffer
解决屏幕卡顿
view的绘制经理 measure layout draw 3个阶段,这3个阶段是有CPU来负责的都是在UI线程完成的
在RederThread线程中将部分数据交给GPU,并将数据缓存在buff中
手机从buff中读取数据显示到屏幕上
GPU向buffer存,屏幕向buffer取这就有并发问题
引入双缓存机制,两个buffer
UI卡顿就是因为丢帧 16ms以内没有完成CPU和GPU的计算 渲染
choreographer编舞者,收到垂直同步信号(vsync),并进行view绘制操作的 ,是一个承上启下的角色
承上: 接受应用层的各种callback输入,包括input,动画 绘制,不会直接执行会缓存到队列中
启下: 负责接受硬件发出的vsync信号,收到信号从队列中取出任务,调用run方法进行绘制
同步屏障消息
handler设置异步消息msg.setAsynchronous(true);
如果给msgQ设置同步屏障,会优先执行异步msg
卡顿的分析->
1 使用shell命令分析CPU耗时
造成卡顿的原因很多种,最终反映到CPU时间上
CPU资源使用: 算法效率低,没有缓存
CPU资源争抢
CPU资源利用率低
2 卡顿检测方案
loop中有一个mLogging对象,msg处理前后都会调用他进行打印
msg执行前mLogging打印 Dispatching to 执行后打印 finished to
主线程发送卡顿肯定是在dispathMessage中发生了耗时操作
通过Looper.getMainLooper().setMessageLogging()传入自定义的Printer对象
执行消息前后都会回调自己,从而判断前后打印的间隔
总结优化方案
setcontentview 布局加载的源码流程
1 setcontentview() 获取context的viewGroup 将id通过 layoutInflater.form().inflate设置给viewGroup
2 inflate() 获取到XmlResourceParser() xml的解析器
3 解析器会调用一个Native方法 openXMLAssetNative()这个方法是通过IO流的方法将xml加载到内存中,因为捕获了IO异常
4 会通过createViewTag中的每一个tag创建具体的view对象
5 内部按照优先顺序Factory2和Factory的onCreateView(), createView(),在方法采用了构造器反射的方式实现
inflate第三个参数 true false左右
false系统会来加载view true是用户自定义view系统不会加载
发现问题
1 xml文件映射到内存会有io操作.io过程可能会卡顿
2 布局加载会反射,反射也有可能卡顿
3 布局的层级
4 不合理的嵌套,会重绘
解决问题
1 异步加载xml文件使用AsyncLayoutInflater,在子线程初始化好通过handler发送给主线程显示
2 使用ConstraintLayout 和merge,减少布局的嵌套
3 减少过渡
4 textview settext() 如果是wrap_content比较耗时,底层会调用checkForRelayout,会根据文字的多少重新计算长度,将宽度设置为固定值或者match_parent的时候会大幅度减少绘制时间
5 使用include复用布局
6 少用wrap_content会增加计算成本,设置已知宽高
1 CPU 减少xml转换成对象的时间
减少嵌套
相同布局抽取(会从GPU缓存中直接使用)
使用merge包裹view合并到上一层, CPU减少一次计算
不合理的使用根布局也有可能会过度重绘
减少重绘
1 去掉activity的主题色 <item name="android:windowBackground">@null</item> 或者 getWindow().setBackgroundDrawable(null)
2 GPU 减少重复绘制的时间
有选择性地移除窗口背景:getWindow().setBackgroundDrawable(null)
将activity默认的背景设置为null
移除xml中非必要的背景,或者条件设置
3 AsyncLayoutInflater
子线程加载布局 ->回调主线程->节约主线程时间
4 X2C框架使用
annotationProcessor 'com.zhangyue.we:x2c-apt:1.1.2'
implementation 'com.zhangyue.we:x2c-lib:1.0.6'
使用的new的方式加载view避免了反射
5 recyclerview 优化
RecycledViewPool pool = new RecycledViewPool();
recyclerView.setRecycledViewPool(pool);
adapter.setTypePool(pool.getTypePool());
3 内存优化
安卓内存管理机制分配值和最大值受设备影响
Dalvik与Art区别
dalvik固定回收算法
art运行期选择
内存泄漏检测(as自带工具中 Total列显示多个就没有回收)
1 查看工具MAT(Memory Analyzer) memory Profiler LeakCanary
2 格式转换工具tools->sdk->platform-tools->hprof_conv.exe(命令转换 hprof-conv -z 1.hprof 1-mat.hprof)
E:\AndroidSDK\platform-tools\hprof-conv.exe hprof-conv
C:\Users\Beepay\Desktop\1.hprof
C:\Users\Beepay\Desktop\1-mat.hprof
3 用Memory Analyzer打开1-mat.hprof (file->Open Heap Dump)
点击Histogram->查找对应的activity->右键GC Root(选择 exclude all) 或者 List(with incomin)右键GC(第一个)
通过反射将对象设置为空
public void z(String name){//传入变量名
try {
InputMethodManager systemService = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
Field declaredField = InputMethodManager.class.getDeclaredField(name);
declaredField.setAccessible(true);
Object view = declaredField.get(systemService);
if(null !=view){
Context context = ((View) view).getContext();
if(context==this){
declaredField.set(systemService,null);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
编码习惯
1 基本数据类型
2 循环用foreach
3 集合 sparseArry和arrayMap 性能不如HaspMap但是使用内存少
4 不用枚举改成常量
5 static final 不会申请内存
6 减少字符串拼接
7 activity组件
减少将activity当上下文传递
不将activity有关的对象用startic修饰(比如 Button)
单利禁止持有activity
非静态内部类和匿名内部类会持有activity
8 service改成intentService
9 使用webview单开一个进程 在xml中添加 android:process=":p"
10资源未关闭->IO bitmap
11注册对象未销毁->Eventbus
12类的静态变量持有大数据对象
13单利
14非静态内部类
15handler 匿名内部类
16webview
==内存优化面试回答点==
内存优化可以 减少卡顿->增加应用存活时间->降低oom
内存优化需要 对象可达性->强软虚弱引用->垃圾回收算法
可能会出现 内存泄漏的情况->内存抖动情况->最后造成oom->内存监控
内存监控
1 通过ActivityManager获取Debug.MemoryInfo获取内存信息数据
2 通过hook住 Debug.MemoryInfo的getMemoryStat(),获取memory中的多项数据,细分内存使用的情况
3 通过Runtime 获取dalvikHeap
4 通过Debug.getnATIVEhEAPallocatedSize获取NativeHeap
优化项目的过程
1 分析现状 确认问题
项目整体运行使用as查看activity是否泄漏,别结合MAT使用 ( LeakCanary->Memory Profiler -> memory Analyzer )
2 针对性优化
可能出现内存抖动.内存泄漏,
减少不必要的内存开销->内存服用
3 效率提高
对bitmap的处理使用弱引用->lruCache 使用ARTHooK解决图片过于大于view控件的大小hook住setImageBitmap进行判断图片是不是太大
4 内存抖动
大对象内存复用
避免频繁回收和开辟内存
webview的activity在新的进程运行 在xml中添加 android:process=":p"
做了内存优化最大的感受
1 学习了一些工具的使用MAT,如果出现问题可以直接查看解决
2 技术优化结合业务代码->删除一些重复的3方库 减少初始化的代码量
3 系统化完善->在线上运行时,将一些数据传递给服务器
oom 优化
1 Java中出现内存溢出的内存有堆内存,方法区内存,虚拟机栈内存,native方法区内存,一般说的oom是堆内存
2 主要原因, APP进程内存上限,手机无可用内存
3 APP进程内存上限->申请内存超过gc回收内存,无用资源无法回收
4 内存无法回收
资源对象没有关闭->全局集合类强引用没清理->activity内存泄漏->handler内存泄漏
内存抖动
1.尽量避免在循环体中创建对象。
2.尽量不要在自定义View的onDraw()方法中创建对象,因为这个方法会被频繁调用。
3.对于能够复用的对象,可以考虑使用对象池把它们缓存起来。
ANR分析
在Android中应用响应性是有activityManager和WindowManager系统服务监视的,当出现卡顿就对特定的应用程序显示anr
常见原因
在ams有个最大超时时间
当前时间没有机会得到处理
当前事件正在处理,没有及时完成
1 主线程耗时
2 死锁
3 主线程等锁耗时
4 主线程太多消息
ANR执行流程
应用发生anr->我们进程收到异常终止信息,写入anr信息(堆栈,cpu,io)->弹出提示框
获取ANR产生的trace文件
ANR产生时, 系统会生成一个traces.txt的文件放在/data/anr/下. 可以通过adb命令将其导出到本地:
$adb pull data/anr/traces.txt .
anr文档可以看到的信息
线程名,优先级,线程内部id,线程状态
线程所属的线程组,线程挂起次数,线程地址
调度优先级,调度策略
栈的大小,CPU调度次数,rq队列等待时间
你是怎么做卡顿优化的
最初我们项目中一些模块卡顿,通过as自带的工具查看CPU状况,结合代码将一些功能异步加载
随着项目增加,线上线下实时检测
减少activity中oncreate和onResume进行延迟操作
anr线上监控-> 第三方ANR-WatchDog
通过主线程的handler post 一个消息将主线程的某个值改变
post之后 sleep一段时间之后,检查值有没有改变
改变了就是没有卡顿
自动化的获取卡顿信息
自定义mLogging对象,主线程发送一个消息
子线程发送延迟消息,延迟时间就是阈值,
主线程消息在规定时间内执行,就取消子线程的消息
主线程消息没有在规定时间内执行,子线程的消息就是上报当前的堆栈信息
在一个周期内多次获取堆栈信息,出现卡顿就压缩上报后台
分析步骤
定位发生的时间点->查看日志->分析是否存在耗时->结合具体代码
service anr流程
1 APP进程 startSercive通知system_service进程 发起启动服务的请求
2 system_service通过binder线程接受请求,向activityManager线程发送消息开始计时
3 binder通过service进程开始进行初始化工作
4 初始化成功通知system_service进程取消计时
4图片压缩
AndroidStudio使用lint清除无用的资源Analyze -> Run Inspection by Name -> 输入:Unused resources -> 跳出弹框选择范围即可 去掉无用资源:打开shrinkResources
Epic第三方 采用的是hook
Https://github.com/tiann/epic
图片压缩代码
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.jett);
/**
* 质量压缩
*/
compress(bitmap, Bitmap.CompressFormat.JPEG,50,Environment.getExternalStorageDirectory()+"/test_q.jpeg");
/**
* 尺寸压缩
*/
//filter 图片滤波处理 色彩更丰富
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, 300, 300, true);
compress(scaledBitmap, Bitmap.CompressFormat.JPEG,100,Environment.getExternalStorageDirectory()+"/test_scaled.jpeg");
//png格式
compress(bitmap, Bitmap.CompressFormat.PNG,100,Environment.getExternalStorageDirectory()+"/test.png");
//webp格式
compress(bitmap, Bitmap.CompressFormat.WEBP,100,Environment.getExternalStorageDirectory()+"/test.webp");
/**
* 压缩图片到制定文件
* @param bitmap 待压缩图片
* @param format 压缩的格式
* @param q 质量
* @param path 文件地址
*/
private void compress(Bitmap bitmap, Bitmap.CompressFormat format, int q, String path) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(path);
bitmap.compress(format,q,fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (null != fos){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
bitmap开启异变(options.inMutable=true)
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable=true;//支持异变
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher,options);
for(int a=0;a<5;a++){
options.inBitmap=bitmap;
//就不会重开辟内存
bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher,options);
}
https://github.com/jakeWharton/DiskLruCache github地址
图片存储
1 如果lru设置的最大缓存数满了,lru就会返回的旧的需要删除的bitmap如果添加了异变复用标记(options.inMutable=true)就将他放到复用池(软引用,是保留图片的内存)中
2 开个线程循环扫描软引用的队列是不是有数据,如果有数据就将bitmap调用recycle()交给native(乃提物)层进行处理,并且清空队列(手动回收比GC快)
3 磁盘缓存 把复用池中多出来的数据拿出来,可以复用就复用不能就直接删除(API19之前只能复用宽高都相同的)之后存入磁盘中.
图片加载
先从内存中获取->如果没有从复用池获取->如果没有就从磁盘存储,并且添加到内存中
图片简单优化
1 质量要求不高使用RGB_565
2 inSampleSize 实现缩放功能
3 inScaled,inDensity和inTargetDensity实现更细的缩放
4 inBitmap 结合lruCache来使用->bitmap放到软引用中
5 图片放到 deawable-xxhdpi下->低分辨设备中图片大小只是压缩,不会增加内存
6 bitmap服用
options.inMutable = true;
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
if (inBitmap != null) {
options.inBitmap = inBitmap;
}
7 是用epic->hook ImageView的setImageBitmap()
通过从写afterHookedMethod()获取图片大小和view大小作比较,图片过大进行压缩
项目重复图片检测
重复图片指的是 bitmap像素数据完全一致,但是有多个不同的对象,获取hprof文件
获取本地drawable下的所有图片,转换成MD5对比
动脑
使用第三方(libjpeg) https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/BUILDING.md
1 安装ndk->用c语言 把所有的像素取到
2 从每一个像素中获取R色,G色,B色->1个像素占3个字节()
3 把RGB存储方式 改成BGR方式
4 去掉了一个阿尔法通道->吧所有的颜色做成二叉树(相同数量最多的颜色做为树根)->哈夫曼算法
加载长图
1 读取图片的宽高
options.inJustDecodeBounds = true//可以读取到图片的宽高 图片不会加载到内存中
2 读取图片
3 开启裂变 内存服用
options.inMutable = true
4 压缩色值 设置成RGB565
5 重建一个解码器
BitmapRegionDecoder.newInstance(inputStream, false) 会吧像素点记录下来
6 在OnMeasure()中获取测量的view的大小
7 创建 Rect()对象设置图片的加载区域
8 获取缩放比例
9 在OnDraw()中开始绘制
10 用解码器弄出刚才设置的区域来加载bitmap
11 通过 matrix对矩阵大小进行缩放
12 画出来
13 处理一下手势滑动防止滑出屏幕
5 电量
使用命令
1 adb shell dumpsys batterystats --reset
获取完整的wakelock信息:
2 adb shell dumpsys batterystats --enable full-wake-history
拔掉USB(让设备不处于充电状态),等待一段时间
获得报告:
>7.0 adb bugreport bugreport.zip
<=6.0 adb bugreport > bugreport.txt
6进程包活
1activity提权 开启一个1像素的activity
2service提权 发送通知
进程划分
前台进程 与用户正在交互 一般系统不会杀死
可见进程 用户看不到没有前台组件 一般不会杀死
服务进程 通过startService开启 与用户看到的界面没有关联 会被杀死
后台进程 随时回收
空进程 杀死
包活方案
oom_adj(系统分配给进程的一个值)值越小 优先级越高 系统的进程都是小于0的 我们的是大于0的 当前进程等于0
1 开启一个像素的activity
在一个新进程中开一个service在其中判断锁屏开屏广播 开启一个像素大小的activity
2 前台服务
在一个服务里面发送通知消息 同时在开启一个服务发送同一个ID的通知消息的取消
3 相互保活
4 jobSheduler
继承JonService
是作为进程死后复活的一种手段 在形成中通过循环活定时间监听主进程的存活,死了拉起来,在5.0上不支持但是 jobSheduler可以代替
5 黏性服务 与系统服务捆绑
NotificationListenerService就是一个监听通知的服务, 在项目中继承这个service 如果有推送通知的权限就行
excludeFromRecents 任务栏隐藏
其实,Android 是允许我们在任务列表里隐藏的,而且很简单,只要在清单中声明了 android:excludeFromRecents="true" 就好了。
6 SyncAdapter
是安卓系统提供一个账号同步机制,属于核心进程基本->进程优先级变为1,仅低于前台正在运行的进程->降低杀掉概率
7apk大小
APK组成1 代码相关:classes.dex
dex是安卓的可执行文件,包含应用程序的全部指令以及运行时数据
Java编译成class->dx工具将class文件整合到一个dex文件中
文件结构更紧凑,比传统jar相比大小缩小
2 资源文件
3 so相关
4 签名信息
混淆的左右和形式
将代码的类文字改成无意义->重写代码中的部分逻辑->打乱代码格式
可以移除未使用的类 方法对字节码进行优化,安全
D8 与 R8 优化
dex的编译时间更短->dex文件小->编译后的dex更好的性能
android.enableD8 = true
android.enableR8=true
android.enableR8.libraries=true
总结
代码 混淆 统一三方库 删除无用代码
dex优化
跨dex调用方法会导致保存被调用dex的方法id->存放的class就会变少
使用redex进可能的吧互相调用的放到一起和dex优化之间取一个平衡点->使用xz utils对dex进行压缩
资源 图片压缩
so 只保留了Armeabi
as提供来一个可以查看apk包各项的大小
lint 是一个静态扫码工具,可以扫描出没有被引用的资源文件( Analyze->Inspect coe)
图片转webp
引入三方库: 考虑是否需要将其代码全部引入,还是只是一部分
相同图片不同颜色使用 android:tint
8加固
混淆
-keep 指定类和类成员(变量和方法)不被混淆。
-keep class com.dongnao.proxy.guard.test.Bug
(保护了类名)
-keep class com.dongnao.proxy.guard.test.Bug{
public static void *();
}
(保护了 public static void的没有参数的函数)
-keep class com.dongnao.proxy.guard.test.Bug{
*;
}
(保护所有)
-keepclassmembers 指定类成员不被混淆(就是-keep的缩小版,不管类名了)。
-keepclassmembers
class com.dongnao.proxy.guard.test.Bug
(都被混淆了)
-keepclasseswithmembers 指定类和类成员不被混淆,前提是指定的类成员存在。
-keepclasseswithmembers class com.dongnao.proxy.guard.test.Bug
(保护类名,但是没指定成员,所以函数名被混淆)
-keepclasseswithmembers class com.dongnao.proxy.guard.test.Bug{
native <methods>;
}
类名被混淆出现错误找不到位置恢复方法
1 先配置 -keepattributes SourceFile,LineNumberTable
2 再执行 retrace.bat -verbose mappint文件 bug文件(build/outputs/mapping/debug/mapping.txt)
3 使用工具 sdk/tools/groguard/bin/retrace.bat
加固
创建一个代理application->dex通过tools加密->加密后的dex和res什么的在组成一个新的apk,交给代理application解码->交给android去加载dex
1 通过代码执行cmd命令 获取dex文件
RunTime.getRunTime.exec("命令") 可以执行命令
2 加密apk中所有的dex文件(比如使用AES加密),将加密好的文件重新压缩成一个apk文件(包括代理的application和自定义的dexClassLoader)
3 用官方通过的cmd命令 进行对齐和签名
4 系统里面会吧dex放到dexElements数组里面->循环数组里面的数据,通过反射转换成class进行执行
将dex加密放到数组中,在通过反射指向到系统中的dexElements数组
5 通过file(getApplicationInfo().sourceDir) 得到加密后的apk文件,解压到一个目录,读取文件进行解密在写到指定的目录 用流进行读写
6 通过反射指向到系统中的dexElements数组,将解密后的文件加载到系统的数组中
代理的application,通过自定义的dexClassLoader读取加密后的dex,
通过反射activityThread,将代理的application执行原来的application,调用oncreate()
9稳定性
1 重在预防,使用第三方线上监控软件
2 长效保存->建立相关的规范
3 崩溃处理
采集信息->处理信息->展示信息设备 数据 版本 使用时长->上报相关同学
4 崩溃优化
出现未捕获异常->使用钩子 UncaughtExceptionHandler 对单进程处理
反混淆上传堆栈信息-> 安卓原生反混淆工具包 retrace.jar 实时解析每个上报的崩溃
java崩溃
1 首先发送crash进程,在创建之初便准备好了defaultUncaughtHandler,处理并输出错误信息->写类实现Thread.UncaughtExceptionHandler接口,崩溃会回调uncaughtException()
2 AMP.handleApplicationCrash,通过binder 传递给 system_server进程
3 调用 binder服务端执行 ams.handleApplicationCrash
4 如果1分钟没有出现2次崩溃,结束栈顶activity,如果出现结束所有activity
native解决方案 都是信号机制
google Beakpad 代码量大
LogCat 需要过滤日志
coffeecatch 存在兼容性
信号机制->信号会在内核态和用户态直接切换
用户空间->中断->内核空间 调用中断服务->用户空间 信号处理->执行完返回内核 恢复内核栈->从内核返回用户空间 继续执行
信号接收->内核代理接收->会将其放到对应进程的消息队列中,向进程发送一个中断->使其陷入内核态
信号处理->用户空间处理
如何解决 SharedPreference apply 引起的 ANR 问题
分析 sp调用 apply 方法->会创建一个锁放到QueuedWork中
将数据持久化封装成一个任务放到队列中->任务结束释放锁
activity退出 会执行 QueuedWork.waitToFinish()等待所有的等待锁释放
解决 Hook activityThrad的handler变量_>为他设置一个callback
handler.dispatchMessage中会先处理callback->在回调中执行队列的清除QueuedWork.waitToFinish()