鸿蒙版图片加载框架Glide(丐版)
目的
从网络上加载一张图片,这基本是每个应用都需要的功能。在android平台我们有ImageLoader,Picasso,Glide,其中又以Glide最为人熟知。为了更方便的开发鸿蒙应用,我们也根据Glide最核心的设计思路,完成鸿蒙下的图片加载框架。
Glide核心设计思路
我把这种设计模式称作为取号机设计,以大家熟悉的银行业务为例,每个进入银行需要办理业务的人,他们需求的业务各有不同,那么待办理的业务内容相当于请求信息(Request),进入银行后会被引导到取号机(RequestManager)取号,然后会被系统安排到空闲的窗口(RequestDispatcher)实际办理业务。
框架实现
定义角色
PixelMapRequest:图片请求封装类
RequestManager:图片请求管理类(单列)
PixelMapDispatcher:真正处理请求的处理类
ImageLoader:对外暴露的api操作类
把其他肉去掉,上面的4个类就是框架的骨架,我们现在需要做的就是在骨架上填充上肌肉。
PixelMapRequest
/**
* 图片请求封装类
*/
public class PixelMapRequest {
/**
* 需要下载的url
*/
private String mUrl;
/**
* 关联的image控件,使用软引用持有
*/
private SoftReference<Image> mImage;
/**
* 上下文对象
*/
private AbilityContext mContext;
/**
* 占位图资源id
*/
private int mResId;
/**
* 请求监听
*/
private RequestListener mListener;
/**
* 请求标志,用于防止图片错乱,和三级缓存
*/
private String mUrlMd5;
public PixelMapRequest(AbilityContext context) {
this.mContext = context;
}
/**
* 设置下载的路径
*
* @param url
* @return
*/
public PixelMapRequest load(String url) {
this.mUrl = url;
if (!StringUtils.isEmpty(url)) {
this.mUrlMd5 = MD5Utils.encrypt(url);
}
return this;
}
/**
* 设置默认图资源
*
* @param resId
* @return
*/
public PixelMapRequest loading(int resId) {
this.mResId = resId;
return this;
}
/**
* 设置请求监听
*
* @param listener
* @return
*/
public PixelMapRequest setListener(RequestListener listener) {
this.mListener = listener;
return this;
}
/**
* 绑定关联的image控件,并将请求加入队列
*
* @param image
*/
public void into(Image image) {
image.setTag(mUrlMd5);
this.mImage = new SoftReference<>(image);
// 发起请求
RequestManager.getInstance().addPixelMapRequest(this);
}
/**
* 获取请求url
*
* @return
*/
public String getUrl() {
return mUrl;
}
/**
* 获取控件实例
*
* @return
*/
public SoftReference<Image> getImage() {
return mImage;
}
/**
* 获取展位图资源id
*
* @return
*/
public int getResId() {
return mResId;
}
/**
* 获取监听器实例
*
* @return
*/
public RequestListener getListener() {
return mListener;
}
/**
* 获取请求标志位
*
* @return
*/
public String getUrlMd5() {
return mUrlMd5;
}
}
封装图片请求信息,如图片对应的url,关联的控件,默认图的资源等。控件被用软引用持有考虑到内存问题,当内存不足时,控件引用可以被GC回收。每个请求有个标识位,其值就是url的md5值,是用来防止图片和控件错位,以及缓存key的作用。设计上模仿Glide的链式调用方式。
RequestManager
/**
* 图片请求管理类(单列)
*/
public class RequestManager {
/**
* 上下文对象
*/
private AbilityContext mContext;
/**
* 缓存请求的队列,考虑到可能会有多个线程操作这个队列,所以这个需要用堵塞式的队列
*/
private LinkedBlockingQueue<PixelMapRequest> mRequestQueue = new LinkedBlockingQueue<>();
/**
* 存放真正处理者的数组
*/
private PixelMapDispatcher[] dispatchers;
/**
* 线程池
*/
public ExecutorService executorService;
private RequestManager() {
}
private static class Holder {
private static RequestManager manager = new RequestManager();
}
public static RequestManager getInstance() {
return Holder.manager;
}
/**
* 初始化,建议放在MyApplication中执行
*
* @param abilityContext
*/
public void init(AbilityContext abilityContext) {
this.mContext = abilityContext;
initThreadExecutor();
start();
}
/**
* 初始化线程池
*/
public void initThreadExecutor() {
int size = Runtime.getRuntime().availableProcessors();
if (size <= 0) {
size = 1;
}
size *= 2;
executorService = Executors.newFixedThreadPool(size);
}
/**
* 启动工作
*/
public void start() {
stop();
startAllDispatcher();
}
/**
* 启动所有处理线程
*/
public void startAllDispatcher() {
final int threadCount = Runtime.getRuntime().availableProcessors();//获取最大线程数
dispatchers = new PixelMapDispatcher[threadCount];
if (dispatchers.length > 0) {
for (int i = 0; i < threadCount; i++) {
PixelMapDispatcher pixelmapDispatcher = new PixelMapDispatcher(mRequestQueue);//创建处理者实例
executorService.execute(pixelmapDispatcher);//加入线程池并启动
dispatchers[i] = pixelmapDispatcher;
}
}
}
/**
* 停止所有工作
*/
public void stop() {
if (dispatchers != null && dispatchers.length > 0) {
for (PixelMapDispatcher pixelmapDispatcher : dispatchers) {
if (!pixelmapDispatcher.isInterrupted()) {
pixelmapDispatcher.interrupt(); // 中断
}
}
}
}
/**
* 增加请求
*
* @param pixelMapRequest
*/
public void addPixelMapRequest(PixelMapRequest pixelMapRequest) {
if (pixelMapRequest == null) {
return;
}
if (!mRequestQueue.contains(pixelMapRequest)) {
mRequestQueue.add(pixelMapRequest); // 将请求加入队列
}
}
}
第一个这是个单例,一个应用中只需要一个实例来管理请求。管理类中包含一个请求队列,一个处理者数组,一个线程池。考虑到请求队列可能被多个线程操作,所以这里使用了LinkedBlockingQueue这种堵塞式的队列,处理者数组的长度取决于系统的能力。其他定义了一些函数,注释很明白了。
PixelMapDispatcher
/**
* 真正处理请求的处理类
*/
public class PixelMapDispatcher extends Thread {
private EventHandler handler = new EventHandler(EventRunner.getMainEventRunner());//用来更新ui的handler
private LinkedBlockingQueue<PixelMapRequest> mRequestQueue;//存放请求的集合
public PixelMapDispatcher(LinkedBlockingQueue<PixelMapRequest> queue) {
this.mRequestQueue = queue;
}
@Override
public void run() {
while (!isInterrupted()) {
if (mRequestQueue == null)
continue;
try {
PixelMapRequest request = mRequestQueue.take();
if (request == null)
continue;
showLoadingImg(request);//1.设置占位图片
PixelMap pixelMap = findPixelMap(request);//2.加载图片数据
showImageView(request, pixelMap);//3.将图片显示到Image
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 设置默认图
*
* @param request
*/
private void showLoadingImg(PixelMapRequest request) {
Image image = request.getImage().get();
int resId = request.getResId();
if (image != null && resId != 0) {
handler.postTask(() -> {
image.setPixelMap(request.getResId());
});
}
}
/**
* 查找PixelMap的逻辑(包含三级缓存)
*
* @param request
* @return
*/
private PixelMap findPixelMap(PixelMapRequest request) {
//1.先在缓存中查找 todo
//2.没有再从网络上下载
PixelMap pixelMap = downloadPixelMap(request);
//3.下载完毕返回并缓存 todo
return pixelMap;
}
/**
* 根据url下载PixelMap
*
* @param request
* @return
*/
private PixelMap downloadPixelMap(PixelMapRequest request) {
InputStream is = null;
PixelMap pixelMap = null;
try {
URL url = new URL(request.getUrl());
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
is = urlConnection.getInputStream();
ImageSource imageSource = ImageSource.create(is, new ImageSource.SourceOptions());
ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
decodingOptions.desiredPixelFormat = PixelFormat.RGB_565;
pixelMap = imageSource.createPixelmap(decodingOptions);
return pixelMap;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* 将结果设置到控件上
*
* @param request
* @param pixelMap
*/
private void showImageView(PixelMapRequest request, PixelMap pixelMap) {
Image image = request.getImage().get();
RequestListener listener = request.getListener();
if (image != null && pixelMap != null && request.getUrlMd5() != null && request.getUrlMd5().equals(image.getTag())) {
handler.postTask(() -> {
image.setPixelMap(pixelMap);
});
if (listener != null)
listener.onSuccess(request.getUrl(), pixelMap);
} else {
if (listener != null)
listener.onFail(request.getUrl());
}
}
}
二级缓存这块还没完善,后续会补上。这里就是从队列中取出请求直接通过HttpURLConnection去下载,序列化成PixelMap对象,然后显示。
ImageLoader
/**
* 调用者api类
*/
public class ImageLoader {
/**
* 返回一个图片请求
*
* @param abilityContext
* @return
*/
public static PixelMapRequest with(AbilityContext abilityContext) {
return new PixelMapRequest(abilityContext);
}
}
测试代码(配合ListContainer使用)
public class ListItemProvider extends BaseItemProvider {
private List<String> urls;
private AbilityContext context;
private LayoutScatter scatter;
public ListItemProvider(List<String> urls, AbilityContext context) {
this.urls = urls;
this.context = context;
this.scatter = LayoutScatter.getInstance(context);
}
@Override
public int getCount() {
return urls.size();
}
@Override
public Object getItem(int i) {
return urls.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public Component getComponent(int i, Component component, ComponentContainer componentContainer) {
ItemHolder holder = null;
if (component == null) {
component = scatter.parse(ResourceTable.Layout_list_item, null, false);
holder = new ItemHolder(component);
component.setTag(holder);
} else {
holder = (ItemHolder) component.getTag();
}
ImageLoader.with(context).loading(ResourceTable.Media_icon).load(urls.get(i)).into(holder.image);
return component;
}
static class ItemHolder {
Image image;
public ItemHolder(Component component) {
image = (Image) component.findComponentById(ResourceTable.Id_list_image);
}
}
}
测试通过,ok
总结
其实只要理解了取号机这个设计,再来看这个框架非常简单,代码逻辑也不复杂,只要一些细节处理注意点就可以了。当然这个和Glide框架差距非常大,比如Glide的缓存机制,生命周期处理方法,高可配置设计都没有,但是核心的脉络已经形成了,缺少的部分后面一点点完善,毕竟那些成熟框架也不是一触而就的。
相关链接
作者:暗影萨满
厉害了,不过还是太复杂,期待更简单的第三方加载网络图片类库。