Gradle plugin + ASM technology is used in many scenarios and open source frameworks. Gradle plugin + ASM technology is used in many scenarios. Such as method time monitoring, code coverage statistics, automatic burying points and so on, so we must master this basic knowledge, the development process will bring a lot of unexpected effects.

1. Demand background

Image loading is the most common scene in our development process, but few students will take the initiative to monitor its legitimacy, for example, we may load a picture of several M, for example, we set the background size of a picture may be 720*1080 while our actual View control is only 100dp*100dp, For example, bugly also has OOM caused by a large number of pictures. We usually use Glide, Picasso or Fresco, so we probably don’t have a problem with third-party libraries. Or I always think about that when I write code, and we have code review mechanisms and so on. But there are surprises, and the only way to make sure things don’t go wrong is if machines don’t cheat. So our requirement this time is to monitor for some abnormal image operations like the ones I just mentioned.

2. Implementation scheme

There are many ways to implement it. ArtHook, ASM plugin and Native Hook that I know about are artHook, ASM plugin and Native Hook. First of all, artHook is not very compatible, so we will give up artHook first. Industry open source solutions like Didi Dokit are implemented using ASM instrumentation, but the idea is to use four mainstream frameworks, Glide, Picasso, Fresco and Image Loader. As mentioned in the last post, it is absolutely necessary to find hook points, which means that if you want to implement a set of your own, you must be familiar with the source code of the four open source frameworks. Of course, I believe that you are familiar with the source of several frameworks should be a piece of cake, but companies usually have their own set of image loading libraries, may be based on these open source frameworks to modify the source, so we had to think of a more general and simple implementation. Of course, didi’s implementation of this scheme also has a benefit, such as access to the image loading address and so on. If you meet the above requirements, just integrate Dokit directly into your project.

Is there a way to get rid of all the adaptations? The answer is certainly there, depending on our willingness to toss and change. We haven’t talked about native hook yet, so what I can think of so far is the setImageBitmap, setImageDrawable and other related functions that hook ImageView, because we can load directly locally or use third-party open source framework. It’s all going to end up inside these functions. The problem is that we have analyzed the asm implementation principle before, and it cannot be done directly, so we have to figure out a way to solve this problem. We first define a custom ImageView and then inherit from the system’s ImageView, then duplicate the methods I just mentioned, and finally we use a custom plug-in to replace all the imageViews in the source code with our own custom ImageView. This makes the plug-in code extremely simple and, secondly, generic.

3. Core code

@override public void setImageDrawable(@nullable Drawable Drawable) {super.setimagedRawable (Drawable Drawable); addImageLegalMonitor(); } @Override public void setImageBitmap(Bitmap bm) { super.setImageBitmap(bm); addImageLegalMonitor(); } @Override public void setImageResource(int resId) { super.setImageResource(resId); addImageLegalMonitor(); } @override public void setBackgroundDrawable(Drawable background) { super.setBackgroundDrawable(background); addImageLegalMonitor(); } @Override public void setBackground(Drawable background) { super.setBackground(background); addImageLegalMonitor(); } @Override public void setBackgroundResource(int resid) { super.setBackgroundResource(resid); addImageLegalMonitor(); } private void addImageLegalMonitor() {looper.myQueue ().removeIdleHandler(this); Looper.myQueue().addIdleHandler(this); } @Override public boolean queueIdle() { try { Drawable drawable = getDrawable(); Drawable background = getBackground(); if (drawable ! = null) {checkIsLegal(drawable, ""); } if (background ! = null) {checkIsLegal(background, "background "); } } catch (Exception e) { e.printStackTrace(); } return false; */ private void checkIsLegal(Drawable Drawable, String tag) {int viewWidth = getMeasuredWidth(); int viewHeight = getMeasuredHeight(); int drawableWidth = drawable.getIntrinsicWidth(); int drawableHeight = drawable.getIntrinsicHeight(); Int imageSize = calculateImageSize(drawable); If (imageSize > MAX_ALARM_IMAGE_SIZE) {log. e(TAG, "imageSize is invalid," + TAG + "size ->" + imageSize); dealWarning(drawableWidth, drawableHeight, imageSize, drawable); } if (MAX_ALARM_MULTIPLE * viewWidth < drawableWidth) {log. e(TAG, "viewWidth +", "+ tag +" -> "+ drawableWidth); dealWarning(drawableWidth, drawableHeight, imageSize, drawable); } if (MAX_ALARM_MULTIPLE * viewHeight < drawableHeight) {log. e(TAG, "+ viewHeight +", "+ tag +" -> "+ drawableHeight) dealWarning(drawableWidth, drawableHeight, imageSize, drawable); } /** * private void dealWarning(int drawableWidth, int drawableHeight, int imageSize, Drawable Drawable) {// The online and offline processing methods need to be inconsistent, pseudo code // online pop-up prompt window to output information, and provide a close to open switch //...... // The code information needs to be collected offline. Where is the code, and the information should be reported to the server //...... */ private int calculateImageSize(drawable drawable) {if (drawable instanceof BitmapDrawable) {if (drawable instanceof BitmapDrawable) {  Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); return bitmap.getByteCount(); } int pixelSize = drawable.getOpacity() ! = PixelFormat.OPAQUE ? 4:2; return pixelSize * drawable.getIntrinsicWidth() * drawable.getIntrinsicHeight(); }Copy the code

The plug-in code

public class MonitorImageViewClassVisitor extends ClassVisitor { private static final String MONITOR_SUPER_CLASS_NAME = "com/tencent/eagle/MonitorImageView"; private static final String IMAGE_VIEW_CLASS_NAME = "android/widget/ImageView"; LifecycleClassVisitor(ClassVisitor cv) { super(Opcodes.ASM5, cv); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {// Replace operations if (! name.equals(MONITOR_SUPER_CLASS_NAME) && superName.equals(IMAGE_VIEW_CLASS_NAME)) { superName = MONITOR_SUPER_CLASS_NAME; } super.visit(version, access, name, signature, superName, interfaces); }}Copy the code

4. Write at the end

It is not recommended that everyone should implement a set of their own, but it should be a necessary skill for us to understand the implementation principle, find and improve some existing deficiencies, and be able to customize according to their own projects. Even if you just integrate the Dokit solution directly into your project, the purpose of this article will be met.

In the past, when talking about framework principles and C++ and Linux, we may feel that such advanced things, small and medium-sized enterprises simply difficult to use, but now to the performance optimization I think we should be able to use, as long as we spend a little time to integrate into the project, we can find all kinds of problems.

Video address: pan.baidu.com/s/1Tm-Fcz3O… Video password: J4D3