/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.weex;

import static org.apache.weex.http.WXHttpUtil.KEY_USER_AGENT;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RestrictTo;
import android.support.annotation.RestrictTo.Scope;
import android.support.annotation.WorkerThread;
import android.support.v4.util.ArrayMap;
import android.text.TextUtils;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ScrollView;
import com.alibaba.fastjson.JSONObject;
import org.apache.weex.adapter.IDrawableLoader;
import org.apache.weex.adapter.IWXConfigAdapter;
import org.apache.weex.adapter.IWXHttpAdapter;
import org.apache.weex.adapter.IWXImgLoaderAdapter;
import org.apache.weex.adapter.IWXJscProcessManager;
import org.apache.weex.adapter.IWXUserTrackAdapter;
import org.apache.weex.adapter.URIAdapter;
import org.apache.weex.appfram.websocket.IWebSocketAdapter;
import org.apache.weex.bridge.EventResult;
import org.apache.weex.bridge.NativeInvokeHelper;
import org.apache.weex.bridge.SimpleJSCallback;
import org.apache.weex.bridge.WXBridgeManager;
import org.apache.weex.bridge.WXModuleManager;
import org.apache.weex.bridge.WXParams;
import org.apache.weex.common.Constants;
import org.apache.weex.common.Destroyable;
import org.apache.weex.common.OnWXScrollListener;
import org.apache.weex.common.RenderTypes;
import org.apache.weex.common.WXConfig;
import org.apache.weex.common.WXErrorCode;
import org.apache.weex.common.WXModule;
import org.apache.weex.common.WXPerformance;
import org.apache.weex.common.WXRefreshData;
import org.apache.weex.common.WXRenderStrategy;
import org.apache.weex.common.WXRequest;
import org.apache.weex.dom.WXEvent;
import org.apache.weex.http.WXHttpUtil;
import org.apache.weex.instance.InstanceOnFireEventInterceptor;
import org.apache.weex.layout.ContentBoxMeasurement;
import org.apache.weex.performance.WXInstanceApm;
import org.apache.weex.performance.WXStateRecord;
import org.apache.weex.performance.WhiteScreenUtils;
import org.apache.weex.render.WXAbstractRenderContainer;
import org.apache.weex.tracing.WXTracing;
import org.apache.weex.ui.action.GraphicActionAddElement;
import org.apache.weex.ui.component.NestedContainer;
import org.apache.weex.ui.component.WXComponent;
import org.apache.weex.ui.component.WXEmbed;
import org.apache.weex.ui.flat.FlatGUIContext;
import org.apache.weex.ui.view.WXScrollView;
import org.apache.weex.utils.Trace;
import org.apache.weex.utils.WXDeviceUtils;
import org.apache.weex.utils.WXExceptionUtils;
import org.apache.weex.utils.WXFileUtils;
import org.apache.weex.utils.WXJsonUtils;
import org.apache.weex.utils.WXLogUtils;
import org.apache.weex.utils.WXReflectionUtils;
import org.apache.weex.utils.WXUtils;
import org.apache.weex.utils.WXViewUtils;
import org.apache.weex.utils.cache.RegisterCache;
import org.apache.weex.utils.tools.LogDetail;
import org.apache.weex.utils.tools.TimeCalculator;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.weex.bridge.WXBridgeManager.BundType;


/**
 * Each instance of WXSDKInstance represents an running weex instance.
 * It can be a pure weex view, or mixed with native view
 */
public class WXSDKInstance implements IWXActivityStateListener,View.OnLayoutChangeListener {

  private static  final  String SOURCE_TEMPLATE_BASE64_MD5 = "templateSourceBase64MD5";

  /**
   * Devtool protocol
   */
  public static String ACTION_DEBUG_INSTANCE_REFRESH = "DEBUG_INSTANCE_REFRESH";
  public static String ACTION_INSTANCE_RELOAD = "INSTANCE_RELOAD";

  //Performance
  public boolean mEnd = false;
  public boolean mHasCreateFinish = false;
  public static final String BUNDLE_URL = "bundleUrl";
  private IWXUserTrackAdapter mUserTrackAdapter;
  private IWXRenderListener mRenderListener;
  private IWXStatisticsListener mStatisticsListener;
  /** package **/ Context mContext;
  private final String mInstanceId;
  private WXAbstractRenderContainer mRenderContainer;
  private WXComponent mRootComp;
  private boolean mRendered;
  private WXRefreshData mLastRefreshData;
  private NestedInstanceInterceptor mNestedInstanceInterceptor;
  private String mBundleUrl = "";
  public static String requestUrl = "requestUrl";
  private boolean isDestroy=false;
  private boolean hasException = false;
  private boolean isRenderSuccess = false;
  private boolean createInstanceHeartBeat = false;
  private Map<String,Serializable> mUserTrackParams;
  private NativeInvokeHelper mNativeInvokeHelper;
  private boolean isCommit=false;
  private WXGlobalEventReceiver mGlobalEventReceiver=null;
  private boolean trackComponent;
  private boolean enableLayerType = true;
  private boolean mNeedValidate = false;
  private boolean mNeedReLoad = false;
  private boolean mUseScroller = false;
  private int mInstanceViewPortWidth = 750;
  private boolean enableFullScreenHeight = false;
  private WXInstanceApm mApmForInstance;
  private @NonNull
  FlatGUIContext mFlatGUIContext =new FlatGUIContext();

  private Map<String,String> mContainerInfo;

  public boolean isNewFsEnd = false;
  private List<JSONObject> componentsInfoExceedGPULimit  = new LinkedList<>();

  /**
   * bundle type
   */
  public BundType bundleType;
  public long mRenderStartNanos;
  public int mExecJSTraceId = WXTracing.nextId();

  private boolean isViewDisAppear = false;

  /**
   *for network tracker
   */
  public String mwxDims[] = new String [5];
  public long measureTimes[] = new long [5];

  public WeakReference<String> templateRef;
  public Map<String,List<String>> responseHeaders = new HashMap<>();

  /**
   * Render strategy.
   */
  private WXRenderStrategy mRenderStrategy = WXRenderStrategy.APPEND_ASYNC;

  private boolean mDisableSkipFrameworkInit = false;

  /**
   * Render start time
   */
  public long mRenderStartTime;
  /**
   * Refresh start time
   */
  private long mRefreshStartTime;
  private WXPerformance mWXPerformance;
  private ScrollView mScrollView;
  private WXScrollView.WXScrollViewListener mWXScrollViewListener;

  private List<OnWXScrollListener> mWXScrollListeners;

  private List<String> mLayerOverFlowListeners;

  private List<ActionBarHandler> mWXActionbarHandlers;

  private List<OnBackPressedHandler> mWXBackPressedHandlers;

  private List<OnActivityResultHandler> mWXOnActivityResultHandlers;

  private WXSDKInstance mParentInstance;

  private String mRenderType = RenderTypes.RENDER_TYPE_NATIVE;

  public TimeCalculator mTimeCalculator;
  /**
   * Default Width And Viewport is 750,
   * when screen width change, we adjust viewport to adapter screen change
   * */
  private boolean mAutoAdjustDeviceWidth = WXEnvironment.AUTO_ADJUST_ENV_DEVICE_WIDTH;

  public  List<JSONObject> getComponentsExceedGPULimit(){return componentsInfoExceedGPULimit;}
  @RestrictTo(Scope.LIBRARY)
  public void setComponentsInfoExceedGPULimit(JSONObject component){
    if(component!= null && !component.isEmpty()){
      componentsInfoExceedGPULimit.add(component);
    }
  }

  public List<String> getLayerOverFlowListeners() {
    return mLayerOverFlowListeners;
  }

  public void addLayerOverFlowListener(String ref) {
    if (mLayerOverFlowListeners == null)
      mLayerOverFlowListeners = new ArrayList<>();
    mLayerOverFlowListeners.add(ref);
  }

  public void removeLayerOverFlowListener(String ref) {
    if (mLayerOverFlowListeners != null)
      mLayerOverFlowListeners.remove(ref);
  }

  /**
   * whether we are in preRender mode
   * */
  private volatile boolean isPreRenderMode;

  private boolean mCurrentGround = false;
  private ComponentObserver mComponentObserver;
  private Map<String, GraphicActionAddElement> inactiveAddElementAction = new ArrayMap<>();

  private Map<Long, ContentBoxMeasurement> mContentBoxMeasurements = new ArrayMap<>();

  private List<InstanceOnFireEventInterceptor> mInstanceOnFireEventInterceptorList;


  /**
   * network handler
   */
  public interface ImageNetworkHandler {
    public String fetchLocal(String url);
  }

  public interface StreamNetworkHandler {
    public String fetchLocal(String url);
  }

  public interface CustomFontNetworkHandler {
    public String fetchLocal(String url);
  }

  private ImageNetworkHandler mImageNetworkHandler;

  private StreamNetworkHandler mStreamNetworkHandler;

  private CustomFontNetworkHandler mCustomFontNetworkHandler;

  public ImageNetworkHandler getImageNetworkHandler() {
    return mImageNetworkHandler;
  }

  public void setImageNetworkHandler(ImageNetworkHandler imageNetworkHandler) {
    this.mImageNetworkHandler = imageNetworkHandler;
  }

  public StreamNetworkHandler getStreamNetworkHandler() {
    return mStreamNetworkHandler;
  }

  public void setStreamNetworkHandler(StreamNetworkHandler streamNetworkHandler) {
    this.mStreamNetworkHandler = streamNetworkHandler;
  }

  public CustomFontNetworkHandler getCustomFontNetworkHandler() {
    return mCustomFontNetworkHandler;
  }

  public void setCustomFontNetworkHandler(CustomFontNetworkHandler customFontNetworkHandler) {
    this.mCustomFontNetworkHandler = customFontNetworkHandler;
  }


  /**
   *  ActionBar Handler
   */

  public interface ActionBarHandler {
    boolean onSupportNavigateUp();
  }

  public interface OnBackPressedHandler {
    boolean onBackPressed();
  }

  public static abstract class OnActivityResultHandler {
    private String id;

    public OnActivityResultHandler(String id) {
      this.id = id;
    }

    public abstract boolean onActivityResult(int requestCode, int resultCode, Intent data, String id);

    public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
      return onActivityResult(requestCode, resultCode, data, id);
    }
  }

  /**
   * set make weexCore run in single process mode
   * @param flag true means weexCore run in single process mode or multi process mode
   */
  public void setUseSingleProcess(boolean flag) {
    WXBridgeManager.getInstance().setUseSingleProcess(flag);
  }

  /**
   * set open SandBox
   * @param flag
   */
  public void setUseSandBox(boolean flag) {
    WXBridgeManager.getInstance().setSandBoxContext(flag);
  }

  public PriorityQueue<WXEmbed> hiddenEmbeds;

  private int maxHiddenEmbedsNum = -1; //max hidden embed num, -1 standard for ulimit

  public int getMaxHiddenEmbedsNum() {
    return maxHiddenEmbedsNum;
  }

  public void setMaxHiddenEmbedsNum(int maxHiddenEmbedsNum) {
    this.maxHiddenEmbedsNum = maxHiddenEmbedsNum;
  }

  @WorkerThread
  @RestrictTo(Scope.LIBRARY)
  public void addInActiveAddElementAction(String ref, GraphicActionAddElement action){
    inactiveAddElementAction.put(ref, action);
  }

  @WorkerThread
  @RestrictTo(Scope.LIBRARY)
  public void removeInActiveAddElmentAction(String ref){
    inactiveAddElementAction.remove(ref);
  }

  @WorkerThread
  @RestrictTo(Scope.LIBRARY)
  public GraphicActionAddElement getInActiveAddElementAction(String ref){
    return inactiveAddElementAction.get(ref);
  }

  /**
   * If anchor is created manually(etc. define a layout xml resource ),
   * be aware do not add it to twice when {@link IWXRenderListener#onViewCreated(WXSDKInstance, View)}.
   * @param a
   */
  public void setRenderContainer(RenderContainer a){
     setWXAbstractRenderContainer(a);
  }

  public void setWXAbstractRenderContainer(WXAbstractRenderContainer a){
    if(a != null) {
      a.setSDKInstance(this);
      a.addOnLayoutChangeListener(this);
    }

    mRenderContainer = a;
    if (mRenderContainer != null && mRenderContainer.getLayoutParams() != null
            && mRenderContainer.getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
      WXBridgeManager.getInstance().post(new Runnable() {
        @Override
        public void run() {
          WXBridgeManager.getInstance().setRenderContentWrapContentToCore(true, getInstanceId());
        }
      });
    } else {
      WXBridgeManager.getInstance().post(new Runnable() {
        @Override
        public void run() {
          WXBridgeManager.getInstance().setRenderContentWrapContentToCore(false, getInstanceId());
        }
      });
    }
  }

  private int mMaxDeepLayer;

  public boolean isTrackComponent() {
    return trackComponent;
  }

  public void setTrackComponent(boolean trackComponent) {
    this.trackComponent = trackComponent;
  }

  /**
   * Tell whether it is enabled to change the layerType
   * {@link android.view.View#setLayerType(int, Paint)}
   * @return True for enable to change the layerType of component, false otherwise. The default
   * is True
   */
  public boolean isLayerTypeEnabled() {
    return enableLayerType;
  }

  /**
   * Enable the ability of changing layerType. e.g. {@link android.view.View#setLayerType(int, Paint)}
   * Disable the ability of changing layerType will have tremendous <strong>performance
   * punishment</strong>.
   *
   * <strong>Do not</strong> set this to false unless you know exactly what you are doing.
   * @param enable True for enable to change the layerType of component, false otherwise. The default
   * is True
   */
  public void enableLayerType(boolean enable) {
    enableLayerType = enable;
  }

  @RestrictTo(RestrictTo.Scope.LIBRARY)
  public @NonNull
  FlatGUIContext getFlatUIContext(){
    return mFlatGUIContext;
  }

  public boolean isNeedValidate() {
    return mNeedValidate;
  }

  public boolean isNeedReLoad() {
    return mNeedReLoad;
  }

  public void setNeedLoad(boolean load) {
    mNeedReLoad = load;
  }

  @RestrictTo(Scope.LIBRARY)
  public void  setEnableFullScreenHeight(boolean fullScreenHeight){enableFullScreenHeight = fullScreenHeight;}

  @RestrictTo(Scope.LIBRARY)
  public boolean isFullScreenHeightEnabled(){return enableFullScreenHeight;}

  public boolean isUseScroller() {
    return mUseScroller;
  }

  public void setUseScroller(boolean use) {
    mUseScroller = use;
  }

  public void setInstanceViewPortWidth(int instanceViewPortWidth) {
    setInstanceViewPortWidth(instanceViewPortWidth,false);

  }
  public void setInstanceViewPortWidth(int instanceViewPortWidth,boolean fromMetaModule){
    this.mInstanceViewPortWidth = instanceViewPortWidth;
    this.mAutoAdjustDeviceWidth = false;
    if(!fromMetaModule){
      WXBridgeManager.getInstance().setViewPortWidth(getInstanceId(), mInstanceViewPortWidth);
    }
  }
  public void resetDeviceDisplayOfPage(){
    WXBridgeManager.getInstance().setDeviceDisplayOfPage(getInstanceId(), WXViewUtils.getScreenWidth(getContext()), WXViewUtils.getScreenHeight(getContext()));
  }
  public void setPageKeepRawCssStyles(){
    WXBridgeManager.getInstance().setPageArgument(getInstanceId(),"reserveCssStyles","true");
  }
  public void reloadPageLayout(){
    WXBridgeManager.getInstance().reloadPageLayout(getInstanceId());
  }

  public void setAutoAdjustDeviceWidth(boolean autoAdjustViewPort){
    this.mAutoAdjustDeviceWidth = autoAdjustViewPort;
  }

  public boolean isAutoAdjustDeviceWidth(){
    return mAutoAdjustDeviceWidth;
  }

  private void setDeviceDisplay(float deviceWith, float deviceHeight, float scale){
    WXBridgeManager.getInstance().setDeviceDisplay(getInstanceId(), deviceWith, deviceHeight, scale);
  }



  public int getInstanceViewPortWidth(){
    return mInstanceViewPortWidth;
  }

  public interface OnInstanceVisibleListener{
    void onAppear();
    void onDisappear();
  }
  private List<OnInstanceVisibleListener> mVisibleListeners = new ArrayList<>();

  public WXSDKInstance(Context context) {
    mInstanceId = WXSDKManager.getInstance().generateInstanceId();
    init(context);
  }

  //for preInit
  public WXSDKInstance(){
    mInstanceId = WXSDKManager.getInstance().generateInstanceId();
    mWXPerformance = new WXPerformance(mInstanceId);
    mApmForInstance = new WXInstanceApm(mInstanceId);
    WXSDKManager.getInstance().getAllInstanceMap().put(mInstanceId,this);
    mTimeCalculator = new TimeCalculator(this);
  }


  /**
   * For unittest only.
   */
  @RestrictTo(Scope.TESTS)
  WXSDKInstance(Context context,String id) {
    mInstanceId = id;
    init(context);
  }

  public WXComponent getRootComponent() {
    return mRootComp;
  }

  public void setNestedInstanceInterceptor(NestedInstanceInterceptor interceptor){
    mNestedInstanceInterceptor = interceptor;
  }

  public final WXSDKInstance createNestedInstance(NestedContainer container){
    WXSDKInstance sdkInstance = newNestedInstance();
    if(mNestedInstanceInterceptor != null){
      mNestedInstanceInterceptor.onCreateNestInstance(sdkInstance,container);
    }
    if(sdkInstance != null){
        sdkInstance.setComponentObserver(this.getComponentObserver());
    }
    return sdkInstance;
  }

  protected WXSDKInstance newNestedInstance() {
    return new WXSDKInstance(mContext);
  }
  public boolean isHasException() {
    return hasException;
  }

  public void setHasException(boolean hasException) {
    this.hasException = hasException;
  }

  public void createInstanceFuncHeartBeat() {
    WXLogUtils.d("createInstanceFuncHeartBeat: " + mInstanceId);
    this.createInstanceHeartBeat = true;
    getApmForInstance().onStage(WXInstanceApm.KEY_PAGE_STAGES_END_EXCUTE_BUNDLE);
  }

  public void addOnInstanceVisibleListener(OnInstanceVisibleListener l){
    mVisibleListeners.add(l);
  }

  public void removeOnInstanceVisibleListener(OnInstanceVisibleListener l){
    mVisibleListeners.remove(l);
  }

  public void init(Context context) {
    RegisterCache.getInstance().idle(true);
    mContext = context;
    mContainerInfo = new HashMap<>(4);
    mNativeInvokeHelper = new NativeInvokeHelper(mInstanceId);

    if (null == mWXPerformance){
      mWXPerformance = new WXPerformance(mInstanceId);
    }
    if (null == mApmForInstance){
      mApmForInstance = new WXInstanceApm(mInstanceId);
    }

    mWXPerformance.WXSDKVersion = WXEnvironment.WXSDK_VERSION;
    mWXPerformance.JSLibInitTime = WXEnvironment.sJSLibInitTime;

    mUserTrackAdapter=WXSDKManager.getInstance().getIWXUserTrackAdapter();

    WXSDKManager.getInstance().getAllInstanceMap().put(mInstanceId,this);

    mContainerInfo.put(WXInstanceApm.KEY_PAGE_PROPERTIES_CONTAINER_NAME, context instanceof Activity
            ? context.getClass().getSimpleName()
            :"unKnowContainer"
    );
    mContainerInfo.put(WXInstanceApm.KEY_PAGE_PROPERTIES_INSTANCE_TYPE,"page");

    // WXBridgeManager.getInstance().checkJsEngineMultiThread();
    mDisableSkipFrameworkInit = isDisableSkipFrameworkInDataRender();

    if(mTimeCalculator == null) {
      mTimeCalculator = new TimeCalculator(this);
    }
  }

  /**
   * Set a Observer for component.
   * This observer will be called in each component, should not doing
   * anything will impact render performance.
   *
   * @param observer
   */
  public void setComponentObserver(ComponentObserver observer){
    mComponentObserver = observer;
  }

  public ComponentObserver getComponentObserver(){
    return mComponentObserver;
  }

  public NativeInvokeHelper getNativeInvokeHelper() {
    return mNativeInvokeHelper;
  }

  @Deprecated
  public void setBizType(String bizType) {
    if (!TextUtils.isEmpty(bizType)) {
      mWXPerformance.bizType = bizType;
    }
  }

  public ScrollView getScrollView() {
    return mScrollView;
  }

  public void setRootScrollView(ScrollView scrollView) {
    mScrollView = scrollView;
    if (mWXScrollViewListener != null && mScrollView instanceof WXScrollView) {
      ((WXScrollView) mScrollView).addScrollViewListener(mWXScrollViewListener);
    }
  }

  @Deprecated
  public void registerScrollViewListener(WXScrollView.WXScrollViewListener scrollViewListener) {
    mWXScrollViewListener = scrollViewListener;
  }

  @Deprecated
  public WXScrollView.WXScrollViewListener getScrollViewListener() {
    return mWXScrollViewListener;
  }

  @Deprecated
  public void setIWXUserTrackAdapter(IWXUserTrackAdapter adapter) {
  }

  public void setContainerInfo(String key,String val){
    mContainerInfo.put(key,val);
  }

  public Map<String, String> getContainerInfo() {
    return mContainerInfo;
  }

  /**
   * Render template asynchronously, use {@link WXRenderStrategy#APPEND_ASYNC} as render strategy
   * @param template bundle js
   * @param options  os   iphone/android/ipad
   *                 weexversion    Weex version(like 1.0.0)
   *                 appversion     App version(like 1.0.0)
   *                 devid        Device id(like Aqh9z8dRJNBhmS9drLG5BKCmXhecHUXIZoXOctKwFebH)
   *                 sysversion    Device system version(like 5.4.4、7.0.4, should be used with os)
   *                 sysmodel     Device model(like iOS:"MGA82J/A", android:"MI NOTE LTE")
   *                 Time    UNIX timestamp, UTC+08:00
   *                 TTID(Optional)
   *                 MarkertId
   *                 Appname(Optional)  tm,tb,qa
   *                 Bundleurl(Optional)  template url
   * @param jsonInitData Initial data for rendering
   */
  public void render(String template, Map<String, Object> options, String jsonInitData) {
    render(template, options, jsonInitData, WXRenderStrategy.APPEND_ASYNC);
  }

  /**
   * Render template asynchronously
   * @param template bundle js
   * @param options  os   iphone/android/ipad
   *                 weexversion    Weex version(like 1.0.0)
   *                 appversion     App version(like 1.0.0)
   *                 devid        Device id(like Aqh9z8dRJNBhmS9drLG5BKCmXhecHUXIZoXOctKwFebH)
   *                 sysversion    Device system version(like 5.4.4、7.0.4, should be used with os)
   *                 sysmodel     Device model(like iOS:"MGA82J/A", android:"MI NOTE LTE")
   *                 Time    UNIX timestamp, UTC+08:00
   *                 TTID(Optional)
   *                 MarkertId
   *                 Appname(Optional)  tm,tb,qa
   *                 Bundleurl(Optional)  template url
   * @param jsonInitData Initial data for rendering
   * @param flag     RenderStrategy {@link WXRenderStrategy}
   */
  @Deprecated
  public void render(String template, Map<String, Object> options, String jsonInitData, WXRenderStrategy flag) {
    render(WXPerformance.DEFAULT, template, options, jsonInitData, flag);
  }

  /**
   * Render template asynchronously
   *
   * @param pageName, used for performance log.
   * @param template bundle js
   * @param options  os   iphone/android/ipad
   *                 weexversion    Weex version(like 1.0.0)
   *                 appversion     App version(like 1.0.0)
   *                 devid        Device id(like Aqh9z8dRJNBhmS9drLG5BKCmXhecHUXIZoXOctKwFebH)
   *                 sysversion    Device system version(like 5.4.4、7.0.4, should be used with os)
   *                 sysmodel     Device model(like iOS:"MGA82J/A", android:"MI NOTE LTE")
   *                 Time    UNIX timestamp, UTC+08:00
   *                 TTID(Optional)
   *                 MarkertId
   *                 Appname(Optional)  tm,tb,qa
   *                 Bundleurl(Optional)  template url
   * @param jsonInitData Initial data for rendering
   * @param flag     RenderStrategy {@link WXRenderStrategy}
   */
  public void render(String pageName, String template, Map<String, Object> options, String jsonInitData, WXRenderStrategy flag) {
    render(pageName, new Script(template), options, jsonInitData, flag);
  }

  public void render(String pageName, Script template, Map<String, Object> options, String jsonInitData, WXRenderStrategy flag) {
    mWXPerformance.beforeInstanceRender(mInstanceId);

    if(WXEnvironment.isApkDebugable() && WXPerformance.DEFAULT.equals(pageName)){

      if (getUIContext() != null) {
        new AlertDialog.Builder(getUIContext())
                .setTitle("Error: Missing pageName")
                .setMessage("We highly recommend you to set pageName. Call" +
                        "\nWXSDKInstance#render(String pageName, String template, Map<String, Object> options, String jsonInitData, WXRenderStrategy flag)\n" +
                        "to fix it.")
                .show();
      }

      return;
    }
    renderInternal(pageName,template,options,jsonInitData,flag);
  }

  /**
   * Render binary template asynchronously in DATA_RENDER_BINARY strategy.
   */
  public void render(String pageName, byte[] template, Map<String, Object> options, String jsonInitData) {
    render(pageName, new Script(template), options, jsonInitData, WXRenderStrategy.DATA_RENDER_BINARY);
  }

  private void ensureRenderArchor(){
    if(mRenderContainer == null){
      if (getContext() != null) {
        setRenderContainer(new RenderContainer(getContext()));
        mRenderContainer.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        mRenderContainer.setBackgroundColor(Color.TRANSPARENT);
        mRenderContainer.setSDKInstance(this);
        mRenderContainer.addOnLayoutChangeListener(this);
      }
    }
  }

  private void renderInternal(String pageName,
                              String template,
                              Map<String, Object> options,
                              String jsonInitData,
                              WXRenderStrategy flag) {
    if (mRendered || TextUtils.isEmpty(template)) {
      return;
    }
    renderInternal(pageName, new Script(template), options, jsonInitData, flag);
  }

  private boolean isPreInit = false;
  public boolean isPreInitMode(){
    return isPreInit;
  }

  private boolean isPreDownLoad =false;
  public boolean isPreDownLoad(){
    return isPreDownLoad;
  }

  public void onInstanceReady(){
    WXLogUtils.d("test->","onInstanceReady");
    mApmForInstance.onStage(WXInstanceApm.KEY_PAGE_STAGES_CONTAINER_READY);
    if (!isPreInit && !isPreDownLoad){
      return;
    }
    mApmForInstance.onInstanceReady(isPreDownLoad);
    if (isPreDownLoad){
        mHttpListener.onInstanceReady();
    }
  }

  public void preInit(String pageName,
                      String script,
                      Map<String, Object> options,
                      String jsonInitData,
                      WXRenderStrategy flag){
    isPreInit = true;
    mRenderStrategy = flag;
    Map<String, Object> renderOptions = options;
    if (renderOptions == null) {
      renderOptions = new HashMap<>();
    }
    //mApmForInstance.doInit();
    mApmForInstance.isReady = false;
    WXSDKManager.getInstance().createInstance(this, new Script(script), renderOptions, jsonInitData);
  }

  public void preDownLoad(String url,
                          Map<String, Object> options,
                          String jsonInitData,
                          WXRenderStrategy flag){
    this.isPreDownLoad =true;
    mRenderStrategy = flag;
    mApmForInstance.isReady = false;
    renderByUrl(url,url,options,jsonInitData,flag);
  }


  private void renderInternal(String pageName,
                              Script template,
                              Map<String, Object> options,
                              String jsonInitData,
                              WXRenderStrategy flag){
    if (mRendered || template == null || template.isEmpty()) {
      return;
    }

    LogDetail logDetail = mTimeCalculator.createLogDetail("renderInternal");
    mRenderStrategy = flag;

    //some case ,from render(template),but not render (url)
    if (!mApmForInstance.hasInit()){
      mApmForInstance.doInit();
    }
    mApmForInstance.setPageName(pageName);
    mApmForInstance.onStage(WXInstanceApm.KEY_PAGE_STAGES_RENDER_ORGIGIN);
    mApmForInstance.doDelayCollectData();

    mWXPerformance.pageName = (TextUtils.isEmpty(pageName) ? "defaultBundleUrl":pageName);
    if (TextUtils.isEmpty(mBundleUrl)) {
      mBundleUrl = mWXPerformance.pageName;
    }

    if (WXTracing.isAvailable()) {
      WXTracing.TraceEvent traceEvent = WXTracing.newEvent("executeBundleJS", mInstanceId, -1);
      traceEvent.traceId = mExecJSTraceId;
      traceEvent.iid = mInstanceId;
      traceEvent.tname = "JSThread";
      traceEvent.ph = "B";
      traceEvent.submit();
      mRenderStartNanos = System.nanoTime();
    }

    ensureRenderArchor();

    Map<String, Object> renderOptions = options;
    if (renderOptions == null) {
      renderOptions = new HashMap<>();
    }

    if (WXEnvironment.sDynamicMode && !TextUtils.isEmpty(WXEnvironment.sDynamicUrl) && renderOptions.get("dynamicMode") == null) {
      renderOptions.put("dynamicMode", "true");
      renderByUrl(pageName, WXEnvironment.sDynamicUrl, renderOptions, jsonInitData, flag);
      return;
    }

    TimeCalculator timeCalculator = new TimeCalculator(this);

    mWXPerformance.JSTemplateSize = template.length() / 1024f;
    mApmForInstance.addStats(WXInstanceApm.KEY_PAGE_STATS_BUNDLE_SIZE,mWXPerformance.JSTemplateSize);
    mRenderStartTime = System.currentTimeMillis();
    WXSDKManager.getInstance().setCrashInfo(WXEnvironment.WEEX_CURRENT_KEY,pageName);
    if(mAutoAdjustDeviceWidth && WXDeviceUtils.isAutoResize(mContext)){
         if(WXEnvironment.AUTO_UPDATE_APPLICATION_SCREEN_SIZE) {
             WXViewUtils.updateApplicationScreen(mContext);
         }
         WXParams params = WXBridgeManager.getInstance().getInitParams();
         if(params != null && !TextUtils.equals(params.getDeviceWidth(), String.valueOf(WXViewUtils.getScreenWidth(mContext)))){
           params.setDeviceWidth(String.valueOf(WXViewUtils.getScreenWidth(mContext)));
           params.setDeviceHeight(String.valueOf(WXViewUtils.getScreenHeight(mContext)));
           float density = WXEnvironment.sApplication.getResources().getDisplayMetrics().density;
           WXEnvironment.addCustomOptions(WXConfig.scale, Float.toString(density));
           String statusBarHeight =  null;
           if(WXViewUtils.getStatusBarHeight(mContext) > 0 ){
             statusBarHeight = String.valueOf(WXViewUtils.getStatusBarHeight(mContext));
           }
           WXBridgeManager.getInstance().updateInitDeviceParams(params.getDeviceWidth(),
                                                                params.getDeviceHeight(),
                                                                Float.toString(density), statusBarHeight);
           setDeviceDisplay(WXViewUtils.getScreenWidth(mContext),
                            WXViewUtils.getScreenHeight(mContext),
                            WXViewUtils.getScreenDensity(mContext));
         }
    }
    logDetail.taskStart();
    if (isPreInitMode()){
      getApmForInstance().onStage(WXInstanceApm.KEY_PAGE_STAGES_LOAD_BUNDLE_START);
      WXBridgeManager.getInstance().loadJsBundleInPreInitMode(getInstanceId(),template.getContent());
    } else {
      WXSDKManager.getInstance().createInstance(this, template, renderOptions, jsonInitData);
    }
    logDetail.taskEnd();
    mRendered = true;

    final IWXJscProcessManager wxJscProcessManager = WXSDKManager.getInstance().getWXJscProcessManager();

    if(wxJscProcessManager != null && wxJscProcessManager.shouldReboot()) {
      WXSDKManager.getInstance().postOnUiThread(new Runnable() {
        @Override
        public void run() {
          checkWhiteScreen();
          if(isDestroy || hasException || isRenderSuccess) {
            return;
          }

          // there is no need to reboot js engine if render by eagle
          if(isDataRender()) {
            return;
          }

          View containerView = getContainerView();
          if(containerView instanceof ViewGroup) {
            if(0 == ((ViewGroup) containerView).getChildCount()) {
              if(wxJscProcessManager.withException(WXSDKInstance.this)) {
                onJSException(String.valueOf(WXErrorCode.WX_ERR_RELOAD_PAGE),"jsc reboot","jsc reboot");
              }
              if(!createInstanceHeartBeat) {
                  WXBridgeManager.getInstance().callReportCrashReloadPage(mInstanceId, null);
                  WXLogUtils.e("callReportCrashReloadPage with jsc reboot");
              }
            }
          }
        }
      }, wxJscProcessManager.rebootTimeout());
    }
  }


  private void checkWhiteScreen(){
    if (isDestroy ||!WhiteScreenUtils.doWhiteScreenCheck()){
      return;
    }

    boolean isWS = WhiteScreenUtils.isWhiteScreen(this);
    if (!isWS){
      return;
    }
    WXErrorCode errorCode = createInstanceHeartBeat?WXErrorCode.WX_ERROR_WHITE_SCREEN:WXErrorCode.WHITE_SCREEN_RESPONSE_TIMEOUT;
    if (WXBridgeManager.getInstance().isRebootExceedLimit()){
      errorCode = WXErrorCode.WHITE_SCREEN_REBOOT_EXCEED_LIMIT;
    }
    Map<String,String> args = new HashMap<>(1);
    String vieTreeMsg = WhiteScreenUtils.takeViewTreeSnapShot(this);
    args.put("viewTree",null == vieTreeMsg?"null viewTreeMsg":vieTreeMsg);
    args.put("weexCoreThreadStackTrace",WXBridgeManager.getInstance().getWeexCoreThreadStackTrace());

    for (Map.Entry<String,String> entry: WXStateRecord.getInstance().getStateInfo().entrySet()){
      args.put(entry.getKey(),entry.getValue());
    }
    WXExceptionUtils.commitCriticalExceptionRT(getInstanceId(),errorCode,"checkEmptyScreen",errorCode.getErrorMsg(),args);
  }


  public boolean skipFrameworkInit(){
    return isDataRender() && !mDisableSkipFrameworkInit;
  }

  private boolean isDataRender() {
    return getRenderStrategy() == WXRenderStrategy.DATA_RENDER_BINARY || getRenderStrategy() == WXRenderStrategy.DATA_RENDER;
  }

  private void renderByUrlInternal(String pageName,
                                   final String url,
                                   Map<String, Object> options,
                                   final String jsonInitData,
                                   WXRenderStrategy flag) {


    LogDetail logDetail = mTimeCalculator.createLogDetail("renderByUrlInternal");
    logDetail.taskStart();
    ensureRenderArchor();
    pageName = wrapPageName(pageName, url);
    mBundleUrl = url;
    mRenderStrategy = flag;
    if(WXSDKManager.getInstance().getValidateProcessor()!=null) {
      mNeedValidate = WXSDKManager.getInstance().getValidateProcessor().needValidate(mBundleUrl);
    }

    Map<String, Object> renderOptions = options;
    if (renderOptions == null) {
      renderOptions = new HashMap<>();
    }
    if (!renderOptions.containsKey(BUNDLE_URL)) {
      renderOptions.put(BUNDLE_URL, url);
    }

    getWXPerformance().pageName = pageName;

    mApmForInstance.doInit();
    mApmForInstance.setPageName(pageName);

    Uri uri = Uri.parse(url);
    if (uri != null && TextUtils.equals(uri.getScheme(), "file")) {
      mApmForInstance.onStage(WXInstanceApm.KEY_PAGE_STAGES_DOWN_BUNDLE_START);
      String template = WXFileUtils.loadFileOrAsset(assembleFilePath(uri), mContext);
      mApmForInstance.onStage(WXInstanceApm.KEY_PAGE_STAGES_DOWN_BUNDLE_END);
      render(pageName,template , renderOptions, jsonInitData, flag);
      return;
    }

    boolean is_wlasm = false;
    if (uri != null && uri.getPath()!=null) {
      if(uri.getPath().endsWith(".wlasm")){
        is_wlasm =  true;
      }
    }
    if (is_wlasm){
      flag = WXRenderStrategy.DATA_RENDER_BINARY;
    }

    IWXHttpAdapter adapter = WXSDKManager.getInstance().getIWXHttpAdapter();

    WXRequest wxRequest = new WXRequest();
    wxRequest.url = rewriteUri(Uri.parse(url), URIAdapter.BUNDLE).toString();
    if(wxRequest != null && !TextUtils.isEmpty(wxRequest.url)){
      requestUrl = wxRequest.url;
    }else {
      requestUrl = pageName;
    }

    if (wxRequest.paramMap == null) {
      wxRequest.paramMap = new HashMap<String, String>();
    }
    wxRequest.instanceId = getInstanceId();
    wxRequest.paramMap.put(KEY_USER_AGENT, WXHttpUtil.assembleUserAgent(mContext,WXEnvironment.getConfig()));
    wxRequest.paramMap.put("isBundleRequest","true");
    mHttpListener = new WXHttpListener(this, pageName, renderOptions, jsonInitData, flag, System.currentTimeMillis());
    mHttpListener.isPreDownLoadMode = isPreDownLoad;
    mHttpListener.setSDKInstance(this);
    mApmForInstance.onStage(WXInstanceApm.KEY_PAGE_STAGES_DOWN_BUNDLE_START);
    adapter.sendRequest(wxRequest, (IWXHttpAdapter.OnHttpListener) mHttpListener);
    logDetail.taskEnd();
  }

  private WXHttpListener mHttpListener = null;

  /**
   * Use {@link #render(String, String, Map, String, WXRenderStrategy)} instead.
   * @param pageName
   * @param template
   * @param options
   * @param jsonInitData
   * @param width
   * @param height
   * @param flag
   */
  @Deprecated
  public void render(String pageName, String template, Map<String, Object> options, String jsonInitData, int width, int height, WXRenderStrategy flag) {
    render(pageName,template,options,jsonInitData,flag);
  }

  /**
   * Render template asynchronously, use {@link WXRenderStrategy#APPEND_ASYNC} as render strategy
   * @param template bundle js
   */
  public void render(String template) {
    render(WXPerformance.DEFAULT, template, null, null, mRenderStrategy);
  }

  /**
   * Use {@link #render(String)} instead.
   * @param template
   * @param width
   * @param height
   */
  @Deprecated
  public void render(String template, int width, int height) {
    render(template);
  }

  /**
   * Use {@link #renderByUrl(String, String, Map, String, WXRenderStrategy)} instead.
   * @param pageName
   * @param url
   * @param options
   * @param jsonInitData
   * @param width
   * @param height
   * @param flag
   */
  @Deprecated
  public void renderByUrl(String pageName, final String url, Map<String, Object> options, final String jsonInitData, final int width, final int height, final WXRenderStrategy flag){
    renderByUrl(pageName,url,options,jsonInitData,flag);
  }

  public void renderByUrl(String pageName, final String url, Map<String, Object> options, final String jsonInitData, final WXRenderStrategy flag) {
    renderByUrlInternal(pageName,url,options,jsonInitData,flag);
  }

  private String wrapPageName(String pageName, String url) {
    if(TextUtils.equals(pageName, WXPerformance.DEFAULT)){
      pageName = url;
      WXExceptionUtils.degradeUrl = pageName;
      try {
        Uri uri=Uri.parse(url);
        if(uri!=null){
          Uri.Builder builder=new Uri.Builder();
          builder.scheme(uri.getScheme());
          builder.authority(uri.getAuthority());
          builder.path(uri.getPath());
          pageName=builder.toString();
        }
      } catch (Exception e) {
      }
    }
    return pageName;
  }

  private String assembleFilePath(Uri uri) {
    if(uri!=null && uri.getPath()!=null){
      return uri.getPath().replaceFirst("/","");
    }
    return "";
  }

  public void reloadPage(boolean reloadThis) {

    WXSDKEngine.reload();

    if (reloadThis) {
      if (mContext != null)  {
        Intent intent = new Intent();
        intent.setAction(ACTION_INSTANCE_RELOAD);
        intent.putExtra("url", mBundleUrl);
        mContext.sendBroadcast(intent);
      }
      // mRendered = false;
      //    destroy();
      // renderInternal(mPackage, mTemplate, mOptions, mJsonInitData, mFlag);
      // refreshInstance("{}");
    } else {
      IWXConfigAdapter adapter = WXSDKManager.getInstance().getWxConfigAdapter();
      if (adapter != null) {
        boolean degrade = Boolean.parseBoolean(adapter
                .getConfig("android_weex_ext_config",
                        "degrade_to_h5_if_not_reload",
                        "true"));
        WXLogUtils.e("degrade : " + degrade);
        if(degrade) {
          onJSException(String.valueOf(WXErrorCode.WX_ERR_RELOAD_PAGE.getErrorCode()),"Do not reloadPage", "Do not reloadPage degradeToH5");
          WXLogUtils.e("Do not reloadPage degradeToH5");
        }
      }
    }
  }

  /**
   * Refresh instance asynchronously.
   * @param data the new data
   */
  public void refreshInstance(Map<String, Object> data) {
    if (data == null) {
      return;
    }
    refreshInstance(WXJsonUtils.fromObjectToJSONString(data));
  }

  /**
   * Refresh instance asynchronously.
   * @param jsonData the new data
   */
  public void refreshInstance(String jsonData) {
    if (jsonData == null) {
      return;
    }
    mRefreshStartTime = System.currentTimeMillis();
    //cancel last refresh message
    if (mLastRefreshData != null) {
      mLastRefreshData.isDirty = true;
    }

    mLastRefreshData = new WXRefreshData(jsonData, false);

    WXSDKManager.getInstance().refreshInstance(mInstanceId, mLastRefreshData);
  }

  public WXRenderStrategy getRenderStrategy() {
    return mRenderStrategy;
  }

  public Context getUIContext() {
    return mContext;
  }

  public String getInstanceId() {
    return mInstanceId;
  }

  public Context getContext() {
    return mContext;
  }

  public int getWeexHeight() {
    return mRenderContainer == null ? 0: mRenderContainer.getHeight();
  }

  public int getWeexWidth() {
    return mRenderContainer == null ? 0: mRenderContainer.getWidth();
  }


  public IWXImgLoaderAdapter getImgLoaderAdapter() {
    return WXSDKManager.getInstance().getIWXImgLoaderAdapter();
  }

  public IDrawableLoader getDrawableLoader() {
    return WXSDKManager.getInstance().getDrawableLoader();
  }

  public URIAdapter getURIAdapter(){
    return WXSDKManager.getInstance().getURIAdapter();
  }

  public Uri rewriteUri(Uri uri,String type){
    return getURIAdapter().rewrite(this,type,uri);
  }

  public IWXHttpAdapter getWXHttpAdapter() {
    return WXSDKManager.getInstance().getIWXHttpAdapter();
  }

  public IWXStatisticsListener getWXStatisticsListener() {
    return mStatisticsListener;
  }

  public @Nullable
  IWebSocketAdapter getWXWebSocketAdapter() {
    return WXSDKManager.getInstance().getIWXWebSocketAdapter();
  }

  @Deprecated
  public void reloadImages() {
    if (mScrollView == null) {
      return;
    }
  }


  public boolean isPreRenderMode() {
    return this.isPreRenderMode;
  }

  public void setPreRenderMode(final boolean isPreRenderMode) {
    WXSDKManager.getInstance().getWXRenderManager().postOnUiThread(new Runnable() {
      @Override
      public void run() {
        WXSDKInstance.this.isPreRenderMode = isPreRenderMode;
      }
    },0);
  }

  public void setContext(@NonNull Context context) {
    this.mContext = context;
  }

  /********************************
   * begin register listener
   ********************************************************/
  public void registerRenderListener(IWXRenderListener listener) {
    mRenderListener = listener;
  }

  @Deprecated
  public void registerActivityStateListener(IWXActivityStateListener listener) {

  }

  public void registerStatisticsListener(IWXStatisticsListener listener) {
    mStatisticsListener = listener;
  }

  /**set render start time*/
  public void setRenderStartTime(long renderStartTime) {
    this.mRenderStartTime = renderStartTime;
  }

  /********************************
   * end register listener
   ********************************************************/


  /********************************
   *  begin hook Activity life cycle callback
   ********************************************************/

  @Override
  public void onActivityCreate() {

    // module listen Activity onActivityCreate
    WXModuleManager.onActivityCreate(getInstanceId());

    if(mRootComp != null) {
      mRootComp.onActivityCreate();
    }else{
        if (WXEnvironment.isApkDebugable()){
            WXLogUtils.w("Warning :Component tree has not build completely,onActivityCreate can not be call!");
        }
    }

    mGlobalEventReceiver=new WXGlobalEventReceiver(this);
    try {
      getContext().registerReceiver(mGlobalEventReceiver, new IntentFilter(WXGlobalEventReceiver.EVENT_ACTION));
    } catch (Throwable e) {
      // Huawei may throw a exception if register more than 500 BroadcastReceivers
      WXLogUtils.e(e.getMessage());
      mGlobalEventReceiver = null;
    }

  }

  @Override
  public void onActivityStart() {

    // module listen Activity onActivityCreate
    WXModuleManager.onActivityStart(getInstanceId());
    if(mRootComp != null) {
      mRootComp.onActivityStart();
    }else{
        if (WXEnvironment.isApkDebugable()) {
            WXLogUtils.w("Warning :Component tree has not build completely,onActivityStart can not be call!");

        }
    }

  }

  public boolean onCreateOptionsMenu(Menu menu) {

    WXModuleManager.onCreateOptionsMenu(getInstanceId(),menu);
    if(mRootComp != null) {
      mRootComp.onCreateOptionsMenu(menu);
    }else{
        if (WXEnvironment.isApkDebugable()) {
            WXLogUtils.w("Warning :Component tree has not build completely,onActivityStart can not be call!");

        }
    }
    return true;
  }

  @Override
  public void onActivityPause() {
    onViewDisappear();
    if(!isCommit){
      if(mUseScroller){
        mWXPerformance.useScroller=1;
      }
      mWXPerformance.maxDeepViewLayer=getMaxDeepLayer();
      mWXPerformance.wxDims = mwxDims;
      mWXPerformance.measureTimes = measureTimes;
      if (mUserTrackAdapter != null) {
        mUserTrackAdapter.commit(mContext, null, IWXUserTrackAdapter.LOAD, mWXPerformance, getUserTrackParams());
      }
      isCommit=true;
    }
    // module listen Activity onActivityPause
    WXModuleManager.onActivityPause(getInstanceId());
    if(mRootComp != null) {
      mRootComp.onActivityPause();
    }else{
        if (WXEnvironment.isApkDebugable()) {
            WXLogUtils.w("Warning :Component tree has not build completely,onActivityPause can not be call!");
        }
    }

    if (!mCurrentGround) {
      WXLogUtils.i("Application to be in the backround");
      Intent intent = new Intent(WXGlobalEventReceiver.EVENT_ACTION);
      intent.putExtra(WXGlobalEventReceiver.EVENT_NAME, Constants.Event.PAUSE_EVENT);
      intent.putExtra(WXGlobalEventReceiver.EVENT_WX_INSTANCEID, getInstanceId());
      /**
       *  Fix NPE just like {@link #onActivityResume()}
       */
      if (null != mContext){
        mContext.sendBroadcast(intent);
      }else {
        try {
          WXEnvironment.getApplication().sendBroadcast(intent);
        } catch (Exception e){
          WXLogUtils.e("weex",e);
        }
      }
      this.mCurrentGround = true;
    }

    //component appear
    if((WXEnvironment.isApkDebugable() || WXEnvironment.isPerf()) && mApmForInstance != null){
        WXLogUtils.e("PerformanceData " + mApmForInstance.toPerfString());
    }
  }


  @Override
  public void onActivityResume() {

    // notify onActivityResume callback to module
    WXModuleManager.onActivityResume(getInstanceId());

    if(mRootComp != null) {
      mRootComp.onActivityResume();
    }else{
        if (WXEnvironment.isApkDebugable()) {
            WXLogUtils.w("Warning :Component tree has not build completely, onActivityResume can not be call!");
        }
    }

    if (mCurrentGround) {
      WXLogUtils.i("Application  to be in the foreground");
      Intent intent = new Intent(WXGlobalEventReceiver.EVENT_ACTION);
      intent.putExtra(WXGlobalEventReceiver.EVENT_NAME, Constants.Event.RESUME_EVENT);
      intent.putExtra(WXGlobalEventReceiver.EVENT_WX_INSTANCEID, getInstanceId());
      //todo tmp solution for gray version
      if (null != mContext){
        mContext.sendBroadcast(intent);
      }else {
        WXEnvironment.getApplication().sendBroadcast(intent);
      }
      this.mCurrentGround = false;
    }

    onViewAppear();
  }

  @Override
  public void onActivityStop() {

    // notify onActivityResume callback to module
    WXModuleManager.onActivityStop(getInstanceId());

    if(mRootComp != null) {
      mRootComp.onActivityStop();
    }else{
        if (WXEnvironment.isApkDebugable()) {
            WXLogUtils.w("Warning :Component tree has not build completely, onActivityStop can not be call!");
        }
    }

  }

  @Override
  public void onActivityDestroy() {
    WXModuleManager.onActivityDestroy(getInstanceId());

    if(mRootComp != null) {
      mRootComp.onActivityDestroy();
    }else{
        if (WXEnvironment.isApkDebugable()) {
            WXLogUtils.w("Warning :Component tree has not build completely, onActivityDestroy can not be call!");
        }
    }
    this.mTimeCalculator.println();
    destroy();
  }

  @Override
  public boolean onActivityBack() {

    WXModuleManager.onActivityBack(getInstanceId());

    if(mRootComp != null) {
      return mRootComp.onActivityBack();
    }else{
        if (WXEnvironment.isApkDebugable()) {
            WXLogUtils.w("Warning :Component tree has not build completely, onActivityBack can not be call!");
        }
    }

    return false;
  }

  public boolean onSupportNavigateUp() {
    if (mWXActionbarHandlers != null) {
      for (ActionBarHandler handler : mWXActionbarHandlers) {
        if (handler.onSupportNavigateUp()) {
          return true;
        }
      }
    }
    return false;
  }

  public boolean onBackPressed() {
    if(mWXBackPressedHandlers != null) {
      for (OnBackPressedHandler handler : mWXBackPressedHandlers) {
        if(handler.onBackPressed()) {
          return true;
        }
      }
    }

    WXComponent comp = getRootComponent();
    if(comp != null) {
      WXEvent events= comp.getEvents();
      boolean hasNativeBackHook = events.contains(Constants.Event.NATIVE_BACK);
      if (hasNativeBackHook) {
        EventResult result = comp.fireEventWait(Constants.Event.NATIVE_BACK, null);
        if (WXUtils.getBoolean(result.getResult(), false)) {
          return true;
        }
      }

      boolean hasBackPressed = events.contains(Constants.Event.CLICKBACKITEM);
      if (hasBackPressed) {
        fireEvent(comp.getRef(), Constants.Event.CLICKBACKITEM,null, null);
      }
      return hasBackPressed;
    }
    return false;
  }

  public void onActivityResult(int requestCode, int resultCode, Intent data){
    WXModuleManager.onActivityResult(getInstanceId(),requestCode,resultCode,data);

    if(mRootComp != null) {
      mRootComp.onActivityResult(requestCode,resultCode,data);
    }else{
        if (WXEnvironment.isApkDebugable()) {
            WXLogUtils.w("Warning :Component tree has not build completely, onActivityResult can not be call!");
        }
    }


    if (mWXOnActivityResultHandlers != null && !mWXOnActivityResultHandlers.isEmpty()) {
      for (OnActivityResultHandler onActivityResultHandler : mWXOnActivityResultHandlers) {
        if (onActivityResultHandler.onActivityResult(requestCode, resultCode, data)) {
          break;
        }
      }
    }

  }


  public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

    WXModuleManager.onRequestPermissionsResult(getInstanceId(),requestCode,permissions,grantResults);

    if(mRootComp != null) {
       mRootComp.onRequestPermissionsResult(requestCode,permissions,grantResults);
    }else{
        if (WXEnvironment.isApkDebugable()) {
            WXLogUtils.w(
                "Warning :Component tree has not build completely, onRequestPermissionsResult can not be call!");
        }
    }
  }

  /********************************
   *  end hook Activity life cycle callback
   ********************************************************/

  public void onViewDisappear(){
    isViewDisAppear = false;
    mApmForInstance.onDisAppear();
    WXComponent comp = getRootComponent();
    if(comp != null) {
      fireEvent(comp.getRef(), Constants.Event.VIEWDISAPPEAR, null, null);
      //call disappear of nested instances
      for(OnInstanceVisibleListener instance:mVisibleListeners){
        instance.onDisappear();
      }
    }
  }

  public boolean isViewDisAppear(){
    return isViewDisAppear;
  }

  public void onViewAppear(){
    isViewDisAppear = true;
    mApmForInstance.onAppear();
    WXComponent comp = getRootComponent();
    if(comp != null) {
      fireEvent( comp.getRef(), Constants.Event.VIEWAPPEAR,null, null);
      for(OnInstanceVisibleListener instance:mVisibleListeners){
        instance.onAppear();
      }
    }
  }


  public void onCreateFinish() {
    if(mHasCreateFinish){
      return;
    }
    if (mContext != null) {
      onViewAppear();
      View wxView= mRenderContainer;
      if(mRenderListener != null) {
        mRenderListener.onViewCreated(WXSDKInstance.this, wxView);
      }
      if (mStatisticsListener != null) {
        mStatisticsListener.onFirstView();
      }
    }
  }

  /**
   * call back when update finish
   */
  public void onUpdateFinish() {
    if (WXEnvironment.isApkDebugable()){
      WXLogUtils.d("Instance onUpdateSuccess");
    }
  }


  public void runOnUiThread(Runnable action) {
    WXSDKManager.getInstance().postOnUiThread(action, 0);
  }

  public void onRenderSuccess(final int width, final int height) {
    isRenderSuccess = true;
    if (!isNewFsEnd){
      getApmForInstance().arriveNewFsRenderTime();
    }

    long time = System.currentTimeMillis() - mRenderStartTime;
    long[] renderFinishTime = WXBridgeManager.getInstance().getRenderFinishTime(getInstanceId());

    mWXPerformance.callBridgeTime = renderFinishTime[0];
    mWXPerformance.cssLayoutTime = renderFinishTime[1];
    mWXPerformance.parseJsonTime = renderFinishTime[2];

    mWXPerformance.totalTime = time;
    if(mWXPerformance.screenRenderTime<0.001){
      mWXPerformance.screenRenderTime =  time;
    }

    if (mRenderListener != null && mContext != null) {
      mRenderListener.onRenderSuccess(WXSDKInstance.this, width, height);
      if (mUserTrackAdapter != null) {
        WXPerformance performance=new WXPerformance(mInstanceId);
        performance.errCode=WXErrorCode.WX_SUCCESS.getErrorCode();
        performance.args=getBundleUrl();
        mUserTrackAdapter.commit(mContext,null,IWXUserTrackAdapter.JS_BRIDGE,performance,getUserTrackParams());
      }
      if (WXEnvironment.isApkDebugable()){
        WXLogUtils.d(WXLogUtils.WEEX_PERF_TAG, mWXPerformance.toString());
      }
    }
    if(WXEnvironment.isPerf()){
      WXLogUtils.e("weex_perf",mWXPerformance.getPerfData());
    }
  }

  public void onRefreshSuccess(final int width, final int height) {
    if (mRenderListener != null && mContext != null) {
      mRenderListener.onRefreshSuccess(WXSDKInstance.this, width, height);
    }
  }

  public void onChangeElement(WXComponent component, boolean isOutOfScreen) {

    if (isDestroy()  || null == mRenderContainer || mWXPerformance == null ){
      return;
    }
    if (null == component || component.isIgnoreInteraction){
        return;
    }

    if (mRenderContainer.hasConsumeEvent()) {
      return;
    }

    long lastElementChangeTime = System.currentTimeMillis();

    long lazyLoadTime;

    if (mHasCreateFinish){
      lazyLoadTime = lastElementChangeTime - mWXPerformance.renderTimeOrigin;
      if (lazyLoadTime > 8000) {
        //force record detail performance data
        return;
      }
    }

    if (component.mIsAddElementToTree) {
      getWXPerformance().localInteractionViewAddCount++;
      if (!isOutOfScreen)
        getWXPerformance().interactionViewAddLimitCount++;
      component.mIsAddElementToTree = false;
    }

    if (!isOutOfScreen) {
      mApmForInstance.arriveInteraction(component);
    }
  }

  public void onRenderError(final String errCode, final String msg) {
    WXStateRecord.getInstance().recordException(getInstanceId(),"onRenderError,"+errCode+","+msg);
    if (mRenderListener != null && mContext != null) {
      WXLogUtils.e("onRenderError "+errCode +","+msg);
      runOnUiThread(new Runnable() {

        @Override
        public void run() {
          if (mRenderListener != null && mContext != null) {
            mRenderListener.onException(WXSDKInstance.this, errCode, msg);
          }
        }
      });
    }
  }

  public void onJSException(final String errCode, final String function, final String exception) {
    WXStateRecord.getInstance().recordException(getInstanceId(),"onJSException,"+errCode+","+function+"|"+exception);
    hasException = true;
    if (mRenderListener != null && mContext != null) {
      WXLogUtils.e("onJSException "+errCode +","+exception);
      runOnUiThread(new Runnable() {

        @Override
        public void run() {
          if (mRenderListener != null && mContext != null) {
            StringBuilder builder = new StringBuilder();
            builder.append(function);
            builder.append(exception);
            mRenderListener.onException(WXSDKInstance.this, errCode, builder.toString());
          }
        }
      });
    }
  }


  @Override
  public final void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int
          oldTop, int oldRight, int oldBottom) {
    if (left != oldLeft || top != oldTop || right != oldRight || bottom != oldBottom) {
      onLayoutChange(v);
    }
  }

  /**
   * Subclass should override this method to get notifications of layout change of GodView.
   * @param godView the godView.
   */
  public void onLayoutChange(View godView) {

  }

  private boolean mCreateInstance =true;
  public void firstScreenCreateInstanceTime(long time) {
    if(mCreateInstance) {
      mWXPerformance.firstScreenJSFExecuteTime = time -mRenderStartTime;
      mCreateInstance =false;
    }
  }

  public void callJsTime(final long time){
    if (!mEnd){
      mWXPerformance.fsCallJsTotalTime+=time;
      mWXPerformance.fsCallJsTotalNum++;
    }
  }

  public void onComponentCreate(WXComponent component,long createTime) {
      mWXPerformance.mActionAddElementCount++;
      mWXPerformance.mActionAddElementSumTime += createTime;
      if (!mEnd){
        mWXPerformance.fsComponentCreateTime+=createTime;
        mWXPerformance.fsComponentCount++;
      }
      mWXPerformance.componentCount++;
      mWXPerformance.componentCreateTime+=createTime;
  }

  public void callActionAddElementTime(long time) {
      mWXPerformance.mActionAddElementSumTime += time;
  }

  public void onOldFsRenderTimeLogic(){
      if (mEnd){
        return;
      }
      mEnd = true;
      if (mStatisticsListener != null && mContext != null) {
        runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if (mStatisticsListener != null && mContext != null) {
              Trace.beginSection("onFirstScreen");
              mStatisticsListener.onFirstScreen();
              Trace.endSection();
            }
          }
        });
      }
      mApmForInstance.arriveFSRenderTime();
      mWXPerformance.fsRenderTime = System.currentTimeMillis();
      mWXPerformance.screenRenderTime = System.currentTimeMillis() - mRenderStartTime;
  }

  public WXSDKInstance getParentInstance() {
    return mParentInstance;
  }

  public void setParentInstance(WXSDKInstance mParentInstance) {
    this.mParentInstance = mParentInstance;
  }

  private void destroyView(View rootView) {
    try {
      if (rootView instanceof ViewGroup) {
        ViewGroup cViewGroup = ((ViewGroup) rootView);
        for (int index = 0; index < cViewGroup.getChildCount(); index++) {
          destroyView(cViewGroup.getChildAt(index));
        }

        cViewGroup.removeViews(0, ((ViewGroup) rootView).getChildCount());
        // Ensure that the viewgroup's status to be normal
        WXReflectionUtils.setValue(rootView, "mChildrenCount", 0);

      }
      if(rootView instanceof Destroyable){
        ((Destroyable)rootView).destroy();
      }
    } catch (Exception e) {
      WXLogUtils.e("WXSDKInstance destroyView Exception: ", e);
    }
  }

  public synchronized void destroy() {
    if(!isDestroy()) {
      if(mParentInstance != null){
         mParentInstance = null;
      }
      mApmForInstance.onEnd();


      if(mRendered) {
        WXSDKManager.getInstance().destroyInstance(mInstanceId);
      }

      try {
        if (mGlobalEventReceiver != null) {
          getContext().unregisterReceiver(mGlobalEventReceiver);
          mGlobalEventReceiver = null;
        }
      }catch (IllegalArgumentException e){
        WXLogUtils.w(WXLogUtils.getStackTrace(e));
      }

      if (mRootComp != null) {
        mRootComp.destroy();
        mRootComp = null;
      }

      if(mRenderContainer != null){
        destroyView(mRenderContainer);
      }


      if (mGlobalEvents != null) {
        mGlobalEvents.clear();
      }

      if (mComponentObserver != null) {
        mComponentObserver = null;
      }

      if (mLayerOverFlowListeners != null) {
        mLayerOverFlowListeners.clear();
      }

      if(mWXOnActivityResultHandlers != null && !mWXOnActivityResultHandlers.isEmpty()) {
        mWXOnActivityResultHandlers.clear();
      }

      if(mWXBackPressedHandlers != null && !mWXBackPressedHandlers.isEmpty()) {
        mWXBackPressedHandlers.clear();
      }

      if(mWXActionbarHandlers != null && !mWXActionbarHandlers.isEmpty()) {
        mWXActionbarHandlers.clear();
      }

      getFlatUIContext().destroy();
      mFlatGUIContext = null;
      mInstanceOnFireEventInterceptorList = null;
      mWXScrollListeners = null;
      mWXActionbarHandlers = null;
      mWXBackPressedHandlers = null;
      mRenderContainer = null;
      mNestedInstanceInterceptor = null;
      mUserTrackAdapter = null;
      mScrollView = null;
      mContext = null;
      mRenderListener = null;
      isDestroy = true;
      mStatisticsListener = null;
      if(responseHeaders != null){
        responseHeaders.clear();
      }
      if(templateRef != null){
        templateRef = null;
      }
      if (null != mContentBoxMeasurements) {
        mContentBoxMeasurements.clear();
      }
      mWXPerformance.afterInstanceDestroy(mInstanceId);

      WXBridgeManager.getInstance().post(new Runnable() {
        @Override
        public void run() {
          WXBridgeManager.getInstance().onInstanceClose(getInstanceId());
          inactiveAddElementAction.clear();
        }
      });

      //when report error in @WXExceptionUtils
      // instance may had destroy and remove,
      // so we delay remove from allInstanceMap
      WXBridgeManager.getInstance().postDelay(new Runnable() {
        @Override
        public void run() {
          WXSDKManager.getInstance().getAllInstanceMap().remove(mInstanceId);
        }
      },1000);
    }
  }

  public boolean isDestroy(){
    return isDestroy;
  }

  /**
   * @return If you use render () the return value may be empty
   */
  public @Nullable String getBundleUrl() {
    return mBundleUrl;
  }

  public View getRootView() {
    if (mRootComp == null)
      return null;
    return mRootComp.getRealView();
  }

  public View getContainerView() {
    return mRenderContainer;
  }

  @Deprecated
  public void setBundleUrl(String url){
    mBundleUrl = url;
    if(WXSDKManager.getInstance().getValidateProcessor()!=null) {
      mNeedValidate = WXSDKManager.getInstance().getValidateProcessor().needValidate(mBundleUrl);
    }
  }

  public void onRootCreated(WXComponent root) {
    this.mRootComp = root;
    this.mRootComp.mDeepInComponentTree =1;
    mRenderContainer.addView(root.getHostView());


    setSize(mRenderContainer.getWidth(),mRenderContainer.getHeight());
  }

  /**
   * Move fixed view to container ,except it's already moved.
   * @param fixedChild
   */
  public void moveFixedView(View fixedChild){
    if(mRenderContainer != null) {
      ViewGroup parent;
      if((parent = (ViewGroup) fixedChild.getParent()) != null){
        if (parent != mRenderContainer) {
          parent.removeView(fixedChild);
          mRenderContainer.addView(fixedChild);
        }
      }else{
        mRenderContainer.addView(fixedChild);
      }
    }
  }

  public void removeFixedView(View fixedChild){
    if(mRenderContainer != null) {
      mRenderContainer.removeView(fixedChild);
    }
  }

  public int getRenderContainerPaddingLeft() {
    if(mRenderContainer != null) {
      return mRenderContainer.getPaddingLeft();
    }
    return 0;
  }

  public int getRenderContainerPaddingRight() {
    if(mRenderContainer != null) {
      return mRenderContainer.getPaddingRight();
    }
    return 0;
  }

  public int getRenderContainerPaddingTop() {
    if(mRenderContainer != null) {
      return mRenderContainer.getPaddingTop();
    }
    return 0;
  }

  public synchronized List<OnWXScrollListener> getWXScrollListeners() {
    return mWXScrollListeners;
  }

  public synchronized void registerOnWXScrollListener(OnWXScrollListener wxScrollListener) {
    if(mWXScrollListeners==null){
      mWXScrollListeners=new ArrayList<>();
    }
    mWXScrollListeners.add(wxScrollListener);
  }

  public synchronized void registerActionbarHandler(ActionBarHandler actionBarHandler) {
    if(actionBarHandler == null) {
      return;
    }
    if(mWXActionbarHandlers == null) {
      mWXActionbarHandlers = new ArrayList<>();
    }

    mWXActionbarHandlers.add(actionBarHandler);
  }

  public synchronized void unRegisterActionbarHandler(ActionBarHandler actionBarHandler) {
    if(mWXActionbarHandlers != null && actionBarHandler != null) {
      mWXActionbarHandlers.remove(actionBarHandler);
    }
  }

  public synchronized void unRegisterOnActivityResultHandler(OnActivityResultHandler onActivityResultHandler) {
    if(mWXOnActivityResultHandlers != null && onActivityResultHandler != null) {
      mWXOnActivityResultHandlers.remove(onActivityResultHandler);
    }
  }

  public synchronized void registerOnActivityResultHandler(OnActivityResultHandler onActivityResultHandler) {
    if(onActivityResultHandler == null) {
      return;
    }

    if(mWXOnActivityResultHandlers == null) {
      mWXOnActivityResultHandlers = new ArrayList<>();
    }
    mWXOnActivityResultHandlers.add(onActivityResultHandler);
  }


  public synchronized void registerBackPressedHandler(OnBackPressedHandler backPressedHandler) {
    if(backPressedHandler == null) {
      return;
    }

    if(mWXBackPressedHandlers == null) {
      mWXBackPressedHandlers = new ArrayList<>();
    }

    mWXBackPressedHandlers.add(backPressedHandler);
  }

  public synchronized void unRegisterBackPressedHandler(OnBackPressedHandler backPressedHandler) {
    if(mWXBackPressedHandlers != null && backPressedHandler != null) {
      mWXBackPressedHandlers.remove(backPressedHandler);
    }
  }

  static int sScreenHeight = -1;
  public void setSize(int width, int height) {
    if (width > 0 && height > 0 & !isDestroy && mRendered && mRenderContainer != null) {
        if (sScreenHeight < 0){
            sScreenHeight = WXViewUtils.getScreenHeight(getContext());
        }
        if (sScreenHeight>0){
            double screenRatio = (double)height/(double)sScreenHeight *100;
            if(screenRatio>100){
              screenRatio =100;
            }
            getApmForInstance().addStats(WXInstanceApm.KEY_PAGE_STATS_BODY_RATIO,screenRatio);
        }
      ViewGroup.LayoutParams layoutParams = mRenderContainer.getLayoutParams();
      if (layoutParams != null) {
        final float realWidth = width;
        final float realHeight = height;
        if (mRenderContainer.getWidth() != width || mRenderContainer.getHeight() != height) {
          layoutParams.width = width;
          layoutParams.height = height;
          mRenderContainer.setLayoutParams(layoutParams);
        }

        if (mRootComp != null && layoutParams != null) {
          final boolean isWidthWrapContent = layoutParams.width == ViewGroup.LayoutParams.WRAP_CONTENT;
          final boolean isHeightWrapContent = layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT;

          WXBridgeManager.getInstance().post(new Runnable() {
            @Override
            public void run() {
              WXBridgeManager.getInstance().setDefaultRootSize(getInstanceId(), realWidth, realHeight, isWidthWrapContent,
                      isHeightWrapContent);
            }
          });
        }
      }
    }
  }

  /*Global Event*/
  private HashMap<String, List<String>> mGlobalEvents = new HashMap<>();

  public void fireGlobalEventCallback(String eventName, Map<String,Object> params){
    List<String> callbacks=mGlobalEvents.get(eventName);
    if(callbacks!=null){
      for(String callback:callbacks){
        WXSDKManager.getInstance().callback(mInstanceId,callback,params,true);
      }
    }
  }

  /**
   * Fire event callback on a element.
   * @param elementRef
   * @param type
   * @param data
   * @param domChanges
   */
  public void fireEvent(String elementRef,final String type, final Map<String, Object> data,final Map<String, Object> domChanges, List<Object> eventArgs){
    fireEvent(elementRef, type, data, domChanges, eventArgs, null);
  }

  public void fireEvent(String elementRef,final String type, final Map<String, Object> data,final Map<String, Object> domChanges, List<Object> eventArgs, EventResult callback) {
    onInterceptInstanceEvent(getInstanceId(), elementRef, type, data, domChanges);
    if (null != mWXPerformance && mWXPerformance.fsCallEventTotalNum<Integer.MAX_VALUE){
      mWXPerformance.fsCallEventTotalNum++;
    }
    mApmForInstance.updateFSDiffStats(WXInstanceApm.KEY_PAGE_STATS_FS_CALL_EVENT_NUM,1);
    WXBridgeManager.getInstance().fireEventOnNode(getInstanceId(),elementRef,type,data,domChanges, eventArgs, callback);
  }


  /**
   * Fire event callback on a element.
   * @param elementRef
   * @param type
   * @param data
   * @param domChanges
   */
  public void fireEvent(String elementRef,final String type, final Map<String, Object> data,final Map<String, Object> domChanges){
    fireEvent(elementRef, type, data, domChanges, null);
  }

  public void fireEvent(String elementRef,final String type, final Map<String, Object> data){
    fireEvent(elementRef,type,data,null);
  }

  public void fireEvent(String ref, String type){
    fireEvent(ref,type,new HashMap<String, Object>());
  }

  protected void addEventListener(String eventName, String callback) {
    if (TextUtils.isEmpty(eventName) || TextUtils.isEmpty(callback)) {
      return;
    }
    List<String> callbacks = mGlobalEvents.get(eventName);
    if (callbacks == null) {
      callbacks = new ArrayList<>();
      mGlobalEvents.put(eventName, callbacks);
    }
    callbacks.add(callback);
  }
  protected void removeEventListener(String eventName, String callback) {
    if (TextUtils.isEmpty(eventName) || TextUtils.isEmpty(callback)) {
      return;
    }
    List<String> callbacks = mGlobalEvents.get(eventName);
    if (callbacks != null) {
      callbacks.remove(callback);
    }
  }

  protected void removeEventListener(String eventName) {
    if (TextUtils.isEmpty(eventName)) {
      return;
    }
    mGlobalEvents.remove(eventName);
  }

  /**
   * Notifies WEEX that this event has occurred
   * @param eventName WEEX register event
   * @param module Events occur in this Module
   * @param params The parameters to be notified to WEEX are required
   */
  public void fireModuleEvent(String eventName, WXModule module,Map<String, Object> params) {
    if (TextUtils.isEmpty(eventName) || module == null) {
      return;
    }

    Map<String, Object> event = new HashMap<>();
    event.put("type", eventName);
    event.put("module", module.getModuleName());
    event.put("data", params);

    List<String> callbacks = module.getEventCallbacks(eventName);
    if (callbacks != null) {
      for (String callback : callbacks) {
        SimpleJSCallback jsCallback = new SimpleJSCallback(mInstanceId, callback);
        if (module.isOnce(callback)) {
          jsCallback.invoke(event);
        } else {
          jsCallback.invokeAndKeepAlive(event);
        }
      }
    }
  }

  /**
   * Check whether the current module registered the event
   * @param eventName EventName register in weex
   * @param module Events occur in this Module
   * @return  register->true
   */
  public boolean checkModuleEventRegistered(String eventName,WXModule module) {
    if (module != null) {
      List<String> events = module.getEventCallbacks(eventName);
      if (events != null && events.size() > 0) {
        return true;
      }
    }
    return false;
  }

  public WXPerformance getWXPerformance(){
    return mWXPerformance;
  }

  public WXInstanceApm getApmForInstance() {
    return mApmForInstance;
  }

  public Map<String, Serializable> getUserTrackParams() {
    return mUserTrackParams;
  }

  public void addUserTrackParameter(String key,Serializable value){
    if(this.mUserTrackParams == null){
      this.mUserTrackParams = new ConcurrentHashMap<>();
    }
    mUserTrackParams.put(key,value);
  }

  public void clearUserTrackParameters(){
    if(this.mUserTrackParams != null){
      this.mUserTrackParams.clear();
    }
  }

  public void removeUserTrackParameter(String key){
    if(this.mUserTrackParams != null){
      this.mUserTrackParams.remove(key);
    }
  }

  public int getMaxDeepLayer() {
    return mMaxDeepLayer;
  }

  public void setMaxDeepLayer(int maxDeepLayer) {
    mMaxDeepLayer = maxDeepLayer;
    mApmForInstance.updateMaxStats(WXInstanceApm.KEY_PAGE_STATS_MAX_DEEP_VIEW,maxDeepLayer);
  }

  public void setMaxDomDeep(int maxDomDeep){
    mApmForInstance.updateMaxStats(WXInstanceApm.KEY_PAGE_STATS_MAX_DEEP_DOM,maxDomDeep);
    if (null == mWXPerformance){
      return;
    }
    if (mWXPerformance.maxDeepVDomLayer <= maxDomDeep){
      mWXPerformance.maxDeepVDomLayer = maxDomDeep;
    }
  }

  public void onHttpStart(){
    if (!mEnd){
      mWXPerformance.fsRequestNum++;
    }
  }

  /**
   * return md5, and bytes length
   * */
  public String getTemplateInfo() {
    String template = getTemplate();
    if(template == null){
      return " template md5 null ,httpHeader:" + JSONObject.toJSONString(responseHeaders);
    }
    if(TextUtils.isEmpty(template)){
      return " template md5  length 0 ,httpHeader" + JSONObject.toJSONString(responseHeaders);
    }
    try {
      byte[] bts = template.getBytes("UTF-8");
      String sourceMD5 = WXFileUtils.md5(bts);
      String sourceBase64MD5 = WXFileUtils.base64Md5(bts);
      ArrayList<String> sourceMD5List = new ArrayList<>();
      ArrayList<String> sourceBase64MD5List = new ArrayList<>();
      sourceMD5List.add(sourceMD5);
      sourceBase64MD5List.add(sourceBase64MD5);
      responseHeaders.put("templateSourceMD5", sourceMD5List);
      responseHeaders.put(SOURCE_TEMPLATE_BASE64_MD5, sourceBase64MD5List);
      return " template md5 " + sourceMD5 + " length " +   bts.length
              + " base64 md5 " + sourceBase64MD5
              + " response header " + JSONObject.toJSONString(responseHeaders);
    } catch (Exception e) {
      return "template md5 getBytes error";
    }

  }

  /**
   * check template header md5 match with header  content-md5
   * */
  public boolean isContentMd5Match(){
    if(responseHeaders == null){
      return true;
    }
    List<String> contentMD5s = responseHeaders.get("Content-Md5");
    if(contentMD5s == null){
      contentMD5s  = responseHeaders.get("content-md5");
    }
    if(contentMD5s == null || contentMD5s.size() <= 0){
      return true;
    }
    String md5 = contentMD5s.get(0);

    List<String> sourceBase64Md5 = responseHeaders.get(SOURCE_TEMPLATE_BASE64_MD5);
    if(sourceBase64Md5 == null){
      getTemplateInfo();
      sourceBase64Md5 = responseHeaders.get(SOURCE_TEMPLATE_BASE64_MD5);
    }
    if(sourceBase64Md5 == null || sourceBase64Md5.size() == 0){
      return  true;
    }
    return  md5.equals(sourceBase64Md5.get(0));
  }

  public String getTemplate() {
    if(templateRef == null){
      return  null;
    }
    return templateRef.get();
  }

  public void setTemplate(String template) {
    this.templateRef = new WeakReference<String>(template);
  }

  public interface NestedInstanceInterceptor {
    void onCreateNestInstance(WXSDKInstance instance, NestedContainer container);
  }

  public void OnVSync() {
    boolean forceLayout = WXBridgeManager.getInstance().notifyLayout(getInstanceId());
    if(forceLayout) {
      WXBridgeManager.getInstance().post(new Runnable() {
        @Override
        public void run() {
          WXBridgeManager.getInstance().forceLayout(getInstanceId());
        }
      });
    }
  }

  public void addContentBoxMeasurement(long renderObjectPtr, ContentBoxMeasurement contentBoxMeasurement) {
    mContentBoxMeasurements.put(renderObjectPtr, contentBoxMeasurement);
  }

  public ContentBoxMeasurement getContentBoxMeasurement(long renderObjectPtr) {
    return mContentBoxMeasurements.get(renderObjectPtr);
  }


  private void onInterceptInstanceEvent(String instanceId, String elementRef, String type, Map<String, Object> data, Map<String, Object> domChanges) {
    if(this.mInstanceOnFireEventInterceptorList == null){
      return;
    }
    for(InstanceOnFireEventInterceptor instanceOnFireEventInterceptor : this.mInstanceOnFireEventInterceptorList){
      instanceOnFireEventInterceptor.onInterceptFireEvent(instanceId, elementRef, type, data, domChanges);
    }
  }

  public List<InstanceOnFireEventInterceptor> getInstanceOnFireEventInterceptorList(){
    if(this.mInstanceOnFireEventInterceptorList == null){
      this.mInstanceOnFireEventInterceptorList = new ArrayList<>();
    }
    return mInstanceOnFireEventInterceptorList;
  }


  public void addInstanceOnFireEventInterceptor(InstanceOnFireEventInterceptor instanceOnFireEventInterceptor) {
    if(instanceOnFireEventInterceptor == null){
      return;
    }
    if(!getInstanceOnFireEventInterceptorList().contains(instanceOnFireEventInterceptor)){
      getInstanceOnFireEventInterceptorList().add(instanceOnFireEventInterceptor);
    }
  }

  public String getRenderType() {
    return mRenderType;
  }

  public void setRenderType(String renderType) {
    this.mRenderType = renderType;
  }

  private static boolean isDisableSkipFrameworkInDataRender() {
    IWXConfigAdapter adapter = WXSDKManager.getInstance().getWxConfigAdapter();
    if (adapter == null) {
      return false;
    }
    String result = adapter.getConfig("wxeagle", "disable_skip_framework_init", "false");
    return "true".equals(result);
  }
}
