purpose

Loading an image from the web is something almost every app needs. On Android we have ImageLoader, Picasso, Glide, and Glide is best known. In order to more convenient development of hongmeng application, we also according to Glide the most core design ideas, completed hongmeng picture loading frame.

Glide core design idea

I call this design mode as the number machine design. Taking the familiar banking business as an example, each person who enters the bank needs to handle business has different business needs, so the business content to be handled is equivalent to Request information. After entering the bank, they will be guided to the number manager to get the number. It will then be assigned to an idle window (RequestDispatcher) to actually handle the business.

Framework implementations

Define roles

PixelMapDispatcher: ImageLoader: exposed API operation class. PixelMapDispatcher: ImageLoader: exposed API operation classCopy the code

Remove the rest of the meat, the top four classes are the skeleton of the frame, and all we need to do now is fill the skeleton with muscle.

PixelMapRequest

/** * public class PixelMapRequest {/** * private String mUrl; /** * Hold */ private SoftReference< image > mImage; /** * context object */ private AbilityContext mContext; /** * private int mResId; /** * private RequestListener mListener; /** * private String mUrlMd5; public PixelMapRequest(AbilityContext context) { this.mContext = context; } /** * public PixelMapRequest load(String url) {this.mUrl = url; if (! StringUtils.isEmpty(url)) { this.mUrlMd5 = MD5Utils.encrypt(url); } return this; } public PixelMapRequest Loading (int resId) {this.mresid = resId; return this; } /** * public PixelMapRequest setListener(RequestListener listener) { this.mListener = listener; return this; ** @param image */ public void into(image image) {image.settag (mUrlMd5); this.mImage = new SoftReference<>(image); / / the initiating RequestManager. GetInstance () addPixelMapRequest (this); } public String getUrl() {return mUrl; Public SoftReference<Image> getImage() {return mImage; } public int getResId() {return mResId; } /** * public RequestListener getListener() {return mListener; } public String getUrlMd5() {return mUrlMd5; }}Copy the code

Encapsulate image request information, such as image corresponding URL, associated controls, default image resources, etc. Controls are held with soft references to consider memory issues, and control references can be reclaimed by GC when memory runs out. Each request has an identifier bit, whose value is the MD5 value of the URL, which is used to prevent images and controls from being misplaced, as well as the function of the cache key. Design imitate Glide chain call way.

RequestManager

Public class RequestManager {/** ** private AbilityContext mContext; /** * Caches the request queue, considering that there may be multiple threads operating on this queue, */ private LinkedBlockingQueue<PixelMapRequest> mRequestQueue = new LinkedBlockingQueue<>(); /** * private PixelMapDispatcher[] dispatchers; /** * thread pool */ public ExecutorService; private RequestManager() { } private static class Holder { private static RequestManager manager = new RequestManager();  } public static RequestManager getInstance() { return Holder.manager; } /** * initialize, * * @param abilityContext */ public void init(abilityContext abilityContext) {this.mContext = abilityContext; initThreadExecutor(); start(); } /** * Init thread pool */ public void initThreadExecutor() {int size = runtime.geTruntime ().availableProcessors(); if (size <= 0) { size = 1; } size *= 2; executorService = Executors.newFixedThreadPool(size); } /** * start work */ public void start() {stop(); startAllDispatcher(); } /** * start all processing threads */ public void startAllDispatcher() {final int threadCount = Runtime.getRuntime().availableProcessors(); PixelMapDispatcher = new PixelMapDispatcher[threadCount]; if (dispatchers.length > 0) { for (int i = 0; i < threadCount; i++) { PixelMapDispatcher pixelmapDispatcher = new PixelMapDispatcher(mRequestQueue); Executorservice. execute(pixelmapDispatcher); [I] = pixelmapDispatcher; }}} /** * stop all work */ public void stop() {if (dispatchers! = null && dispatchers.length > 0) { for (PixelMapDispatcher pixelmapDispatcher : dispatchers) { if (! pixelmapDispatcher.isInterrupted()) { pixelmapDispatcher.interrupt(); // interrupt}}}} /** * add request ** @param pixelMapRequest */ public void addPixelMapRequest(pixelMapRequest) {  if (pixelMapRequest == null) { return; } if (! mRequestQueue.contains(pixelMapRequest)) { mRequestQueue.add(pixelMapRequest); // Queue requests}}}Copy the code

The first one is a singleton, where only one instance of an application is required to manage requests. The management class contains a request queue, an array of handlers, and a thread pool. Given that the request queue may be operated on by multiple threads, a blocking queue like LinkedBlockingQueue is used, and the length of the array of handlers depends on the system’s capacity. Others define some functions, and the comments are clear.

PixelMapDispatcher

Public class PixelMapDispatcher extends Thread {private EventHandler Handler = new EventHandler(EventRunner.getMainEventRunner()); 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); PixelMap = findPixelMap(request); ShowImageView (request, pixelMap); 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()); }); ** @param Request * @return */ private PixelMap findPixelMap(PixelMapRequest request) { / / 1. PixelMap = downloadPixelMap(request); Todo return pixelMap; } /** ** downloadPixelMap ** @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; } /** * 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()); }}}Copy the code

Level 2 cache is not perfect, it will be fixed later. This is to fetch the request from the queue directly via HttpURLConnection, serialize it into a PixelMap object, and display it.

ImageLoader

/** * public class ImageLoader {/** * return an image request ** @param abilityContext * @return */ public static PixelMapRequest with(AbilityContext abilityContext) { return new PixelMapRequest(abilityContext); }}Copy the code

Test code (used with 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); }}}Copy the code

Test passed, ok

conclusion

In fact, as long as you understand the design of the number machine, then look at the framework is very simple, the code logic is not complex, as long as some details can be paid attention to. Of course, the gap between this and Glide framework is very big, such as Glide cache mechanism, life cycle processing method, highly configurable design are not, but the core context has been formed, the missing part of the later a little bit more perfect, after all, those mature framework is not a touch.

A link to the

Github:https://github.com/loubinfeng2013/HarmonyTools
Email:[email protected]
Copy the code