AndroidAutoSize
AndroidAutoSize copied to clipboard
Fresco 加载为圆形图片,图片偏移错位不居中问题分析!
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();
}
本贴在上面的配置方案现在存在的新问题如下: 加载网络图正常的情况下,会走本帖上面的配置,强制将网络下载后创建的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();
}
}
}
以上代码纯粹是 亡羊补牢,有更好的解决方案的朋友请,留下你们宝贵的想法
用了上面的方法,如果图片实际size比imageview size小的话,貌似图片缩放有问题?@cocowobo 老哥有遇到么?
用了上面的方法,如果图片实际size比imageview size小的话,貌似图片缩放有问题?@cocowobo 老哥有遇到么?
好久没用. Fresco.无论我如何配置,内存占用都很高,使用难度也大,所以,我还是用回了glide. 图片太小,出现的问题,我没出现过,或许我的图都大吧.
用了上面的方法,如果图片实际size比imageview size小的话,貌似图片缩放有问题?@cocowobo 老哥有遇到么?
好久没用. Fresco.无论我如何配置,内存占用都很高,使用难度也大,所以,我还是用回了glide. 图片太小,出现的问题,我没出现过,或许我的图都大吧.
内存占用都很高 是指使用这种修改方式导致的么?还是指Fresco本身就很占内存? 修改bitmap的density貌似是会导致占用内存更高
我下载了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包,放在自己项目中。
太棒了!感谢!!!
我下载了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
roundWithOverlayColor="#00000000" 加上这个属性就好了
roundWithOverlayColor="#00000000" 加上这个属性就好了
那这样圆角就失效了
roundWithOverlayColor="#ffffff"