鸿蒙版图片加载框架Glide(丐版)

footballboy
发布于 2021-4-7 10:23
浏览
0收藏

目的


从网络上加载一张图片,这基本是每个应用都需要的功能。在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的缓存机制,生命周期处理方法,高可配置设计都没有,但是核心的脉络已经形成了,缺少的部分后面一点点完善,毕竟那些成熟框架也不是一触而就的。

 

相关链接

 

Github:https://github.com/loubinfeng2013/HarmonyTools

 

 

 

 

 

作者:暗影萨满

分类
已于2021-4-7 10:23:44修改
2
收藏
回复
举报
1条回复
按时间正序
/
按时间倒序
vsrrrrrb
vsrrrrrb

厉害了,不过还是太复杂,期待更简单的第三方加载网络图片类库。

回复
2021-10-17 02:26:26
回复
    相关推荐