AndroidAutoSize icon indicating copy to clipboard operation
AndroidAutoSize copied to clipboard

Fresco 加载为圆形图片,图片偏移错位不居中问题分析!

Open cocowobo opened this issue 6 years ago • 10 comments

autosize在已经修改了activity的Density的情况下.fresco虽然持有了修改后的Density值,但是在完成图片加载二进制数据流,缓存为 bitmap对象的时候没有对创建的bitmap应用新的Density. 此段代码可以断点调试com.facebook.imagepipeline.memory.BucketsBitmapPool#alloc方法, 贴出此方法源码.

  @Override
  protected Bitmap alloc(int size) {
    return Bitmap.createBitmap(
        1,
        (int) Math.ceil(size / (double) BitmapUtil.RGB_565_BYTES_PER_PIXEL),
        Bitmap.Config.RGB_565);
  }

创建的bitmap调用的是不传DisplayMetrics display,对象的创建构造函数,则会给bitmap赋值为系统Density值. 且在之后的使用此bitmap创建BitmapDrawable的时候 创建时机在 com.facebook.drawee.drawable.RoundedBitmapDrawable 的构造中

  public RoundedBitmapDrawable(Resources res, @Nullable Bitmap bitmap, @Nullable Paint paint) {
    super(new BitmapDrawable(res, bitmap));
    mBitmap = bitmap;
    if (paint != null) {
      mPaint.set(paint);
    }

    mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
    mBorderPaint.setStyle(Paint.Style.STROKE);
  }

,因为BitmapDrawable的源码android.graphics.drawable.BitmapDrawable#computeBitmapSize方法

    private void computeBitmapSize() {
        final Bitmap bitmap = mBitmapState.mBitmap;
        if (bitmap != null) {
            mBitmapWidth = bitmap.getScaledWidth(mTargetDensity);
            mBitmapHeight = bitmap.getScaledHeight(mTargetDensity);
        } else {
            mBitmapWidth = mBitmapHeight = -1;
        }
    }

会根据传入resourse对象和bitmap中的Density对比来缩放或者扩大生成的BitmapDrawable

后面的逻辑无需跟进了.太过复杂,都是些矩阵之类的缩放计算在onDraw方法中,只需要知道 在这里构造方法中创建的BitmapDrawable的时候因为传入的 Density和传入的bitmap中记录的 Density不同导致创建BitmapDrawable的宽高发生变动.


到这里似乎我看到了解决的曙光. 于是乎开始试验.我试着从重写 com.facebook.imagepipeline.memory.BucketsBitmapPool#alloc方法,入手 于是我继承了生成BucketsBitmapPool类对象的PoolFactory工厂类.重写其getBitmapPool方法 在

 mBitmapPool =
              new BucketsBitmapPool(
                  mConfig.getMemoryTrimmableRegistry(),
                  DefaultBitmapPoolParams.get(),
                  mConfig.getBitmapPoolStatsTracker());

加入了

 mBitmapPool =
              new BucketsBitmapPool(
                  mConfig.getMemoryTrimmableRegistry(),
                  DefaultBitmapPoolParams.get(),
                  mConfig.getBitmapPoolStatsTracker()){
      @Override protected Bitmap alloc(int size) {
        Bitmap mAlloc = super.alloc(size);
        if (mAlloc != null) {
          mAlloc.setDensity(MyApplication.getContext().getResources().getDisplayMetrics().densityDpi);
        }
        return mAlloc;
      };

很遗憾的是没有效果.重新调试发现.源码中又一次重新生成了bitmap.具体可以看 com.facebook.imagepipeline.platform.DefaultDecoder#decodeFromStream方法 此方法中

@Nullable Bitmap **bitmapToReuse** = null;
         ......
bitmapToReuse = mBitmapPool.get(sizeInBytes);

就是我上面处理后的bitmap.可惜的是在比方法后半段.源码有重新生成 bitmap.

 Bitmap decodedBitmap = null;
       ......................
**options.inBitmap** = **bitmapToReuse**;
decodedBitmap = BitmapFactory.decodeStream(inputStream, null, options);

而且这里重新生成的bitmap的inDensity又被重置了.具体可以看源码方法追踪

到此上面的方法已经不可行.难道逼我去修改源码吗?很难过. 中间省略 6个小时.知道半夜5点多的追踪和分析和查资料. 最后发现重新生成的bitmap可以在某一个fresce愿意对外提供配置的对象里面可以拿到. 具体配置方法我就直接贴代码了,提供的 方法可以按照自己的理解去修改,注意一定配置 setImageDecoder 设置才可以解决设置新的autosize的计算的density


  public static ImagePipelineConfig getFrescoConfigFix(Context mContext) {
    String IMAGE_PIPELINE_CACHE_DIR = "image_cache";
    String IMAGE_PIPELINE_SMALL_CACHE_DIR = "image_small_cache";
    int MAX_DISK_SMALL_CACHE_SIZE = 10 * ByteConstants.MB;
    int MAX_DISK_SMALL_ONLOWDISKSPACE_CACHE_SIZE = 5 * ByteConstants.MB;
    //----------------------------------------------------------------------
    Supplier<MemoryCacheParams> mBitmapMemoryCacheParamsSupplier =
        new BitmapMemoryCacheParamsSupplier(
            (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE));
    //----------------------------------------------------------------------
    MemoryTrimmableRegistry mMemoryTrimmableRegistry = NoOpMemoryTrimmableRegistry.getInstance();
    mMemoryTrimmableRegistry.registerMemoryTrimmable(new MemoryTrimmable() {
      @Override      // 当内存紧张时采取的措施
      public void trim(MemoryTrimType trimType) {
        final double suggestedTrimRatio = trimType.getSuggestedTrimRatio();
        MLog.i("Fresco onCreate suggestedTrimRatio = " + suggestedTrimRatio);

        if (MemoryTrimType.OnCloseToDalvikHeapLimit.getSuggestedTrimRatio() == suggestedTrimRatio
            || MemoryTrimType.OnSystemLowMemoryWhileAppInBackground.getSuggestedTrimRatio()
            == suggestedTrimRatio
            || MemoryTrimType.OnSystemLowMemoryWhileAppInForeground.getSuggestedTrimRatio()
            == suggestedTrimRatio
        ) {
          // 清除内存缓存
          Fresco.getImagePipeline().clearMemoryCaches();
        }
      }
    });

    //----------------------------------------------------------------------
    /*
     * 推荐缓存到应用本身的缓存文件夹,这么做的好处是:
     * 1、当应用被用户卸载后能自动清除缓存
     * 2、一些内存清理软件可以扫描出来,进行内存的清理
     */
    File fileCacheDir = mContext.getCacheDir();
    DiskCacheConfig mMainDiskCacheConfig = DiskCacheConfig.newBuilder(mContext)
        .setBaseDirectoryName(IMAGE_PIPELINE_CACHE_DIR)
        .setBaseDirectoryPath(fileCacheDir)
        .build();

    DiskCacheConfig mSmallImageDiskCacheConfig = DiskCacheConfig.newBuilder(mContext)
        .setBaseDirectoryPath(fileCacheDir)
        .setBaseDirectoryName(IMAGE_PIPELINE_SMALL_CACHE_DIR)
        .setMaxCacheSize(MAX_DISK_SMALL_CACHE_SIZE)
        .setMaxCacheSizeOnLowDiskSpace(MAX_DISK_SMALL_ONLOWDISKSPACE_CACHE_SIZE)
        .build();
    //----------------------------------------------------------------------
    AppComponent appComponent = ArtUtils.obtainAppComponentFromContext(mContext);
    OkHttpNetworkFetcher mNetworkFetcher = new OkHttpNetworkFetcher(appComponent.okHttpClient());
    //----------------------------------------------------------------------
    Set<RequestListener> mRequestListeners = new HashSet<>();
    mRequestListeners.add(new RequestLoggingListener());

    //----------------------------------------------------------------------
    ImagePipelineConfig mConfig = ImagePipelineConfig
        .newBuilder(mContext)
        .setBitmapMemoryCacheParamsSupplier(mBitmapMemoryCacheParamsSupplier)
        .setMemoryTrimmableRegistry(mMemoryTrimmableRegistry)
        .setMainDiskCacheConfig(mMainDiskCacheConfig) // 设置主磁盘配置
        .setSmallImageDiskCacheConfig(mSmallImageDiskCacheConfig) // 设置小图的磁盘配置
        .setBitmapsConfig(Bitmap.Config.ARGB_8888) // 若不是要求忒高清显示应用,就用使用RGB_565吧(默认是ARGB_8888)
        .setNetworkFetcher(mNetworkFetcher)
        .setRequestListeners(mRequestListeners)

        .build();
    //----------------------------------------------------------------------
    PlatformDecoder mPlatformDecoder =
        PlatformDecoderFactory.buildPlatformDecoder(
            mConfig.getPoolFactory(), mConfig.getExperiments().isGingerbreadDecoderEnabled());
    //----------------------------------------------------------------------
    PlatformBitmapFactory mPlatformBitmapFactory =
        PlatformBitmapFactoryProvider.buildPlatformBitmapFactory(
            mConfig.getPoolFactory(), mPlatformDecoder);
    //----------------------------------------------------------------------
    CountingMemoryCache<CacheKey, CloseableImage> mBitmapCountingMemoryCache =
        BitmapCountingMemoryCacheFactory.get(
            mConfig.getBitmapMemoryCacheParamsSupplier(),
            mConfig.getMemoryTrimmableRegistry(),
            mConfig.getBitmapMemoryCacheTrimStrategy());
    //----------------------------------------------------------------------
    final AnimatedFactory animatedFactory = AnimatedFactoryProvider.getAnimatedFactory(
        mPlatformBitmapFactory,
        mConfig.getExecutorSupplier(),
        mBitmapCountingMemoryCache,
        mConfig.getExperiments().shouldDownscaleFrameToDrawableDimensions());
    //----------------------------------------------------------------------
    ImageDecoder gifDecoder = null;
    ImageDecoder webPDecoder = null;

    if (animatedFactory != null) {
      gifDecoder = animatedFactory.getGifDecoder(mConfig.getBitmapConfig());
      webPDecoder = animatedFactory.getWebPDecoder(mConfig.getBitmapConfig());
    }
    //----------------------------------------------------------------------
    ImageDecoder mImageDecoder;
    if (mConfig.getImageDecoderConfig() == null) {
      mImageDecoder = new DefaultImageDecoder(
          gifDecoder,
          webPDecoder,
          mPlatformDecoder) {
        @Override
        public CloseableImage decode(EncodedImage encodedImage, int length, QualityInfo qualityInfo,
            ImageDecodeOptions options) {
          CloseableImage mDecode = super.decode(encodedImage, length, qualityInfo, options);
          if (mDecode instanceof CloseableStaticBitmap) {
            Bitmap mUnderlyingBitmap = ((CloseableStaticBitmap) mDecode).getUnderlyingBitmap();
            if (mUnderlyingBitmap != null) {
              mUnderlyingBitmap
                  .setDensity(mContext.getResources().getDisplayMetrics().densityDpi);
            }
          }
          return mDecode;
        }
      };
    } else {
      mImageDecoder = new DefaultImageDecoder(
          gifDecoder,
          webPDecoder,
          mPlatformDecoder,
          mConfig.getImageDecoderConfig().getCustomImageDecoders()) {
        @Override
        public CloseableImage decode(EncodedImage encodedImage, int length, QualityInfo qualityInfo,
            ImageDecodeOptions options) {
          CloseableImage mDecode = super.decode(encodedImage, length, qualityInfo, options);
          if (mDecode instanceof CloseableStaticBitmap) {
            Bitmap mUnderlyingBitmap = ((CloseableStaticBitmap) mDecode).getUnderlyingBitmap();
            if (mUnderlyingBitmap != null) {
              mUnderlyingBitmap
                  .setDensity(mContext.getResources().getDisplayMetrics().densityDpi);
            }
          }
          return mDecode;
        }
      };
      // Add custom image formats if needed
      ImageFormatChecker.getInstance()
          .setCustomImageFormatCheckers(
              mConfig.getImageDecoderConfig().getCustomImageFormats());
    }
    //----------------------------------------------------------------------
    return ImagePipelineConfig.newBuilder(mContext)
        .setBitmapMemoryCacheParamsSupplier(mBitmapMemoryCacheParamsSupplier) // 设置内存配置
        .setMemoryTrimmableRegistry(mMemoryTrimmableRegistry) // 报内存警告时的监听
        .setMainDiskCacheConfig(mMainDiskCacheConfig) // 设置主磁盘配置
        .setSmallImageDiskCacheConfig(mSmallImageDiskCacheConfig) // 设置小图的磁盘配置
        .setBitmapsConfig(Bitmap.Config.ARGB_8888) // 若不是要求忒高清显示应用,就用使用RGB_565吧(默认是ARGB_8888)
        .setNetworkFetcher(mNetworkFetcher)
        .setRequestListeners(mRequestListeners)
        .setDownsampleEnabled(true) // 在解码时改变图片的大小,支持PNG、JPG以及WEBP格式的图片,与ResizeOptions配合使用
        .setImageDecoder(mImageDecoder)
        .build();
  }

cocowobo avatar Aug 24 '19 21:08 cocowobo

本贴在上面的配置方案现在存在的新问题如下: 加载网络图正常的情况下,会走本帖上面的配置,强制将网络下载后创建的bitmap的density进行更改, 但是在部分华为手机上, 如果图片地址无效,则会按照 xml中配置的默认展位图和 失败图来加载,,问题就出现在这里,展位图和失败图的在这部分华为手机上居中是有问题的, 对比手头上的机子,就只有华为的机子会出现默认图的density异常问题,大部分手机的默认图无论是否使用本帖的修复配置,默认图都是加载没问题的,只有华为手机无论是否应用上面的配置,都会造成加载占位图异常

经过调试,发现是 华为手机的mContent.getResources().getDrawable方法加载出来的BitmapDrawable的desnity是原始的数值导致的., 初步解决方案是继承SimpleDraweeView,重写inflateHierarchy方法,在完成SimpleDraweeView的初始化方法前,将预加载的 展位图和失败图的density 修改就可以了,

  public class SimpleDraweeViewFix extends SimpleDraweeView {
  
    protected void inflateHierarchy(Context context, @Nullable AttributeSet attrs) {
      if (FrescoSystrace.isTracing()) {
        FrescoSystrace.beginSection("GenericDraweeView#inflateHierarchy");
      }
      GenericDraweeHierarchyBuilder builder =
          GenericDraweeHierarchyInflater.inflateBuilder(context, attrs);
      setAspectRatio(builder.getDesiredAspectRatio());
      Drawable mPlaceholderImage = builder.getPlaceholderImage();
      if (mPlaceholderImage instanceof BitmapDrawable) {
        ((BitmapDrawable) mPlaceholderImage).getBitmap().setDensity(context.getResources().getDisplayMetrics().densityDpi);
      }
      Drawable mFailureImage = builder.getFailureImage();
      if (mFailureImage instanceof BitmapDrawable) {
        ((BitmapDrawable) mFailureImage).getBitmap().setDensity(context.getResources().getDisplayMetrics().densityDpi);
      }
      setHierarchy(builder.build());
      if (FrescoSystrace.isTracing()) {
        FrescoSystrace.endSection();
      }
    }
  }

以上代码纯粹是 亡羊补牢,有更好的解决方案的朋友请,留下你们宝贵的想法

cocowobo avatar Sep 24 '19 16:09 cocowobo

用了上面的方法,如果图片实际size比imageview size小的话,貌似图片缩放有问题?@cocowobo 老哥有遇到么?

snowberry001 avatar Jul 02 '20 12:07 snowberry001

用了上面的方法,如果图片实际size比imageview size小的话,貌似图片缩放有问题?@cocowobo 老哥有遇到么?

好久没用. Fresco.无论我如何配置,内存占用都很高,使用难度也大,所以,我还是用回了glide. 图片太小,出现的问题,我没出现过,或许我的图都大吧.

cocowobo avatar Jul 23 '20 11:07 cocowobo

用了上面的方法,如果图片实际size比imageview size小的话,貌似图片缩放有问题?@cocowobo 老哥有遇到么?

好久没用. Fresco.无论我如何配置,内存占用都很高,使用难度也大,所以,我还是用回了glide. 图片太小,出现的问题,我没出现过,或许我的图都大吧.

内存占用都很高 是指使用这种修改方式导致的么?还是指Fresco本身就很占内存? 修改bitmap的density貌似是会导致占用内存更高

snowberry001 avatar Jul 27 '20 03:07 snowberry001

我下载了fresco的源码,在生成RoundedBitmapDrawable之前的WrappingUtils类里,改了bitmap的Density,在自己的项目试了,没发现问题。 private static Drawable applyLeafRounding( Drawable drawable, RoundingParams roundingParams, Resources resources) { if (drawable instanceof BitmapDrawable) { final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; Bitmap bitmap =bitmapDrawable.getBitmap(); bitmap.setDensity(resources.getDisplayMetrics().densityDpi); RoundedBitmapDrawable roundedBitmapDrawable = new RoundedBitmapDrawable( resources,bitmap, bitmapDrawable.getPaint()); applyRoundingParams(roundedBitmapDrawable, roundingParams); return roundedBitmapDrawable; } else if (drawable instanceof NinePatchDrawable) { final NinePatchDrawable ninePatchDrawableDrawable = (NinePatchDrawable) drawable; RoundedNinePatchDrawable roundedNinePatchDrawable = new RoundedNinePatchDrawable(ninePatchDrawableDrawable); applyRoundingParams(roundedNinePatchDrawable, roundingParams); return roundedNinePatchDrawable; } else if (drawable instanceof ColorDrawable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { RoundedColorDrawable roundedColorDrawable = RoundedColorDrawable.fromColorDrawable((ColorDrawable) drawable); applyRoundingParams(roundedColorDrawable, roundingParams); return roundedColorDrawable; } else { FLog.w(TAG, "Don't know how to round that drawable: %s", drawable); } return drawable; }

加了 Bitmap bitmap =bitmapDrawable.getBitmap(); bitmap.setDensity(resources.getDisplayMetrics().densityDpi); 然后打包成aar包,放在自己项目中。

canyinghao avatar Mar 18 '21 08:03 canyinghao

太棒了!感谢!!!

SiXiWanZi avatar Nov 17 '21 12:11 SiXiWanZi

我下载了fresco的源码,在生成RoundedBitmapDrawable之前的WrappingUtils类里,改了bitmap的Density,在自己的项目试了,没发现问题。 private static Drawable applyLeafRounding( Drawable drawable, RoundingParams roundingParams, Resources resources) { if (drawable instanceof BitmapDrawable) { final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; Bitmap bitmap =bitmapDrawable.getBitmap(); bitmap.setDensity(resources.getDisplayMetrics().densityDpi); RoundedBitmapDrawable roundedBitmapDrawable = new RoundedBitmapDrawable( resources,bitmap, bitmapDrawable.getPaint()); applyRoundingParams(roundedBitmapDrawable, roundingParams); return roundedBitmapDrawable; } else if (drawable instanceof NinePatchDrawable) { final NinePatchDrawable ninePatchDrawableDrawable = (NinePatchDrawable) drawable; RoundedNinePatchDrawable roundedNinePatchDrawable = new RoundedNinePatchDrawable(ninePatchDrawableDrawable); applyRoundingParams(roundedNinePatchDrawable, roundingParams); return roundedNinePatchDrawable; } else if (drawable instanceof ColorDrawable && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { RoundedColorDrawable roundedColorDrawable = RoundedColorDrawable.fromColorDrawable((ColorDrawable) drawable); applyRoundingParams(roundedColorDrawable, roundingParams); return roundedColorDrawable; } else { FLog.w(TAG, "Don't know how to round that drawable: %s", drawable); } return drawable; }

加了 Bitmap bitmap =bitmapDrawable.getBitmap(); bitmap.setDensity(resources.getDisplayMetrics().densityDpi); 然后打包成aar包,放在自己项目中。

同学,我按照你的方式试了,发现图片无法下载了。。。版本是2.6.0

zhangnin avatar Apr 01 '22 02:04 zhangnin

roundWithOverlayColor="#00000000" 加上这个属性就好了

yubaokang avatar Jun 09 '22 02:06 yubaokang

roundWithOverlayColor="#00000000" 加上这个属性就好了

那这样圆角就失效了

nodzhang avatar Aug 15 '22 02:08 nodzhang

roundWithOverlayColor="#ffffff"

songsong0218 avatar Jul 19 '23 09:07 songsong0218