Android 성능 튜닝을 위한 비트맵 최적화

배경

Android 개발에서 너무 많은 사진이나 너무 큰 사진을 로드하면 일반적인 메모리 오버플로인 OutOfMemoryError 예외가 쉽게 발생할 수 있습니다. Android는 단일 애플리케이션에 메모리 제한을 부과하기 때문에 기본 할당 메모리는 시스템에 따라 몇 M에 불과합니다. 로드된 이미지가 JPG와 같은 압축 형식인 경우(JPG는 최고 수준의 압축을 지원하지만 압축은 손실이 많습니다.) 메모리에서 확장하면 메모리 공간을 많이 차지하며 메모리 오버플로우가 발생하기 쉽습니다. 따라서 Bitmap을 효율적으로 로드하는 것이 매우 중요합니다. 비트맵은 Android에서 그림을 의미하며 그림의 형식은 .jpg .png .webp 및 기타 일반적인 형식입니다.

그림 형식을 선택하는 방법

하나의 원칙: 이미지가 시각적으로 왜곡되지 않는다는 전제 하에 가능한 한 크기를 줄입니다.

Android에서 현재 일반적으로 사용되는 사진 형식은 png, jpeg 및 webp입니다.

  • png : 무손실 압축 이미지 형식, 알파 채널 지원, 안드로이드 컷 이미지 자료는 대부분 이 형식을 채택
  • jpeg: 손실 압축 이미지 형식, 투명한 배경을 지원하지 않음, 사진과 같은 풍부한 색상으로 큰 이미지 압축에 적합, 로고에는 적합하지 않음
  • webp: 손실 압축과 무손실 압축을 동시에 제공하는 그림 형식 비디오 인코딩 형식 VP8에서 파생됨 Google 공식 웹 사이트에 따르면 무손실 webp는 png보다 평균 26% 작으며 손실 webp는 jpeg보다 평균 25% 작음 ~34%, 무손실 webp는 알파 채널 지원, 손실 webp는 특정 조건에서 지원, 손실 webp는 Android4.0(API 14) 이후 지원, 무손실 및 투명 지원은 Android4.3(API18) 이후

webp를 사용하면 사진의 선명도를 유지하면서 사진이 차지하는 디스크 공간을 효과적으로 줄일 수 있습니다.

이미지 압축

이미지 압축은 세 가지 측면에서 고려할 수 있습니다.

  1. 품질

    품질 압축은 메모리의 이미지 크기를 변경하지 않고 이미지가 차지하는 디스크 공간의 크기만 줄입니다. 품질 압축은 이미지의 해상도를 변경하지 않으며 메모리의 이미지 크기는 가로세로 1픽셀에 점유 한 바이트수로 계산하면 세로와 세로는 변하지 않고 메모리에 점유되는 크기는 당연히 바뀌지 않습니다.품질 압축의 원리는 그림이 차지하는 디스크 공간을 줄이는 것입니다. 그림의 비트 심도와 투명도를 변경하므로 섬네일로 적합하지 않지만 화질을 유지하면서 그림이 차지하는 디스크 공간을 줄이는 데 사용할 수 있습니다. 또한 png는 무손실 압축이므로 품질 설정은 무효,

  /**
 * 质量压缩
 *
 * @param format  图片格式 jpeg,png,webp
 * @param quality 图片的质量,0-100,数值越小质量越差
 */
public static void compress(Bitmap.CompressFormat format, int quality) {
    
    
    File sdFile = Environment.getExternalStorageDirectory();
    File originFile = new File(sdFile, "originImg.jpg");
    Bitmap originBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath());
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    originBitmap.compress(format, quality, bos);
    try {
    
    
        FileOutputStream fos = new FileOutputStream(new File(sdFile, "resultImg.jpg"));
        fos.write(bos.toByteArray());
        fos.flush();
        fos.close();
    } catch (FileNotFoundException e) {
    
    
        e.printStackTrace();
    } catch (IOException e) {
    
    
        e.printStackTrace();
    }
}
  1. 샘플링 속도

    샘플링 속도 압축은 BitmapFactory.Options.inSampleSize를 설정하여 이미지의 해상도를 낮추어 이미지가 차지하는 디스크 공간과 메모리 크기를 줄이는 것입니다.

    inSampleSize를 설정하면 압축된 이미지의 너비와 높이가 1/inSampleSize가 되고 전체 크기는 원본 이미지의 inSampleSize의 1/4이 됩니다. 물론 몇 가지 유의할 사항이 있습니다.

    • 1보다 작거나 같은 inSampleSize는 1로 처리됩니다.

    • inSampleSize는 2의 제곱으로만 설정할 수 있습니다. 2의 제곱이 아니면 결국 가장 가까운 2의 제곱으로 줄어듭니다. 예를 들어 7로 설정하면 4로 압축되고, 15로 설정하면 8로 압축됩니다.

/**
 * 
 * @param inSampleSize  可以根据需求计算出合理的inSampleSize
 */
public static void compress(int inSampleSize) {
    
    
    File sdFile = Environment.getExternalStorageDirectory();
    File originFile = new File(sdFile, "originImg.jpg");
    BitmapFactory.Options options = new BitmapFactory.Options();
    //设置此参数是仅仅读取图片的宽高到options中,不会将整张图片读到内存中,防止oom
    options.inJustDecodeBounds = true;
    Bitmap emptyBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath(), options);

    options.inJustDecodeBounds = false;
    options.inSampleSize = inSampleSize;
    Bitmap resultBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath(), options);
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, bos);
    try {
    
    
        FileOutputStream fos = new FileOutputStream(new File(sdFile, "resultImg.jpg"));
        fos.write(bos.toByteArray());
        fos.flush();
        fos.close();
    } catch (FileNotFoundException e) {
    
    
        e.printStackTrace();
    } catch (IOException e) {
    
    
        e.printStackTrace();
    }
}
  1. 썸네일을 캐시하는 데 사용할 수 있는 이미지의 픽셀을 줄여 이미지의 디스크 공간 크기와 메모리 크기를 줄입니다.

 /**
     *  缩放bitmap
     * @param context
     * @param id
     * @param maxW
     * @param maxH
     * @return
     */
    public static Bitmap resizeBitmap(Context context,int id,int maxW,int maxH,boolean hasAlpha,Bitmap reusable){
    
    
        Resources resources = context.getResources();
        BitmapFactory.Options options = new BitmapFactory.Options();
        // 只解码出 outxxx参数 比如 宽、高
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources,id,options);
        //根据宽、高进行缩放
        int w = options.outWidth;
        int h = options.outHeight;
        //设置缩放系数
        options.inSampleSize = calcuteInSampleSize(w,h,maxW,maxH);
        if (!hasAlpha){
    
    
            options.inPreferredConfig = Bitmap.Config.RGB_565;
        }
        options.inJustDecodeBounds = false;
        //设置成能复用
        options.inMutable=true;
        options.inBitmap=reusable;
        return BitmapFactory.decodeResource(resources,id,options);
    }

    /**
     * 计算缩放系数
     * @param w
     * @param h
     * @param maxW
     * @param maxH
     * @return 缩放的系数
     */
    private static int calcuteInSampleSize(int w,int h,int maxW,int maxH) {
    
    
        int inSampleSize = 1;
        if (w > maxW && h > maxH){
    
    
            inSampleSize = 2;
            //循环 使宽、高小于 最大的宽、高
            while (w /inSampleSize > maxW && h / inSampleSize > maxH){
    
    
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}

JPEG 라이브러리를 사용하여 jni 레이어에서 Huffman 알고리즘을 사용하여 이미지를 압축합니다.

Android의 이미지 엔진은 이미지 압축에서 Huffman 알고리즘을 제거하는 거세 버전의 스키아 엔진을 사용합니다.

void write_JPEG_file(uint8_t *data, int w, int h, jint q, const char *path) {
    
    
//    3.1、创建jpeg压缩对象
    jpeg_compress_struct jcs;
    //错误回调
    jpeg_error_mgr error;
    jcs.err = jpeg_std_error(&error);
    //创建压缩对象
    jpeg_create_compress(&jcs);
//    3.2、指定存储文件  write binary
    FILE *f = fopen(path, "wb");
    jpeg_stdio_dest(&jcs, f);
//    3.3、设置压缩参数
    jcs.image_width = w;
    jcs.image_height = h;
    //bgr
    jcs.input_components = 3;
    jcs.in_color_space = JCS_RGB;
    jpeg_set_defaults(&jcs);
    //开启哈夫曼功能
    jcs.optimize_coding = true;
    jpeg_set_quality(&jcs, q, 1);
//    3.4、开始压缩
    jpeg_start_compress(&jcs, 1);
//    3.5、循环写入每一行数据
    int row_stride = w * 3;//一行的字节数
    JSAMPROW row[1];
    while (jcs.next_scanline < jcs.image_height) {
    
    
        //取一行数据
        uint8_t *pixels = data + jcs.next_scanline * row_stride;
        row[0]=pixels;
        jpeg_write_scanlines(&jcs,row,1);
    }
//    3.6、压缩完成
    jpeg_finish_compress(&jcs);
//    3.7、释放jpeg对象
    fclose(f);
    jpeg_destroy_compress(&jcs);
}

jni 부분이 포함되어 있기 때문에 당분간은 사용하는 코드만 올리도록 하고 jni 부분에 대한 블로그를 몇 개 작성하여 나중에 공유하도록 하겠습니다.

설정 사진 재사용 가능

이미지 다중화는 주로 메모리 블록의 다중화를 의미하며, 이 비트맵에 대해 새로운 메모리를 신청할 필요가 없으므로 메모리 할당 및 복구를 피하여 작업 효율성을 향상시킵니다.

inBitmap은 3.0 이후에만 사용할 수 있습니다. 2.3에서 비트맵 데이터는 Dalvik 메모리 힙이 아닌 기본 메모리 영역에 저장됩니다.

inBitmap을 사용하면 4.4 이전에는 동일한 크기의 비트맵 메모리 영역만 재사용할 수 있지만 4.4 이후에는 이 메모리가 메모리를 할당할 비트맵보다 큰 경우 모든 비트맵 메모리 영역을 재사용할 수 있습니다. 여기서 가장 좋은 방법은 비트맵을 캐시하기 위해 LRUCache를 사용하는 것입니다.나중에 새로운 비트맵이 나옵니다.메모리 영역을 재사용하기 위해 API 버전에 따라 캐시에서 재사용하기에 가장 적합한 비트맵을 찾을 수 있습니다.

   BitmapFactory.Options options = new BitmapFactory.Options();
        // 只解码出 outxxx参数 比如 宽、高
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources,id,options);
        //根据宽、高进行缩放
        int w = options.outWidth;
        int h = options.outHeight;
        //设置缩放系数
        options.inSampleSize = calcuteInSampleSize(w,h,maxW,maxH);
        if (!hasAlpha){
    
    
            options.inPreferredConfig = Bitmap.Config.RGB_565;
        }
        options.inJustDecodeBounds = false;
        //设置成能复用
        options.inMutable=true;
        options.inBitmap=reusable;

이미지 캐시 사용

안드로이드에는 LruCache가 있는데, 이것은 가장 많이 기억되고 가장 적게 사용되는 알고리즘을 기반으로 구현된 스레드 안전 데이터 캐시 클래스입니다. LruCache는 LinkedHashMap을 사용하여 구현되며 get/put과 같은 관련 메서드를 캡슐화하여 캐시 크기 및 요소를 제거하지만 null 키 및 값은 지원하지 않습니다. JakeWharton에서 제공하는 오픈 소스 라이브러리를 사용하여 github.com/JakeWharton…이미지 캐시의 논리를 구현할 수 있습니다.

메모리 및 디스크 섹션은 생략됩니다.

모두가 포괄적이고 명확한 방식으로 성능 최적화를 더 잘 이해할 수 있도록 관련 핵심 정보를 준비했습니다(기본 논리로 돌아가기).https://qr18.cn/FVlo89

성능 최적화 핵심 참고 사항:https://qr18.cn/FVlo89

시작 최적화

메모리 최적화

UI

최적화 네트워크 최적화

비트맵 최적화 및 이미지 압축 최적화 : 다중 스레드 동시성 최적화 및 데이터 전송 효율성 최적화 볼륨 패키지 최적화https://qr18.cn/FVlo89




"안드로이드 성능 모니터링 프레임워크":https://qr18.cn/FVlo89

"안드로이드 프레임워크 학습 매뉴얼":https://qr18.cn/AQpN4J

  1. 부팅 초기화 프로세스
  2. 부팅 시 Zygote 프로세스 시작
  3. 부팅 시 SystemServer 프로세스 시작
  4. 바인더 드라이버
  5. AMS 시작 프로세스
  6. PMS의 시작 프로세스
  7. 런처 시작 프로세스
  8. Android의 4가지 주요 구성요소
  9. Android 시스템 서비스 - 입력 이벤트 분배 프로세스
  10. Android 기본 렌더링 화면 새로 고침 메커니즘 소스 코드 분석
  11. Android 소스 코드 분석 실습

추천

출처blog.csdn.net/weixin_61845324/article/details/132093024