模仿字的英文翻译英语怎么说-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

protectedListgetPackages(){

returnArrays.asList(

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();

ListmoduleSpecs=newArrayList<>();

MapreactModuleInfoMap=newHashMap<>();

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

protectedListgetPackages(){

returnArrays.asList(

newMainReactPackage()

);

}

@Nullable

@Override

protectedStringgetJSBundleFile(){

FilebundleFile=newFile(getCacheDir()+\"/react_native千山暮雪免费看到全集 \",\"\");

if(()){

olutePath();

}

undleFile();

}

@Nullable

@Override

protectedStringgetBundleAssetName(){

dleAssetName();

}

};

getJSBundleFile首先会尝试在sd卡目录下

data/data//cache/react_native/

看是否存在文件,如果有,那么就会使用该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);

BmobQueryquery=newBmobQuery<>();

it(1);

reGreaterThan(\"version\",\"1.0.0\");

jects(newFindListener(){

@Override

publicvoiddone(Listlist,BmobExceptione){

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

publicMapgetConstants(){

Mapconstants=hMap();

//跟随apk一起打包的bundle基础版本号

StringbundleVersion=_VERSION;

//bundle更新后的当前版本号

StringcacheBundleVersion=ing(BUNDLE_VERSION,\"\");

if(!y(cacheBundleVersion)){

bundleVersion=cacheBundleVersion;

}

(BUNDLE_VERSION,bundleVersion);

returnconstants;

}

@ReactMethod

publicvoidcheck(StringcurrVersion){

BmobQueryquery=newBmobQuery<>();

it(1);

reGreaterThan(\"version\",currVersion);

jects(newFindListener(){

@Override

publicvoiddone(Listlist,BmobExceptione){

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在线翻译读音例