模仿字的英文翻译英语怎么说-marry什么意思
2023年4月2日发(作者:阁楼王世子)
reactnative实战系列教程之热更新原
理分析与实现
很多人在技术选型的时候,会选择RN是因为它具有热更新,而且这是它的一个特性,所以
实现起来会相对比较简单,不像原生那样,原生的热更新是一个大工程。那就目前来看,
RN的热更新方案已有的,有微软的CodePush和reactnative中文网的pushy。实话说,这两
个我还没有体验过。一来是当初选择RN是因为它不但拥有接近原生的体验感还具有热更新
特性,那么就想自己来实现一下热更新,研究一下它的原理;二来,把自己的东西放在别人
的服务器上总是觉得不是最好的办法,为什么不自己实现呢?因此,这篇文章便是记录自己
的一些研究。
reactnative加载bundle过程
这篇文章是基于RNAndroid0.38.1
当我们创建完RN的基础项目后,打开android项目,项目只有MainActivity和
MainApplication。
打开MainActivity,只有一个重写方法getMainComponentName,返回主组件名称,它继承
于ReactActivity。
我们打开ReactActivity,它使用了代理模式,通过ReactActivityDelegatemDelegate对象将
Activity需要处理的逻辑放在了代理对象内部,并通过getMainComponentName方法来设置
(匹配)JS端erComponent端启动的入口组件。
Activity渲染出界面前,先是调用onCreate,所以我们进入代理对象的onCreate方法
//
protectedvoidonCreate(BundlesavedInstanceState){
//判断是否支持dev模式,也就是RN常见的那个红色弹窗
if(getReactNativeHost().getUseDeveloperSupport()&&_INT>=23)
{
//Getpermissiontoshowredboxindevbuilds.
if(!wOverlays(getContext())){
IntentserviceIntent=new
Intent(_MANAGE_OVERLAY_PERMISSION);
getContext().startActivity(serviceIntent);
FLog.w(,REDBOX_PERMISSION_MESSAGE);
xt(getContext(),REDBOX_PERMISSION_MESSAGE,
_LONG).show();
}
}
if(mMainComponentName!=null){
//加载app
loadApp(mMainComponentName);
}
//android模拟器dev模式下,双击R重新加载
mDoubleTapReloadRecognizer=newDoubleTapReloadRecognizer();
}
上面的代码并没什么实质的东西,主要是调用了loadApp,我们跟进看下
protectedvoidloadApp(StringappKey){
if(mReactRootView!=null){
thrownewIllegalStateException(\"CannotloadAppwhileappisalreadyrunning.\");
}
mReactRootView=createRootView();
eactApplication(
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
getPlainActivity().setContentView(mReactRootView);
}
生成了一个ReactRootView对象,然后调用它的startReactApplication方法,最后
setContentView将它设置为内容视图。再跟进startReactApplication里
//
publicvoidstartReactApplication(
ReactInstanceManagerreactInstanceManager,
StringmoduleName,
@NullableBundlelaunchOptions){
OnUiThread();
//TODO(6788889):UsePOJOinsteadofbundlehere,apparentlywecan\'tjustuse
WritableMap
//hereasitmaybedeallocatedinnativeafterpassingviaJNIbridge,butwewanttoreuse
//itinthecaseofre-creatingthecatalystinstance
Condition(
mReactInstanceManager==null,
\"Thisrootviewhasalreadybeenattachedtoacatalystinstancemanager\");
//配置项管理
mReactInstanceManager=reactInstanceManager;
//入口组件名称
mJSModuleName=moduleName;
//用于传递给JS端初始组件props参数
mLaunchOptions=launchOptions;
//判断是否已经加载过
if(!rtedCreatingInitialContext()){
//去加载bundle文件
ReactContextInBackground();
}
//WeneedtowaitfortheinitialonMeasure,ifthisviewhasnotyetbeenmeasured,weset
which
//willmakethisviewstartReactApplicationitselftoinstancemanageronceonMeasureis
called.
if(mWasMeasured){
//去渲染ReactRootView
attachToReactInstanceManager();
}
}
startReactApplication传入三个参数,第一个ReactInstanceManager配置项管理类(非常重要);
第二个是MainComponentName入口组件名称;第三个是AndroidBundle类型,用于传递给
JS端初返景入深林是什么意思 始组件的props参数。首先,会根据ReactInstanceManager的配置去加载bundle过程,
然后去渲染ReactRootView,将UI展示出来。现在我们不用去管attachToReactInstanceManager
是如何去渲染ReactRootView,我们主要是研究如何加载bundle的,所以,我们跟进
createReactContextInBackground,发现它是抽象类ReactInstanceManager的一个抽象方法。
那它具体实现逻辑是什么呢?那我们就需要知道ReactInstanceManager的具体类的实例对象
是谁了【1】。
好了,现在我们回到的loadApp,在ReactRootView的
startReactApplication传入的ReactInstanceManager对象是
getReactNativeHost().getReactInstanceManager()
//
eactApplication(
getReactNativeHost().getReactInstanceManager(),
appKey,
getLaunchOptions());
getReactNativeHost(),又是什么呢?
//从Application获取ReactNativeHost
protectedReactNativeHostgetReactNativeHost(){
return((ReactApplication)getPlainActivity().getApplication()).getReactNativeHost();
}
所以我们在打开MainApplication类
publicclassMainApplicationextendsApplicationimplementsReactApplication{
privatefinalReactNativeHostmReactNativeHost=newReactNativeHost(this){
@Override
protectedbooleangetUseDeveloperSupport(){
;
}
@Override
protectedList
returnArrays.
newMainReactPackage()
);
}
};
@Override
publicReactNativeHostgetReactNativeHost(){
returnmReactNativeHost;
}
}
MainApplication实现了ReactApplication接口,在getReactNativeHost()方法返回配置好的
ReactNativeHost对象。由于我们把项目的Application配置成了MainApplication,所以
ReacActivityDelegate的getReactNativeHost方法,返回的就是MainApplication
mReactNativeHost对象。接着我们看下ReactNativeHost的getReactInstanceManager()方法,
里面直接调用了createReactInstanceManager()方法,所以我们直接看
createReactInstanceManager()
//
protectedReactInstanceManagercreateReactInstanceManager(){
rbuilder=r()
.setApplication(mApplication)
.setJSMainModuleName(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setRedBoxHandler(getRedBoxHandler())
.setUIImplementationProvider(getUIImplementationProvider())
.setInitialLifecycleState(_CREATE);
for(ReactPackagereactPackage:getPackages()){
kage(reactPackage);
}
StringjsBundleFile=getJSBundleFile();
if(jsBundleFile!=null){
undleFile(jsBundleFile);
}else{
dleAssetName(NotNull(getBundleAssetName()));
}
();
}
createReactInstanceManager()通过使用r构造器来设置一些配置
并生成对象。从这里看,我们可以从MainApplication的mReactNativeHost对象来配置
ReactInstanceManager,比如JSMainModuleName、UseDeveloperSupport、Packages、
JSBundleFile、BundleAssetName等,也可以重写createReactInstanceManager方法,自己手
动生成ReactInstanceManager对象。
这里看下jsBundleFile的设置,先判断了getJSBundleFile()是否为null,项目默认是没有重写
的,所以默认就是null,那么走dleAssetName分支,看下getBundleAssetName(),
默认是返回””
//dleAssetName
publicBuildersetBundleAssetName(StringbundleAssetName){
mJSBundleAssetUrl=(bundleAssetName==null?null:\"assets://\"+bundleAssetName);
mJSBundleLoader=null;文心雕龙神思篇主要观点
returnthis;
}
所以,默认情况下,mJSBundleAssetUrl=”assets://”,mJSBundleLoader=
null。
接着往下看,builder最后调用build()来生成ReactInstanceManager实例对象。我们进去build()
方法看下。
//r
publicReactInstanceManagerbuild(){
NotNull(
mApplication,
\"Applicationpropertyhasnotbeensetwiththisbuilder\");
Condition(
mUseDeveloperSupport||mJSBundleAssetUrl!=null||mJSBundleLoader!=null,
\"JSBundleFileorAssetURLhastobeprovidedwhendevsupportisdisabled\");
Condition(
mJSMainModuleName!=null||mJSBundleAssetUrl!=null||mJSBundleLoader!=
null,
\"EitherMainModuleNameorJSBundleFileneedstobeprovided\");
if(mUIImplementationProvider==null){
//createdefaultUIImplementationProvideriftheprovidedoneisnull.
mUIImplementationProvider=newUIImplementationProvider();
}
returnnewXReactInstanceManagerImpl(
mApplication,
mCurrentActivity,
mDefaultHardwareBackBtnHandler,
(mJSBundleLoader==null&&mJSBundleAssetUrl!=null)?
AssetLoader(mApplication,mJSBundleAssetUrl):
mJSBundleLoader,
mJSMainModuleName,
mPackages,
mUseDeveloperSupport,
mBridgeIdleDebugListener,
NotNull(mInitialLifecycleState,\"Initiallifecyclestatewasnotset\"),
mUIImplementationProvider,
mNativeModuleCallExceptionHandler,
mJSCConfig,
mRedBoxHandler,
mLazyNativeModulesEnabled,
mLazyViewManagersEnabled);
}
从上面看来,XReactInstanceManagerImpl的第四个参数,传入的是一个JSBundleLoader,并
且默认是AssetLoader。
new的是XReactInstanceManagerImpl对象,也就是说,XReactInstanceManagerImpl是抽象
类ReactInstanceManager的具体实现类。
好了,在【1】处留下的疑问,我们现在就解决了。也就是,说调用ReactInstanceManager
的createReactContextInBackground方法,是去执行XReactInstanceManagerImpl的
reateReactContextInBackground方法。
进去reateReactContextInBackground方法后,它调用了
recreateReactContextInBackgroundInner()一个内部方法,直接看下
recreateReactContextInBackgroundInner的实现代码
//
privatevoidrecreateReactContextInBackgroundInner(){
OnUiThread();
//判断是否是dev模式
if(mUseDeveloperSupport&&mJSMainModuleName!=null){
finalDeveloperSettingsdevSettings=Settings();
//IfremoteJSdebuggingisenabled,loadfromdevserver.
if(oDateJSBundleInCache()&&
!teJSDebugEnabled()){
//Ifthereisaup-to-datebundledownloadedfromserver,
//withremoteJSdebuggingdisabled,alwaysusethat.
onJSBundleLoadedFromServer();
}elseif(mBundleLoader==null){
ReloadJS();
}else{
agerRunning(
erStatusCallback(){
@Override
publicvoidonPackagerStatusFetched(finalbooleanpackagerIsRunning){
iThread(
newRunnable(){
@Override
publicvoidrun(){
if(packagerIsRunning){
ReloadJS();
}else{
//Ifdevserverisdown,disabletheremoteJSdebugging.
oteJSDebugEnabled(false);
recreateReactContextInBackgroundFromBundleLoader();
}
}
});
}
});
}
return;
}
recreateReactContextInBackgroundFromBundleLoader();
}
由于我们发布出去的apk包,最后都是关闭了dev模式的,所以dev模式下的bundle加载
流程我们先不需要太多的关注,那么mUseDeveloperSupport就是false,它就不会走进if里
面,而是调十首最火的草原歌曲 用了recreateReactContextInBackgroundFromBundleLoader()方法。其实,你简单
看下if里面的判断和方法调用也能知道,其实它就是去拉取通过React-nativestart启动起来
的packages服务器窗口,再者如果打开了远程调试,那么它就走浏览器代理去拉取bundle。
recreateReactContextInBackgroundFromBundleLoader又调用了
recreateReactContextInBackground
privatevoidrecreateReactContextInBackground(
yjsExecutorFactory,
JSBundleLoaderjsBundleLoader){
OnUiThread();
ReactContextInitParamsinitParams=
newReactContextInitParams(jsExecutorFactory,jsBundleLoader);
if(mReactContextInitAsyncTask==null){
//Nobackgroundtasktocreatereactcontextiscurrentlyrunning,createandexecuteone.
mReactContextInitAsyncTask=newReactContextInitAsyncTask();
eOnExecutor(_POOL_EXECUTOR,
initParams);
}else{
//Backgroundtaskiscurrentlyrunning,queueupmostrecentinitparamstorecreate
context
//oncetaskcompletes.
mPendingReactCo形容未来可期的诗句 ntextInitParams=initParams;
}
}
到这里,recreateReactContextInBackground使用了ReactContextInitAsyncTask(继承
AsyncTask)开启线程去执行,并且将ReactContextInitParams当作参数,传递到了AsyncTask
的doInBackground。ReactContextInitParams只是将jsExecutorFactory、jsBundleLoader两个
参数封装成一个内部类,方便传递参数。
那么ReactContextInitAsyncTask开启线程去执行了什么?该类也是个内部类,我们直接看它
的doInBackground方法。
@Override
protectedResult
params){
eadPriority(_PRIORITY_DEFAULT);
Condition(params!=null&&>0&¶ms[0]!=null);
try{
JavaScriptExecutorjsExecutor=params[0].getJsExecutorFactory().create();
(createReactContext(jsExecutor,params[0].getJsBundleLoader()));
}catch(Exceptione){
//PassexceptiontoonPostExecute()soitcanbehandledonthemainthread
(e);
}
}
好像也没处理什么,就是使用ReactContextInitParams传递进来的两个参数,去调用了
createReactContext
privateReactApplicationContextcreateReactContext(
JavaScriptExecutorjsExecutor,
JSBundleLoaderjsBundleLoader){
FLog.i(,\"Creatingreactcontext.\");
ker(CREATE_REACT_CONTEXT_START);
mSourceUrl=rceUrl();
List
Map
rjsModulesBuilder=new
r();
finalReactApplicationContextreactContext=new
ReactApplicationContext(mApplicationContext);
if(mUseDeveloperSupport){
iveModuleCallExceptionHandler(mDevSupportManager);
}
ker(PROCESS_PACKAGES_START);
ection(
TRACE_TAG_REACT_JAVA_BRIDGE,
\"createAndProcessCoreModulesPackage\");
try{
CoreModulesPackagecoreModulesPackage=
newCoreModulesPackage(this,mBackBtnHandler,mUIImplementationProvider);
processPackage(
coreModulesPackage,
reactContext,
moduleSpecs,
reactModuleInfoMap,
jsModulesBuilder);
}finally{
tion(TRACE_TAG_REACT_JAVA_BRIDGE);
}
//TODO(6818138):Solveuse-caseofnative/jsmodulesoverriding
for(ReactPackagereactPackage:mPackages){
ection(
TRACE_TAG_REACT_JAVA_BRIDGE,
\"createAndProcessCustomReactPackage\");
try{
processPackage(
reactPackage,
reactContext,
moduleSpecs,
reactModuleInfoMap,
jsModulesBuilder);
}finally{
tion(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
ker(PROCESS_PACKAGES_END);
ker(BUILD_NATIVE_MODULE_REGISTRY_START);
ection(TRACE_TAG_REACT_JAVA_BRIDGE,
\"buildNativeModuleRegistry\");
NativeModuleRegistrynativeModuleRegistry;
try{
nativeModuleRegistry=newNativeModuleRegistry(moduleSpecs,
reactModuleInfoMap);
}finally{
tion(TRACE_TAG_REACT_JAVA_BRIDGE);
ker(BUILD_NATIVE_MODULE_REGISTRY_END);
}
NativeModuleCallExceptionHandlerexceptionHandler=
mNativeModuleCallExceptionHandler!=null
?mNativeModuleCallExceptionHandler
:mDevSupportManager;
rcatalystInstanceBuilder=r()
.setReactQueueConfigurationSpec(Default())
.setJSExecutor(jsExecutor)
.setRegistry(nativeModuleRegistry)
.setJSModuleRegistry(())
.setJSBundleLoader(jsBundleLoader)
.setNativeModuleCallExceptionHandler(exceptionHandler);
ker(CREATE_CATALYST_INSTANCE_START);
//CREATE_CATALYST_INSTANCE_
ection(TRACE_TAG_REACT_JAVA_BRIDGE,\"createCatalystInstance\");
finalCatalystInstancecatalystInstance;
try{
catalystInstance=();
}finally{
tion(TRACE_TAG_REACT_JAVA_BRIDGE);
ker(CREATE_CATALYST_INSTANCE_END);
}
if(mBridgeIdleDebugListener!=null){
dgeIdleDebugListener(mBridgeIdleDebugListener);
}
lizeWithInstance(catalystInstance);
undle();
returnreactContext;
}
这个方法代码有点多,首先它执行设置了RN自带的和开发者自定义的模块组件
(PackageModule),然后同样使用了构造器r生成了
catalystInstance对象,最后调用了undle()。跟进去是一个接口类
CatalystInstance,那么我们又要去看它的实现类CatalystInstanceImpl
//
@Override
publicvoidrunJSBundle(){
Condition(!mJSBundleHasLoaded,\"JSbundlewasalreadyloaded!\");
mJSBundleHasLoaded=true;
//incrementPendingJSCalls();
ript();
synchronized(mJSCallsPendingInitLock){
//LoadingthebundleisqueuedontheJSthread,butmaynothave
//\'ssavetosetthishere,though,sinceanyworkit
//ontheJSthreadbehindtheload.
mAcceptCalls=true;
for(PendingJSCallcall:mJSCallsPendingInit){
callJSFunction(torToken,e,d,ents);
}
();
}
//ThisisregisteredafterJSstartssinceitmakesaJScall
erListener(mTraceListener);
}
到这里,可以看到mJSBundleLoader调用了loadScript去加载bundle。进去方法看下,发现
它又是个抽象类,有两个抽象方法,一个是loadScript加载bundle,一个是getSourceUrl返
回bundle的地址,并且提供了4个静态工厂方法。
由之前分析知道,JSBundleLoader默认是使用了AssetLoader来创建的
实例
//
publicstaticJSBundleLoadercreateAssetLoader(
finalContextcontext,
finalStringassetUrl){
returnnewJSBundleLoader(){
@Override
publicvoidloadScript(CatalystInstanceImplinstance){
riptFromAssets(ets(),assetUrl);
}
@Override
publicStringgetSourceUrl(){
returnassetUrl;
}
}情商高的聊天语句900句 ;
}
我们看到loadScript最后是调用了CatalystInstanceImpl的loadScriptFromAssets。跟进去之后
发现,它是一个native方法,也就是最后的实现RN把它放在了jni层来完成最后加载bundle
的过程。
并且CatalystInstanceImpl不止loadScriptFromAssets一个native方法,它还提供了
loadScriptFromFile和loadScriptFromOptimizedBundle。其中前面两个,分别是从androidassets
目录下加载bundle,另一个是从androidSD卡文件夹目录下加载bundle。而
loadScriptFromOptimizedBundle是在UnpackingJSBundleLoader类里调用,但是
UnpackingJSBundleLoader目前好像是没有用到,有知道它的作用的朋友们可以告知一下。
至此,bundle的加载流程我们已经走一遍了,下面用一张流程图来总结下
加载bundle文件的几个途径
从上面的分析过程,我们可以得出,bundle的加载路径来源取决于JSBundleLoader的
loadScript,而loadScript又调用了CatalystInstanceImpl的loadScriptFromAssets或者
loadScriptFromFile,所以,加载bundle文件的途径本质上有两种方式
loadScriptFromAssets
从android项目下的assets文件夹下去加载,这也是RN发布版的默认加载方式,也就是在
cmd命令行下使用gradlewassembleRelease命令打包签名后的apk里面的assets就包含有
bundle文件
如果你打包后发现里面没有bundle文件,那么你将它安装到系统里,运行也是会报错的
reactnativegradleassembleRelease打包运行失败,没有生成bundle文件
loadScriptFromFile
第二种方式是从android文件系统也就是sd卡下去加载bundle。
我们只要事先在sd卡下存放bundle文件,然后在ReactNativeHost的getJSBundleFile返回
文件路径即可。
//
privatefinalReactNativeHostmReactNativeHost=newReactNativeHost(this){
@Override
protectedbooleangetUseDeveloperSupport(){
;
}
@Override
protectedList
returnArrays.
newMainReactPackage()
);
}
@Nullable
@Override
protectedStringgetJSBundleFile(){
FilebundleFile=newFile(getCacheDir()+\"/react_native千山暮雪免费看到全集 \",\"\");
if(()){
olutePath();
}
undleFile();
}
@Nullable
@Override
protectedStringgetBundleAssetName(){
dleAssetName();
}
};
getJSBundleFile首先会尝试在sd卡目录下
data/data/
看是否存在文件,如果有,那么就会使用该bundle,如果没有,那么就
会返回null,这时候就是去加载assets下的bundle了。
热更新的实现
如果你了解ReactNativebundle命令,那么就会知道,其实该命令分两部分,一部分是生成
bundle文件,一部分是生成图片资源。对android的来说,也就是app/
中下面这句
applyfrom:\"../../node_modules/react-native/\"
该脚本就是去执行reactnativebundle命令,它将生成的bundle文件放在assets下,且将生
成的图片资源放在drawable下。
但是当我们自定义getJSBundleFile路径之后,bundle的所有加载过程都是在该目录下,包
括图片资源,所以我们服务器上存放的应该是个bundlepatch,包括bundle文件和图片资源。
关于RN的图片热更新问题,可以看这个React-Native图片热更新初探
有了前面的分析和了解后,那么就可以自己动手来实现bundle的热更新了。
那么热更新主要包括
-bundlepatch从服务器下载到sd卡
-程序中加载bundle
接下来,进行模拟版本更新:将旧版本中‘我的’tab的列表中‘观看历史’item去掉,也
就是新版本中不再有‘观看历史’功能,效果如下
更新之前如下:
bundlepatch的下载
我这里服务器使用的bmob后台,将要更新的bundle文件存放在服务器上。
先将去掉‘观看历史’后的新版本bundlepatchs打包出来,上传到服务器上(bmob)。
通过react-nativebundle命令手动将patchs包打包出来
react-nativebundle--platformandroid--devfalse--r
--bundle-outputF:GrayReactNativeXiF
--assets-destF:GrayReactNativeXiFanbundle
然后,在客户端定义一个实体类来存放更新对象
publicclassAppInfoextendsBmobObject{
privateStringversion;//bundle版本
privateStringupdateContent;//更新内容
privateBmobFilebundle;//要下载的bundlepatch文件
}
然后,程序启动的时候去检测更新
//
@Override
protectedvoidonCreate(BundlesavedInstanceState){
te(savedInstanceState);
BmobQuery
it(1);
reGreaterThan(\"version\",\"1.0.0\");
jects(newFindListener
@Override
publicvoiddone(List
if(e==null){
if(list!=null&&!y()){
AppInfoinfo=(0);
FilereactDir=newFile(getCacheDir(),\"react_native\");
if(!()){
();
}
BmobFilepatchFile=dle();
finalFilesaveFile=newFile(reactDir,\"\");
if(()){
return;
}
//下载文件
ad(saveFile,newDownloadFileListener(){
@Override
publicvoiddone(Strings,BmobExceptione){
if(e==null){
n(\"下载完成\");
//解压patch文件到react_native文件夹下
unzip(saveFile);
}else{
Log.e(\"bmob\",ng());
}
}
@Override
publicvoidonProgress(Integerinteger,longl){
n(\"下载中....\"+integer);
}
});
}
}else{
Log.e(\"bmob\",ng());
}
}
});
}
当将bundle-patch保存完并解压之后,接下去就是加载bundle了。
加载bundle
根据bug的紧急/重要程度,可以把加载bundle的时机分为:立马加载和下次启动加载,我
这里将它们分别称为热加载和冷加载。
冷加载
冷加载方式比较简单,不用做任何特殊处理,下载并解压完包之后,当应用完全
退出之后(应用在后台不算完全退出,应用被杀死才算),用户再次启动应用,就会去加载新
的bundle了。
热加载
热加载需要特殊处理一下,处理也很简单,只要在解压unzip之后,调用以下代码即可
//
//清空ReactInstanceManager配置
getReactNativeHost().clear();
//重启activity
recreate();
结合JS端,实现完整热更新流程
热更新的总体思路是,JS端通过Module发起版本检测请求,如果检测到有新版本bundle,
就去下载bundle,下载完成后根据更新的紧急程度来决定是冷加载还是热加载。
那么首先我们需要定义一个UpdateCheckModule来建立起JS端和android端之间的检测更
新通信。
classUpdateCheckModuleextendsReactContextBaseJavaModule{
privatestaticfinalStringTAG=\"UpdateCheckModule\";
privatestaticfinalStringBUNDLE_VERSION=\"CurrentBundleVersion\";
privateSharedPreferencesmSP;
UpdateCheckModule(ReactApplicationContextreactContext){
super(reactContext);
mSP=redPreferences(\"react_bundle\",_PRIVATE);
}
@Override
publicStringgetName(){
return\"UpdateCheck\";
}
@Nullable
@Override
publicMap
Map
//跟随apk一起打包的bundle基础版本号
StringbundleVersion=_VERSION;
//bundle更新后的当前版本号
StringcacheBundleVersion=ing(BUNDLE_VERSION,\"\");
if(!y(cacheBundleVersion)){
bundleVersion=cacheBundleVersion;
}
(BUNDLE_VERSION,bundleVersion);
returnconstants;
}
@ReactMethod
publicvoidcheck(StringcurrVersion){
BmobQuery
it(1);
reGreaterThan(\"version\",currVersion);
jects(newFindListener
@Override
publicvoiddone(List
if(e==null){
if(list!=null&&!y()){
finalAppInfoinfo=(0);
FilereactDir=new
File(getReactApplicationContext().getCacheDir(),\"react_native\");
//获取到更新消息,说明bundle有新版,在解压前先删除掉旧版
deleteDir(reactDir);
if(!()){
();
}
finalFilesaveFile=newFile(reactDir,\"\");
BmobFilepatchFile=dle();
//下载文件
ad(saveFile,newDownloadFileListener(){
@Override
publicvoiddone(Strings,BmobExceptione){
if(e==null){
log(\"下载完成\");
//解压patch文件到react_native文件夹下
booleanresult=unzip(saveFile);
if(result){//解压成功后保存当前最新bundle的版本
().putString(BUNDLE_VERSION,sion()).apply();
if(diately()){//立即加载bundle
((ReactApplication)
getReactApplicationContext()).getReactNativeHost().clear();
getCurrentActivity().recreate();
}
}else{//解压失败应该删除掉有问题的文件,防止
RN加载错误的bundle文件
FilereactDir=new
File(getReactApplicationContext().getCacheDir(),\"react_native\");
deleteDir(reactDir);
}
}else{
tackTrace();
log(\"下载bundlepatch失败\");
}
}
@Override
publicvoidonProgress(Integerper,longsize){
}
});
}
}else{
tackTrace();
log(\"获取版本信息失败\");
}
}
});
}
}
代码中注释已经解释了其中的重要部分,需要注意的是,AppInfo增加了个boolean型
immediately字段,来控制bundle是否立即生效
publicclassAppInfoextendsBmobObject{
privateStringversion;//bundle版本
privateStringupdateContent;//更新内容
privateBooleanimmediately;//bundle是否立即生效
privateBmobFilebundle;//要下载的bundle文件
}
还有在getConstants()方法获取当前bundle版本时,使用_VERSION来
标记和apk一起打包的bundle基础版本号,也就是assets下的bundle版本号,该字段是通过
gradle的buildConfigField来定义的。打开app/,然后在下面所示的位置添加
buildConfigField定义,具体如下:
//省略了其它代码
android{
defaultConfig{
buildConfigField\"String\",\"BUNDLE_VERSION\",\'\"1.0.0\"\'
}
}
接着,不要忘记将自定义的UpdateCheckModule注册到Packages里。如果,你对自定义module
还不是很了解,请看这里
最后,就是在JS端使用UpdateCheckModule来发起版本检测更新了。
我们先在XiFan/js/db创建一个配置文件
constConfig={
bundleVersion:\'1.0.0\'
};
exportdefaultConfig;
代码很简单,Config里面只是定义了个bundleVersion字段,表示当前bundle版本号。
每次要发布新版bundle时,更新下这个文件的bundleVersion即可。
然后,我们在的componentDidMount()函数中发起版本检测更新
//
//省略了其他代码
import{
NativeModules
}from\'react-native\';
importConfigfrom\'./db/Config\';
varUpdateCheck=Check;
exportdefaultclassMainSceneextendsComponent{
componentDidMount(){
(\'当前版本号:\'+tBundleVersion);
(Version)
}
}
这样就完成了,基本的bundle更新流程了。
总结
本篇文章主要分析了RNandroid端bundle的加载过程,并且在分析理解下,实现了完整
bundle包的基本热更新,但是这只是热更新的一部分,还有很多方面可以优化,比如:多模
块的多bundle热更新、bundle拆分差量更新、热更新的异常回退处理、多版本bundle的动
态切换、bundle的更新和apk的更新相结合等等,这也是之后继续研究学习的方向。
更多推荐
recreate是什么意思reate在线翻译读音例
发布评论