在实践的项目开发中,引入图片的办法根本能够分为两种:本地图片和云端图片,关于云端图片来说,能够动态地装备图片的巨细,假如服务端的同伴下发的图片很大导致程序异常,那么能够随时修正端上无法发版修正。可是由于本地引入的图片,e.g. 背景图,位图 过大导致程序呈现异常,则有必要要紧迫发版修正线上用户问题。
其实在开发过程中是能够规避这个问题的,只不过是少了一个check的过程,并且大部分由于开发过程中不标准导致问题的发生,UI切图的不标准,或者开发者没有介意尺寸巨细而随意引入到工程中。由于开发中或许频频地installDebug,一些内存问题并不会发现,而到了线上之后,在用户场景中或许就会呈现。
咱们都知道,大图是导致OOM的真凶之一,对大图做工程级的check,就能防止类似问题的呈现。
1 图片加载到内存的巨细
关于这个问题,同伴们应该也比较熟悉,看下面这张图
这张图的总巨细只需73kb,可是这个是一个具有欺骗性的数据,这个巨细只会决议传输速度,而不是在内存中就占用73kb,那么怎样核算这张图片加载到内存中有多大呢?其实是有一个核算公式的:分辨率 * 每个像素点巨细。
所以这张图片加载到内存中占用:512 x 432 x 4(从图中看,一个像素32bit = 4Byte) = 884736Byte
也便是说,这张图片加载到内存中就需求占用885k的内存空间,并且体系关于res目录下的资源加载,假如是不同的drawable目录,例如drawable-xhdpi、drawable-xxhdpi,都是先进行分辨率的转化,再加载到内存。 并且咱们在显现这张图片的时分,设置ImageView的巨细为40dpx40dp,明显这张图片是过大了。
2 大图检测插件的落地
前面咱们说到,假如呈现这种大图,加载到内存中其实会糟蹋一些内存资源,那么咱们有什么手段去做做优化或者防止这种状况发生呢?
2.1 大图加载的优化策略
其实图片在加载到内存中时,便是会走体系的BitmapFactory工厂类,在BitmapFactory.Option中也是供给了对应的办法;
(1)在没有加载到内存之前,获取图片的宽高,进行等比缩放;
(2)经过inBitmap完结内存复用。
想要具体了解其间的完结原理,能够看下面的两篇文章:
Android进阶宝典 — 学会Bitmap内存管理,你的App内存还会暴增吗?
Android性能优化 — 大图治理
除此之外,咱们还会用到一些图片加载框架,像Glide,它能够在加载的图片的时分,根据容器的巨细按需加载,可是也是存在局限,便是无法处理xml文件中的background特点或者src特点,也就本地的图片无法做到兼容处理,所以使用Glide依然无法做到工程等级的大图兼容问题。
所以本文介绍的大图检测插件,便是处理Glide无法兼容本地图片加载的问题,关于开发者引入的大图能够在运行的时分做检测,并提示开发者存在不合理的大图,需求进行修正。
2.2 大图检测的思维
其实咱们在加载本地图片的时分,大部分都是经过ImageView来进行展现,即便是自定义View,也都是经过承继自ImageView或者AppCompatImageView来进行逻辑处理,因而咱们需求关注下ImageView展现图片的逻辑。
public void setImageDrawable(@Nullable Drawable drawable) {
if (mDrawable != drawable) {
mResource = 0;
mUri = null;
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
updateDrawable(drawable);
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
}
@android.view.RemotableViewMethod(asyncImpl="setImageIconAsync")
public void setImageIcon(@Nullable Icon icon) {
setImageDrawable(icon == null ? null : icon.loadDrawable(mContext));
}
@android.view.RemotableViewMethod
public void setImageBitmap(Bitmap bm) {
// Hacky fix to force setImageDrawable to do a full setImageDrawable
// instead of doing an object reference comparison
mDrawable = null;
if (mRecycleableBitmapDrawable == null) {
mRecycleableBitmapDrawable = new BitmapDrawable(mContext.getResources(), bm);
} else {
mRecycleableBitmapDrawable.setBitmap(bm);
}
setImageDrawable(mRecycleableBitmapDrawable);
}
@android.view.RemotableViewMethod(asyncImpl="setImageResourceAsync")
public void setImageResource(@DrawableRes int resId) {
// The resource configuration may have changed, so we should always
// try to load the resource even if the resId hasn't changed.
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
updateDrawable(null);
mResource = resId;
mUri = null;
resolveUri();
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
咱们看这几个比较常用的办法,其实终究收口都是调用了setImageDrawable办法,即便是传入了Bitmap,那么也会将其转化成一个Drawable目标并调用setImageDrawable,所以咱们要做大图检测一定要找一个收口的地方,因而在setImageDrawable办法调用的时分,检测当时图片的巨细是否超过了ImageView的巨细,就能判别是否是一张大图了。
public class MyImageView extends ImageView {
public MyImageView(Context context) {
super(context);
}
public MyImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public MyImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public void setImageDrawable(@Nullable Drawable drawable) {
super.setImageDrawable(drawable);
//容器的巨细
int widthContainer = this.getWidth();
int heightContainer = this.getHeight();
if (drawable != null){
//获取图片的巨细
int intrinsicWidth = drawable.getIntrinsicWidth();
int intrinsicHeight = drawable.getIntrinsicHeight();
//只需有一个方向超了,都会报警
if (intrinsicWidth > widthContainer * 2 || intrinsicHeight > heightContainer * 2 ){
Log.e("BigViewCheck","BigViewCheck | checkIsBigView | $drawable $intrinsicWidth * $intrinsicHeight is bigView");
}
}
}
}
所以在ImageView履行setImageDrawable办法的时分,经过字节码插桩的形式刺进这个办法中的代码,就能够完结大图的检测。
2.3 大图检测插件完结
假如不熟悉字节码插桩的同伴,能够回头看看这篇文章Android 字节码插桩全流程解析
大图检测的插件:
public class ViewCheckPlugin implements Plugin<Project> {
@Override
public void apply(Project project) {
AppExtension extension = project.getExtensions().getByType(AppExtension.class);
if (extension != null){
extension.registerTransform(new ViewCheckTransform());
}
}
}
2.3.1 Transform的完结
public class ViewCheckTransform extends Transform {
@Override
public String getName() {
return "ViewCheckTransform";
}
@Override
public Set<QualifiedContent.ContentType> getInputTypes() {
return TransformManager.CONTENT_CLASS;
}
@Override
public Set<QualifiedContent.Scope> getScopes() {
HashSet hashSet = new HashSet();
hashSet.add(QualifiedContent.Scope.PROJECT);
hashSet.add(QualifiedContent.Scope.SUB_PROJECTS);
hashSet.add(QualifiedContent.Scope.EXTERNAL_LIBRARIES);
return hashSet;
}
@Override
public boolean isIncremental() {
return false;
}
@Override
public void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
System.out.println("ViewCheckTransform start transform");
//需求对jar包处理,ImageView存在Android SDK的jar包里
inputs.forEach(new Consumer<TransformInput>() {
@Override
public void accept(TransformInput transformInput) {
//文件夹交给下一级Transform即可,DirectoryInput仅限于咱们自己的工程下的目录
transformInput.getDirectoryInputs().forEach(new Consumer<DirectoryInput>() {
@Override
public void accept(DirectoryInput directoryInput) {
File dest = outputProvider.getContentLocation(directoryInput.getName(),
directoryInput.getContentTypes(),
directoryInput.getScopes(), Format.DIRECTORY);
try {
FileUtils.copyDirectory(directoryInput.getFile(),dest);
} catch (IOException e) {
e.printStackTrace();
}
}
});
transformInput.getJarInputs().forEach(new Consumer<JarInput>() {
@Override
public void accept(JarInput jarInput) {
findClass(jarInput.getFile());
File dest = outputProvider.getContentLocation(jarInput.getName(), jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR);
try {
FileUtils.copyFile(jarInput.getFile(),dest);
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
});
}
private void findClass(File file){
if (file.isDirectory()){
for (File temp:file.listFiles()){
findClass(temp);
}
}else {
//假如是文件,判别是不是ImageView
handleFile(file);
}
}
private void handleFile(File file) {
System.out.println("ViewCheckTransform | handleFile | "+file);
}
}
已然咱们想要找到ImageView,那么关于getDirectoryInputs,它只限于在咱们自己写的工程代码中进行插桩处理,所以需求对getJarInputs中拿到的全部的jar包进行处理,终究生成一个新的jar包(对ImageView做过处理),交给下一个Transform来处理。
2.3.2 jar包处理
由于现在看到网上关于jar包处理的文章很少,鉴于此我这里做一次比较具体的介绍,由于在日常的开发中或许不仅仅局限于咱们对业务插桩,关于体系源码的Hook也会有。
当咱们拿到每一个jar包之后,咱们能够经过JarFile类来进行jar包文件的读取,从中获取是否存在咱们想要的AppCompatImageView这个类的class文件。
private void handleFile(File file) {
try {
JarFile jarFile = new JarFile(file);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
if (jarEntry.getName().startsWith("androidx/appcompat/widget/AppCompatImageView")){
System.out.println("ViewCheckTransform | handleFile | jarEntry " + jarEntry);
InputStream inputStream = jarFile.getInputStream(jarEntry);
System.out.println("ViewCheckTransform | inputStream "+inputStream);
//完结字节码插桩
handleASM(inputStream);
}
}
} catch (Exception exp) {
}
}
在JarFile中,每个class文件都是一个JarEntry个别,咱们能够经过获取JarEntry的name来判别,是否是androidx/appcompat/widget/AppCompatImageView这个类,假如获取到这类的class文件之后,能够经过getInputStream办法来获取class文件的输入流,进行字节码插桩。
private byte[] handleASM(InputStream inputStream) {
//
try {
ClassReader classReader = new ClassReader(inputStream);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
classReader.accept(new ViewCheckVisitor(Opcodes.ASM9,classWriter),ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
return classWriter.toByteArray();
} catch (Exception e) {
e.printStackTrace();
}
}
关于ClassVisitor,我这里就不再具体赘述了,首要便是用来遍历拜访这个类中的一切办法,能够在这个类办法履行之前和履行之后,进行字节码的刺进,代码如下:
public class ViewCheckVisitor extends ClassVisitor {
public ViewCheckVisitor(int api) {
super(api);
}
public ViewCheckVisitor(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
System.out.println("ViewCheckVisitor | visitMethod | name "+name);
MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
return new ViewCheckAdapter(api, mv, access, name, descriptor);
}
}
public class ViewCheckAdapter extends AdviceAdapter {
/**
* Constructs a new {@link AdviceAdapter}.
*
* @param api the ASM API version implemented by this visitor. Must be one of {@link
* Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
* @param methodVisitor the method visitor to which this adapter delegates calls.
* @param access the method's access flags (see {@link Opcodes}).
* @param name the method's name.
* @param descriptor the method's descriptor (see {@link Type Type}).
*/
protected ViewCheckAdapter(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
super(api, methodVisitor, access, name, descriptor);
}
@Override
protected void onMethodEnter() {
super.onMethodEnter();
}
}
从下面打印出来的日志就能够看到,咱们之前说到的ImageView中的核心办法都能够拜访到,咱们首要便是拜访setImageDrawable这个办法。
ViewCheckVisitor | visitMethod | name <init>
ViewCheckVisitor | visitMethod | name <init>
ViewCheckVisitor | visitMethod | name <init>
ViewCheckVisitor | visitMethod | name setImageResource
ViewCheckVisitor | visitMethod | name setImageDrawable
ViewCheckVisitor | visitMethod | name setImageBitmap
ViewCheckVisitor | visitMethod | name setImageURI
ViewCheckVisitor | visitMethod | name setBackgroundResource
ViewCheckVisitor | visitMethod | name setBackgroundDrawable
ViewCheckVisitor | visitMethod | name setSupportBackgroundTintList
ViewCheckVisitor | visitMethod | name getSupportBackgroundTintList
ViewCheckVisitor | visitMethod | name setSupportBackgroundTintMode
ViewCheckVisitor | visitMethod | name getSupportBackgroundTintMode
ViewCheckVisitor | visitMethod | name setSupportImageTintList
ViewCheckVisitor | visitMethod | name getSupportImageTintList
ViewCheckVisitor | visitMethod | name setSupportImageTintMode
ViewCheckVisitor | visitMethod | name getSupportImageTintMode
ViewCheckVisitor | visitMethod | name drawableStateChanged
ViewCheckVisitor | visitMethod | name hasOverlappingRendering
ViewCheckVisitor | visitMethod | name setImageLevel
先不着急插桩,先考虑一下,当完结字节码插桩之后,怎样替换jar包中的class文件呢?
首要先告知大家,假如在jar包中修正了class文件,是不能够直接原路返回写进原先的jar包中,这样会破坏jar包的文件结构,因而需求新建一个jar包,然后完结jar的替换即可。
private File handleJarFile(File file) {
try {
//将jar包读写到内存中
JarFile jarFile = new JarFile(file);
//创立一个新的jar包
File newJarFile = new File(file.getParentFile(), "temp_" + file.getName());
System.out.println("ViewCheckTransform | newJarFile | name " + getName());
if (newJarFile.exists()) newJarFile.delete();
JarOutputStream jos =
new JarOutputStream(new BufferedOutputStream(new FileOutputStream(newJarFile)));
//读取jar包内容
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
//获取jar包文件输入流
JarEntry jarEntry = entries.nextElement();
InputStream inputStream = jarFile.getInputStream(jarEntry);
//先往新jar包里加Entry
jos.putNextEntry(new JarEntry(jarEntry.getName()));
//判别是否需求字节码插桩
if (jarEntry.getName().startsWith("androidx/appcompat/widget/AppCompatImageView")) {
System.out.println("ViewCheckTransform | handleFile | jarEntry " + jarEntry);
System.out.println("ViewCheckTransform | inputStream " + inputStream);
//完结字节码插桩
byte[] bytes = handleASM(inputStream);
jos.write(bytes);
jos.flush();
inputStream.close();
} else {
//假如不需求修正,那么就把entry写到新的jar里就行了
jos.write(IOUtils.toByteArray(inputStream));
inputStream.close();
}
}
//当时jar包处理完结
jarFile.close();
jos.closeEntry();
jos.flush();
jos.close();
return newJarFile;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
具体的逻辑,我这里就不再多讲了,同伴们假如有疑问能够直接在谈论区问,首要的思维便是经过创立一个新的jar包,然后将不需求插桩的文件流写入到新的jar包以及需求插桩的并且修正往后的文件写入新的jar包。
private File findClass(File file) {
if (file.isDirectory()) {
for (File temp : file.listFiles()) {
findClass(temp);
}
} else {
//假如是文件,判别是不是ImageView
return handleJarFile(file);
}
return null;
}
transformInput.getJarInputs().forEach(new Consumer<JarInput>() {
@Override
public void accept(JarInput jarInput) {
File srcFile = findClass(jarInput.getFile());
System.out.println("ViewCheckTransform | findClass | srcFile "+srcFile);
File dest = outputProvider.getContentLocation(jarInput.getName(), jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR);
try {
FileUtils.copyFile(srcFile, dest);
} catch (IOException e) {
e.printStackTrace();
}
}
});
终究遍历完结一切的jar包之后,拿到新的jar包,将它扔给下一级的Transform来进行处理。
2.3.3 完结代码插桩逻辑
一切的插桩逻辑都是在onMethodEnter中,也便是在办法履行之前履行,这里先简略打一条日志,看是否是插桩成功 的。
@Override
protected void onMethodEnter() {
super.onMethodEnter();
visitLdcInsn("ViewCheck");
visitLdcInsn("\u5f00\u542f\u63d2\u6869\u4e86");
visitMethodInsn(INVOKESTATIC, "android/util/Log", "e", "(Ljava/lang/String;Ljava/lang/String;)I", false);
visitInsn(POP);
}
经过日志看好像是成功了,可是并不知道是哪个页面调用了setImageDrawable办法。
2023-04-08 21:02:55.356 12309-12309/com.lay.dm E/ViewCheck: 敞开插桩了
2023-04-08 21:02:56.172 12309-12309/com.lay.dm E/ViewCheck: 敞开插桩了
2023-04-08 21:02:56.286 12309-12309/com.lay.dm E/ViewCheck: 敞开插桩了
2023-04-08 21:02:56.648 12309-12309/com.lay.dm E/ViewCheck: 敞开插桩了
2023-04-08 21:02:56.966 12309-12309/com.lay.dm E/ViewCheck: 敞开插桩了
经过前面咱们写的一段需求注入的代码,终究生成的字节码文件如下:
L0
LINENUMBER 36 L0
LDC "ViewCheck"
LDC "\u5f00\u542f\u63d2\u6869\u4e86"
INVOKESTATIC android/util/Log.e (Ljava/lang/String;Ljava/lang/String;)I
POP
L1
LINENUMBER 37 L1
ALOAD 0
INVOKEVIRTUAL com/lay/dm/MyImageView.getWidth ()I
ISTORE 2
L2
LINENUMBER 38 L2
ALOAD 0
INVOKEVIRTUAL com/lay/dm/MyImageView.getHeight ()I
ISTORE 3
L3
LINENUMBER 40 L3
ALOAD 1
IFNULL L4
L5
LINENUMBER 42 L5
ALOAD 1
INVOKEVIRTUAL android/graphics/drawable/Drawable.getIntrinsicWidth ()I
ISTORE 4
L6
LINENUMBER 43 L6
ALOAD 1
INVOKEVIRTUAL android/graphics/drawable/Drawable.getIntrinsicHeight ()I
ISTORE 5
L7
LINENUMBER 46 L7
ILOAD 4
ILOAD 2
ICONST_2
IMUL
IF_ICMPGT L8
ILOAD 5
ILOAD 3
ICONST_2
IMUL
IF_ICMPLE L4
L8
LINENUMBER 47 L8
FRAME FULL [com/lay/dm/MyImageView android/graphics/drawable/Drawable I I I I] []
LDC "BigViewCheck"
LDC "BigViewCheck | checkIsBigView | $drawable $intrinsicWidth * $intrinsicHeight is bigView"
INVOKESTATIC android/util/Log.e (Ljava/lang/String;Ljava/lang/String;)I
POP
L4
LINENUMBER 50 L4
FRAME FULL [com/lay/dm/MyImageView android/graphics/drawable/Drawable I I] []
RETURN
L9
LOCALVARIABLE intrinsicWidth I L6 L4 4
LOCALVARIABLE intrinsicHeight I L7 L4 5
LOCALVARIABLE this Lcom/lay/dm/MyImageView; L0 L9 0
LOCALVARIABLE drawable Landroid/graphics/drawable/Drawable; L0 L9 1
LOCALVARIABLE widthContainer I L2 L9 2
LOCALVARIABLE heightContainer I L3 L9 3
MAXSTACK = 3
MAXLOCALS = 6
其实这里面有一个特别坑的地方,我大约花了一天的时间,才发现其间的问题,咱们看一下L3中的这段字节码,
L3
LINENUMBER 40 L3
ALOAD 1
IFNULL L4
这段字节码代表,假如var1,便是办法中传入的第一个参数为空,那么就跳转到L4中。
L4
LINENUMBER 50 L4
FRAME FULL [com/lay/dm/MyImageView android/graphics/drawable/Drawable I I] []
RETURN
像FRAME FULL这种需求核算栈帧的,其实能够在ClassWriter中选择COMPUTE_MAXS或者COMPUTE_FRAME,后者包含前者的功能,就不需求核算栈帧,而是会帮你自定核算。
然后L4中最后一个字节码是RETURN,坑就在这里,由于是往ImageView的源码中刺进代码,其实在setImageDrawable办法中也存在一些源码,咱们只是在办法开端的时分刺进代码,因而在ASM插桩的时分,我调用了visitInsn(RETURN),结果发现setImageDrawable源码中的代码没有了,只需刺进的代码。
后来在查资料的时分发现,本来调用visitInsn(RETURN)是会清除办法体中的代码,才导致体系的源码找不见了,终于处理了我心中的抑郁,同伴们在碰到这种状况的时分,关于RETURN能够视状况不用处理。
@Override
protected void onMethodEnter() {
super.onMethodEnter();
visitLdcInsn("ViewCheck");
visitLdcInsn("\u5f00\u542f\u63d2\u6869\u4e86");
visitMethodInsn(INVOKESTATIC, "android/util/Log", "e", "(Ljava/lang/String;Ljava/lang/String;)I", false);
visitInsn(POP);
visitVarInsn(ALOAD, 0);
visitMethodInsn(INVOKEVIRTUAL, "android/widget/ImageView", "getWidth", "()I");
visitVarInsn(ISTORE, 2);
visitVarInsn(ALOAD, 0);
visitMethodInsn(INVOKEVIRTUAL, "android/widget/ImageView", "getHeight", "()I");
visitVarInsn(ISTORE, 3);
visitVarInsn(ALOAD, 1);
Label nullLabel = new Label();
visitJumpInsn(IFNULL, nullLabel);
visitVarInsn(ALOAD, 1);
visitMethodInsn(INVOKEVIRTUAL, "android/graphics/drawable/Drawable", "getIntrinsicWidth", "()I");
visitVarInsn(ISTORE, 4);
visitVarInsn(ALOAD, 1);
visitMethodInsn(INVOKEVIRTUAL, "android/graphics/drawable/Drawable", "getIntrinsicHeight", "()I");
visitVarInsn(ISTORE, 5);
visitVarInsn(ILOAD, 4);
visitVarInsn(ILOAD, 2);
visitInsn(ICONST_2);
visitInsn(IMUL);
Label printLabel = new Label();
visitJumpInsn(IF_ICMPGT, printLabel);
visitVarInsn(ILOAD, 5);
visitVarInsn(ILOAD, 3);
visitInsn(ICONST_2);
visitInsn(IMUL);
visitJumpInsn(IF_ICMPLE, nullLabel);
visitLabel(printLabel);
visitLdcInsn("BigViewCheck");
visitLdcInsn("BigViewCheck | checkIsBigView | $drawable $intrinsicWidth * $intrinsicHeight is bigView");
visitMethodInsn(INVOKESTATIC, "android/util/Log", "e", "(Ljava/lang/String;Ljava/lang/String;)I", false);
visitInsn(POP);
visitLabel(nullLabel);
}
上面便是整个插桩的代码,其实这个算是比较复杂的了,假如把握了这些场景中的代码,后续根本上一切的问题就都不是问题了,咱们看下插桩后,体系AppCompatImageView的class文件是什么样的。
public void setImageDrawable(@Nullable Drawable var1) {
Log.e("ViewCheck", "敞开插桩了");
int var2 = this.getWidth();
int var3 = this.getHeight();
if (var1 != null) {
int var4 = var1.getIntrinsicWidth();
int var5 = var1.getIntrinsicHeight();
if (var4 > var2 * 2 || var5 > var3 * 2) {
Log.e("BigViewCheck", "BigViewCheck | checkIsBigView | $drawable $intrinsicWidth * $intrinsicHeight is bigView");
}
}
if (this.mImageHelper != null && var1 != null && !this.mHasLevel) {
this.mImageHelper.obtainLevelFromDrawable(var1);
}
super.setImageDrawable(var1);
if (this.mImageHelper != null) {
this.mImageHelper.applySupportImageTint();
if (!this.mHasLevel) {
this.mImageHelper.applyImageLevel();
}
}
}
咱们能够看到,在setImageDrawable中,咱们刺进的代码已经收效了。
但这里有一个问题便是,假如在setImageDrawable的时分,去获取容器的宽高,这个时分,拿到的或许是0,由于view还没有完全渲染完结,因而最好调用View # post,完结宽高的获取。
其实这篇文章更多的是介绍一个思路吧,如何去往体系sdk中的办法中进行字节码插桩,关于TransformInputs # jarInputs 的处理,体系jar包的替换等,假如想要获取其他场景中的数据,能够自行扩展。