Android图片智能下载库(MediaPhenix)是基于手机淘宝开放的整套移动端图片下载、加载、存储的开发库。主要包括以下两部分:
图片策略库: 图片策略库是针对百川多媒体CDN图片URL做出智能匹配处理的一套工具,可以加载指定尺寸、质量的图片,也可自动根据网络场景加载不同质量的图片。
Phenix: Phenix提供网络图像加载、多级缓存策略、本地文件图像加载、支持WebP全格式、减少重复请求、Bitmap空间复用等功能。
解压后的目录结构如下所示:
|—— MediaPhenix: 该文件夹即为Android图片库 SDK。 |—— libs |—— AndroidManifest.xml |—— PhenixDemo: 该文件夹为图片库SDK的调用示例。 |—— res |—— src |—— AndroidManifest.xml |—— README.md: 介绍文档
将MediaPhenix和PhenixDemo导入到IDE中,使PhenixDemo依赖MediaPhenix。
编译并运行,Demo界面如下图所示,包含三个示例。
这三个示例在加载图片的统计结果如下所示。
开发者在Application初始化时,需要完成对图片库Phenix的初始化,类似下面这样。
FLog.setMinLevel(Log.DEBUG); // 使用全局上下文初始化Phenix单例 Phenix.instance().with(applicationContext);
如果你手上有一个ImageView,有一个Url,那么你就可以像下面这样直接调用:
Phenix.instance().with(getContext()).load(url) .placeholder(android.R.drawable.ic_menu_rotate) .error(android.R.drawable.ic_delete) .into(imageView);
Phenix这时获取ImageView的宽高(如果能够成功获取的话,失败则默认使用屏幕宽高)来做为图片解码为Bitmap重采样的最大宽高,以计算准确的倍率inSample值,达到节省内存开销的目的。
如果你明确知道调用into的时候,还不能获取到正确的ImageView的宽高,并且你想使用你自己的默认最大宽高值,则像下面这样调用(myDefaultMaxWidth, myDefaultMaxHeight都必须为int正数):
Phenix.instance().with(getContext()).load(url) .placeholder(android.R.drawable.ic_menu_rotate) .error(android.R.drawable.ic_delete) .into(imageView, myDefaultMaxWidth, myDefaultMaxHeight);
还有一种情况,你觉得使用ImageView的宽高/默认宽高解出来的Bitmap仍然过大,内存上仍然吃不销,你可以在调用into时自己设置一个比率,像下面这样计算Bitmap最大宽高时使用的是原有宽高大小的1/2:
Phenix.instance().with(getContext()).load(url) .placeholder(android.R.drawable.ic_menu_rotate) .error(android.R.drawable.ic_delete) .into(imageView, 2.0f);
在ListView或者RecyclerView这类列表组件中加载图片时,如果存在convertView组件复用的情况,使用into接口时需要特别注意,需要对图片的下载任务作个简单的管理,否则可能出现图片跳动而与最终资源不符的问题。即在复用ImageView前,要对该View绑定的上一个加载任务进行取消,如下所示:
// 取消原有的加载任务 if (view.getTag() instanceof PhenixTicket) { ((PhenixTicket) view.getTag()).cancel(); } // 重新绑定新的加载任务 PhenixTicket ticket = Phenix.instance().with(view.getContext()).load(realUrl).into(view); view.setTag(ticket);
有些时候你可能只需要依据一个Url下载一个BitmapDrawable到内存,不需要绑定到ImageView显示而是保存到磁盘或者另作它用,这时你可以使用下面的代码:
Phenix.instance().with(getContext()) .load(url) .succListener(new IPhenixListener<SuccPhenixEvent>() { @Override public boolean onHappen(SuccPhenixEvent event) { if (event.getDrawable() != null && !event.isIntermediate()) { BitmapDrawable bitmap = event.getDrawable(); // to do what you want with the final drawable } return true; } }).fetch();
这里有两点需要注意的:
* OnHappen可能回调多次,故需要判断非空与操作是否重复;
* 你可以调用SuccPhenixEvent的isImmediate()或者isIntermediate()方法,来判断当前回调值是否为**立即值**或是**中间值**;
当你接入图片库Phenix时,你从Phenix拿到可用的Drawable之后并未通知Phenix这个Drawable什么时候被解引用,什么时候可以回收利用了。对此,为了获得更好的性能,就需要你在Drawable解引用时显式地通知Phenix回收。具体的做法分两种,一种是Drawable与ImageView发生了绑定,在ImageView不可见或者销毁时,你可以通过getDrawable()拿到Drawable然后调用下面的代码;另外一种是Drawable未与任何ImageView发生绑定,在你明确已使用完此Drawalbe后,同样地调用下面的代码:
if (drawable instanceof RecyclingBitmapDrawable) { ((RecyclingBitmapDrawable) drawable).releaseFromExternal(); }
本地图片加载与网络图片加载的唯一区别就是URI传入到Phenix时的路径不一样,本地文件路径以**单个左斜杠开头**,如下代码所示:
Phenix.instance().with(getContext()) .load("/storage/emulated/0/DCIM/P50719-104759.jpg") .into(imageView);
如果你在加载一张网络图像之前,明确地知道曾经一个URL图像已经加载过,可能存在内存缓存/磁盘缓存当中,而你想用这张图片先作为某个ImageView的临时占位符,那么你可以像下面这样,调用**secondary(String)**方法来设置一个曾加载过的URL:
Phenix.instance().with(getContext()).load(url) .secondary(placeHolderUrl) .placeholder(android.R.drawable.ic_menu_rotate) .error(android.R.drawable.ic_delete) .into(imageView);
开发者可以实现HttpLoader接口,来自定义网络加载器。
默认提供的实现为DefaultHttpLoader。该实现是直接使用系统的HttpURLConnection连接下载。当然你实现了自己的网络加载器UserHttpLoader,可以像下面这样在初始化时注入。另外你可以使用第三方比如OkHttp来实现自己的网络加载器。
Phenix.instance().httpLoaderBuilder().with(new UserHttpLoader(applicationContext));
开发者可以实现DiskCache接口,来自定义磁盘缓存组件。
默认提供的实现为NonCatalogDiskCache,这套方案是每个图像文件单独存储在ExternalCache,并按照hash值分文件夹存储,同时也避免了FAT32存储设备上单目录存放大量文件Bug,控制在一个目录下最大放置的文件不超过100个。如果你有自己的实现,可以像下面这样初始化:
Phenix.instance().diskCacheBuilder().with(new NonCatalogDiskCache());
开发者可以实现LruCache<String, ChainBitmapDrawable>接口,来自定义内存缓存组件。
默认提供的实现为DefaultDrawableMemCache组件,基于Oracle改进的冷热端循环淘汰的Lru算法HotEndLruCache。如果你有自己的实现,可以像下面这样初始化:
Phenix.instance().memCacheBuilder().with(new DefaultDrawableMemCache());
开发者可以实现FileLoader接口,来自定义内存缓存组件。
默认提供的实现为DefaultFileLoader,里面目前只实现了对绝对路径的本地文件的加载。其他两类(assets以及res图像)暂不支持。如果你有自己的实现,像下面这样初始化:
Phenix.instance().fielLoaderBuilder().with(new DefaultFileLoader());
开发者可以实现BytesPool接口,来自定义缓冲字节复用池。
默认提供的实现为LinkedBytesPool,并且总容量为1MB。如果你有自己的实现,像下面这样初始化:
Phenix.instance().bytesPoolBuilder().with(new LinkedBytesPool());
针对存储在百川多媒体空间中的图片,开发者可以通过策略库指定图片的尺寸和质量等参数,百川多媒体服务根据设置的参数返回匹配的图片。
策略库和图片库Phenix一样,在应用创建时也需要初始化。
/** * 初始化策略库环境 * getConfigString,用以获取最新的配置项,或者没有配置中心接入,可直接返回默认值 * isSupportWebP,当前环境是否支持WebP,使用来自Phenix的方法,用以得到当前图片库能否支持WebP格式 * isNetworkSlow,当前是否为弱网,这里只简单判断了下是否为WiFi,准确的做法是借助网络库测速判断 * **/ ImageInitBusinss.newInstance(getApplication(), new IImageStrategySupport() { @Override public String getConfigString(String group, String key, String defaultVal) { return defaultVal; } @Override public boolean isSupportWebP() { return BitmapDecodeHelper.isSupportWebp(); } @Override public boolean isNetworkSlow() { return !isWifi(); } }).notifyConfigsChange();
完成初始化后,你就可以通过ImageStrategyDecider来依据View的宽高自动适配CDN URL了。然后把适配好的URL直接交给Phenix下载即可。这里需要注意的是,如果ImageView在布局xml已经指定了固定宽高,可以从LayoutParams直接取得宽高。如果布局中没有指定或者你的View大小是动态变化的,那么**一定在ImageView布局完成(onLayout)后**通过getWidth()/getHeight()来获得准确的宽高,以保证适配的尺寸正确。
String decidedUrl = ImageStrategyDecider.decideUrl( ConstantData.TAE_OSS_CDN_URL, imageView.getLayoutParams().width, imageView.getLayoutParams().height, null ); Phenix.instance().load(decidedUrl).into(imageView);
你可以通过ImageStrategyConfig自定义各种策略。里面提供各种开关,以满足你特殊的需求,详细的控制开关见下面注释。
/** * Builder下可以使用的开关如下: * * 强制指定最终使用的质量参数 * setFinalImageQuality(TaobaoImageUrlStrategy.ImageQuality.q50) * * 不使用质量控制,这时通过setFinalImageQuality指定的将被忽略 * enableQuality(false) * * 不使用WebP格式 * enableWebP(false) * * 不使用图片锐化 * enableSharpen(false) * * 关闭尺寸分级阶梯复用 * enableLevelModel(false) * * 指定图片裁减方式 * setCutType(TaobaoImageUrlStrategy.CutType.xz) * * 指定图片定宽还是定高,另外一边按比例缩放 * setSizeLimitType(ImageStrategyConfig.SizeLimitType.HEIGHT_LIMIT) * **/ ImageStrategyConfig config = ImageStrategyConfig.newBuilderWithName("test").enableQuality(false).build(); String decidedUrl = ImageStrategyDecider.decideUrl( ConstantData.TAE_OSS_CDN_URL, imageView.getLayoutParams().width, imageView.getLayoutParams().height, config ); Phenix.instance().load(decidedUrl).into(imageView);