How to load 100M pictures without bursting memory, a 100M big picture, how to prevent OOM?

I still remember that an interviewer in the interview asked me how to load the giant map so as not to burst the memory. I did not answer. He said that the fragmentation is displayed. I wonder if fragmentation can reduce memory usage? ? Now you can slap him in the face!

Content expansion

1. In the three-level cache of the picture, the picture is loaded into the memory. If the memory is about to burst, what will happen? How to deal with it?
2. If a 500*500 png high-definition picture is loaded in the memory, how much memory should it occupy?
3. How does Bitmap handle a large picture, such as a 30M large picture, and how to prevent OOM?

In Android development, sometimes there is a need to load a giant image. How to load a large image without generating OOM? It BitmapRegionDecodercan be done easily using this class provided by the system .

Effect picture:

BitmapRegionDecoder: Area decoder, can be used to decode a rectangular area image, with this we can customize a rectangular area, and then move the position of the rectangular area according to the gesture to slowly see the whole picture.

The core principle of OK is that simple, but there are still some details to deal with. The following is a step by step to complete a loaded large image, support drag view, double click to zoom, and gesture zoom.


The first step is to initialize variables

  private void init(){
    mOptions = new BitmapFactory.Options();
    //滑动器
    mScroller = new Scroller(getContext());
    //所放器
    mMatrix = new Matrix();
    //手势识别
    mGestureDetector = new GestureDetector(getContext(),this);
    mScaleGestureDetector = new ScaleGestureDetector(getContext(),this);
}

BitmapFactory.OptionsWe are familiar with it, used to configure Bitmap-related parameters, such as getting the width and height of Bitmap, memory reuse and other parameters.

GestureDetectorUsed to identify double-click events and ScaleGestureDetectorto monitor finger zooming events, which are all classes provided by the system, which are more convenient to use.


The second step is to set the picture to be loaded

  public void setImage(InputStream is){
      mOptions.inJustDecodeBounds = true;
      BitmapFactory.decodeStream(is,null,mOptions);
      mImageWidth = mOptions.outWidth;
      mImageHeight = mOptions.outHeight;
      mOptions.inPreferredConfig = Bitmap.Config.RGB_565;
      mOptions.inJustDecodeBounds = false;
      try {
          //区域解码器
          mRegionDecoder = BitmapRegionDecoder.newInstance(is,false);
      } catch (IOException e) {
          e.printStackTrace();
      }
      requestLayout();
  }

Set the picture that needs to be loaded, and you can get an input stream of the picture no matter where the picture is placed, so the input stream is used as the parameter, and BitmapFactory.Optionsthe real width and height of the picture are obtained.

inPreferredConfigBy default Bitmap.Config.ARGB_8888, this parameter is changed to here Bitmap.Config.RGB_565, removing the transparent channel can reduce the memory usage by half. Finally, initialize the region decoder BitmapRegionDecoder.

ARGB_8888It is composed of 4 8 bits that is 32 bits, RGB_565 means R is 5 bits, G is 6 bits, and B is 5 bits, a total of 16 bits


The third step is to obtain the width and height of the View and calculate the zoom value

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
     super.onSizeChanged(w, h, oldw, oldh);
     mViewWidth = w;
     mViewHeight = h;
     mRect.top = 0;
     mRect.left = 0;
     mRect.right = (int) mViewWidth;
     mRect.bottom = (int) mViewHeight;
     mScale = mViewWidth/mImageWidth;
     mCurrentScale = mScale;
  }

onSizeChangedDuring the layout of the method, when the size of this view changes, this method will be called, the first time onMeasureit is called later, you can easily get the width and height of the View.

Then mRectassign values ​​to the top, bottom, left, and right boundaries of our custom rectangle . Under normal circumstances, we use this custom View to display large images, which are all occupying this View, so the initial size of the rectangle here is the same size as the View.

mScaleIt is used to record the original to-to-square ratio and mCurrentScaleto record the current to-to-square ratio, because there are double-tap to zoom in and gesture zoom, which mCurrentScalechange with the gesture.


The fourth step is to draw

  @Override
  protected void onDraw(Canvas canvas) {
      super.onDraw(canvas);
      if(mRegionDecoder == null){
          return;
      }
      //复用内存
      mOptions.inBitmap = mBitmap;
      mBitmap = mRegionDecoder.decodeRegion(mRect,mOptions);
      mMatrix.setScale(mCurrentScale,mCurrentScale);
      canvas.drawBitmap(mBitmap,mMatrix,null);
  }

The drawing is also very simple, decode a rectangular area through the area decoder, return a Bitmap object, and then draw the Bitmap through the canvas. Need to pay attention mOptions.inBitmap = mBitmap; this configuration can reuse memory to ensure that the use of memory is always only a rectangular area.

Run here and you can draw a part of the picture. If you want to see all the pictures, you need to drag your finger to see it, which requires processing various events.


The fifth step is to distribute the event

  @Override
  public boolean onTouchEvent(MotionEvent event) {
      mGestureDetector.onTouchEvent(event);

      mScaleGestureDetector.onTouchEvent(event);
      return true;
  }

onTouchEventThe middle is very simple, the events are all handled by the two gesture detectors.


The sixth step, GestureDetectorthe event being processed

  @Override
  public boolean onDown(MotionEvent e) {
      //如果正在滑动,先停止
      if(!mScroller.isFinished()){
          mScroller.forceFinished(true);
      }
      return true;
  }

When the finger is pressed, if the picture is sliding fast, then stop

  @Override
  public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
      //滑动的时候,改变mRect显示区域的位置
      mRect.offset((int)distanceX,(int)distanceY);
      //处理上下左右的边界
      if(mRect.left<0){
          mRect.left = 0;
          mRect.right = (int) (mViewWidth/mCurrentScale);
      }
      if(mRect.right>mImageWidth){
          mRect.right = (int) mImageWidth;
          mRect.left = (int) (mImageWidth-mViewWidth/mCurrentScale);
      }
      if(mRect.top<0){
          mRect.top = 0;
          mRect.bottom = (int) (mViewHeight/mCurrentScale);
      }
      if(mRect.bottom>mImageHeight){
          mRect.bottom = (int) mImageHeight;
          mRect.top = (int) (mImageHeight-mViewHeight/mCurrentScale);
      }
      invalidate();
      return false;
  }

onScrollIn the middle processing slide, move the rectangle drawing area according to the parameters of the finger movement. Here you need to deal with each boundary point. For example, the minimum on the left is 0, and the maximum on the right is the width of the picture. It cannot exceed the boundary or an error will be reported.

  @Override
  public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
      mScroller.fling(mRect.left,mRect.top,-(int)velocityX,-(int)velocityY,0,(int)mImageWidth
             ,0,(int)mImageHeight);
      return false;
  }

  @Override
  public void computeScroll() {
      super.computeScroll();
      if(!mScroller.isFinished()&&mScroller.computeScrollOffset()){
          if(mRect.top+mViewHeight/mCurrentScale<mImageHeight){
              mRect.top = mScroller.getCurrY();
              mRect.bottom = (int) (mRect.top + mViewHeight/mCurrentScale);
          }
          if(mRect.bottom>mImageHeight) {
              mRect.top = (int) (mImageHeight - mViewHeight/mCurrentScale);
              mRect.bottom = (int) mImageHeight;
          }
          invalidate();
      }
  }

In the onFlingcalling method slider Scrollerof fling method inertial sliding finger off after treatment. The distance of inertial movement computeScroll()is calculated in the View method, and you also need to pay attention to boundary issues and do not slide out of the boundary.


The seventh step is to handle the double-click event

  @Override
  public boolean onDoubleTap(MotionEvent e) {
      //处理双击事件
      if (mCurrentScale>mScale){
          mCurrentScale = mScale;
      } else {
          mCurrentScale = mScale*mMultiple;
      }
      mRect.right = mRect.left+(int)(mViewWidth/mCurrentScale);
      mRect.bottom = mRect.top+(int)(mViewHeight/mCurrentScale);
      //处理边界
      if(mRect.left<0){
          mRect.left = 0;
          mRect.right = (int) (mViewWidth/mCurrentScale);
      }
      if(mRect.right>mImageWidth){
          mRect.right = (int) mImageWidth;
          mRect.left = (int) (mImageWidth-mViewWidth/mCurrentScale);
      }
      if(mRect.top<0){
          mRect.top = 0;
          mRect.bottom = (int) (mViewHeight/mCurrentScale);
      }
      if(mRect.bottom>mImageHeight){
          mRect.bottom = (int) mImageHeight;
          mRect.top = (int) (mImageHeight-mViewHeight/mCurrentScale);
      }

      invalidate();
      return true;
  }

mMultipleTo zoom in several times after double-clicking, set 3 times here. Double-click the first time to zoom in 3 times, and double-click the second time to return to the original state. After the zoom is completed, the boundary of the drawing area needs to be reset according to the current zoom ratio. Finally, you need to reposition the border, because if you use two fingers to zoom in, then double-click to return to the original state. If the border is not processed, the position will be wrong. The code for processing boundaries can be extracted.


The eighth step is to handle the finger zoom event

  @Override
  public boolean onScale(ScaleGestureDetector detector) {
      //处理手指缩放事件
      //获取与上次事件相比,得到的比例因子
      float scaleFactor = detector.getScaleFactor();
  //        mCurrentScale+=scaleFactor-1;
      mCurrentScale*=scaleFactor;
      if(mCurrentScale>mScale*mMultiple){
          mCurrentScale = mScale*mMultiple;
      }else if(mCurrentScale<=mScale){
          mCurrentScale = mScale;
      }
      mRect.right = mRect.left+(int)(mViewWidth/mCurrentScale);
      mRect.bottom = mRect.top+(int)(mViewHeight/mCurrentScale);
      invalidate();
      return true;
  }

  @Override
  public boolean onScaleBegin(ScaleGestureDetector detector) {
      //当 >= 2 个手指碰触屏幕时调用,若返回 false 则忽略改事件调用
      return true;
  }

onScaleBeginThe method needs to return true, otherwise the gesture zoom cannot be detected. onScaleGet the zoom factor in the method, this zoom factor is compared with the last event. Therefore, *= is used here, and mRectthe boundary of the drawing area needs to be reset after completion .

The various functions are completed here~


At last

Finally, I want to say: For programmers, there are too many knowledge content and technologies to learn. If you don’t want to be eliminated by the environment, you have to constantly improve yourself. We always adapt to the environment, not the environment to adapt to us. !

Here the big cattle sorted out Android studying architecture PDF + Video + source notes , as well as advanced technical architecture Advanced Brain Mapping, Android interview with thematic development, senior advanced information architecture to share out, help you learn advanced upgrade, we also save Learn when you search for information online, or you can share it with your friends

If you want the above information, you can follow me [Homepage Introduction] or [Short Letter] Get it for free

It’s easy to be a programmer. Being a good programmer requires continuous learning. From junior programmer to senior programmer, from junior architect to senior architect, or to management, from technical manager to technical director, every stage Need to master different abilities. Determine your career direction early to get rid of your peers in your work and ability improvement.

Guess you like

Origin blog.csdn.net/ajsliu1233/article/details/110452186