Bitmap的分析与使用

Bitmap的分析与使用

  • Bitmap的创建

    • 创建Bitmap的时候,Java不提供new Bitmap()的形式去创建,而是通过BitmapFactory中的静态方法去创建,如:BitmapFactory.decodeStream(is);//通过InputStream去解析生成Bitmap(这里就不贴BitmapFactory中创建Bitmap的方法了,大家可以自己去看它的源码),我们跟进BitmapFactory中创建Bitmap的源码,最终都可以追溯到这几个native函数
        private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage,
                    Rect padding, Options opts);
        private static native Bitmap nativeDecodeFileDescriptor(FileDescriptor fd,
                Rect padding, Options opts);
        private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);
        private static native Bitmap nativeDecodeByteArray(byte[] data, int offset,
                int length, Options opts);
    

    Bitmap又是Java对象,这个Java对象又是从native,也就是C/C++中产生的,所以,在Android中Bitmap的内存管理涉及到两部分,一部分是native,另一部分是dalvik,也就是我们常说的java堆(如果对java堆与栈不了解的同学可以戳),到这里基本就已经了解了创建Bitmap的一些内存中的特性(大家可以使用adb shell dumpsys meminfo去查看Bitmap实例化之后的内存使用情况)。

  • Bitmap的使用

    • 我们已经知道了BitmapFactory是如何通过各种资源创建Bitmap了,那么我们如何合理的使用它呢?以下是几个我们使用Bitmap需要关注的点
      1. Size

        • 这里我们来算一下,在Android中,如果采用Config.ARGB_8888的参数去创建一个Bitmap这是Google推荐的配置色彩参数,也是Android4.4及以上版本默认创建Bitmap的Config参数(Bitmap.Config.inPreferredConfig的默认值),那么每一个像素将会占用4byte,如果一张手机照片的尺寸为1280×720,那么我们可以很容易的计算出这张图片占用的内存大小为 1280x720x4 = 3686400(byte) = 3.5M,一张未经处理的照片就已经3.5M了! 显而易见,在开发当中,这是我们最需要关注的问题,否则分分钟OOM!
        • 那么,我们一般是如何处理Size这个重要的因素的呢?,当然是调整Bitmap的大小到适合的程度啦!辛亏在BitmapFactory中,我们可以很方便的通过BitmapFactory.Options中的options.inSampleSize去设置Bitmap的压缩比,官方给出的说法是

        If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory…For example, inSampleSize == 4 returns
        an image that is 1/4 the width/height of the original, and 1/16 the
        number of pixels. Any value <= 1 is treated the same as 1.

        很简洁明了啊!也就是说,只要按计算方法设置了这个参数,就可以完成我们Bitmap的Size调整了。那么,应该怎么调整姿势才比较舒服呢?下面先介绍其中一种通过InputStream的方式去创建Bitmap的方法,上一段从Gallery中获取照片并且将图片Size调整到合适手机尺寸的代码:

          static final int PICK_PICS = 9;
          
          public void startGallery(){
              Intent i = new Intent();
              i.setAction(Intent.ACTION_PICK);
              i.setType("image/*");
              startActivityForResult(i,PICK_PICS);
          }
          
           private int[] getScreenWithAndHeight(){
              WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
              DisplayMetrics dm = new DisplayMetrics();
              wm.getDefaultDisplay().getMetrics(dm);
              return new int[]{dm.widthPixels,dm.heightPixels};
          }
          
          /**
           *
           * @param actualWidth 图片实际的宽度,也就是options.outWidth
           * @param actualHeight 图片实际的高度,也就是options.outHeight
           * @param desiredWidth 你希望图片压缩成为的目的宽度
           * @param desiredHeight 你希望图片压缩成为的目的高度
           * @return
           */
          private int findBestSampleSize(int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
              double wr = (double) actualWidth / desiredWidth;
              double hr = (double) actualHeight / desiredHeight;
              double ratio = Math.min(wr, hr);
              float n = 1.0f;
              //这里我们为什么要寻找 与ratio最接近的2的倍数呢?
              //原因就在于API中对于inSimpleSize的注释:最终的inSimpleSize应该为2的倍数,我们应该向上取与压缩比最接近的2的倍数。
              while ((n * 2) <= ratio) {
                  n *= 2;
              }
      
              return (int) n;
          }
      
          @Override
          protected void onActivityResult(int requestCode, int resultCode, Intent data) {
              if(resultCode == RESULT_OK){
                  switch (requestCode){
                      case PICK_PICS:
                          Uri uri = data.getData();
                          InputStream is = null;
                          try {
                              is = getContentResolver().openInputStream(uri);
                          } catch (FileNotFoundException e) {
                              e.printStackTrace();
                          }
      
                          BitmapFactory.Options options = new BitmapFactory.Options();
                          //当这个参数为true的时候,意味着你可以在解析时候不申请内存的情况下去获取Bitmap的宽和高
                          //这是调整Bitmap Size一个很重要的参数设置
                          options.inJustDecodeBounds = true;
                          BitmapFactory.decodeStream( is,null,options );
      
                          int realHeight = options.outHeight;
                          int realWidth = options.outWidth;
      
                          int screenWidth = getScreenWithAndHeight()[0];
                          
                          int simpleSize = findBestSampleSize(realWidth,realHeight,screenWidth,300);
                          options.inSampleSize = simpleSize;
                          //当你希望得到Bitmap实例的时候,不要忘了将这个参数设置为false
                          options.inJustDecodeBounds = false;
      
                          try {
                              is = getContentResolver().openInputStream(uri);
                          } catch (FileNotFoundException e) {
                              e.printStackTrace();
                          }
      
                          Bitmap bitmap = BitmapFactory.decodeStream(is,null,options);
      
                          iv.setImageBitmap(bitmap);
      
                          try {
                              is.close();
                              is = null;
                          } catch (IOException e) {
                              e.printStackTrace();
                          }
      
                          break;
                  }
              }
              super.onActivityResult(requestCode, resultCode, data);
          }
      

    我们来看看这段代码的功效:
    压缩前:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-71RdDTMP-1686982219413)(null)]
    压缩后:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UMmm4QUO-1686982219094)(null)]
    对比条件为:1080P的魅族Note3拍摄的高清无码照片

     2.  Reuse
     上面介绍了BitmapFactory通过InputStream去创建Bitmap的这种方式,以及BitmapFactory.Options.inSimpleSize 和 BitmapFactory.Options.inJustDecodeBounds的使用方法,但将单个Bitmap加载到UI是简单的,但是如果我们需要一次性加载大量的图片,事情就会变得复杂起来。Bitmap是吃内存大户,我们不希望多次解析相同的Bitmap,也不希望可能不会用到的Bitmap一直存在于内存中,所以,这个场景下,Bitmap的重用变得异常的重要。
     在这里只介绍一种BitmapFactory.Options.inBitmap的重用方式,下一篇文章会介绍使用三级缓存来实现Bitmap的重用。
     
           根据官方文档[在Android 3.0 引进了BitmapFactory.Options.inBitmap](https://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inBitmap),如果这个值被设置了,decode方法会在加载内容的时候去重用已经存在的bitmap. 这意味着bitmap的内存是被重新利用的,这样可以提升性能, 并且减少了内存的分配与回收。然而,使用inBitmap有一些限制。特别是在Android 4.4 之前,只支持同等大小的位图。
         我们看来看看这个参数最基本的运用方法。
    
          new BitmapFactory.Options options = new BitmapFactory.Options();
          //inBitmap只有当inMutable为true的时候是可用的。
          options.inMutable = true;
          Bitmap reusedBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.reused_btimap,options);
          options.inBitmap = reusedBitmap;
 
          
            这样,当你在下一次decodeBitmap的时候,将设置了`options.inMutable=true`以及`options.inBitmap`的`Options`传入,Android就会复用你的Bitmap了,具体实例:
          
  
          @Override
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(reuseBitmap());
          }
  
          private LinearLayout reuseBitmap(){
              LinearLayout linearLayout = new LinearLayout(this);
              linearLayout.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
              linearLayout.setOrientation(LinearLayout.VERTICAL);
      
              ImageView iv = new ImageView(this);
              iv.setLayoutParams(new ViewGroup.LayoutParams(500,300));
      
              options = new BitmapFactory.Options();
              options.inJustDecodeBounds = true;
              //inBitmap只有当inMutable为true的时候是可用的。
              options.inMutable = true;
              BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options);
              
              //压缩Bitmap到我们希望的尺寸
              //确保不会OOM
              options.inSampleSize = findBestSampleSize(options.outWidth,options.outHeight,500,300);
              options.inJustDecodeBounds = false;
      
              Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options);
              options.inBitmap = bitmap;
      
              iv.setImageBitmap(bitmap);
      
              linearLayout.addView(iv);
      
              ImageView iv1 = new ImageView(this);
              iv1.setLayoutParams(new ViewGroup.LayoutParams(500,300));
              iv1.setImageBitmap( BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options));
              linearLayout.addView(iv1);
      
              ImageView iv2 = new ImageView(this);
              iv2.setLayoutParams(new ViewGroup.LayoutParams(500,300));
              iv2.setImageBitmap( BitmapFactory.decodeResource(getResources(),R.drawable.big_pic,options));
              linearLayout.addView(iv2);
      
      
              return linearLayout;
          }
         以上代码中,我们在解析了一次一张1080P分辨率的图片,并且设置在`options.inBitmap`中,然后分别decode了同一张图片,并且传入了相同的`options`。最终只占用一份第一次解析`Bitmap`的内存。
        
    3. Recycle
    一定要记得及时回收Bitmap,否则如上分析,你的native以及dalvik的内存都会被一直占用着,最终导致OOM
    
    
    
    // 先判断是否已经回收
    if(bitmap != null && !bitmap.isRecycled()){
        // 回收并且置为null
        bitmap.recycle();
        bitmap = null;
    }
    System.gc();
   
    
- Enjoy Android  :) 如果有误,轻喷,欢迎指正。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Frida是一种针对Android应用程序的动态插桩工具。它允许开发使用JavaScript进行代码注入和修改,以实现对目标应用程序的动态分析和修改。其中一个常见的用途就是使用Frida来钩取(Hook)Bitmap对象。 BitmapAndroid中用于表示图像的类,可以用来处理和操纵图像像素数据。通过使用Frida来Hook Bitmap对象,我们可以实时监控和修改应用程序中的图像数据。 要实现Bitmap的Hook,首先需要在目标应用程序中找到Bitmap对象的实例,可以通过遍历应用程序中的所有对象,筛选出Bitmap对象,或者根据特定的条件来搜索。一旦找到了Bitmap对象的实例,就可以通过Frida提供的API来Hook该对象,以获得对其数据的访问权限。 通过Hook Bitmap对象,可以进行多种操作,例如: 1. 实时监控:可以在Bitmap对象上注册监听器,以便实时监视图像的变化,例如检测图像是否被修改或篡改。 2. 数据修改:可以直接修改Bitmap对象的像素数据,例如改变图像的颜色、大小或者进行图像特效处理。 3. 数据拷贝:可以将Bitmap对象的数据拷贝到本地内存中,以便对图像数据进行离线处理。 4. 数据分析:可以通过Hook Bitmap对象,获取图像的像素数据,并进行进一步的分析、统计或处理,例如提取图像中的特征。 总之,通过Frida Hook Bitmap,我们可以对Android应用程序中的图像数据进行实时监控和修改,以实现各种图像相关的操作和分析。这对于应用程序的开发、逆向工程和安全分析都具有重要意义。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值