Android内存优化(六)LeakCanary使用详解
https://cloud.tencent.com/developer/article/1034262
install方法会返回RefWatcher用来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。最后为了举例,我们在一段存在内存泄漏的代码中引入LeakCanary监控,如下所示。
LeakCanary
leakcanary-android-no-op
07
LeakCanary.install方法会返回RefWatcher用来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。最后为了举例,我们在一段存在内存泄漏的代码中引入LeakCanary监控,如下所示。
RefWatcher
LeakApplication.23(watch)
中还要提供getRefWatcher静态方法来返回全局RefWatcher。最后为了举例,我们在一段存在内存泄漏的
MainActivity存在内存泄漏,原因就是非静态内部类LeakThread持有外部类MainActivity的引用,LeakThread中做了耗时操作,导致MainActivity无法被释放。关于内存泄漏可以查看Android内存优化(三)避免可控的内存泄漏这篇文章。
refWa.watch this ActivityRef
hprof文件)和info信息分享出去,如下图所示。
hprof文件)和info信息分享出去,如下图所示。 watch
45
内存泄漏分析框架LeakCanary的使用与原理解析
https://blog.csdn.net/AndrExpert/article/details/103781575
JVM)在内存管理方面给我们变成带来的便利。JVM的这一大特性使Java程序员从繁琐的内存管理工作中得到了一定解放,但是JVM的这个特点的实现也是有代价的,并且它也并非万能
LeakCanary 使用方法
Java堆内存
内存泄漏的表述可为:当一个对象已经不需要再使用本该被回收时,
另外一个正在使用的对象持有它的引用从而导致它不能被垃圾收集器回收,
结果它们就一直存在于内存中(通常指Java堆内存),
占用有效空间,永远无法被删除。
GC Roots回收的对象必须是当前没有任何引用的对象
GC非静态内部类
1.2 “静态实例” 造成内存泄漏
,由于SomeResources类是一个非静态内部类,
它默认持有外部类StaticInstanceActivity的引用,就会导SomeResources的对象一直持有该引用,造成内存泄漏。
内部类 外部类StaticInstanceActivity
Application context
1.3 “Handler” 造成的内存泄漏
MessageQueue
非静态内部类创建静态实例造成的内存泄漏案例
在优化时,可以为WebView开启另外一个进程,通过AIDL与主线程进行通信,便于WebVIew所在的进程可以根据业务需要选择合适的时机进行销毁。
Process.killProcess Process.myPid
2.1 LeakCanary使用方法
support-fragment:1.6.3
a46
RefWatcher.watch(myDetachedView)
RefWatcher.watch
AndroidRefWatcherBuilder
AndroidExcludedRefs.createAppDefaults.build
buildAndInstall
listenerServiceClass
setEnabledAsync
LeakCanaryInternals.setEnabledAsync
ActivityRefWatcher.install
FragmentRefWatcher.Helper.install
LeakCanaryInternals.setEnabledAsync
AndroidRefWatcherBuilder.build
ActivityRefWatcher activityRefWatcher =
registerActivityLifecycleCallbacks
ActivityLifecycleCallbacks.onActivityDestroyed
refWatcher.watch(activity);
randomUUID
KeyedWeakReference
ensureGoneAsync reference
WeakReference ReferenceQueue
ReferenceQueue
RefWatcher.ensureGoneAsync
RefWatcher.ensureGone
RefWatcher.ensureGoneAsync
watchExecutor.execute
removeWeaklyReachableReferences
retainedKeys key
gone reference
gcTrigger.runGc生成堆内存快照
System.nanoTime
heapdumpFile
heapDumper.dumpHeap
heapDumpBuilder.heapDumpFile
heapdumpListener.analyze heapDump
analyze
HeapAnalyzerService
HeapAnalyzerService
ensureGone
removeWeaklyReachableReferences gone
retainedKeys Set
KeyedWeakReference
!retainedKeys.contains
queue.poll
.remove
(2)将堆内存转储到文件并分析,获取泄漏对象的GC最短强引用路径
heapdumpListener.RefWatch
HeapAnalyzerService.runAnalysis(runAnalysis)
DisplayLeakService.class
DisplayLeakService.class
AbstractAnalysisResultService
AbstractAnalysisResultService listenerServiceClass
HeapAnalyzerService.runAnalysis
AbstractAnalysisResultService listenerServiceClass
=DisplayLeakService
AndroidRefWatcherBuilder
,该方法中最终调用的是HeapAnalyzerService#runAnalysis方法在后台服务中执行堆快照分析任务。
HeapAnalyzerService.runAnalysis
extends
ForegroundService AnalyzerProgressListener
listenerServiceClass.getName
ContextCompat.startForegroundService context,intent
IntentService Service
onHandleIntentInForeground
LISTENER_CLASS_EXTRA HEAPDUMP_EXTRA
DisplayLeakService.class
HeapAnalyzer heapAnalyzer = new
HeapAnalyzer heapDump.excludedRefs,
heapDump.reachabilityInspectorClasses
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer heapDumper.excludedRefs,
heapDumper.reachabilityInspectorClasses
excludedRefs reachabilityInspectorClasses
// 即分析堆内存快照,找出 GC roots 的最短强引用路径,并确定是否是泄露
HeapAnalyzer
GC Roots回收的对象必须是当前没有任何引用的对象
AnalysisResult result = heapAnalyzer.checkForLeak
sendResultToListener listenerClassName heapdump result
分析堆内存
listener.onProgressUpdatell
MemoryMappedFileBuffer
HprofBuffer HprofParser
//移除相同 GC roots
deduplicateGcRoots snapshot
//
findLeakingReference
.getClassObj.getClassName()
findLeakTrace
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
由上述源码可知,该方法最终调用findLeakingReference方法来判断是否真的存在内存泄漏,如果存在(leakingRef!=null),就调用findLeakTrace方法找出这个泄漏对象的GC Root最短强引用路径
GC Root
registerActivityLifecycleCallbacks Activity
onActivityDestroyed
RefWatcher.watch
至此,LeakCanary框架实现原理分析完毕,最后我们再总结一下:LeakCanary是通过在Application的registerActivityLifecycleCallbacks方法实现对Activity销毁监听的,该方法主要用来统一管理所有activity的生命周期。所有Activity在销毁时在其OnDestory方法中都会回调ActivityLifecycleCallbacks#onActivityDestroyed方法,而LeakCanary要做的就是在该方法中调用RefWatcher#watch方法实现对activity进行内存泄漏监控。那么,LeakCanary是如何判断某个Activity可能会发生内存泄漏呢?答案是:WeakReference和ReferenceQueue,即LeakCanary利用了Java的WeakReference和ReferenceQueue,通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果能够被回收,则说明引用可达,垃圾回收器就会将该WeakReference引用存放到ReferenceQueue中。假如我们要监视某个activity对象,LeakCanary就会去ReferenceQueue找这个对象的引用,如果找到了,说明该对象是引用可达的,能被GC回收,如果没有找到,说明该对象有可能发生了内存泄漏。最后,LeakCanary会将Java堆转储到一个.hprof文件中,再使用Shark(堆分析工具)分析.hprof文件并定位堆转储中“滞留”的对象,并对每个"滞留"的对象找出 GC roots 的最短强引用路径,并确定是否是泄露,如果泄漏,建立导致泄露的引用链。最后,再将分析完毕的结果以通知的形式展现出来。
.hprof GC roots Shark Shark工具
并对每隔滞留的对象找出GCroots的最短强引用路径。
.23
Class Loader SubS Runtime
VM Stack】 Native
Stack Frame
reference类型 returnAddress类型
OufOfMemoryError出现在如果虚拟机栈可以动态扩展,但是扩展后仍然无法申请到足够的内存。
-XX:MaxMetaspaceSzie来指定元数据区的大小。
Direct Memory
DirectByteBuffer
23
Java 堆 GC
Gc Roots
Android性能优化(1):常见内存泄漏与优化(一)
https://blog.csdn.net/AndrExpert/article/details/102467011
GC Roots
Eden和Survivor区域 Minor GC
-XX:PretenureSizeThreshold参数,使得大于这个设置值得对象直接在老年代内存区域分配,这样做的目的在于避免在Eden区及两个Survivor区之间发生大量的内存复制。
MaxTenuringThreshold参数设置老年代年龄阈值。
HandlePromation
JVM的类加载机制
优化/避免内存泄漏原则:
涉及到使用Context时,尽量使用Application的Context;
对于非静态内部类、匿名内部类,需将其独立出来或者改为静态类;
在静态内部类中持有外部类(非静态)的对象引用,使用弱引用来处理;
不再使用的资源(对象),需显示释放资源(对象置为null),如果是集合需要清空;
保持对对象生命周期的敏感,尤其注意单例、静态对象、全局性集合等的生命周期
23
在剖析Handler消息机制原理一文中我们知道
由于主线程的Looper对象会随着应用进程一直存在的且Java类中的非静态内部类和匿名内部类默认持有外部类的引用
成内存泄漏。优化:将Handler类独立出来,或者使用静态内部类,因为静态内部类不持有外部类的引用。
Looper MessageQueue Message Handler
WeakReference
弱引用
用来描述非必须的对象,比软引用更弱一些,由WeakReference类实现。被弱引用的对象只能生产到下一次垃圾收集发生之前,无论当前内存是否足够。
线程(非静态内部类或匿名内部类)造成的内存泄漏
new MyAsyncTask this.execute
new Thread MyRunnable.start
非静态内部类和匿名内部类默认持有外部类的引用;
GC、Major
泄漏。优化:将MyRunnable和MyAsyncTask独立出来,或使用静态内部类,因为静态内部类不持有外部类的引用
4) 静态实例造成的内存泄漏
StaticInstanceActivity
优化:使用单例模式实现SomeResources,或者将其改成静态内部类。如果需要传入Context参数,必须使用Application的Context。
(5) 资源未关闭或监听器未移除(注销)引起的内存泄露情况
OOM
资源未关闭或监听器未移除(注销)引起的内存泄露情况
BroadcastReceiver
TypedArray obtainStyledAttributes AttrDeclareView_background_color
getColor recycle
除了上述常见的5种内存泄漏外,还有包括无限循环动画、使用ListView、
使用集合容器以及使用WebView也会造成内存泄漏,其中,
无限循环动画造成泄漏的原因是没有再Activity的onDestory中停止动画;
使用ListView造成泄漏的原因是构造Adapter时没有使用缓存的convertView;
使用集合容器造成泄漏的原因是在不使用相关对象时,没有清理掉集合中存储的对象引用。
在优化时,在退出程序之前将集合中的元素(引用)全部清理掉,再置为null;
使用WebView造成泄漏的原因是在不使用WebView时没有调用其destory方法来销毁它,
导致其长期占用内存且不能被回收。在优化时,可以为WebView开启另外一个进程,
通过AIDL与主线程进行通信,便于WebVIew所在的进程可以根据业务需要选择合适的时机进行销毁。
使用Proguard混淆代码打造APP安全第一层防护
https://blog.csdn.net/AndrExpert/article/details/55002749
ProGuard
class
-keepattributes Signature
-keepattributes EnclosingMethod
-dontwarn -keep .**
-libraryjars
-dontwarn
-keep class Serializable
AndroidMainfest中的类及其子类不能混淆,包括四大组件、Application的子类等;
23(Application)
Parcelable Creator
-keep class * implements android.os.Parcelable{
public static final android.os.Parcelable$Creator *;
}
-keep classcom.xxx.xxx.** { *; }
-keep classcom.** {*;}
-keepclasseswithmembernamesclass * {
native
}
ProGuard
-dontshrink
-23
=proguard-project
指定混淆时采用的算法,后面的参数是一个过滤器
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
!class/merging/*
class_specification -keep [,modifier,..]
native methods
-libraryjars
!**.gif,images/**,匹配images目录下所有除gif格式文件
!**.gif,images/**,匹配images目录下所有除gif格式文件
project.properties
-keepattributes *Annotation*
-keepclasseswithmembers
Context, AttributeSet
-keepclassmembers
**.R$*
32
static
Parcelable&Creator CREATOR
jeb.exe 23
通过jeb.exe或者dex2jar、jd-gui.exe查看混淆后的apk或classes.class文件
ProGuard
libraryjars libraryjars
Android性能优化(2):常见内存泄漏与优化(二)
https://blog.csdn.net/AndrExpert/article/details/102956524
JVM与Dalvik区别
Dalvik ART
Android Profiler
Allocation Tracker Heap Dump
MAT LeakCanary23
.class ->.dex DEX工具
er
JVM Dalvik ART
JVM运行的.class文件,Dalvik运行的是.dex(即Dalvik Executable)文件。''
.class .jar
23 String ClassNotFoundException异常
Const Pool
Field45d
Dalvik经过优化,允许在有限的内存中同时运行多个进程,或说同时运行多个Dalvik虚拟机的实例。
Dalvik Zygote
Dalvik拥有Zygote进程与共享机制
.dex->(.odex文件)
.dex->(23).oat native
Dalvik虚拟机的运行时堆使用标记--清除(Mark--Sweep)算法进行GC,
Mark-Sweep GC
Zygote Space
4个Space分别是Zygote Space、Allocation Space、Image Space以及Large Object Space
Allocation Stack Live Mark Stack idel JIT
init.rc、init
class AppRuntime : public AndroidRuntime
{};
//AppRuntime
runtime.start(23);
startVm &mJavaVM,&env,zygote
jclass startClass
jclass startClass = env->FindClass(slashClassName);
env->CallStaticVoidMethod Zygote fork
persist.sys
get_library_system_property
Android Profiler Android Monitor的Memory
于Android Monitor的Memory Monitor
Memory Profiler
View-Tool
saved stop -(destroy)
Native Graphics:图像缓存等,包括GL Stack Code:用于处理代码和资源(如
Code:用于处理代码和资源(如
Allocation Tracker
Android Monitor ->Memory Profiler
Live 34 Dump Java hea
Arrange Arrange by Package
Filters Regex Match Case。红色方框中其他选项意义:
Instance View
Allocation Call Stack标签 Stack
Head Dump
Dump Java Heap
对象所分配到的调用堆栈(Android
Depth:从任意 Native Shallow Retained
Allocations Depth:从任意
(2) 黄色方框
Depth:从任意 GC root 到所选实例的最短 hop 数。 hop root
hprof hprof
MAT
Leak Suspects Systems Overview
Leaks
standar.hprof standar.hprof
Leak Suspects Report Suspects reo//
Historgram:列出每个类的所有对象。从类的角度进行分析,注重量的分析;
Dorminitor 23 Tree = ;
Leak
Shallow Heap Retained Heap就是当前对象被GC后,从Heap上总共能释放掉多大的内存空间,这部分内存空间被称之为Retained
GC 掉
Dorminitor Tree
Tree,可以清晰地得到一个对象的直接支配的对象,如果直接支配对象中出现了不该有的对象,就说明发生了内存泄漏。
从上图可知,被选中的SingleInstanceActivity对象的直接支配对象出现了不该有的CommonUtils对象,因为SingleInstanceActivity是要被回收的。换句话说,CommonUtils持有SingleInstanceActivity对象的引用,导致SingleInstanceActivity对象无法被正常回收,从而导致了内存泄漏。
CommonUtils SingleActivity
Android性能优化(4):UI渲染机制以及优化 -Android
https://blog.csdn.net/AndrExpert/article/details/103156318
Overdraw
Profile GPU Rendering
卡顿优化
SysTrace TraceView
SysTrace TraceView
Rasterization
Poly Texture
View Hierachy Display List Drawables等都是一起打包到统一的纹理 Ph
Draw Phase
VSync CPU GPU OpenGL
Refresh Frame
VSYNC
卡顿现象
布局Layout过于复杂,无法在16ms内完成渲染;
同一时间动画执行的次数过多,导致CPU或GPU负载过重;
View过度绘制,导致某些像素在同一帧时间内被绘制多次
在UI线程中做了很多耗时的操作;
GC回收时暂停时间过长或者频繁GC产生大量的暂停时间(内存抖动);
内存抖动
GC回收时暂停时间过长或者频繁GC产生大量的暂停时间
Memory Churn
Yong Generation Yong Generation
Display List23)
主要有Profile GPU Rendering、SysTrace以及TraceView等。
SysTrace TraceView
在XML布局中,控件有重叠且都有设置背景;
View的onDraw在同一区域绘制多次;
View 的onDraw 在同一区域绘制多次;
Show GPU overdraw
Show GPU overdraw
Profile GPU Rendering
overdraw ->Rendering
TraceView Hierachy Viewer,前者能够详细分析问题原因;后者能够查看布局的层次和每个View所花费的时间。Systrace
总之,我们应尽量从以下几个方面避免过度绘制。
移除无用的背景图;
减少视图层级,尽量使用扁平化布局,比如Relativeayout;
减轻自定义控件复杂度,重叠区域可以使用canvas.clipRect方法指定绘制区域;`
34
canvas.clipRect方法指定绘制区域;`
对于UI显示性能,比如动画播放不流畅、渲染卡顿等问题提供了分析数据
Measure
Layout Record Execute的数据计算工作,以将其计算成的Polygons
Rasterization GPU SCREEN
Draw Phase
Convert to GPU description
Cache as Display List
Execute Phase
VSYNC
Chrome商店下载 trace.html
Alerts、
Frames Kernel CPU
Frames和Kernel
animator
DrawFrame
Interactions Alerts、 CPU usage
用Chrome分析trace.html -> Chrome trace.html
Frames -(16.66)
deliverInputEvent
Frames animator .elevation
opacity
RenderThread
自动场景暂时注销->成员
Kernel CPU
HeapTaskDeamon
Running thread Start
Duration https://img-blog.csdnimg.cn/20191120093738233.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9qaWFuZ2RnLmJsb2cuY3Nkbi5uZXQ=,size_16,color_FFFFFF,t_70
2.2.2 TraceView
start Method Profiling
.invoke dispatchMessage
MemoryIntArry.nativeSize
是因为在主线程的FibonacciActivity中递归调用了
computeFubonacci()方法,
导致CPU被长期占用,
从而导致渲染线程无法获得CPU资源出现无法正常渲染的性能问题。
.nativePollOnce
.next
.run
.doFrame
CallbackRecord.run
从Android 6.0源码的角度剖析View的绘制原理
https://blog.csdn.net/AndrExpert/article/details/100519701
requestlayout
scheduleTraversals
getLooper.getQueue.removeSyncBarrier
doTraversal
ActivityStackSuper();
View绘制过程分析
getSuggestedMinimumHeight
getSuggestedMinimumWidth
specMode specSize
34
specSize由View本身LayoutParams和父容器的MeasureSpec共同决定,
它可能是一个精确的数值,也可能是父容器的大小。具体操作如下所示:
455
自身Layoutparams 父容器MeasureSpec specSize
setMeasuredDimensionRaw
mMeasureWidth
mMeasuredHeight
MeasureSpec和View的measureSpec获取
MODE_MASK &
即View的MeasureSpec创建受父容器和本身LayoutParams的影响,
在测量的过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个MeasureSpec来测量出View的宽高。
其中,这个父容器对于顶层View来说就是Window,
对于普通View来说,就是ViewGroup。View -> Window View - > ViewGroup
performMeasure
makeMeasureSpec
普通View
对于普通View来说,它的父容器是ViewGroup,构建普通View的measureSpec是通过ViewGroup的measureChildWithMargins方法实现的。在该方法中又调用了ViewGroup的getChildMeasureSpec方法,这个方法接收三个参数,即父容器的parentWidthMeasureSpec、父容器的padding属性值+子View的margin属性值(left+right)以及子View的LayoutParams的width属性值。(同理height)
getRootMeasureSpec
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,
MeasureSpec.EXACTLY);
makeMeasureSpec
rootDimension= ();
measureChildWithMargins = measureChildWithMargins
measureChildWithMargins =
getChildMeasureSpec
child.measure childWidthMeasureSpec ,childHeightMeasureSpec
childDimension MeasureSpec.EXACTLY
resultSize = size
sUseZeroUnspecifiedMeasureSpec ? 0:size
.makeMeasureSpec
childDimension = LayoutParams.MATCH_PARENT
34
specMode = MeasureSpec.UNSPECIFIED
childDimension = lp.width lp.height
2. ViewGroup的measure过程
2. ViewGroup的measure过程
ViewGroup继承于View,是用于装载多个子View的容器,由于它是一个抽象类,不同的视图容器表现风格有所区别,因此,ViewGroup并没有重写View的onMeasure方法来测量ViewGroup的大小,
而是将其具体的测量任务交给它的子类,以便子类实现其特有的功能属性。
这两个参数是测量LinearLayout尺寸的MeasureSpec,由其自身的LayoutParams和父容器计算得出。LinearLayout.onMeasure()源码如下:
MeasureSpec -> LayoutParams 和父容器
getVirtualChildCount
hasDividerBeforeChildAt
divider setDraw
measureChildBeforeLayout
mBaselineChildTop = mTotalLength
lp.width = LayoutParams.MATCH_PARENT
widthMode != MeasureSpec.EXACTLY
setMeasuredDimension
resolveSizeAndState
// 计算heightMeasureSpec
int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
childMeasuredState result
2.4 Layout过程
于Layout过程的作用是ViewGroup用来确定子元素的位置
ViewGroup OnLayout View layout
onLayoutChange
如果我们自定义一个ViewGroup,就必须要重写onLayout方法,以便确定子元素的位置
absoluteGravity & HORIZONTAL_GRAVITY_MASK
CENTER_HORIZONTAL
即mLeft/mTop/mRight/mBottom - >setFrame
private void setChildFrame(View child, int left, int top, int width, int height) {
// 设置子View位置
// 其中,width和height为测量得到的大小值
child.layout(left, top, left + width, top + height);
}
setChildFrame
2.4 Draw过程
dispatchDraw
canvas.translate
draw
scrollX
translate -scrollX
// View.dispatchDraw()
// 该方法是一个空方法,由View的子类实现,比如ViewGroup
// View绘制过程的传递就是通过该方法实现,它会遍历调用所有子元素的draw方法
protected void dispatchDraw(Canvas canvas) {
}
ii
measure->onMeasure layout->onLayout draw->onDraw View
Android性能优化(5):APK瘦身优化
https://blog.csdn.net/AndrExpert/article/details/103483635
ProGuard AndResProguard
Android Lint tinypng WebP libs
assets&res lib MERA-INF目录:保存应用程序的签名信息,签名信息可以验证APK文件的完整性。
res AndroidManifest.xml classes.dex resource.arsc
JSON配置文件、二进制数据、HTML5等。系统在编译时不会编译该目录下的资源,因此不会像res目录样能被直接通过R
R.xxx.xxx () res/raw目录存储的也是原生的资源文件,但是它能够被Android映射成R
BitmapFactory.debug版、release版 .decodeStream
openRawResource R.raw.logo
MERA-INF目录:保存应用程序的签名信息,签名信息可以验证APK文件的完整性。
MANIFEST.MF
CERT.SF:该文件保存了MANIFEST
CERT.RSA:该文件保存了APK包的签名和证书的公钥信息;
classes.dex (分包处理)
多个dex文件
Resources.arsc
优化dex文件
ProGuard)
minifyEnabled true
proguardFiles getDefaultProguardFile
('
')
ProGuard工作原理
Shrink Optimize (Obfuscate) Preverify
确保
class文件 是可执行的
-verbose -dontshrink
-optimizations !field/,!class/merging/
!code/simplification/arithmetic
artimetic
-keepattributes SourceFile,LineNumberTable抛出异常保留代码行号
.**{*;}
-keepclasseswithmembers class_specification[]
[,modifier,]
-printseeds
-libraryjars libs/alipaysdk.jar
File Filters(../)
!.gif/,images/,匹配images目录下所有除gif格式文件
2.1.2 AndResGuard
AndResGuard
安装包解压 极限压缩
buildscript {
repositories {
jcenter
google
}
}
AndResProguard
tencent.mm.AndResGuard-gradle-plugin
mappingFile = file keep
keepRoot = false
mergeDuplicatedRes = true
specsName ,keep
whiteList= [
R.drawable.icon
R.string.google_app_id
R.string.gcm_defaultSenderId
]
sevenzip {
}
artifact
assemble [BuildType | Flavor]
finalApkBackupPath = ${ project.rootDir} /final.apk
project.rootDir /final.apk
AndResGuard_app
andreguard (23)
java - jar andResGuard.jar
-zipalign -7zip
java -jar andResGuard
2.2 优化资源文件大小
2.2.1 Android Lint Android Lint
metadata(元数据,从
Converting Images to WebP
transparency/alpha channel
Encoding quality用于设置压缩质量;
alpha minSdkVersion
.webp格式图片占用体积相较于jpg图片大约减少40%,相较于无损png图片大约减少30%。目前移动端Android4
.webp
帧动画
尽量不要使用帧动画,因为一个帧动画会包含多张图片;
将较大的资源文件放到服务端,APP启动后自动下载使用;
针对所有屏幕密度,尽量使用一套图片资源、一套布局、多套dimens.xml文件,在使用最小资源的情况下实现多分辨率的适配;
去除无用或功能上重复的第三方库,因为这些库也包含了自身的资源文件;
对assets资源目录作一定取舍,因为该目录资源保存的是原始资源。
多分辨率的适配
assets&res
//移
shrinkResources true
proguardFiles
andreguard
buildTypes
release
2.3 优化libs目录大小
2.3.1 裁剪libs目录
libs
armeabi
Android性能优化(6):浅析类加载机制与热修复技术
https://blog.csdn.net/AndrExpert/article/details/103756056
Java 中的ClassLoader
Android 中的ClassLoader
Tinker
Robust
AndFix
.class java.lang.Class
Link JRE Initialize JVM加载类机制
Bootstap ClassLoader
Extension ClassLoader
java.lang.ClassLoader
.class.getClassLoader
java.class.path
ExtClassLoader
BootstrapClassLoader
BootClassLoader、PathClassLoader和DexClassLoader,
URLClassLoader
SecureClassLoader
Frament Dialog
DownLoadManager
ZygoteInit main BootClassLoader
FindBugsSuppressWarnings
DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED
findClass String name
Class>
//根据指定全类路径创建其Class对象
Class.classForName name,false,null
PathClassLoader
BaseDexClassLoader
SystemServer
optimizedDirectory
BootClassLoader、PathClassLoader和DexClassLoader,
不支持自定义解压dex文件存储路径
解压
/data/dalvik-cache
optimizedDirectory
DexClassLoader extends BaseDexClassLoader
.class.getClassLoader .getParent
PathClassLoader确实是用于加载已经安装了的dex或apk文件,
1.1.2 双亲委托模式
JVM Android Class
其中查找的位置为该层类加载器指定指定加载类的目录
ApplicationClassLoader
java -classpath n java.class.path类和jar包
Extension ClassLoader
java.ext.dirs jar
jre/lib/ext
Bootstap ClassLoader
jre/lib
-XBootclassPath
AppClassLoader BootstrapClassLoader
ExtensionClassLoader
它的字节码内容(静态数据)会被转换成方法区的运行时数据结构
java.lang.Class
1.1.3 ClassLoader的加载过程
loadClassBinaryName defineClass
loadClassBinaryName
defineClass
DexPathList DexFile
Class> c
findLoadedClass name
findBootstrapClassOrNull
JVM classpath Dalvik/ART利用
DexPathList
.dex .jar .zip
nativeLibraryDirectories
systemNativeLibraryDirectories
Element element dexElements
element.findClass
dexElements dex class
Element 封装了 dex文件路径和 DexFile
findClass string name,ClassLoader definingContext,
dexFile.
loadClassBinaryName
defineClass
过程,即首先会遵循双亲委托模式检查此前是否已经加载过传入的类(指的是该类的.class文件),如果没有检查到就调用ClassLoader的findClass方法进行查找流程,Java层最终会调用DexFile的defineClassNative方法来执行查找流程。
ClassLoader
DexFile
defineClassNative
.class文件
DexFile defineClassNative
1.1.4 类的链接
JRE都为其保留一个不变的Class类型的对象,一个Class对象包含了特定某个结构
把类的二进制数据合并到JRE中
加载 验证
primitive/type/void/[]
class getClass
Class clazz = ClassLoader.loadClass("com.jiangdg.test.Person");
ClassLoader.loadClass
Class.forName
1.2 Java反射机制
Java 反射机制
Class Field Method Constructor类
.java->.class
.class->.java
getName getSimpleName getPackage 包名
getDeclaredField setAccessible true
invoke 对象名 方法名
getParameter
getConstructors
2. 热修复技术 hotfix Patch
类加载机制 底层替换机制和Instant Run热插拔机制三种方式实现,并且这些热修复框架的主要提供代码修复、资源修复和动态链接库修复三种核心技术
tinker QZone
不在将patch.dex增加到elements数组中
而是以差量的方式给出patch.dex,然后将patch.dex与应用的classes.dex合并,然后整体替换掉旧的DEX,达到修复的目的。
Base.apk
classes.dex Fix.apk classes.dex
QZone超级补丁基于dex分包(multidex dex)技术实现QZone超级补丁基于dex分包(multidex dex)技术实现
multidex dex
把BUG方法修复以后,放到一个单独的dex补丁文件,让程序在运行期间加载dex补丁,即将dex文件插入到dexElements数组的最前面,然后再让虚拟机执行修复后的方法。
ClassLoader Key.class Patch.dex
Element Classes.dex
AndFix基于在native层动态替换java层的方法技术实现,它是一种方法替换方案,能够及时生效,无需重启APP
AndFix native java
native
ClassObject setup方法 public setFieldFlag方法
replaceMethod方法 AndFix
Instant Run热插拔机制三种方式实现,并且这些热修复框架的主要提供代码修复、资源修复和动态链接库修复三种核心技术
2.2 热修复实战
俗话说:“纵使有千言万语,却抵不过认真撸一次“。为了加深对热修复技术的理解,本小将以基于multidex方案为例,实现一个简化版的热修复框架。该框架实现流程如下:
multidex
dexElements Element[]
patch.dex classes.dex
PathClassLoader DexPathList pathList dexElements
path.dex
patches.add patchFile
odex MODE_PRIVATE
getClassLoader pathClassLoader
pathListField = pathClassLoader,pathList
optimizedDirectory = dex->odex
makePathElementsMethod
List.class File.class List.class
Class> ..paramters
Object[] patchElements = (Object[]) makePathElementsMthod.
invoke(pathList, patches, mOdexDirectory, suppressedExceptions);
Array. newInstance
dexElementsField.set(pathList, newElements);
dexElementsField.set pathList,newElements
编译 启动阶段
dx.bat工具生成补丁包,该工具位于Android
Android SDK
dx.bat工具生成补丁包,该工具位于Android
dx --dex --output= patch.jar
Github项目地址:HotFix
最后,对目前主流的热修复框架作个小结:Android平台的热修复框架种类繁多,
根据实现原理大体可分为三类,即基于multidex、
基于native hook方案以及基于Instant Run热插拔机制。
其中,基于multidex的热修复框架,如NuWa、Tinker、Qzone超级补丁等,
这种方案兼容性高,但是需要反射更改DexElements,改变Dex的加载顺序,
这使得patch需要在下次启动时才能生效,实时性就受到了影响;
基于native hook的热修复框架,如Andfixd等,
这种方法能够实时生效且几乎无性能损耗,
但是需要针对dalvik虚拟机和art虚拟机做适配,
需要考虑指令集的兼容问题,需要native代码支持,兼容性上会有一定的影响。
基于Instant Run热插拔机制,
如Robust,这种方案兼容性高且实时生效,但是会增加包体积,
且暂时不支持so文件和资源替换。
multidex native hook方案以及基于Instant Run热插拔机制。