Public number: byte array

Hope to help you 🤣🤣

Loading an image is a very common operation, but it is also a “costly” activity, because loading an image may require multiple network requests, I/O reads and writes, memory usage, and so on. We usually load images through open source libraries such as Coil and Glide, and do not care about the loading process, which may hide a very unreasonable situation: the loaded image is a large image, which is beyond the display requirements

Loading images required for display will cause unnecessary performance waste, and may cause OOM. Therefore, one point of application performance optimization is to detect the global image loading status of the application. This article will introduce how to achieve global image detection through bytecode pegs

First of all, what kind of picture is a big picture? I think it can be determined from two aspects:

  • The image size is larger than the ImageView itself. For example, ImageView is only 100 dp wide and high, but the image has 200 DP
  • The size of an image exceeds a threshold. For example, we can stipulate that a single image cannot exceed 1 MB at most, and any image larger than that is considered a large image

There are two types of ImageView used in our project:

  • System built inandroid.widget.ImageView. It is usually passed in an XML file<ImageView/>Tag for use
  • Developer custom ImageView subclass. It is also commonly used in XML files

Therefore, the basic implementation idea is: When the setImageDrawable and setImageBitmap methods are called, a unified ImageView is defined for project global use instead of the built-in ImageView and the direct parent of each custom subclass. The size and size of Drawable are detected, and the data is reported according to the actual service situation when a large graph is detected

open class MonitorImageView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : android.widget.ImageView(context, attrs, defStyleAttr), MessageQueue.IdleHandler {

    companion object {

        private const val MAX_ALARM_IMAGE_SIZE = 1024

    }

    override fun setImageDrawable(drawable: Drawable?). {
        super.setImageDrawable(drawable)
        monitor()
    }

    override fun setImageBitmap(bm: Bitmap?). {
        super.setImageBitmap(bm)
        monitor()
    }

    private fun monitor(a) {
        Looper.myQueue().removeIdleHandler(this)
        Looper.myQueue().addIdleHandler(this)}override fun queueIdle(a): Boolean {
        checkDrawable()
        return false
    }

    private fun checkDrawable(a) {
        valmDrawable = drawable ? :return
        val drawableWidth = mDrawable.intrinsicWidth
        val drawableHeight = mDrawable.intrinsicHeight
        val viewWidth = measuredWidth
        val viewHeight = measuredHeight
        val imageSize = calculateImageSize(mDrawable)
        if (imageSize > MAX_ALARM_IMAGE_SIZE) {
            log(log = "Image size out of order ->$imageSize")}if (drawableWidth > viewWidth || drawableHeight > viewHeight) {
            log(log = > drawable:$drawableWidth x $drawableHeightThe view:$viewWidth x $viewHeight")}}private fun calculateImageSize(drawable: Drawable): Int {
        return when (drawable) {
            is BitmapDrawable -> {
                drawable.bitmap.byteCount
            }
            else- > {0}}}private fun log(log: String) {
        Log.e(javaClass.simpleName, log)
    }

}
Copy the code

Of course, it is not possible to hardcode directly to modify the original logic in the project; it is too costly, inflexible, and does not take into account external dependencies. At this point, bytecode staking becomes a more economical and efficient scheme to achieve multi-project reuse

For a developer-defined ImageView subclass, we simply check that the current Class inherits directly from the system’s ImageView during the Transform phase and change it to inherit from MonitorImageView. A bit more cumbersome is the
tag declared in XML

We know that each control declared in the layout file corresponds to a specific View instance object when used, and that converting a static XML declaration into a dynamic instance object requires parsing the XML file and reflecting the instance object according to the classpath. This part of the logic is hidden in the LayoutInflater, which parses the layoutResID we passed in

When we are in the new Activity today, on the other hand, are generally not direct inheritance in the system’s built-in android. The app. The Activity, but instead USES androidx. Appcompat. App. AppCompatActivity, Appcompatactivities provide more compatibility guarantees, including layoutInflaters for custom implementations

AppCompatActivity parses XML files on AppCompatViewInflater, and when you know that you are declaring a system control (such as TextView, ImageView, Button, etc.), The corresponding AppCompatXXX will be used to generate instance objects, and ImageView will correspond to AppCompatImageView

So, in most cases we use the ImageView instance corresponding are androidx. Appcompat. Widget. AppCompatImageView, rather than android. Widget. The ImageView. This provides a hook point: as long as we can modify the parent class of AppCompatImageView to our custom MonitorImageView, we can implement a unified big picture detection function for the whole application

With the above ideas, the corresponding peg code is very simple

class LegalBitmapTransform(private val config: LegalBitmapConfig) : BaseTransform() {

    private companion object {

        private const val ImageViewClass = "android/widget/ImageView"

        private const val AppCompatImageViewClass = "androidx/appcompat/widget/AppCompatImageView"

    }

    override fun modifyClass(byteArray: ByteArray): ByteArray {
        val classReader = ClassReader(byteArray)
        val className = classReader.className
        val superName = classReader.superName
        Log.log("className: $className superName: $superName")
        return if((superName == ImageViewClass && className ! = config.formatMonitorImageViewClass) || className == AppCompatImageViewClass) {val classWriter = ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
            val classVisitor = object : ClassVisitor(Opcodes.ASM6, classWriter) {
                override fun visit(
                    version: Int,
                    access: Int,
                    name: String? , signature:String? , superName:String? , interfaces:Array<out String>? {
                    super.visit(
                        version,
                        access,
                        name,
                        signature,
                        config.formatMonitorImageViewClass,
                        interfaces
                    )
                }
            }
            classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES)
            classWriter.toByteArray()
        } else {
            byteArray
        }
    }

}
Copy the code

Finally also give the complete source code: ASM_Transform

For more practical scenarios of bytecode staking, see here:

  • ASM bytecode staking: double – click anti-shake
  • ASM bytecode staking: Thread remediation
  • ASM bytecode Staking: Facilitating privacy compliance