package com.mozz.htmlnative; import android.content.Context; import android.os.SystemClock; import android.support.annotation.MainThread; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import android.view.View; import android.view.ViewGroup; import com.mozz.htmlnative.css.AttrsSet; import com.mozz.htmlnative.css.InheritStylesRegistry; import com.mozz.htmlnative.css.StyleSheet; import com.mozz.htmlnative.css.Styles; import com.mozz.htmlnative.css.selector.CssSelector; import com.mozz.htmlnative.css.stylehandler.LayoutStyleHandler; import com.mozz.htmlnative.css.stylehandler.StyleHandler; import com.mozz.htmlnative.css.stylehandler.StyleHandlerFactory; import com.mozz.htmlnative.dom.AttachedElement; import com.mozz.htmlnative.dom.DomElement; import com.mozz.htmlnative.dom.HNDomTree; import com.mozz.htmlnative.exception.AttrApplyException; import com.mozz.htmlnative.view.HNRootView; import com.mozz.htmlnative.view.LayoutParamsLazyCreator; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import static com.mozz.htmlnative.HNEnvironment.PERFORMANCE_TAG; /** * Render views */ public final class HNRenderer { /** * cache the constructor for later use */ private static final HashMap<String, Constructor<? extends View>> sConstructorMap = new HashMap<>(); private static final Map<String, ViewFactory> sViewFactory = new HashMap<>(); private static final Object[] sConstructorArgs = new Object[1]; private static final Class<?>[] sConstructorSignature = new Class[]{Context.class}; private InheritStyleStack mInheritStyleStack; private Tracker mTracker; private HNRenderer() { mInheritStyleStack = new InheritStyleStack(); mTracker = new Tracker(); } @NonNull public static HNRenderer get() { return new HNRenderer(); } public static InheritStyleStack computeInheritStyle(View view) { StyleHandler viewStyleHandler = StyleHandlerFactory.get(view); StyleHandler extraStyleHandler = StyleHandlerFactory.extraGet(view); LayoutStyleHandler parentLayoutAttr = StyleHandlerFactory.parentGet(view); InheritStyleStack inheritStyleStack = new InheritStyleStack(); inheritStyleStack.push(); Iterator<String> itr = InheritStylesRegistry.iterator(); while (itr.hasNext()) { String params = itr.next(); Object val = Styles.getStyle(view, params, viewStyleHandler, extraStyleHandler, parentLayoutAttr); if (val != null) { inheritStyleStack.newStyle(params, val); } } return inheritStyleStack; } @MainThread final View render(@NonNull Context context, @NonNull HNSegment segment, @NonNull ViewGroup .LayoutParams params) throws HNRenderException { mTracker.reset(); HNLog.d(HNLog.RENDER, "start to render " + segment.toString()); HNRootView rootViewGroup = new HNRootView(context); HNSandBoxContext sandBoxContext = HNSandBoxContextImpl.createContext(rootViewGroup, segment, context); mInheritStyleStack.reset(); LayoutParamsLazyCreator rootCreator = new LayoutParamsLazyCreator(); long renderStartTime = SystemClock.currentThreadTimeMillis(); View v = renderInternal(context, sandBoxContext, segment.getDom(), segment, rootViewGroup, rootCreator, rootViewGroup, segment.getStyleSheet()); if (v != null) { rootViewGroup.addContent(v, LayoutParamsLazyCreator.createLayoutParams(rootViewGroup, rootCreator)); mTracker.record("Render View", SystemClock.currentThreadTimeMillis() - renderStartTime); long createTime = SystemClock.currentThreadTimeMillis(); this.performCreate(sandBoxContext); mTracker.record("Create View", SystemClock.currentThreadTimeMillis() - createTime); long afterCreate = SystemClock.currentThreadTimeMillis(); this.performCreated(sandBoxContext); mTracker.record("After View Created", SystemClock.currentThreadTimeMillis() - afterCreate); Log.i(PERFORMANCE_TAG, mTracker.dump()); HNLog.d(HNLog.RENDER, sandBoxContext.allIdTag()); return rootViewGroup; } return null; } private View renderInternal(@NonNull Context context, @NonNull HNSandBoxContext sandBoxContext, HNDomTree tree, HNSegment segment, @NonNull ViewGroup parent, @NonNull LayoutParamsLazyCreator paramsCreator, @NonNull HNRootView root, StyleSheet styleSheet) throws HNRenderException { AttrsSet attrsSet = segment.getInlineStyles(); if (tree.isLeaf()) { View v = createView(tree, tree, sandBoxContext, parent, context, attrsSet, paramsCreator, styleSheet, mInheritStyleStack); mInheritStyleStack.pop(); return v; } else { View view = createView(tree, tree, sandBoxContext, parent, context, attrsSet, paramsCreator, styleSheet, mInheritStyleStack); if (view == null) { return null; } if (view instanceof ViewGroup) { final ViewGroup viewGroup = (ViewGroup) view; List<HNDomTree> children = tree.children(); for (HNDomTree child : children) { LayoutParamsLazyCreator childCreator = new LayoutParamsLazyCreator(); // Recursively render child. final View v = renderInternal(context, sandBoxContext, child, segment, viewGroup, childCreator, root, styleSheet); if (v != null) { viewGroup.addView(v, LayoutParamsLazyCreator.createLayoutParams(parent, childCreator)); } else { HNLog.e(HNLog.RENDER, "error when inflating " + child.getType()); } } } else { HNLog.e(HNLog.RENDER, "View render from HNRenderer is not " + "an " + "viewGroup" + view.getClass().getSimpleName() + ", but related HNDomTree has children. Will ignore its children!"); } mInheritStyleStack.pop(); return view; } } public static View createView(AttrsSet.AttrsOwner owner, @NonNull DomElement tree, @NonNull HNSandBoxContext sandBoxContext, ViewGroup parent, @NonNull Context context, AttrsSet attrsSet, @NonNull LayoutParamsLazyCreator layoutCreator, StyleSheet styleSheet, InheritStyleStack stack) throws HNRenderException { String type = tree.getType(); if (stack != null) { stack.push(); } try { View v; if (HtmlTag.isGroupingElement(type)) { v = createAndroidViewGroup(context, type, owner, attrsSet, layoutCreator); } else { v = createAndroidView(context, type); } if (v == null) { HNLog.e(HNLog.RENDER, "createView createDiv: view is null with tag " + type); return null; } //attach the dom element to view DomElement domElement = AttachedElement.cloneIfNecessary(tree); domElement.setParent((DomElement) parent.getTag()); v.setTag(AttachedElement.cloneIfNecessary(tree)); // save the id if element has one String id = tree.getId(); if (id != null) { sandBoxContext.registerId(id, v); } // ------- below starts the styleSheet process part ------- // 1 - find the related StyleHandler StyleHandler viewStyleHandler = StyleHandlerFactory.get(v); StyleHandler extraStyleHandler = StyleHandlerFactory.extraGet(v); LayoutStyleHandler parentLayoutAttr = StyleHandlerFactory.parentGet(v); // 2 - set initial style to an view try { Styles.setDefaultStyle(context, sandBoxContext, v, tree, parent, viewStyleHandler, extraStyleHandler, parentLayoutAttr, layoutCreator); } catch (AttrApplyException e) { e.printStackTrace(); } // 3 - use parent inherit style try { /** * First apply the parent styleSheet style to it. */ if (stack != null) { for (Styles.StyleEntry entry : stack) { // here pass InheritStyleStack null to Styles, is to prevent Style being // stored in InheritStyleStack twice Styles.applyStyle(context, sandBoxContext, v, tree, layoutCreator, parent, viewStyleHandler, extraStyleHandler, parentLayoutAttr, entry, false, null); } } } catch (AttrApplyException e) { e.printStackTrace(); HNLog.e(HNLog.RENDER, "wrong when apply inherit attr to " + type); } // 4 - use CSS to render if (styleSheet != null) { CssSelector[] matchedSelectors = styleSheet.matchedSelector(type, tree.getId(), tree.getClazz()); for (CssSelector selector : matchedSelectors) { if (selector != null) { if (selector.matchWhole(tree)) { try { Styles.apply(context, sandBoxContext, styleSheet, v, selector, tree, parent, layoutCreator, false, false, viewStyleHandler, extraStyleHandler, parentLayoutAttr, stack); } catch (AttrApplyException e) { e.printStackTrace(); HNLog.e(HNLog.RENDER, "Wrong when apply css style to " + type); } } } } } // 5 - use inline-style to render try { if (attrsSet != null) { Styles.apply(context, sandBoxContext, attrsSet, v, owner, tree, parent, layoutCreator, true, false, viewStyleHandler, extraStyleHandler, parentLayoutAttr, stack); } } catch (AttrApplyException e) { e.printStackTrace(); HNLog.e(HNLog.RENDER, "wrong when apply inline attr to " + type); } return v; } catch (ClassNotFoundException e) { e.printStackTrace(); throw new HNRenderException("class not found " + type); } catch (NoSuchMethodException e) { e.printStackTrace(); throw new HNRenderException("class's constructor is missing " + type); } catch (IllegalAccessException e) { e.printStackTrace(); throw new HNRenderException("class's constructor can not be accessed " + type); } catch (InstantiationException e) { e.printStackTrace(); throw new HNRenderException("class's constructor can not be invoked " + type); } catch (InvocationTargetException e) { e.printStackTrace(); throw new HNRenderException("class's method has something wrong " + type); } } @Nullable static View createAndroidView(@NonNull Context context, @Nullable String typeName) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { String viewClassName = ViewTypeRelations.findClassByType(typeName); if (viewClassName == null) { return null; } HNLog.d(HNLog.RENDER, "createContext view" + viewClassName + " with type" + typeName); // first let viewFactory to hook the create process View view = createViewByViewFactory(context, viewClassName); if (view != null) { return view; } Constructor<? extends View> constructor = sConstructorMap.get(viewClassName); Class<? extends View> clazz; if (constructor == null) { // Class not found in the cache, see if it's real, and try to add it clazz = context.getClassLoader().loadClass(viewClassName).asSubclass(View.class); constructor = clazz.getConstructor(sConstructorSignature); constructor.setAccessible(true); sConstructorMap.put(viewClassName, constructor); } sConstructorArgs[0] = context; view = constructor.newInstance(sConstructorArgs); // release the context sConstructorArgs[0] = null; return view; } static View createAndroidViewGroup(@NonNull Context context, @Nullable String typeName, AttrsSet.AttrsOwner owner, AttrsSet attrsSet, LayoutParamsLazyCreator layoutParamsCreator) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { // set the <body> width to 100% layoutParamsCreator.width = ViewGroup.LayoutParams.MATCH_PARENT; layoutParamsCreator.width = ViewGroup.LayoutParams.WRAP_CONTENT; if (attrsSet != null) { Object displayObj = attrsSet.getStyle(owner, "display"); if (displayObj != null && displayObj instanceof String) { String display = (String) displayObj; switch (display) { case Styles.VAL_DISPLAY_FLEX: return createAndroidView(context, "flexbox"); case Styles.VAL_DISPLAY_ABSOLUTE: return createAndroidView(context, "box"); case Styles.VAL_DISPLAY_BOX: default: return createAndroidView(context, "linearbox"); } } } return createAndroidView(context, "linearbox"); } public static void renderStyle(Context context, final HNSandBoxContext sandBoxContext, View v, DomElement domElement, @NonNull LayoutParamsLazyCreator layoutCreator, @NonNull ViewGroup parent, String styleName, Object style, boolean isParent, InheritStyleStack stack) throws AttrApplyException { StyleHandler viewStyleHandler = StyleHandlerFactory.get(v); StyleHandler extraStyleHandler = StyleHandlerFactory.extraGet(v); LayoutStyleHandler parentLayoutAttr = StyleHandlerFactory.parentGet(v); Styles.applyStyle(context, sandBoxContext, v, domElement, layoutCreator, parent, viewStyleHandler, extraStyleHandler, parentLayoutAttr, styleName, style, isParent, stack); } public static void renderStyle(Context context, @NonNull final HNSandBoxContext sandBoxContext, @NonNull View v, DomElement domElement, @NonNull LayoutParamsLazyCreator layoutCreator, ViewGroup parent, @NonNull Map<String, Object> styles, boolean isParent, InheritStyleStack stack) throws AttrApplyException { final StyleHandler viewStyleHandler = StyleHandlerFactory.get(v); final StyleHandler extraStyleHandler = StyleHandlerFactory.extraGet(v); final LayoutStyleHandler parentAttr = StyleHandlerFactory.parentGet(v); for (Map.Entry<String, Object> entry : styles.entrySet()) { try { Styles.applyStyle(v.getContext(), sandBoxContext, v, domElement, layoutCreator, parent, viewStyleHandler, extraStyleHandler, parentAttr, entry.getKey(), entry.getValue(), isParent, stack); } catch (AttrApplyException e) { e.printStackTrace(); } } } public static void registerViewFactory(String androidClassName, ViewFactory factory) { sViewFactory.put(androidClassName, factory); } public static void unregisterViewFactory(String androidClassName) { sViewFactory.remove(androidClassName); } private static View createViewByViewFactory(Context context, @NonNull String viewClassName) { ViewFactory factory = sViewFactory.get(viewClassName); if (factory != null) { return factory.create(context); } return null; } private void performCreate(HNSandBoxContext sandBoxContext) { if (sandBoxContext != null) { sandBoxContext.onViewCreate(); } } private void performCreated(HNSandBoxContext sandBoxContext) { if (sandBoxContext != null) { sandBoxContext.onViewLoaded(); } } public static class HNRenderException extends Exception { public HNRenderException() { super(); } public HNRenderException(String detailMessage, Throwable throwable) { super(detailMessage, throwable); } public HNRenderException(String detailMessage) { super(detailMessage); } public HNRenderException(Throwable throwable) { super(throwable); } } }