当业务发展到一定地步的时候,业务的bundle可能会变大,也可能会有动态下载新的业务包到本地的逻辑,当要加载到本地业务包的时候加载业务资源是一个问题,其实Android这块官网已经默认实现了加载本地资源包的问题,这种情况在多bridge下是没问题的,但是一但用到单bridge下就会出现问题,我们主要说一下单bridge下加载的资源路径切换问题
问题
- 在单bridge加载的情况下,如果先加载apk内部的bundle,那么后续的资源都会从assets下去找,如果第一次加载的是本地bundle,那么后续的资源都会从bundle的同目录下去找,这个是rn本身的缓存机制导致的问题,所以我们应该如何去改
分析
- 由于RN中基本上每个前端的view都会对于安卓里的一个view,比如Text会对应到安卓原生的TextView这个类,所以我们猜测是不是加载图片的逻辑也可能在原生这边来实现的,毕竟你会去加载assets下的资源,根据最后找到了加载图片的管理类:ReactImageView,然后的问题就是怎么替换成我们自己的
解决方案
根据上面的分析,我们找到这个类会在MainReactPackage中注册,然后我们就去把这个注册的类替换成自己的即可,下面JsLoaderUtil是一个用来管理bundle加载的类,可以自行定义。
-
第一步找到对应的ReactImageManager,然后从列表中移除,添加我们自己的类,当然这里注册的时候需要把系统的MainReactPackage替换成我们自己的ReactPackage。 @Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
List<ViewManager> viewManagers = super.createViewManagers(reactContext);
int index = -1;
for (int i = 0; i < viewManagers.size(); i++) {
if (viewManagers.get(i) instanceof ReactImageManager) {
index = i;
break;
}
}
if (index != -1) {
viewManagers.remove(index);
viewManagers.add(new EduReactImageManager());
}
return viewManagers;
}
-
在ReactImageManager中创建自己的ImageView对象 override fun createViewInstance(context: ThemedReactContext): ReactImageView {
val callerContext = if (mCallerContextFactory != null) mCallerContextFactory.getOrCreateCallerContext(context.moduleName, null as String?) else this.mCallerContext
var imageView = EduReactImageView(context, this.draweeControllerBuilder, mGlobalImageLoadListener, callerContext)
return imageView
}
-
EduReactImageView是拷贝的原生的ReactImageView类,基本不用改,只需要把对应的ImageSource对应改成我们自己的,这里只是一部分,所有的地方都要改 public void setSource(@Nullable ReadableArray sources) {
List<ImageSource> tmpSources = new LinkedList();
if (sources != null && sources.size() != 0) {
if (sources.size() == 1) {
ReadableMap source = sources.getMap(0);
String uri = source.getString("uri");
ImageSource imageSource = new EduImageSource(this.getContext(), uri);
tmpSources.add(imageSource);
if (Uri.EMPTY.equals(imageSource.getUri())) {
this.warnImageSource(uri);
}
} else {
for (int idx = 0; idx < sources.size(); ++idx) {
ReadableMap source = sources.getMap(idx);
String uri = source.getString("uri");
ImageSource imageSource = new EduImageSource(this.getContext(), uri, source.getDouble("width"), source.getDouble("height"));
tmpSources.add(imageSource);
if (Uri.EMPTY.equals(imageSource.getUri())) {
this.warnImageSource(uri);
}
}
}
} else {
ImageSource imageSource = new EduImageSource(this.getContext(), "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=");
tmpSources.add(imageSource);
}
-
核心的逻辑来了,加载资源的逻辑需要做对应的修改,其他代码基本不用改,只需要修改计算Uri的逻辑
- 当从sdcard去加载bundle的时候,不是资源路径不是以file://开头,则拿到资源名去匹配,由于不确定是jpg还是png的文件,所以去匹配一下,哪个存在就用哪个,如果都没有匹配就走系统的逻辑(第一次bundle从asset加载的情况)
- 当从sdcard去加载bundle的时候,如果资源路径是file://开头的,是从sdcard加载的,先去判断sdcard有没有资源,如果有就走默认逻辑,如果没有就去加载assets中是的资源(加载本地bundle优先加载bundle目录下的资源,sdcard没有的话去尝试加载assets下找)
- 当从asset中加载bundle的时候,如果资源路径是file://开头的,则替换把路径啥的都去掉,只取资源名,因为从asset下加载只需要资源名(第一bundle从sdcard加载的情况)
- 当从asset中加载bundle的时候,如果资源路径不是file://开头的,则走默认逻辑即可
static {
suffixs.add(".jpg");
suffixs.add(".png");
suffixs.add(".webp");
}
private Uri computeUri(Context context) {
try {
if (isLoadFromFile) {
//加载本地的bundle,去匹配文件系统里的资源
if (this.mSource != null && !this.mSource.startsWith(LOCAL_FILE_URI)) {
String resName = JsLoaderUtil.getReactCacheDir() + DRAWABLE_NAME + File.separator + mSource;
for (String suffix : suffixs) {
if (new File(resName + suffix).exists()) {
this.mSource = LOCAL_FILE_URI + resName + suffix;
break;
}
}
} else if (this.mSource != null) {
//加载本地的bundle,文件系统里没有则继续调用asset里的
String fileName = this.mSource.replace(LOCAL_FILE_URI, "");
if (!new File(fileName).exists()) {
loadFromAsset();
}
}
} else {
//加载本地的bundle,只会从本地去加载资源
loadFromAsset();
}
Uri uri = Uri.parse(this.mSource);
return uri.getScheme() == null ? this.computeLocalUri(context) : uri;
} catch (Exception var3) {
return this.computeLocalUri(context);
}
}
private void loadFromAsset() {
if (this.mSource != null && this.mSource.startsWith(LOCAL_FILE_URI)) {
int index = mSource.lastIndexOf(File.separator);
if (index != -1) {
String resName = mSource.substring(index + 1, mSource.length());
int indexSuffix = resName.indexOf(".");
if (indexSuffix != -1) {
this.mSource = resName.substring(0, indexSuffix);
} else {
this.mSource = resName;
}
}
}
}
|