From 611fd5857dd21635709d98ad023d60b8e44ba04a Mon Sep 17 00:00:00 2001 From: iPel Date: Mon, 20 Nov 2023 11:51:32 +0800 Subject: [PATCH 01/15] fix(android): crash when rotating screen in dev mode --- .../main/java/com/tencent/mtt/hippy/utils/DevtoolsUtil.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/utils/DevtoolsUtil.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/utils/DevtoolsUtil.java index 81ef4c9abb9..a8fa329c0af 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/utils/DevtoolsUtil.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/utils/DevtoolsUtil.java @@ -251,7 +251,7 @@ private static void callbackScreenShot(View view, Bitmap bitmap, float scale, in } private static String bitmapToBase64Str(Bitmap bitmap, float scale, int viewWidth, int viewHeight) { - String result = null; + String result = ""; ByteArrayOutputStream outputStream = null; try { if (bitmap != null) { @@ -269,6 +269,8 @@ private static String bitmapToBase64Str(Bitmap bitmap, float scale, int viewWidt byte[] bitmapBytes = outputStream.toByteArray(); result = Base64.encodeToString(bitmapBytes, Base64.NO_WRAP); } + } catch (IllegalArgumentException e) { + sCacheBitmapRef.clear(); } catch (IOException e) { LogUtils.e(TAG, "bitmapToBase64Str, scale exception:", e); } finally { From 750e88a72e2be2b430ae331c579d6f70bcf20d0a Mon Sep 17 00:00:00 2001 From: wwwcg Date: Wed, 23 Aug 2023 21:54:59 +0800 Subject: [PATCH 02/15] fix(ios): text will be clipped when exceeds lineHeight --- .../component/text/NativeRenderObjectText.mm | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/renderer/native/ios/renderer/component/text/NativeRenderObjectText.mm b/renderer/native/ios/renderer/component/text/NativeRenderObjectText.mm index f62e5760264..c117dd055fb 100644 --- a/renderer/native/ios/renderer/component/text/NativeRenderObjectText.mm +++ b/renderer/native/ios/renderer/component/text/NativeRenderObjectText.mm @@ -612,13 +612,18 @@ - (void)_setParagraphStyleOnAttributedString:(NSMutableAttributedString *)attrib heightOfTallestSubview:(CGFloat)heightOfTallestSubview isNestedText:(BOOL)isNestedText { NSTextStorage *textStorage = [[NSTextStorage alloc] initWithAttributedString:attributedString]; - if (fabs(_lineHeight - 0) < DBL_EPSILON) { - _lineHeight = fontLineHeight; + BOOL hasSetLineHeight = NO; + if (fabs(self.lineHeight - 0) < DBL_EPSILON) { + // If no fixed lineHeight is set, fontLineHeight is used. + self.lineHeight = fontLineHeight; + } else if (!self.adjustsFontSizeToFit) { + // Only when adjustsFontSizeToFit is not set, the fixed lineHeight can be used. + hasSetLineHeight = YES; } // check if we have lineHeight set on self __block BOOL hasParagraphStyle = NO; - if (_lineHeight || _textAlignSet || 1.f != _lineHeightMultiple) { + if (hasSetLineHeight || _textAlignSet || 1.0 != _lineHeightMultiple) { hasParagraphStyle = YES; } @@ -644,8 +649,7 @@ - (void)_setParagraphStyleOnAttributedString:(NSMutableAttributedString *)attrib self.lineHeight = newLineHeight; } - __block CGFloat maximumFontLineHeight = 0; - + __block CGFloat maximumFontLineHeight = 0.0; [textStorage enumerateAttribute:NSFontAttributeName inRange:NSMakeRange(0, attributedString.length) options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired @@ -671,7 +675,7 @@ - (void)_setParagraphStyleOnAttributedString:(NSMutableAttributedString *)attrib paragraphStyle.lineHeightMultiple = _lineHeightMultiple; maxHeight = MAX(maxHeight, maximumFontLineHeight); paragraphStyle.minimumLineHeight = lineHeight; - paragraphStyle.maximumLineHeight = maxHeight; + paragraphStyle.maximumLineHeight = hasSetLineHeight ? self.lineHeight : maxHeight; [attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:(NSRange) { 0, attributedString.length }]; From 7ed00491e89dac253ec257067544d370dbf23697 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Wed, 15 Nov 2023 16:22:28 +0800 Subject: [PATCH 03/15] refactor(ios): Generating logs by default, regardless of Debug or not --- modules/ios/base/HippyLog.h | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/modules/ios/base/HippyLog.h b/modules/ios/base/HippyLog.h index 066421f5a86..f2555c25504 100644 --- a/modules/ios/base/HippyLog.h +++ b/modules/ios/base/HippyLog.h @@ -24,14 +24,6 @@ #import #import "HippyDefines.h" -#ifndef HIPPY_LOG_ENABLED -#ifdef DEBUG -#define HIPPY_LOG_ENABLED 1 -#else -#define HIPPY_LOG_ENABLED 0 -#endif //#ifdef DEBUG -#endif //#ifndef HIPPY_LOG_ENABLED - /** * Thresholds for logs to display a redbox. You can override these values when debugging * in order to tweak the default logging behavior. @@ -125,13 +117,7 @@ HIPPY_EXTERN void HippyPerformBlockWithLogPrefix(void (^block)(void), NSString * /** * Private logging function - ignore this. */ -#if HIPPY_LOG_ENABLED #define _HippyLog(lvl, ...) HippyLogNativeInternal(lvl, __FILE__, __LINE__, __VA_ARGS__); -#else -#define _HippyLog(lvl, ...) \ - do { \ - } while (0) -#endif HIPPY_EXTERN void HippyLogNativeInternal(HippyLogLevel, const char *, int, NSString *, ...) NS_FORMAT_FUNCTION(4, 5); From 4dbb0fe19680db47b5c4fe2db9160b232d2f178a Mon Sep 17 00:00:00 2001 From: wwwcg Date: Tue, 1 Aug 2023 16:07:36 +0800 Subject: [PATCH 04/15] fix(android)!: change Image getSize failure parameter type BREAKING CHANGE:: failure parameter on Android used to be a String, now is an object --- .../nativemodules/image/ImageLoaderModule.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/framework/android/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/image/ImageLoaderModule.java b/framework/android/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/image/ImageLoaderModule.java index 81d48b7ebb6..74022f7eb10 100644 --- a/framework/android/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/image/ImageLoaderModule.java +++ b/framework/android/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/image/ImageLoaderModule.java @@ -34,6 +34,7 @@ public class ImageLoaderModule extends HippyNativeModuleBase { private final VfsManager mVfsManager; + private static final String ERROR_KEY_MESSAGE = "message"; public ImageLoaderModule(HippyEngineContext context) { super(context); @@ -50,7 +51,9 @@ private void decodeImageData(@NonNull final String url, @NonNull byte[] data, fi jsObject.set("height", options.outHeight); promise.resolve(jsObject); } catch (OutOfMemoryError | Exception e) { - promise.reject("Fetch image failed, url=" + url + ", msg=" + e.getMessage()); + JSObject jsObject = new JSObject(); + jsObject.set(ERROR_KEY_MESSAGE, "Fetch image failed, url=" + url + ", msg=" + e.getMessage()); + promise.reject(jsObject); } } @@ -64,7 +67,9 @@ private HashMap generateRequestParams() { @HippyMethod(name = "getSize") public void getSize(final String url, final Promise promise) { if (TextUtils.isEmpty(url)) { - promise.reject("Url parameter is empty!"); + JSObject jsObject = new JSObject(); + jsObject.set(ERROR_KEY_MESSAGE, "Url parameter is empty!"); + promise.reject(jsObject); return; } mVfsManager.fetchResourceAsync(url, null, generateRequestParams(), @@ -77,7 +82,10 @@ public void onFetchCompleted(@NonNull final ResourceDataHolder dataHolder) { || bytes.length <= 0) { String message = dataHolder.errorMessage != null ? dataHolder.errorMessage : ""; - promise.reject("Fetch image failed, url=" + url + ", msg=" + message); + String errorMsg = "Fetch image failed, url=" + url + ", msg=" + message; + JSObject jsObject = new JSObject(); + jsObject.set(ERROR_KEY_MESSAGE, errorMsg); + promise.reject(jsObject); } else { decodeImageData(url, bytes, promise); } From 60c4215693e52ac6d4c37cbb8482b1cbe92f4c60 Mon Sep 17 00:00:00 2001 From: maxli Date: Wed, 22 Nov 2023 11:03:24 +0800 Subject: [PATCH 05/15] fix(android): compatible with the HippyModulePromise type --- .../hippy/modules/nativemodules/HippyNativeModuleInfo.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/framework/android/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/HippyNativeModuleInfo.java b/framework/android/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/HippyNativeModuleInfo.java index b8c07575558..4cf1b6ad1f4 100644 --- a/framework/android/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/HippyNativeModuleInfo.java +++ b/framework/android/src/main/java/com/tencent/mtt/hippy/modules/nativemodules/HippyNativeModuleInfo.java @@ -26,6 +26,7 @@ import com.tencent.mtt.hippy.annotation.HippyNativeModule.Thread; import com.tencent.mtt.hippy.common.HippyArray; import com.tencent.mtt.hippy.common.Provider; +import com.tencent.mtt.hippy.modules.HippyModulePromise; import com.tencent.mtt.hippy.modules.Promise; import com.tencent.mtt.hippy.modules.PromiseImpl; import com.tencent.mtt.hippy.runtime.builtins.array.JSDenseArray; @@ -208,7 +209,11 @@ private Object[] prepareArguments(@NonNull Object args, PromiseImpl promise) int size = mUseJSValueType ? ((JSDenseArray) args).size() : ((HippyArray) args).size(); for (int i = 0; i < mParamTypes.length; i++) { Type paramCls = mParamTypes[i]; - if (paramCls == Promise.class) { + // It is necessary to be compatible with the HippyModulePromise type here, + // as some host custom modules interface promise parameters are directly defined + // using the HippyModulePromise type, resulting in parameter mismatches when + // calling the interface. + if (paramCls == Promise.class || paramCls == HippyModulePromise.class) { params[i] = promise; promise.setNeedResolveBySelf(false); } else { From be2347403ca880b817b1a188fae1c37928e5f194 Mon Sep 17 00:00:00 2001 From: iPel Date: Wed, 22 Nov 2023 14:35:14 +0800 Subject: [PATCH 06/15] feat(android): Image load events (#3621) Co-authored-by: OpenHippy <124017524+open-hippy@users.noreply.github.com> --- .../com/tencent/renderer/NativeRenderer.java | 5 +- .../component/image/ImageComponent.java | 80 +++++++++----- .../component/text/TextImageSpan.java | 103 ++++++++++++++---- .../renderer/node/ImageVirtualNode.java | 4 +- .../renderer/node/TextVirtualNode.java | 4 +- .../tencent/renderer/node/VirtualNode.java | 20 ++-- .../renderer/node/VirtualNodeManager.java | 9 +- 7 files changed, 158 insertions(+), 67 deletions(-) diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/NativeRenderer.java b/renderer/native/android/src/main/java/com/tencent/renderer/NativeRenderer.java index 3bd489681f0..ea487a53522 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/NativeRenderer.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/NativeRenderer.java @@ -395,8 +395,9 @@ public void dispatchEvent(int rootId, int nodeId, @NonNull String eventName, if (lowerCaseEventName.startsWith(EVENT_PREFIX)) { lowerCaseEventName = lowerCaseEventName.substring(EVENT_PREFIX.length()); } - if (eventType != EventType.EVENT_TYPE_GESTURE && !mRenderManager.checkRegisteredEvent( - rootId, nodeId, lowerCaseEventName)) { + if (eventType != EventType.EVENT_TYPE_GESTURE + && !mRenderManager.checkRegisteredEvent(rootId, nodeId, lowerCaseEventName) + && !mVirtualNodeManager.checkRegisteredEvent(rootId, nodeId, lowerCaseEventName)) { return; } LogUtils.d(TAG, "dispatchEvent: id " + nodeId + ", eventName " + eventName diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageComponent.java b/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageComponent.java index 11926bf7dfa..bce4e74f39c 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageComponent.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageComponent.java @@ -186,9 +186,10 @@ public void setTintColorBlendMode(int tintColorBlendMode) { } protected void onFetchImageStart() { - if (mHostRef.get() != null) { + final RenderNode host = mHostRef.get(); + if (host != null) { // send onLoadStart event - EventUtils.sendComponentEvent(mHostRef.get(), EVENT_IMAGE_LOAD_START, null); + EventUtils.sendComponentEvent(host, EVENT_IMAGE_LOAD_START, null); } } @@ -217,17 +218,28 @@ private void onFetchImageSuccess(@NonNull String uri, ImageSourceType sourceType mImageHolder = imageHolder; mImageFetchState = ImageFetchState.LOADED; setImageData(imageHolder); - if (mHostRef.get() != null && !loadFromCache) { + final RenderNode host = mHostRef.get(); + if (host != null) { + final int width = imageHolder.getImageWidth(); + final int height = imageHolder.getImageHeight(); // send onLoad event - EventUtils.sendComponentEvent(mHostRef.get(), EVENT_IMAGE_ON_LOAD, null); - HashMap params = new HashMap<>(); - params.put("success", 1); - HashMap imageSize = new HashMap<>(); - imageSize.put("width", imageHolder.getImageWidth()); - imageSize.put("height", imageHolder.getImageHeight()); - params.put("image", imageSize); + HashMap onLoad = new HashMap<>(); + onLoad.put("width", width); + onLoad.put("height", height); + onLoad.put("url", uri); + EventUtils.sendComponentEvent(host, EVENT_IMAGE_ON_LOAD, onLoad); // send onLoadEnd event - EventUtils.sendComponentEvent(mHostRef.get(), EVENT_IMAGE_LOAD_END, params); + HashMap onLoadEnd = new HashMap<>(); + onLoadEnd.put("success", 1); + onLoadEnd.put("width", width); + onLoadEnd.put("height", height); + onLoadEnd.put("url", uri); + @Deprecated + HashMap imageSize = new HashMap<>(); + imageSize.put("width", width); + imageSize.put("height", height); + onLoadEnd.put("image", imageSize); + EventUtils.sendComponentEvent(host, EVENT_IMAGE_LOAD_END, onLoadEnd); } } else if (sourceType == ImageSourceType.DEFAULT) { if (!uri.equals(mDefaultUri)) { @@ -247,35 +259,45 @@ private void onFetchImageSuccess(@NonNull String uri, ImageSourceType sourceType postInvalidateDelayed(0); } - private void onFetchImageFail() { + private void onFetchImageFail(String url, Throwable throwable) { mImageFetchState = ImageFetchState.UNLOAD; - if (mHostRef.get() == null) { + final RenderNode host = mHostRef.get(); + if (host == null) { return; } // send onError event - EventUtils.sendComponentEvent(mHostRef.get(), EVENT_IMAGE_LOAD_ERROR, null); - HashMap params = new HashMap<>(); - params.put("success", 0); + HashMap onError = new HashMap<>(); + onError.put("error", String.valueOf(throwable)); + onError.put("errorCode", -1); + onError.put("errorURL", url); + EventUtils.sendComponentEvent(host, EVENT_IMAGE_LOAD_ERROR, onError); // send onLoadEnd event - EventUtils.sendComponentEvent(mHostRef.get(), EVENT_IMAGE_LOAD_END, params); + HashMap onLoadEnd = new HashMap<>(); + onLoadEnd.put("success", 0); + onLoadEnd.put("error", String.valueOf(throwable)); + onLoadEnd.put("errorCode", -1); + onLoadEnd.put("url", url); + EventUtils.sendComponentEvent(host, EVENT_IMAGE_LOAD_END, onLoadEnd); } protected void onFetchImageProgress(float total, float loaded) { - if (mHostRef.get() == null) { + final RenderNode host = mHostRef.get(); + if (host == null) { return; } HashMap params = new HashMap<>(); params.put("loaded", loaded); params.put("total", total); - EventUtils.sendComponentEvent(mHostRef.get(), EVENT_IMAGE_LOAD_PROGRESS, params); + EventUtils.sendComponentEvent(host, EVENT_IMAGE_LOAD_PROGRESS, params); } private void doFetchImage(final String uri, final ImageSourceType sourceType) { - int width = (mHostRef.get() != null) ? mHostRef.get().getWidth() : 0; - int height = (mHostRef.get() != null) ? mHostRef.get().getHeight() : 0; + final RenderNode host = mHostRef.get(); + int width = (host != null) ? host.getWidth() : 0; + int height = (host != null) ? host.getHeight() : 0; Map params = new HashMap<>(); - if (mHostRef.get() != null) { - params.put("props", mHostRef.get().getProps()); + if (host != null) { + params.put("props", host.getProps()); } assert mImageLoader != null; mImageLoader.fetchImageAsync(uri, new ImageRequestListener() { @@ -298,7 +320,7 @@ public void onRequestSuccess(ImageDataSupplier imageData) { @Override public void onRequestFail(Throwable throwable) { if (sourceType == ImageSourceType.SRC) { - onFetchImageFail(); + onFetchImageFail(uri, throwable); } else { mDefaultImageFetchState = ImageFetchState.UNLOAD; } @@ -311,17 +333,17 @@ private void fetchImageWithUrl(String uri, ImageSourceType sourceType) { return; } LogUtils.d(TAG, "fetchImageWithUrl: host id " + getHostId() + ", uri " + uri); - ImageDataSupplier imageData = mImageLoader.getImageFromCache(uri); - if (imageData != null && imageData.checkImageData()) { - onFetchImageSuccess(uri, sourceType, imageData, true); - return; - } if (sourceType == ImageSourceType.SRC) { mImageFetchState = ImageFetchState.LOADING; onFetchImageStart(); } else { mDefaultImageFetchState = ImageFetchState.LOADING; } + ImageDataSupplier imageData = mImageLoader.getImageFromCache(uri); + if (imageData != null && imageData.checkImageData()) { + onFetchImageSuccess(uri, sourceType, imageData, true); + return; + } if (UrlUtils.isWebUrl(uri)) { uri = uri.trim().replaceAll(" ", "%20"); } diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextImageSpan.java b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextImageSpan.java index eece831774e..680ad01550c 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextImageSpan.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/component/text/TextImageSpan.java @@ -16,7 +16,10 @@ package com.tencent.renderer.component.text; +import static com.tencent.renderer.utils.EventUtils.EVENT_IMAGE_LOAD_END; import static com.tencent.renderer.utils.EventUtils.EVENT_IMAGE_LOAD_ERROR; +import static com.tencent.renderer.utils.EventUtils.EVENT_IMAGE_LOAD_PROGRESS; +import static com.tencent.renderer.utils.EventUtils.EVENT_IMAGE_LOAD_START; import static com.tencent.renderer.utils.EventUtils.EVENT_IMAGE_ON_LOAD; import android.annotation.SuppressLint; @@ -50,9 +53,9 @@ import com.tencent.renderer.node.ImageVirtualNode; import com.tencent.renderer.node.TextVirtualNode; import com.tencent.renderer.utils.EventUtils.EventType; -import com.tencent.vfs.UrlUtils; import java.lang.ref.WeakReference; import java.lang.reflect.Field; +import java.util.HashMap; public class TextImageSpan extends ImageSpan { @@ -88,7 +91,7 @@ public class TextImageSpan extends ImageSpan { private Movie mGifMovie; @Deprecated @Nullable - private LegacyIAlignConfig mAlignConfig; + private final LegacyIAlignConfig mAlignConfig; @Nullable private Paint mGifPaint; private float mHeightRate = 0; @@ -161,11 +164,12 @@ private int legacyGetSize(@NonNull Paint paint, CharSequence text, int start, in return super.getSize(paint, text, start, end, fm); } Drawable drawable = getDrawable(); + assert mAlignConfig != null; return mAlignConfig.getSize(paint, text, start, end, fm, drawable); } - public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, - int bottom, Paint paint) { + public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, + int bottom, @NonNull Paint paint) { if (mUseLegacy) { legacyDraw(canvas, text, start, end, x, top, y, bottom, paint); return; @@ -225,6 +229,7 @@ private void legacyDraw(Canvas canvas, CharSequence text, int start, int end, fl legacyDrawGIF(canvas, x + mLeft, transY + mTop, width, height); } else { Drawable drawable = getDrawable(); + assert mAlignConfig != null; mAlignConfig.draw(canvas, text, start, end, x, top, y, bottom, paint, drawable, mBackgroundPaint); } } @@ -233,6 +238,7 @@ private void legacyDraw(Canvas canvas, CharSequence text, int start, int end, fl @SuppressWarnings("unused") public void setDesiredSize(int width, int height) { if (mUseLegacy) { + assert mAlignConfig != null; mAlignConfig.setDesiredSize(width, height); } } @@ -241,6 +247,7 @@ public void setDesiredSize(int width, int height) { public void setActiveSizeWithRate(float heightRate) { mHeightRate = heightRate; if (mUseLegacy) { + assert mAlignConfig != null; mAlignConfig.setActiveSizeWithRate(heightRate); } } @@ -249,39 +256,40 @@ public void setActiveSizeWithRate(float heightRate) { @SuppressWarnings("unused") public void setMargin(int marginLeft, int marginRight) { if (mUseLegacy) { + assert mAlignConfig != null; mAlignConfig.setMargin(marginLeft, marginRight); } } - protected boolean shouldUseFetchImageMode(String url) { - return UrlUtils.isWebUrl(url) || UrlUtils.isFileUrl(url); - } - @MainThread private void loadImageWithUrl(@NonNull final String url) { - NativeRender nativeRender = mNativeRendererRef.get(); - ImageLoaderAdapter imageLoader = nativeRender != null ? nativeRender.getImageLoader() : null; + final NativeRender nativeRenderer = mNativeRendererRef.get(); + final ImageLoaderAdapter imageLoader = nativeRenderer != null ? nativeRenderer.getImageLoader() : null; if (mImageLoadState == STATE_LOADING || imageLoader == null) { return; } + nativeRenderer.dispatchEvent(mRootId, mId, EVENT_IMAGE_LOAD_START, null, false, false, + EventType.EVENT_TYPE_COMPONENT); mImageLoadState = STATE_LOADING; imageLoader.fetchImageAsync(url, new ImageRequestListener() { @Override public void onRequestStart(ImageDataSupplier imageData) { + handleFetchImageStart(); } @Override public void onRequestProgress(long total, long loaded) { + handleFetchImageProgress(total, loaded); } @Override public void onRequestSuccess(final ImageDataSupplier imageData) { - handleFetchImageResult(imageData); + handleFetchImageResult(url, imageData, null); } @Override public void onRequestFail(Throwable throwable) { - handleFetchImageResult(null); + handleFetchImageResult(url, null, throwable); } }, null, mWidth, mHeight); } @@ -405,23 +413,74 @@ private void legacyShouldReplaceDrawable(@NonNull ImageDataHolder imageHolder) { postInvalidateDelayed(0); } - private void handleFetchImageResult(@Nullable final ImageDataSupplier imageHolder) { - String eventName; + private void handleFetchImageStart() { + NativeRender nativeRenderer = mNativeRendererRef.get(); + if (nativeRenderer != null) { + // send onLoadStart event + nativeRenderer.dispatchEvent(mRootId, mId, EVENT_IMAGE_LOAD_START, null, false, false, + EventType.EVENT_TYPE_COMPONENT); + } + } + + private void handleFetchImageProgress(float total, float loaded) { + NativeRender nativeRenderer = mNativeRendererRef.get(); + if (nativeRenderer != null) { + // send onProgress event + HashMap onProgress = new HashMap<>(); + onProgress.put("loaded", loaded); + onProgress.put("total", total); + nativeRenderer.dispatchEvent(mRootId, mId, EVENT_IMAGE_LOAD_PROGRESS, onProgress, false, false, + EventType.EVENT_TYPE_COMPONENT); + } + } + + private void handleFetchImageResult(@NonNull final String url, @Nullable final ImageDataSupplier imageHolder, + final @Nullable Throwable throwable) { + NativeRender nativeRenderer = mNativeRendererRef.get(); if (imageHolder == null || !imageHolder.checkImageData()) { mImageLoadState = STATE_UNLOAD; - eventName = EVENT_IMAGE_LOAD_ERROR; + if (nativeRenderer != null) { + // send onError event + HashMap onError = new HashMap<>(); + onError.put("error", String.valueOf(throwable)); + onError.put("errorCode", -1); + onError.put("errorURL", url); + nativeRenderer.dispatchEvent(mRootId, mId, EVENT_IMAGE_LOAD_ERROR, onError, false, false, + EventType.EVENT_TYPE_COMPONENT); + // send onLoadEnd event + HashMap onLoadEnd = new HashMap<>(); + onLoadEnd.put("url", url); + onLoadEnd.put("success", 0); + onLoadEnd.put("error", String.valueOf(throwable)); + onLoadEnd.put("errorCode", -1); + nativeRenderer.dispatchEvent(mRootId, mId, EVENT_IMAGE_LOAD_END, onLoadEnd, false, false, + EventType.EVENT_TYPE_COMPONENT); + } } else { if (imageHolder instanceof ImageDataHolder) { shouldReplaceDrawable((ImageDataHolder) imageHolder); } mImageLoadState = STATE_LOADED; - eventName = EVENT_IMAGE_ON_LOAD; - } - NativeRender nativeRender = mNativeRendererRef.get(); - if (nativeRender != null) { - nativeRender.dispatchEvent(mRootId, mId, eventName, null, false, false, - EventType.EVENT_TYPE_COMPONENT); - } + if (nativeRenderer != null) { + int width = imageHolder.getImageWidth(); + int height = imageHolder.getImageHeight(); + // send onLoad event + HashMap onLoad = new HashMap<>(); + onLoad.put("width", width); + onLoad.put("height", height); + onLoad.put("url", url); + nativeRenderer.dispatchEvent(mRootId, mId, EVENT_IMAGE_ON_LOAD, onLoad, false, false, + EventType.EVENT_TYPE_COMPONENT); + // send onLoadEnd event + HashMap onLoadEnd = new HashMap<>(); + onLoadEnd.put("success", 1); + onLoadEnd.put("width", width); + onLoadEnd.put("height", height); + onLoadEnd.put("url", url); + nativeRenderer.dispatchEvent(mRootId, mId, EVENT_IMAGE_LOAD_END, onLoadEnd, false, false, + EventType.EVENT_TYPE_COMPONENT); + } + } } public void setTintColor(final int tintColor) { diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/node/ImageVirtualNode.java b/renderer/native/android/src/main/java/com/tencent/renderer/node/ImageVirtualNode.java index 5933f16500e..7ab62c59c21 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/node/ImageVirtualNode.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/node/ImageVirtualNode.java @@ -175,9 +175,9 @@ protected void createSpanOperation(List ops, builder.append(IMAGE_SPAN_TEXT); int end = start + IMAGE_SPAN_TEXT.length(); ops.add(new SpanOperation(start, end, mImageSpan)); - if (mGestureTypes != null && mGestureTypes.size() > 0) { + if (mEventTypes != null && mEventTypes.size() > 0) { TextGestureSpan span = new TextGestureSpan(mId); - span.addGestureTypes(mGestureTypes); + span.addGestureTypes(mEventTypes); ops.add(new SpanOperation(start, end, span)); } } diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java b/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java index 92952b7b0fb..eeaea5e552f 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/node/TextVirtualNode.java @@ -438,9 +438,9 @@ protected void createSpanOperationImpl(@NonNull List ops, new TextShadowSpan(mShadowOffsetDx, mShadowOffsetDy, mShadowRadius, mShadowColor))); } - if (mGestureTypes != null && mGestureTypes.size() > 0) { + if (mEventTypes != null && mEventTypes.size() > 0) { TextGestureSpan span = new TextGestureSpan(mId); - span.addGestureTypes(mGestureTypes); + span.addGestureTypes(mEventTypes); ops.add(new SpanOperation(start, end, span)); } if (useChild) { diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/node/VirtualNode.java b/renderer/native/android/src/main/java/com/tencent/renderer/node/VirtualNode.java index b8234212653..644b0e0c2eb 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/node/VirtualNode.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/node/VirtualNode.java @@ -38,7 +38,7 @@ public abstract class VirtualNode { @Nullable protected VirtualNode mParent; @Nullable - protected List mGestureTypes; + protected List mEventTypes; public VirtualNode(int rootId, int id, int pid, int index) { mRootId = rootId; @@ -68,19 +68,23 @@ public int getAncestorId() { protected abstract void createSpanOperation(List ops, SpannableStringBuilder builder, boolean useChild); - public void addGesture(String event) { - if (mGestureTypes == null) { - mGestureTypes = new ArrayList<>(); + public void addEventType(String event) { + if (mEventTypes == null) { + mEventTypes = new ArrayList<>(); } - mGestureTypes.add(event); + mEventTypes.add(event); } - public void removeGesture(String event) { - if (mGestureTypes != null) { - mGestureTypes.remove(event); + public void removeEventType(String event) { + if (mEventTypes != null) { + mEventTypes.remove(event); } } + public boolean hasEventType(String event) { + return mEventTypes != null && mEventTypes.contains(event); + } + public boolean isDirty() { return mDirty; } diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/node/VirtualNodeManager.java b/renderer/native/android/src/main/java/com/tencent/renderer/node/VirtualNodeManager.java index 34d1a784ecb..cf754246b1f 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/node/VirtualNodeManager.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/node/VirtualNodeManager.java @@ -113,9 +113,9 @@ public boolean updateEventListener(int rootId, int nodeId, @NonNull Map endBatch(int rootId) { updateNodes.clear(); return layoutToUpdate; } + + public boolean checkRegisteredEvent(int rootId, int nodeId, String eventName) { + VirtualNode node = getVirtualNode(rootId, nodeId); + return node != null && node.hasEventType(eventName); + } } From 117e087f07aa6d9ac320dbb85cc63d741ecbb0d5 Mon Sep 17 00:00:00 2001 From: ilikethese Date: Wed, 22 Nov 2023 15:21:48 +0800 Subject: [PATCH 07/15] fix(dom): fix dom memory leak (#3628) Co-authored-by: OpenHippy <124017524+open-hippy@users.noreply.github.com> --- dom/src/dom/deserializer_unittests.cc | 7 ++++++ dom/src/dom/dom_argument.cc | 1 + dom/src/dom/dom_manager.cc | 6 +++-- dom/src/dom/serializer_unittests.cc | 22 ++++++++++++++++++- .../footstone/include/footstone/serializer.h | 13 +++++++++++ .../cpp/src/renderer/native_render_manager.cc | 18 ++++++++------- 6 files changed, 56 insertions(+), 11 deletions(-) diff --git a/dom/src/dom/deserializer_unittests.cc b/dom/src/dom/deserializer_unittests.cc index 07e425d4a6d..40cd696b3d6 100644 --- a/dom/src/dom/deserializer_unittests.cc +++ b/dom/src/dom/deserializer_unittests.cc @@ -44,6 +44,7 @@ void CheckUint32(uint32_t value) { footstone::value::HippyValue hippy_value; deserializer.ReadObject(hippy_value); + footstone::value::SerializerHelper::DestroyBuffer(buffer); EXPECT_TRUE(hippy_value.GetType() == footstone::value::HippyValue::Type::kNumber); EXPECT_TRUE(hippy_value.GetNumberType() == footstone::value::HippyValue::NumberType::kUInt32); EXPECT_TRUE(hippy_value.ToUint32Checked() == value); @@ -60,6 +61,7 @@ void CheckInt32(int32_t value) { footstone::value::HippyValue hippy_value; deserializer.ReadObject(hippy_value); + footstone::value::SerializerHelper::DestroyBuffer(buffer); EXPECT_TRUE(hippy_value.GetType() == footstone::value::HippyValue::Type::kNumber); EXPECT_TRUE(hippy_value.GetNumberType() == footstone::value::HippyValue::NumberType::kInt32); EXPECT_TRUE(hippy_value.ToInt32Checked() == value); @@ -76,6 +78,7 @@ void CheckDouble(double value) { footstone::value::HippyValue hippy_value; deserializer.ReadObject(hippy_value); + footstone::value::SerializerHelper::DestroyBuffer(buffer); EXPECT_TRUE(hippy_value.GetType() == footstone::value::HippyValue::Type::kNumber); EXPECT_TRUE(hippy_value.GetNumberType() == footstone::value::HippyValue::NumberType::kDouble); EXPECT_TRUE(hippy_value.ToDoubleChecked() == value); @@ -92,6 +95,7 @@ void CheckString(std::string value) { footstone::value::HippyValue hippy_value; deserializer.ReadObject(hippy_value); + footstone::value::SerializerHelper::DestroyBuffer(buffer); EXPECT_TRUE(hippy_value.GetType() == footstone::value::HippyValue::Type::kString); EXPECT_TRUE(hippy_value.ToStringChecked() == value); EXPECT_TRUE(hippy_value.ToStringChecked().length() == value.length()); @@ -108,6 +112,7 @@ void CheckMap(footstone::value::HippyValue::HippyValueObjectType value) { footstone::value::HippyValue hippy_value; deserializer.ReadObject(hippy_value); + footstone::value::SerializerHelper::DestroyBuffer(buffer); EXPECT_TRUE(hippy_value.GetType() == footstone::value::HippyValue::Type::kObject); EXPECT_TRUE(hippy_value.IsObject()); EXPECT_TRUE(hippy_value.ToObjectChecked().size() == value.size()); @@ -134,6 +139,7 @@ void CheckArray(footstone::value::HippyValue::HippyValueArrayType value) { footstone::value::HippyValue hippy_value; deserializer.ReadObject(hippy_value); + footstone::value::SerializerHelper::DestroyBuffer(buffer); EXPECT_TRUE(hippy_value.GetType() == footstone::value::HippyValue::Type::kArray); EXPECT_TRUE(hippy_value.IsArray()); EXPECT_TRUE(hippy_value.ToArrayChecked().size() == value.size()); @@ -157,6 +163,7 @@ TEST(DeserializerTest, ReadHeader) { footstone::value::Deserializer deserializer(buffer.first, buffer.second); deserializer.ReadHeader(); EXPECT_EQ(deserializer.version_, tdf::base::kLatestVersion); + footstone::value::SerializerHelper::DestroyBuffer(buffer); } TEST(DeserializerTest, Uint32) { diff --git a/dom/src/dom/dom_argument.cc b/dom/src/dom/dom_argument.cc index 10bd581d03e..e0b84233723 100644 --- a/dom/src/dom/dom_argument.cc +++ b/dom/src/dom/dom_argument.cc @@ -65,6 +65,7 @@ bool DomArgument::ConvertObjectToBson(const footstone::value::HippyValue& hippy_ std::pair pair = serializer.Release(); bson.resize(pair.second); memcpy(&bson[0], pair.first, sizeof(uint8_t) * pair.second); + footstone::value::SerializerHelper::DestroyBuffer(pair); return true; } diff --git a/dom/src/dom/dom_manager.cc b/dom/src/dom/dom_manager.cc index 533466a2d3c..f14eeb827ef 100644 --- a/dom/src/dom/dom_manager.cc +++ b/dom/src/dom/dom_manager.cc @@ -223,8 +223,10 @@ DomManager::byte_string DomManager::GetSnapShot(const std::shared_ptr& Serializer serializer; serializer.WriteHeader(); serializer.WriteValue(HippyValue(array)); - auto ret = serializer.Release(); - return {reinterpret_cast(ret.first), ret.second}; + auto buffer_pair = serializer.Release(); + byte_string bs = {reinterpret_cast(buffer_pair.first), buffer_pair.second}; + footstone::value::SerializerHelper::DestroyBuffer(buffer_pair); + return bs; } bool DomManager::SetSnapShot(const std::shared_ptr& root_node, const byte_string& buffer) { diff --git a/dom/src/dom/serializer_unittests.cc b/dom/src/dom/serializer_unittests.cc index 98babf341d0..33a433fd69d 100644 --- a/dom/src/dom/serializer_unittests.cc +++ b/dom/src/dom/serializer_unittests.cc @@ -1,3 +1,23 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2022 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed 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. + */ + #define private public #define protected public @@ -124,7 +144,7 @@ void CheckString(footstone::value::Serializer& serializer, std::string value, si TEST(SerializerTest, Release) { footstone::value::Serializer serializer; - serializer.Release(); + footstone::value::SerializerHelper(serializer.Release()); EXPECT_EQ(serializer.buffer_ == nullptr, true) << "Serializer buffer is not equal to nullptr."; EXPECT_EQ(serializer.buffer_size_, 0) << "Serializer buffer_size is not equal to 0."; EXPECT_EQ(serializer.buffer_capacity_, 0) << "Serializer buffer_capacity is not equal to 0."; diff --git a/modules/footstone/include/footstone/serializer.h b/modules/footstone/include/footstone/serializer.h index ea3f44c9caf..27869af8747 100644 --- a/modules/footstone/include/footstone/serializer.h +++ b/modules/footstone/include/footstone/serializer.h @@ -69,6 +69,15 @@ enum class SerializationTag : uint8_t { kEndDenseJSArray = '$', }; +class SerializerHelper { + public: + static void DestroyBuffer(const std::pair& pair) { + if (pair.first) { + free(pair.first); + } + } +}; + class Serializer { public: Serializer(); @@ -80,7 +89,11 @@ class Serializer { void WriteValue(const HippyValue& hippy_value); + /** + * @brief 获取 HippyValue 对应序列化数据,注意 Release 后指针交给外部管理,需要自行释放 + */ std::pair Release(); + private: void WriteOddball(Oddball oddball); diff --git a/renderer/native/android/src/main/cpp/src/renderer/native_render_manager.cc b/renderer/native/android/src/main/cpp/src/renderer/native_render_manager.cc index d32681a42e6..543d6dd08bf 100644 --- a/renderer/native/android/src/main/cpp/src/renderer/native_render_manager.cc +++ b/renderer/native/android/src/main/cpp/src/renderer/native_render_manager.cc @@ -124,7 +124,7 @@ void NativeRenderManager::CreateRenderNode(std::weak_ptr root_node, } uint32_t root_id = root->GetId(); - serializer_->Release(); + footstone::value::SerializerHelper::DestroyBuffer(serializer_->Release()); serializer_->WriteHeader(); auto len = nodes.size(); @@ -182,8 +182,8 @@ void NativeRenderManager::CreateRenderNode(std::weak_ptr root_node, } serializer_->WriteValue(HippyValue(dom_node_array)); std::pair buffer_pair = serializer_->Release(); - CallNativeMethod("createNode", root->GetId(), buffer_pair); + footstone::value::SerializerHelper::DestroyBuffer(buffer_pair); } void NativeRenderManager::UpdateRenderNode(std::weak_ptr root_node, @@ -201,7 +201,7 @@ void NativeRenderManager::UpdateRenderNode(std::weak_ptr root_node, } } - serializer_->Release(); + footstone::value::SerializerHelper::DestroyBuffer(serializer_->Release()); serializer_->WriteHeader(); auto len = nodes.size(); @@ -242,8 +242,8 @@ void NativeRenderManager::UpdateRenderNode(std::weak_ptr root_node, } serializer_->WriteValue(HippyValue(dom_node_array)); std::pair buffer_pair = serializer_->Release(); - CallNativeMethod("updateNode", root->GetId(), buffer_pair); + footstone::value::SerializerHelper::DestroyBuffer(buffer_pair); } void NativeRenderManager::MoveRenderNode(std::weak_ptr root_node, @@ -253,7 +253,7 @@ void NativeRenderManager::MoveRenderNode(std::weak_ptr root_node, return; } - serializer_->Release(); + footstone::value::SerializerHelper::DestroyBuffer(serializer_->Release()); serializer_->WriteHeader(); auto len = nodes.size(); @@ -295,6 +295,7 @@ void NativeRenderManager::MoveRenderNode(std::weak_ptr root_node, JNIEnvironment::ClearJEnvException(j_env); j_env->DeleteLocalRef(j_buffer); j_env->DeleteLocalRef(j_class); + footstone::value::SerializerHelper::DestroyBuffer(buffer_pair); } void NativeRenderManager::DeleteRenderNode(std::weak_ptr root_node, @@ -343,7 +344,7 @@ void NativeRenderManager::UpdateLayout(std::weak_ptr root_node, return; } - serializer_->Release(); + footstone::value::SerializerHelper::DestroyBuffer(serializer_->Release()); serializer_->WriteHeader(); auto len = nodes.size(); @@ -367,8 +368,8 @@ void NativeRenderManager::UpdateLayout(std::weak_ptr root_node, } serializer_->WriteValue(HippyValue(dom_node_array)); std::pair buffer_pair = serializer_->Release(); - CallNativeMethod("updateLayout", root->GetId(), buffer_pair); + footstone::value::SerializerHelper::DestroyBuffer(buffer_pair); } void NativeRenderManager::MoveRenderNode(std::weak_ptr root_node, @@ -637,11 +638,12 @@ void NativeRenderManager::HandleListenerOps(std::weak_ptr root_node, return; } - serializer_->Release(); + footstone::value::SerializerHelper::DestroyBuffer(serializer_->Release()); serializer_->WriteHeader(); serializer_->WriteValue(HippyValue(event_listener_ops)); std::pair buffer_pair = serializer_->Release(); CallNativeMethod(method_name, root->GetId(), buffer_pair); + footstone::value::SerializerHelper::DestroyBuffer(buffer_pair); } void NativeRenderManager::MarkTextDirty(std::weak_ptr weak_root_node, uint32_t node_id) { From dac1069d581a549cb40b3a39d3d63cf8736f5f72 Mon Sep 17 00:00:00 2001 From: ruifanyuan Date: Wed, 22 Nov 2023 15:40:54 +0800 Subject: [PATCH 08/15] fix(ios): add local html, request body support to WebView --- .../webview/NativeRenderSimpleWebView.m | 57 ++++++++++++++++--- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/renderer/native/ios/renderer/component/webview/NativeRenderSimpleWebView.m b/renderer/native/ios/renderer/component/webview/NativeRenderSimpleWebView.m index 9a5fb108822..5a5552b3617 100644 --- a/renderer/native/ios/renderer/component/webview/NativeRenderSimpleWebView.m +++ b/renderer/native/ios/renderer/component/webview/NativeRenderSimpleWebView.m @@ -36,40 +36,81 @@ - (instancetype)init { - (void)setSource:(NSDictionary *)source { _source = source; - if (source && [source[@"uri"] isKindOfClass:[NSString class]]) { - NSString *urlString = source[@"uri"]; + if(source == nil){ + return; + } + + if ([source[@"uri"] isKindOfClass:[NSString class]]) { + // load uri + NSString *uri = source[@"uri"]; NSString *method = source[@"method"]; + NSDictionary* headers = source[@"headers"]; + NSString* body = source[@"body"]; // Wait for other properties to be updated dispatch_async(dispatch_get_main_queue(), ^{ - [self loadUrl:urlString withMethod:method]; + //[self loadUrl:urlString withMethod:method]; + [self loadURI:uri method:method headers:headers body:body]; + }); + }else if([source[@"html"] isKindOfClass:[NSString class]]){ + // load html + NSString* html = source[@"html"]; + NSString* baseUrl = source[@"baseUrl"]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [self loadHtml:html baseUrl:baseUrl]; }); } } -- (void)loadUrl:(NSString *)urlString withMethod:(NSString*)method { - _url = urlString; - NSURL *url = HippyURLWithString(urlString, NULL); +- (void)loadURI:(NSString*)uri method:(NSString*)method headers:(NSDictionary*)headers body:(NSString*)body{ + _url = uri; + NSURL *url = HippyURLWithString(uri, NULL); if (!url) { return; } NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url]; + + // set request method method = [method uppercaseString]; if([method isEqualToString:@"GET"]){ - request.HTTPMethod = @"GET"; + request.HTTPMethod = @"GET"; }else if ([method isEqualToString:@"POST"]){ - request.HTTPMethod = @"POST"; + request.HTTPMethod = @"POST"; + if(body){ + [request setHTTPBody:[body dataUsingEncoding:kCFStringEncodingUTF8]]; + } }else{ // System default is 'GET' no need to be specified explicitly } + + // set request headers + // FIXME: temporary disabled because we don't have the support in our android counterpart + // if(headers){ + // [request setAllHTTPHeaderFields:headers]; + // } + + // set user agent NSString* ua = self.userAgent; if(ua){ self.customUserAgent = ua; } + [self loadRequest:request]; } +- (void)loadHtml:(NSString*)html baseUrl:(NSString*)baseUrl{ + _url = baseUrl; + NSURL *url = HippyURLWithString(baseUrl, NULL); + if (url) { + if(self.userAgent){ + self.customUserAgent = self.userAgent; + } + [self loadHTMLString:html baseURL:url]; + } +} + - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation { if (_onLoadStart) { NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:1]; From d073aa9d8764a368cbd87966dd79aa41402eddd6 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Wed, 15 Nov 2023 17:25:21 +0800 Subject: [PATCH 09/15] fix(ios): fix UICollectionView exception caused by incremental diff --- .../NativeRenderBaseListViewDataSource.mm | 98 ++----------------- .../NativeRenderWaterfallViewDataSource.mm | 8 ++ 2 files changed, 16 insertions(+), 90 deletions(-) diff --git a/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewDataSource.mm b/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewDataSource.mm index 0c983002771..01b5a9ee71a 100644 --- a/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewDataSource.mm +++ b/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewDataSource.mm @@ -159,6 +159,14 @@ - (void)applyDiff:(NativeRenderBaseListViewDataSource *)another changedConext:(WaterfallItemChangeContext *)context forWaterfallView:(UICollectionView *)view completion:(void(^)(BOOL success))completion { + [view reloadData]; + completion(YES); + return; + + // 注意,如下原增量刷新逻辑存在BUG,会导致系统UICollectionView出现运行异常,临时删除,后续重构 + // FIXME: 需重构 + // https://bugly.woa.com/v2/exception/crash/issues/detail?productId=d591c6d8f7&pid=2&feature=073EF7E1204E4EAEB2F02B726D0FACBF + if (!another || !context || ![[another cellRenderObjectViews] count]) { @@ -265,95 +273,5 @@ static inline void EnumCellRenderObjects(NSArray *)cellViewChangeInvocation:(NativeRenderWaterfallViewDataSource *)another - context:(WaterfallItemChangeContext *)context - forCollectionView:(UICollectionView *)collectionView { - //todo 如果包含move的item,直接返回吧,不好算 -// if ([[context movedItems] count]) { -// NSInvocation *invocation = InvocationFromSelector(collectionView, @selector(reloadData), nil); -// return @[invocation]; -// } - - NSMutableArray *invocations = [NSMutableArray arrayWithCapacity:8]; - NSHashTable<__kindof HippyShadowView *> *insertedItems = [context addedItems]; - NSMutableSet<__kindof HippyShadowView *> *deletedItems = [[context deletedItems] mutableCopy]; - NSHashTable<__kindof HippyShadowView *> *frameChangedItems = [context frameChangedItems]; - //get section number change - //section number increased or decreased - NSUInteger selfSectionCount = [self.cellRenderObjectViews count]; - NSUInteger anotherSectionCount = [another.cellRenderObjectViews count]; - if (selfSectionCount > anotherSectionCount) { - NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet]; - do { - //remove added items from [WaterfallItemChangeContext addedItems] to avoid insertItemsAtIndexes: below - NSArray<__kindof HippyShadowView *> *objects = [self.cellRenderObjectViews objectAtIndex:anotherSectionCount]; - [objects enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [insertedItems removeObject:obj]; - }]; - [indexSet addIndex:anotherSectionCount]; - anotherSectionCount++; - } while (selfSectionCount != anotherSectionCount); - NSInvocation *invocation = InvocationFromSelector(collectionView, @selector(insertSections:), indexSet); - [invocations addObject:invocation]; - } - else if (selfSectionCount < anotherSectionCount) { - NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet]; - do { - anotherSectionCount--; - //remove deleted items from [WaterfallItemChangeContext deletedItems] to avoid deleteItemsAtIndexPaths: below - NSArray<__kindof HippyShadowView *> *objects = [another.cellRenderObjectViews objectAtIndex:anotherSectionCount]; - [objects enumerateObjectsUsingBlock:^(__kindof HippyShadowView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - [deletedItems removeObject:obj]; - }]; - [indexSet addIndex:anotherSectionCount]; - } while (selfSectionCount != anotherSectionCount); - NSInvocation *invocation = InvocationFromSelector(collectionView, @selector(deleteSections:), indexSet); - [invocations addObject:invocation]; - } - //section number unchanged - else { - //get inserted items and frame changed items if exists - if ([insertedItems count] || [frameChangedItems count]) { - NSMutableArray *insertedIndexPaths = [NSMutableArray arrayWithCapacity:16]; - NSMutableArray *frameChangedIndexPaths = [NSMutableArray arrayWithCapacity:16]; - EnumCellRenderObjects(self.cellRenderObjectViews, ^(__kindof HippyShadowView *object, NSUInteger section, NSUInteger row) { - if ([insertedItems count] && [insertedItems containsObject:object]) { - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; - [insertedIndexPaths addObject:indexPath]; - } - if ([frameChangedItems count] && [frameChangedItems containsObject:object]) { - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; - [frameChangedIndexPaths addObject:indexPath]; - } - }); - if ([insertedIndexPaths count]) { - NSInvocation *invocation = - InvocationFromSelector(collectionView, @selector(insertItemsAtIndexPaths:), insertedIndexPaths); - [invocations addObject:invocation]; - } - if ([frameChangedIndexPaths count]) { - NSInvocation *invocation = - InvocationFromSelector(collectionView, @selector(reloadItemsAtIndexPaths:), frameChangedIndexPaths); - [invocations addObject:invocation]; - } - } - //get deleted items - if ([deletedItems count]) { - NSMutableArray *deletedIndexPaths = [NSMutableArray arrayWithCapacity:16]; - EnumCellRenderObjects(another.cellRenderObjectViews, ^(__kindof HippyShadowView *object, NSUInteger section, NSUInteger row) { - if ([deletedItems containsObject:object]) { - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:section]; - [deletedIndexPaths addObject:indexPath]; - } - }); - if ([deletedIndexPaths count]) { - NSInvocation *invocation = - InvocationFromSelector(collectionView, @selector(deleteItemsAtIndexPaths:), deletedIndexPaths); - [invocations addObject:invocation]; - } - } - } - return invocations; -} @end diff --git a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.mm b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.mm index 676fac99b92..58ea08137ed 100644 --- a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.mm +++ b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.mm @@ -303,6 +303,14 @@ - (void)applyDiff:(NativeRenderWaterfallViewDataSource *)another changedConext:(WaterfallItemChangeContext *)context forWaterfallView:(UICollectionView *)view completion:(void(^)(BOOL success))completion{ + [view reloadData]; + completion(YES); + return; + + // 注意: + // FIXME: 如下代码存在BUG,会导致系统UICollectionView出现运行异常,后续删除 + // https://bugly.woa.com/v2/exception/crash/issues/detail?productId=d591c6d8f7&pid=2&feature=073EF7E1204E4EAEB2F02B726D0FACBF + // if (!another || 0 == [self.cellRenderObjectViews count] || 0 == [another.cellRenderObjectViews count]) { From 944e737a1c87e423baf04bbff37973b3ed4012f9 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Mon, 20 Nov 2023 20:34:05 +0800 Subject: [PATCH 10/15] fix(ios): remove the diff-update code of waterfall and listview --- .../NativeRenderBaseListViewDataSource.mm | 110 +----- .../NativeRenderWaterfallViewDataSource.h | 9 - .../NativeRenderWaterfallViewDataSource.mm | 348 +----------------- 3 files changed, 2 insertions(+), 465 deletions(-) diff --git a/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewDataSource.mm b/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewDataSource.mm index 01b5a9ee71a..23ad0c1c2d0 100644 --- a/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewDataSource.mm +++ b/renderer/native/ios/renderer/component/listview/NativeRenderBaseListViewDataSource.mm @@ -162,115 +162,7 @@ - (void)applyDiff:(NativeRenderBaseListViewDataSource *)another [view reloadData]; completion(YES); return; - - // 注意,如下原增量刷新逻辑存在BUG,会导致系统UICollectionView出现运行异常,临时删除,后续重构 - // FIXME: 需重构 - // https://bugly.woa.com/v2/exception/crash/issues/detail?productId=d591c6d8f7&pid=2&feature=073EF7E1204E4EAEB2F02B726D0FACBF - - if (!another || - !context || - ![[another cellRenderObjectViews] count]) { - [view reloadData]; - completion(YES); - return; - } - - // Attention, we do partial reload only when 1 item change! - // because differential refreshing causes the display event of the cell to not trigger, - // So we limit this to when only one cell is updated - if (context.allChangedItems.count > 1) { - [view reloadData]; - completion(YES); - return; - } - - // The following logic is not perfect and needs to be further refined - NSMutableArray *batchUpdate = [NSMutableArray arrayWithCapacity:8]; - [self cellDiffFromAnother:another - sectionStartAt:0 - frameChangedItems:context.frameChangedItems - result:^(NSArray *reloadedItemIndexPath, - NSArray *InsertedIndexPath, - NSArray *deletedIndexPath, - NSIndexSet *insertedSecionIndexSet, - NSIndexSet *deletedSectionIndexSet) { - if ([insertedSecionIndexSet count]) { - NSInvocation *invocation = InvocationFromSelector(view, @selector(insertSections:), insertedSecionIndexSet); - if (invocation) { - [batchUpdate addObject:invocation]; - } - } - if ([deletedSectionIndexSet count]) { - NSInvocation *invocation = InvocationFromSelector(view, @selector(deleteSections:), deletedSectionIndexSet); - if (invocation) { - [batchUpdate addObject:invocation]; - } - } - if ([reloadedItemIndexPath count]) { - NSInvocation *invocation = InvocationFromSelector(view, @selector(reloadItemsAtIndexPaths:), reloadedItemIndexPath); - if (invocation) { - [batchUpdate addObject:invocation]; - } - } - if ([InsertedIndexPath count]) { - NSInvocation *invocation = InvocationFromSelector(view, @selector(insertItemsAtIndexPaths:), InsertedIndexPath); - if (invocation) { - [batchUpdate addObject:invocation]; - } - } - if ([deletedIndexPath count]) { - NSInvocation *invocation = InvocationFromSelector(view, @selector(deleteItemsAtIndexPaths:), deletedIndexPath); - if (invocation) { - [batchUpdate addObject:invocation]; - } - } - }]; - - BOOL success = YES; - if ([batchUpdate count]) { - [CATransaction begin]; - [CATransaction setDisableActions:YES]; - @try { - [view performBatchUpdates:^{ - for (NSInvocation *invocation in batchUpdate) { - [invocation invoke]; - } - } completion:nil]; - } @catch (NSException *exception) { - [view reloadData]; - success = NO; - } - [CATransaction commit]; - } - completion(success); -} - -static NSInvocation *InvocationFromSelector(id object, SEL selector, id param) { - if (!selector || !object) { - return nil; - } - NSMethodSignature *methodSignature = [UICollectionView instanceMethodSignatureForSelector:selector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; - [invocation setTarget:object]; - [invocation setSelector:selector]; - if (param) { - [invocation setArgument:¶m atIndex:2]; - } - if (![invocation argumentsRetained]) { - [invocation retainArguments]; - } - return invocation; -} - -static inline void EnumCellRenderObjects(NSArray *> *objects, - void (^ _Nonnull block)(__kindof HippyShadowView * object, NSUInteger section, NSUInteger row)) { - for (NSUInteger section = 0; section < [objects count]; section ++) { - NSArray<__kindof HippyShadowView *> *sectionObjects = [objects objectAtIndex:section]; - for (NSUInteger row = 0; row < [sectionObjects count]; row++) { - __kindof HippyShadowView *object = [sectionObjects objectAtIndex:row]; - block(object, section, row); - } - } + // Note:原增量刷新代码存在大量问题,已删除 } diff --git a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.h b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.h index 7155e4b135a..7309b6d4b4c 100644 --- a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.h +++ b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.h @@ -52,15 +52,6 @@ NS_ASSUME_NONNULL_BEGIN forWaterfallView:(UICollectionView *)view completion:(void(^)(BOOL success))completion; -- (void)cellDiffFromAnother:(NativeRenderWaterfallViewDataSource *)another - sectionStartAt:(NSUInteger)startSection - frameChangedItems:(NSHashTable<__kindof HippyShadowView *> *)frameChangedItems - result:(void(^)(NSArray *reloadedItemIndexPath, - NSArray *InsertedIndexPath, - NSArray *deletedIndexPath, - NSIndexSet *insertedSecionIndexSet, - NSIndexSet *deletedSectionIndexSet))result; - @end NS_ASSUME_NONNULL_END diff --git a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.mm b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.mm index 58ea08137ed..86c1f7c946a 100644 --- a/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.mm +++ b/renderer/native/ios/renderer/component/waterfalllist/NativeRenderWaterfallViewDataSource.mm @@ -178,127 +178,6 @@ - (NSInteger)flatIndexForIndexPath:(NSIndexPath *)indexPath { return index; } -static BOOL ObjectViewNeedReload(HippyShadowView *object1, - HippyShadowView *object2, - NSHashTable<__kindof HippyShadowView *> *frameChangedItems) { - if (object1 != object2) { - return YES; - } - if ([frameChangedItems containsObject:object1]) { - return YES; - } - return NO; -} - -static void ObjectsArrayDiff(NSArray *objects1, - NSArray *objects2, - NSHashTable<__kindof HippyShadowView *> *frameChangedItems, - void(^result)(NSIndexSet *reloadIndex, NSIndexSet *insertedIndex, NSIndexSet *deletedIndex)) { - NSMutableIndexSet *reloadIndex = [NSMutableIndexSet indexSet]; - NSMutableIndexSet *insertedIndex = [NSMutableIndexSet indexSet]; - NSMutableIndexSet *deletedIndex = [NSMutableIndexSet indexSet]; - - NSEnumerator *obj1Enumer = [objects1 objectEnumerator]; - NSEnumerator *obj2Enumer = [objects2 objectEnumerator]; - NSUInteger index = 0; - do { - HippyShadowView *object1 = [obj1Enumer nextObject]; - HippyShadowView *object2 = [obj2Enumer nextObject]; - if (object1 && object2) { - if (ObjectViewNeedReload(object1, object2, frameChangedItems)) { - [reloadIndex addIndex:index]; - } - } - else if (object1) { - [insertedIndex addIndex:index]; - obj2Enumer = nil; - } - else if (object2){ - [deletedIndex addIndex:index]; - obj1Enumer = nil; - } - else { - obj1Enumer = nil; - obj2Enumer = nil; - } - index++; - } while (obj1Enumer || obj2Enumer); - result([reloadIndex copy], [insertedIndex copy], [deletedIndex copy]); -} - -static NSArray *IndexPathForIndexSet(NSUInteger section, NSIndexSet *indexSet) { - if (!indexSet || 0 == [indexSet count]) { - return nil; - } - NSUInteger indexBuffer[[indexSet count]]; - NSUInteger resultCount = [indexSet getIndexes:indexBuffer maxCount:[indexSet count] inIndexRange:nil]; - HippyAssert(resultCount == [indexSet count], @"Should get all index from indexset"); - NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:resultCount]; - for (NSUInteger i = 0; i < resultCount; i++) { - NSUInteger index = indexBuffer[i]; - NSIndexPath *indexPath = [NSIndexPath indexPathForItem:index inSection:section]; - [indexPaths addObject:indexPath]; - } - return [indexPaths copy]; -} - -- (void)cellDiffFromAnother:(NativeRenderWaterfallViewDataSource *)another - sectionStartAt:(NSUInteger)startSection - frameChangedItems:(NSHashTable<__kindof HippyShadowView *> *)frameChangedItems - result:(void(^)(NSArray *reloadedItemIndexPath, - NSArray *InsertedIndexPath, - NSArray *deletedIndexPath, - NSIndexSet *insertedSecionIndexSet, - NSIndexSet *deletedSectionIndexSet))result { - NSMutableArray *reloadedItemIndexPath = [NSMutableArray arrayWithCapacity:16]; - NSMutableArray *InsertedIndexPath = [NSMutableArray arrayWithCapacity:16]; - NSMutableArray *deletedIndexPath = [NSMutableArray arrayWithCapacity:16]; - NSMutableIndexSet *insertedSecionIndexSet = [NSMutableIndexSet indexSet]; - NSMutableIndexSet *deletedSectionIndexSet = [NSMutableIndexSet indexSet]; - - NSArray *> *currenCellObjects = self.cellRenderObjectViews; - NSArray *> *anotherCellObjects = another.cellRenderObjectViews; - //compare sections - //sections number equal, - NSEnumerator *obj1Enumer = [currenCellObjects objectEnumerator]; - NSEnumerator *obj2Enumer = [anotherCellObjects objectEnumerator]; - NSUInteger section = startSection; - do { - NSArray *objects1 = [obj1Enumer nextObject]; - NSArray *objects2 = [obj2Enumer nextObject]; - if (objects1 && objects2) { - ObjectsArrayDiff(objects1, objects2, frameChangedItems, ^(NSIndexSet *reloadIndex, NSIndexSet *insertedIndex, NSIndexSet *deletedIndex) { - NSArray *reloadIndics = IndexPathForIndexSet(section, reloadIndex); - if (reloadIndics) { - [reloadedItemIndexPath addObjectsFromArray:reloadIndics]; - } - NSArray *insertedIndics = IndexPathForIndexSet(section, insertedIndex); - if (insertedIndics) { - [InsertedIndexPath addObjectsFromArray:insertedIndics]; - } - NSArray *deletedIndics = IndexPathForIndexSet(section, deletedIndex); - if (deletedIndics) { - [deletedIndexPath addObjectsFromArray:deletedIndics]; - } - }); - } - else if (objects1) { - [insertedSecionIndexSet addIndex:section]; - obj2Enumer = nil; - } - else if (objects2) { - [deletedSectionIndexSet addIndex:section]; - obj1Enumer = nil; - } - else { - obj1Enumer = nil; - obj2Enumer = nil; - } - section++; - } while (obj2Enumer || obj2Enumer); - result([reloadedItemIndexPath copy], [InsertedIndexPath copy], [deletedIndexPath copy], [insertedSecionIndexSet copy], [deletedSectionIndexSet copy]); -} - - (void)applyDiff:(NativeRenderWaterfallViewDataSource *)another changedConext:(WaterfallItemChangeContext *)context forWaterfallView:(UICollectionView *)view @@ -306,232 +185,7 @@ - (void)applyDiff:(NativeRenderWaterfallViewDataSource *)another [view reloadData]; completion(YES); return; - - // 注意: - // FIXME: 如下代码存在BUG,会导致系统UICollectionView出现运行异常,后续删除 - // https://bugly.woa.com/v2/exception/crash/issues/detail?productId=d591c6d8f7&pid=2&feature=073EF7E1204E4EAEB2F02B726D0FACBF - // - if (!another || - 0 == [self.cellRenderObjectViews count] || - 0 == [another.cellRenderObjectViews count]) { - [view reloadData]; - completion(YES); - return; - } - - //check banner view change - NSMutableArray *batchUpdate = [NSMutableArray arrayWithCapacity:8]; - NSArray *bannerInvocation = [self bannerViewChangeInvocation:another - context:context - forCollectionView:view]; - if (bannerInvocation) { - [batchUpdate addObjectsFromArray:bannerInvocation]; - } -// NSArray *cellInvocation = [self cellViewChangeInvocation:another -// context:context -// forCollectionView:view]; -// if (cellInvocation) { -// [batchUpdate addObjectsFromArray:cellInvocation]; -// } - NSUInteger section = self.containBannerView ? 1 : 0; - [self cellDiffFromAnother:another - sectionStartAt:section - frameChangedItems:context.frameChangedItems - result:^(NSArray *reloadedItemIndexPath, - NSArray *InsertedIndexPath, - NSArray *deletedIndexPath, - NSIndexSet *insertedSecionIndexSet, - NSIndexSet *deletedSectionIndexSet) { - if ([insertedSecionIndexSet count]) { - NSInvocation *invocation = InvocationFromSelector(view, @selector(insertSections:), insertedSecionIndexSet); - if (invocation) { - [batchUpdate addObject:invocation]; - } - } - if ([deletedSectionIndexSet count]) { - NSInvocation *invocation = InvocationFromSelector(view, @selector(deleteSections:), deletedSectionIndexSet); - if (invocation) { - [batchUpdate addObject:invocation]; - } - } - if ([reloadedItemIndexPath count]) { - NSInvocation *invocation = InvocationFromSelector(view, @selector(reloadItemsAtIndexPaths:), reloadedItemIndexPath); - if (invocation) { - [batchUpdate addObject:invocation]; - } - } - if ([InsertedIndexPath count]) { - NSInvocation *invocation = InvocationFromSelector(view, @selector(insertItemsAtIndexPaths:), InsertedIndexPath); - if (invocation) { - [batchUpdate addObject:invocation]; - } - } - if ([deletedIndexPath count]) { - NSInvocation *invocation = InvocationFromSelector(view, @selector(deleteItemsAtIndexPaths:), deletedIndexPath); - if (invocation) { - [batchUpdate addObject:invocation]; - } - } - }]; - BOOL success = YES; - if ([batchUpdate count]) { - [CATransaction begin]; - [CATransaction setDisableActions:YES]; - @try { - [view performBatchUpdates:^{ - for (NSInvocation *invocation in batchUpdate) { - [invocation invoke]; - } - } completion:nil]; - } @catch (NSException *exception) { - [view reloadData]; - success = NO; - } - [CATransaction commit]; - } - completion(success); -} - -static NSInvocation *InvocationFromSelector(id object, SEL selector, id param) { - if (!selector || !object) { - return nil; - } - NSMethodSignature *methodSignature = [UICollectionView instanceMethodSignatureForSelector:selector]; - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature]; - [invocation setTarget:object]; - [invocation setSelector:selector]; - if (param) { - [invocation setArgument:¶m atIndex:2]; - } - if (![invocation argumentsRetained]) { - [invocation retainArguments]; - } - return invocation; -} - -- (NSArray *)bannerViewChangeInvocation:(NativeRenderWaterfallViewDataSource *)another - context:(WaterfallItemChangeContext *)context - forCollectionView:(UICollectionView *)collectionView { - NSIndexSet *bannerIndexSet = [NSIndexSet indexSetWithIndex:0]; - //check if banner view changed - if (self.containBannerView == another.containBannerView) { - if ([[context frameChangedItems] containsObject:self.bannerView]) { - NSInvocation *invocation = - InvocationFromSelector(collectionView, - @selector(reloadSections:), - bannerIndexSet); - return invocation ? @[invocation] : nil; - } - return nil; - } - //banner view added or deleted - SEL sel = self.containBannerView ? @selector(insertSections:) : @selector(deleteSections:); - NSInvocation *invocation = InvocationFromSelector(collectionView, sel, bannerIndexSet); - return invocation ? @[invocation] : nil; -} - -static NSComparisonResult ContainViewComparison(NativeRenderWaterfallViewDataSource *source1, NativeRenderWaterfallViewDataSource *source2) { - if (source1.containBannerView == source2.containBannerView) { - return NSOrderedSame; - } - else if (source1.containBannerView) { - return NSOrderedDescending; - } - else { - return NSOrderedAscending; - } -} - -- (NSArray *)cellViewChangeInvocation:(NativeRenderWaterfallViewDataSource *)another - context:(WaterfallItemChangeContext *)context - forCollectionView:(UICollectionView *)collectionView { - //todo 计算太麻烦了,先直接reload all吧 - NSHashTable<__kindof HippyShadowView *> *movedItems = [context movedItems]; - if ([movedItems count]) { - NSInvocation *invocation = - InvocationFromSelector(collectionView, @selector(reloadData), nil); - return @[invocation]; - } - //waterfall contains only one section - -// NSComparisonResult result = ContainViewComparison(self, another); - //new and old data sources contain cell view section, update cell items section - NSMutableArray *invocations = [NSMutableArray arrayWithCapacity:8]; - NSIndexSet *cellSectionIndexSet = [NSIndexSet indexSetWithIndex:self.containBannerView ? 1 : 0]; - if ([[self.cellRenderObjectViews firstObject] count] && [[another.cellRenderObjectViews firstObject] count]) { - //get inserted items - NSHashTable<__kindof HippyShadowView *> *addedItems = [context addedItems]; - NSIndexSet *insertedItemsIndexSet = [[self.cellRenderObjectViews firstObject] indexesOfObjectsPassingTest:^BOOL(HippyShadowView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - if ([addedItems containsObject:obj]) { - return YES; - } - return NO; - }]; - if ([insertedItemsIndexSet count]) { - NSMutableArray *insertedItemsIndexPaths = [NSMutableArray arrayWithCapacity:[insertedItemsIndexSet count]]; - [insertedItemsIndexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:[cellSectionIndexSet firstIndex]]; - [insertedItemsIndexPaths addObject:indexPath]; - }]; - NSInvocation *invocation = - InvocationFromSelector(collectionView, - @selector(insertItemsAtIndexPaths:), - insertedItemsIndexPaths); - [invocations addObject:invocation]; - } - //get deleteed items - NSSet<__kindof HippyShadowView *> *deletedItems = [context deletedItems]; - if ([deletedItems count]) { - NSMutableIndexSet *deletedItemsIndexSet = [NSMutableIndexSet indexSet]; - [deletedItems enumerateObjectsUsingBlock:^(__kindof HippyShadowView * _Nonnull obj, BOOL * _Nonnull stop) { - NSUInteger index = [[[another cellRenderObjectViews]firstObject] indexOfObject:obj]; - if (NSNotFound != index) { - [deletedItemsIndexSet addIndex:index]; - } - }]; - if ([deletedItemsIndexSet count]) { - NSMutableArray *deletedIndexPaths = [NSMutableArray arrayWithCapacity:[deletedItemsIndexSet count]]; - [deletedItemsIndexSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:idx inSection:[cellSectionIndexSet firstIndex]]; - [deletedIndexPaths addObject:indexPath]; - }]; - NSInvocation *invocation = - InvocationFromSelector(collectionView, - @selector(deleteItemsAtIndexPaths:), - deletedIndexPaths); - [invocations addObject:invocation]; - } - } - //get frame update items - NSHashTable<__kindof HippyShadowView *> *frameChangedItems = [context frameChangedItems]; - if ([frameChangedItems count]) { - NSMutableArray *frameChangedIndexPaths = [NSMutableArray arrayWithCapacity:[frameChangedItems count]]; - NSEnumerator *enumerator = [frameChangedItems objectEnumerator]; - HippyShadowView *objectView = [enumerator nextObject]; - while (objectView) { - NSUInteger index = [[self.cellRenderObjectViews firstObject] indexOfObject:objectView]; - if (NSNotFound != index) { - NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:[cellSectionIndexSet firstIndex]]; - [frameChangedIndexPaths addObject:indexPath]; - } - } - NSInvocation *invocation = - InvocationFromSelector(collectionView, @selector(reloadItemsAtIndexPaths:), frameChangedIndexPaths); - [invocations addObject:invocation]; - } - //get moved items - } - //only the new one contains cell items, insert cell items section - else if ([[self.cellRenderObjectViews firstObject] count]) { - NSInvocation *invocation = InvocationFromSelector(collectionView, @selector(insertSections:), cellSectionIndexSet); - [invocations addObject:invocation]; - } - //only old contains data sources, delete cell items section - else if ([[another.cellRenderObjectViews firstObject] count]) { - NSInvocation *invocation = InvocationFromSelector(collectionView, @selector(deleteSections:), cellSectionIndexSet); - [invocations addObject:invocation]; - } - return [invocations copy]; + // Note:原增量刷新代码存在大量问题,已删除 } @end From 5ae7d6791c32d2a7b5eb61a2aaf7dbea794fd9a9 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Mon, 31 Jul 2023 16:45:03 +0800 Subject: [PATCH 11/15] feat(ios): add param for image onLoadEnd on iOS --- docs/api/hippy-react/components.md | 2 +- docs/api/hippy-vue/components.md | 2 +- renderer/native/ios/renderer/component/image/HippyImageView.m | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api/hippy-react/components.md b/docs/api/hippy-react/components.md index 33567fe4a45..62854f358f7 100644 --- a/docs/api/hippy-react/components.md +++ b/docs/api/hippy-react/components.md @@ -53,7 +53,7 @@ import icon from './qb_icon_new.png'; | onLayout | 当元素挂载或者布局改变的时候调用,参数为: `nativeEvent: { layout: { x, y, width, height } }`,其中 `x` 和 `y` 为相对父元素的坐标位置 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | | onLoad | 加载成功完成时调用此回调函数。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | | onLoadStart | 加载开始时调用。 例如, `onLoadStart={() => this.setState({ loading: true })}` | `Function` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | -| onLoadEnd | 加载结束后,不论成功还是失败,调用此回调函数。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | +| onLoadEnd | 加载结束后,不论成功还是失败,调用此回调函数。参数为:`nativeEvent: { success: number, width: number, height: number}` | `Function` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | | resizeMode | 决定当组件尺寸和图片尺寸不成比例的时候如何调整图片的大小。`注意:hippy-react-web、Web-Renderer 不支持 repeat` | `enum (cover, contain, stretch, repeat, center)` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | | onError | 当加载错误的时候调用此回调函数,参数为 `nativeEvent: { error }` | `Function` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | | onProgress | 在加载过程中不断调用,参数为 `nativeEvent: { loaded: number, total: number }`, `loaded` 表示加载中的图片大小, `total` 表示图片总大小 | `Function` | `iOS、Voltron` | diff --git a/docs/api/hippy-vue/components.md b/docs/api/hippy-vue/components.md index adb283c888c..ebf0b27e556 100644 --- a/docs/api/hippy-vue/components.md +++ b/docs/api/hippy-vue/components.md @@ -186,7 +186,7 @@ | layout | 当元素挂载或者布局改变的时候调用,参数为: `nativeEvent: { layout: { x, y, width, height } }`,其中 `x` 和 `y` 为相对父元素的坐标位置 | `Function` | `Android、iOS、Web-Renderer、Voltron` | | load | 加载成功完成时调用此回调函数。 | `Function` | `Android、iOS、Web-Renderer、Voltron` | | loadStart | 加载开始时调用。 | `Function` | `Android、iOS、Web-Renderer、Voltron` | -| loadEnd | 加载结束后,不论成功还是失败,调用此回调函数。 | `Function` | `Android、iOS、Web-Renderer、Voltron` | +| loadEnd | 加载结束后,不论成功还是失败,调用此回调函数。参数为:`nativeEvent: { success: number, width: number, height: number}` | `Function` | `Android、iOS、Web-Renderer、Voltron` | | error | 当加载错误的时候调用此回调函数。| `Function` | `Android、iOS、Web-Renderer、Voltron` | | progress | 在加载过程中不断调用,参数为 `nativeEvent: { loaded: number, total: number }`, `loaded` 表示加载中的图片大小, `total` 表示图片总大小 | `Function` | `iOS、Voltron` | | touchstart | 触屏开始事件,最低支持版本 2.6.2,参数为 `evt: { touches: [{ clientX: number, clientY: number }] }`,`clientX` 和 `clientY` 分别表示点击在屏幕内的绝对位置 | `Function` | `Android、iOS、Web-Renderer、Voltron` | diff --git a/renderer/native/ios/renderer/component/image/HippyImageView.m b/renderer/native/ios/renderer/component/image/HippyImageView.m index 8638dfda40d..361f46e678f 100644 --- a/renderer/native/ios/renderer/component/image/HippyImageView.m +++ b/renderer/native/ios/renderer/component/image/HippyImageView.m @@ -421,7 +421,7 @@ - (void)loadImage:(UIImage *)image url:(NSString *)url error:(NSError *)error ne _onError(@{ @"error": error.localizedDescription, @"errorCode": @(error.code), @"errorURL": url }); } if (_onLoadEnd) { - _onLoadEnd(nil); + _onLoadEnd(@{ @"success": @0 }); } return; } @@ -443,7 +443,7 @@ - (void)loadImage:(UIImage *)image url:(NSString *)url error:(NSError *)error ne if (strongSelf.onLoad) strongSelf.onLoad(@{ @"width": @(image.size.width), @"height": @(image.size.height), @"url": url ?: @"" }); if (strongSelf.onLoadEnd) - strongSelf.onLoadEnd(nil); + strongSelf.onLoadEnd(@{ @"success": @1, @"width": @(image.size.width), @"height": @(image.size.height) }); }; if (_blurRadius > 100 && [NSProcessInfo processInfo].physicalMemory <= 1024 * 1024 * 1000) { From 73abae9b8dc536c594ed09a34c0d5597fa9fd806 Mon Sep 17 00:00:00 2001 From: etkmao Date: Thu, 23 Nov 2023 15:35:27 +0800 Subject: [PATCH 12/15] fix(core): JSCCtx CreateObject crash protect --- driver/js/src/napi/jsc/jsc_ctx.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/driver/js/src/napi/jsc/jsc_ctx.cc b/driver/js/src/napi/jsc/jsc_ctx.cc index 6849b759a0a..71ab2905343 100644 --- a/driver/js/src/napi/jsc/jsc_ctx.cc +++ b/driver/js/src/napi/jsc/jsc_ctx.cc @@ -792,6 +792,10 @@ std::shared_ptr JSCCtx::CreateObject(const std::unordered_map(it.second); + if (!ctx_value) { + auto error = CreateException("CreateObject2"); + return nullptr; + } JSObjectSetProperty(context_, obj, object_key, ctx_value->value_, kJSPropertyAttributeNone, &exception); if (exception) { SetException(std::make_shared(context_, exception)); From d4026a25afca02bad5fb85d0f2768604ddb3c3ca Mon Sep 17 00:00:00 2001 From: etkmao Date: Thu, 23 Nov 2023 15:42:48 +0800 Subject: [PATCH 13/15] fix(core): JSCCtx CreateObject crash protect --- driver/js/src/napi/jsc/jsc_ctx.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/js/src/napi/jsc/jsc_ctx.cc b/driver/js/src/napi/jsc/jsc_ctx.cc index 71ab2905343..02062c3b310 100644 --- a/driver/js/src/napi/jsc/jsc_ctx.cc +++ b/driver/js/src/napi/jsc/jsc_ctx.cc @@ -793,7 +793,7 @@ std::shared_ptr JSCCtx::CreateObject(const std::unordered_map(it.second); if (!ctx_value) { - auto error = CreateException("CreateObject2"); + auto error = CreateException("CreateObject ctx_value is nullptr"); return nullptr; } JSObjectSetProperty(context_, obj, object_key, ctx_value->value_, kJSPropertyAttributeNone, &exception); From 538aa1cb0e02e4d8c641614522232ffc20c06ca6 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Wed, 22 Nov 2023 17:05:02 +0800 Subject: [PATCH 14/15] fix(ios): Fine-tune callback timing to stay compatible with hippy2 rootView:didLoadFinish: callback --- renderer/native/ios/renderer/HippyRootView.mm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/renderer/native/ios/renderer/HippyRootView.mm b/renderer/native/ios/renderer/HippyRootView.mm index c2d308993c2..20964c745c5 100644 --- a/renderer/native/ios/renderer/HippyRootView.mm +++ b/renderer/native/ios/renderer/HippyRootView.mm @@ -132,6 +132,10 @@ - (instancetype)initWithBridge:(HippyBridge *)bridge } else { __weak __typeof(self)weakSelf = self; [bridge loadBundleURL:businessURL completion:^(NSURL * _Nullable url, NSError * _Nullable error) { + // Execute loadInstance first and then do call back, maintain compatibility with hippy2 + if (!error) { + [weakSelf runHippyApplication]; + } dispatch_async(dispatch_get_main_queue(), ^{ __strong __typeof(weakSelf)strongSelf = weakSelf; // 抛出业务包(BusinessBundle aka SecondaryBundle)加载完成通知, for hippy2兼容 @@ -145,9 +149,6 @@ - (instancetype)initWithBridge:(HippyBridge *)bridge [delegate rootView:strongSelf didLoadFinish:(error == nil)]; } }); - if (!error) { - [weakSelf runHippyApplication]; - } }]; } } From e677e0d36f019abbef96d4d927f668f4a0d55818 Mon Sep 17 00:00:00 2001 From: wwwcg Date: Fri, 17 Nov 2023 17:21:51 +0800 Subject: [PATCH 15/15] fix(ios)!: make transparent as default border color --- .../renderer/component/view/HippyViewManager.mm | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/renderer/native/ios/renderer/component/view/HippyViewManager.mm b/renderer/native/ios/renderer/component/view/HippyViewManager.mm index 648a702f301..0a0621426f6 100644 --- a/renderer/native/ios/renderer/component/view/HippyViewManager.mm +++ b/renderer/native/ios/renderer/component/view/HippyViewManager.mm @@ -392,10 +392,21 @@ - (void)loadImageSource:(NSString *)path forView:(HippyView *)view { } } HIPPY_CUSTOM_VIEW_PROPERTY(borderColor, CGColor, HippyView) { + CGColorRef color = nil; if ([view respondsToSelector:@selector(setBorderColor:)]) { - view.borderColor = json ? [HippyConvert CGColor:json] : defaultView.borderColor; + if (json) { + color = [HippyConvert CGColor:json]; + } else { + color = defaultView ? defaultView.borderColor : [UIColor clearColor].CGColor; + } + view.borderColor = color; } else { - view.layer.borderColor = json ? [HippyConvert CGColor:json] : defaultView.layer.borderColor; + if (json) { + color = [HippyConvert CGColor:json]; + } else { + color = defaultView ? defaultView.layer.borderColor : [UIColor clearColor].CGColor; + } + view.layer.borderColor = color; } }