Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ios): add custom image loader support #3830

Merged
merged 3 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion docs/development/ios-3.0-upgrade-guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@

由于3.0中关于image source的调用约定发生了变化(从 `NSArray` 类型的 `source` 调整为了 `NSString` 类型的 `src`),因此,如自定义了Image组件,请注意在对应的ViewManager中补充实现 `src` 属性,否则图片可能无法正常显示。

4. 删除Image组件的内置图片缓存
4. 删除了Image组件的内置图片缓存

3.0中删除了2.0内置的背景图片缓存管理类,即`HippyBackgroundImageCacheManager`,图片缓存逻辑交由业务方自行定制。如果您有缓存图片的需求,请通过自定义ImageLoader来实现。

5. 自定义ImageLoader的协议和实现变更:

Hippy 2.0提供了`HippyImageViewCustomLoader`协议,用于业务按需定制图片资源加载器。通常,App一般使用第三方图片库实现该协议,如SDWebImage等,从而实现更灵活的图片加载和支持更多图片类型的解码。然而,2.0中的这一协议约定存在些许问题,无法达到最佳的性能表现,而且已经与3.0的VFS模块设计不再兼容,因此在3.0中我们更新了该协议的约定。

注意,为便于及时发现该变更,在3.0中该协议名从`HippyImageViewCustomLoader`调整为了`HippyImageCustomLoaderProtocol`,协议方法也有一些变化,因此如果您使用了该协议,升级时将遇到少许编译问题,但其基本功能依旧保持不变。
80 changes: 57 additions & 23 deletions docs/development/native-adapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,52 +65,86 @@ Hippy SDK 提供默认空实现 `DefaultEngineMonitorAdapter`。当你需要查

---

## HippyImageViewCustomLoader
## HippyImageCustomLoaderProtocol

在Hippy SDK中, 前端 `<Image>` 组件默认对应的 HippyImageView 会根据 source 属性使用默认行为下载图片数据并显示。但是某些情况下,业务方希望使用自定义的图片加载逻辑(比如业务使用了缓存,或者拦截特定URL的数据),为此 SDK 提供了`HippyImageViewCustomLoader` 协议。
在Hippy SDK中, 前端 `<Image>` 组件默认对应的 HippyImageView 会根据 src 属性使用默认行为下载图片数据并显示。但是某些情况下,业务方希望使用自定义的图片加载逻辑(比如业务使用了缓存,或者拦截特定URL的数据),为此 SDK 提供了`HippyImageCustomLoaderProtocol` 协议。

用户实现此协议,自行根据图片的URL返回数据即可,HippyImageView将根据返回的数据展示图片。
用户实现此协议,自行根据图片的URL返回数据即可,HippyImageView将根据返回的数据展示图片。注意该支持返回待解码的NSData类型图片数据,也支持直接返回解码后的UIImage图片,请根据需要选择合适方案。

```objectivec
@protocol HippyImageViewCustomLoader<HippyBridgeModule>
/// A Resource Loader for custom image loading
@protocol HippyImageCustomLoaderProtocol <HippyBridgeModule>

@required
/**
* imageView:
*/
- (void)imageView:(HippyImageView *)imageView
loadAtUrl:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
context:(void *)context
progress:(void (^)(long long, long long))progressBlock
completed:(void (^)(NSData *, NSURL *, NSError *))completedBlock;

- (void)cancelImageDownload:(HippyImageView *)imageView withUrl:(NSURL *)url;

/// Load Image with given URL
/// Note that If you want to skip the decoding process lately,
/// such as using a third-party SDWebImage to decode,
/// Just set the ControlOptions parameters in the CompletionBlock.
///
/// - Parameters:
/// - imageUrl: image url
/// - extraInfo: extraInfo
/// - progressBlock: progress block
/// - completedBlock: completion block
- (void)loadImageAtUrl:(NSURL *)imageUrl
extraInfo:(nullable NSDictionary *)extraInfo
progress:(nullable HippyImageLoaderProgressBlock)progressBlock
completed:(nullable HippyImageLoaderCompletionBlock)completedBlock;

@end
```

## 协议实现

```objectivec
@interface CustomImageLoader : NSObject <HippyImageViewCustomLoader>
@interface CustomImageLoader : NSObject <HippyImageCustomLoaderProtocol>

@end

@implementation CustomImageLoader
HIPPY_EXPORT_MODULE()
- (void)imageView:(HippyImageView *)imageView loadAtUrl:(NSURL *)url placeholderImage:(UIImage *)placeholderImage context:(void *)context progress:(void (^)(long long, long long))progressBlock completed:(void (^)(NSData *, NSURL *, NSError *))completedBlock {

NSError *error = NULL;
HIPPY_EXPORT_MODULE() // 全局注册该模块至Hippy

- (void)loadImageAtUrl:(NSURL *)url
extraInfo:(NSDictionary *)extraInfo
progress:(HippyImageLoaderProgressBlock)progressBlock
completed:(HippyImageLoaderCompletionBlock)completedBlock {

// 1、如果获取的是NSData数据:
// 业务方自行获取图片数据,返回数据或者错误
NSError *error = NULL;
NSData *imageData = getImageData(url, &error);
// 将结果通过block通知
completedBlock(imageData, url, error);
// 将结果通过block回调
completedBlock(imageData, url, error, nil, kNilOptions);

// 2、如果可以直接获取UIImage数据,可跳过Hippy内置解码过程,避免重复解码:
UIImage *image = getImage(xxx);
// 传入控制参数,跳过内部解码
HippyImageLoaderControlOptions options = HippyImageLoaderControl_SkipDecodeOrDownsample;
// 将结果通过block回调
completedBlock(nil, url, error, image, options);
}
@end
```

业务方需要务必添加 `HIPPY_EXPORT_MODULE()` 代码以便在 Hippy 框架中注册此 ImageLoader 模块,系统将自动寻找实现了`HippyImageViewCustomLoader` 协议的模块当做 ImageLoader。
## 协议注册

与Hippy框架注册其他模块的方法一样,ImageLoader同样既可以选择通过Hippy框架提供的 `HIPPY_EXPORT_MODULE()` 宏注册到App全局(注意,全局注册的含义是App内的所有HippyBridge实例均会获取和使用该模块),又可通过 `HippyBridge` 初始化参数列表中的 `moduleProvider` 参数来注册到特定bridge。

除此之外,`HippyBridge` 还提供了一个注册方法,便于业务注册ImageLoader实例:

```objectivec
/// Set a custom Image Loader for current `hippyBridge`
/// The globally registered ImageLoader is ignored when set by this method.
///
/// - Parameter imageLoader: id
- (void)setCustomImageLoader:(id<HippyImageCustomLoaderProtocol>)imageLoader;
```

在上述实现代码中,我们使用了 `HIPPY_EXPORT_MODULE()` 宏来实现将此 ImageLoader 模块自动注册至 Hippy 框架中,框架内部将自动寻找实现了`HippyImageCustomLoaderProtocol` 协议的模块作为 ImageLoader。

PS: 若有多个模块实现 `HippyImageViewCustomLoader` 协议,系统只会使用其中一个作为默认 ImageLoader
!> 注意,同时只可有一个ImageLoader生效。若有多个模块实现了 `HippyImageCustomLoaderProtocol` 协议,框架使用最后一个作为生效的 ImageLoader。Hippy框架优先使用通过 `setCustomImageLoader:` 方法注册的ImageLoader。



Expand Down
66 changes: 66 additions & 0 deletions framework/ios/base/bridge/HippyBridge+Private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*!
* iOS SDK
*
* Tencent is pleased to support the open source community by making
* Hippy available.
*
* Copyright (C) 2019 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.
*/

#ifndef HippyBridge_Private_h
#define HippyBridge_Private_h

#import "HippyBridge.h"
#include <memory>

class VFSUriLoader;
class NativeRenderManager;

namespace hippy {
inline namespace dom {
class DomManager;
class RootNode;
class RenderManager;
};
};


@protocol HippyBridgeInternal <NSObject>

/// The C++ version of RenderManager instance, bridge holds
@property (nonatomic, assign) std::shared_ptr<NativeRenderManager> renderManager;

/// URI Loader
@property (nonatomic, assign) std::weak_ptr<VFSUriLoader> vfsUriLoader;

@end


@interface HippyBridge (Private) <HippyBridgeInternal>

/**
* Set basic configuration for native render
* @param domManager DomManager
* @param rootNode RootNode
*/
- (void)setupDomManager:(std::shared_ptr<hippy::DomManager>)domManager
rootNode:(std::weak_ptr<hippy::RootNode>)rootNode;

@end



#endif /* HippyBridge_Private_h */
54 changes: 20 additions & 34 deletions framework/ios/base/bridge/HippyBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,14 @@
#import "HippyMethodInterceptorProtocol.h"
#import "HippyModulesSetup.h"
#import "HippyImageProviderProtocol.h"
#import "HippyImageViewCustomLoader.h"
#import "HippyInvalidating.h"
#import "HippyDefines.h"

#ifdef __cplusplus
#include <memory>
#endif

@class HippyJSExecutor;
@class HippyModuleData;
@class HippyRootView;

#ifdef __cplusplus
class VFSUriLoader;
class NativeRenderManager;

namespace hippy {
inline namespace dom {
class DomManager;
class RootNode;
class RenderManager;
};
};
#endif

NS_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -167,9 +152,23 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass);
- (void)loadBundleURL:(NSURL *)bundleURL
completion:(void (^_Nullable)(NSURL * _Nullable, NSError * _Nullable))completion;

#ifdef __cplusplus
@property(nonatomic, assign)std::weak_ptr<VFSUriLoader> VFSUriLoader;
#endif

#pragma mark - Image Related

/// Get the custom Image Loader
///
/// Note that A custom ImageLoader can be registered in two ways:
/// One is through the registration method provided below,
/// The other is to register globally with the HIPPY_EXPORT_MODULE macro.
///
/// Only one image loader can take effect at a time.
@property (nonatomic, strong, nullable, readonly) id<HippyImageCustomLoaderProtocol> imageLoader;

/// Set a custom Image Loader for current `hippyBridge`
/// The globally registered ImageLoader is ignored when set by this method.
///
/// - Parameter imageLoader: id
- (void)setCustomImageLoader:(id<HippyImageCustomLoaderProtocol>)imageLoader;

/**
* Image provider method
Expand All @@ -178,15 +177,8 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass);
- (void)addImageProviderClass:(Class<HippyImageProviderProtocol>)cls;
- (NSArray<Class<HippyImageProviderProtocol>> *)imageProviderClasses;

#ifdef __cplusplus
/**
* Set basic configuration for native render
* @param domManager DomManager
* @param rootNode RootNode
*/
- (void)setupDomManager:(std::shared_ptr<hippy::DomManager>)domManager
rootNode:(std::weak_ptr<hippy::RootNode>)rootNode;
#endif

#pragma mark -

/**
* Load instance for root view and show views
Expand All @@ -207,12 +199,6 @@ HIPPY_EXTERN NSString *HippyBridgeModuleNameForClass(Class bridgeModuleClass);
@property (nonatomic, readonly) HippyJSExecutor *javaScriptExecutor;


#ifdef __cplusplus
/// The C++ version of RenderManager instance, bridge holds
@property (nonatomic, assign) std::shared_ptr<NativeRenderManager> renderManager;
#endif


/**
* JS invocation methods
*/
Expand Down
35 changes: 32 additions & 3 deletions framework/ios/base/bridge/HippyBridge.mm
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*/

#import "HippyBridge.h"
#import "HippyBridge+Private.h"
#import "HippyBundleLoadOperation.h"
#import "HippyBundleExecutionOperation.h"
#import "HippyBundleOperationQueue.h"
Expand Down Expand Up @@ -173,6 +174,9 @@ @interface HippyBridge() {

@implementation HippyBridge

@synthesize renderManager = _renderManager;
@synthesize imageLoader = _imageLoader;

dispatch_queue_t HippyJSThread;

dispatch_queue_t HippyBridgeQueue() {
Expand Down Expand Up @@ -238,7 +242,7 @@ - (instancetype)initWithDelegate:(id<HippyBridgeDelegate>)delegate
[self setUp];

[self addImageProviderClass:[HippyDefaultImageProvider class]];
[self setVFSUriLoader:[self createURILoaderIfNeeded]];
[self setVfsUriLoader:[self createURILoaderIfNeeded]];
[self setUpNativeRenderManager];

[HippyBridge setCurrentBridge:self];
Expand Down Expand Up @@ -404,6 +408,31 @@ - (BOOL)moduleIsInitialized:(Class)moduleClass {
}


#pragma mark - Image Config Related

- (id<HippyImageCustomLoaderProtocol>)imageLoader {
@synchronized (self) {
if (!_imageLoader) {
// Only the last imageloader takes effect,
// compatible with Hippy 2.x
_imageLoader = [[self modulesConformingToProtocol:@protocol(HippyImageCustomLoaderProtocol)] lastObject];
}
}
return _imageLoader;
}

- (void)setCustomImageLoader:(id<HippyImageCustomLoaderProtocol>)imageLoader {
@synchronized (self) {
if (imageLoader != _imageLoader) {
if (_imageLoader) {
HippyLogWarn(@"ImageLoader change from %@ to %@", _imageLoader, imageLoader);
}
_imageLoader = imageLoader;
}
}
}


#pragma mark - Debug Reload

- (void)reload {
Expand Down Expand Up @@ -607,7 +636,7 @@ - (void)rootViewSizeChangedEvent:(NSNumber *)tag params:(NSDictionary *)params {
[self sendEvent:@"onSizeChanged" params:dic];
}

- (void)setVFSUriLoader:(std::weak_ptr<VFSUriLoader>)uriLoader {
- (void)setVfsUriLoader:(std::weak_ptr<VFSUriLoader>)uriLoader {
[_javaScriptExecutor setUriLoader:uriLoader];
#ifdef ENABLE_INSPECTOR
auto devtools_data_source = _javaScriptExecutor.pScope->GetDevtoolsDataSource();
Expand All @@ -621,7 +650,7 @@ - (void)setVFSUriLoader:(std::weak_ptr<VFSUriLoader>)uriLoader {
#endif
}

- (std::weak_ptr<VFSUriLoader>)VFSUriLoader {
- (std::weak_ptr<VFSUriLoader>)vfsUriLoader {
return _uriLoader;
}

Expand Down
Loading
Loading