android 版的 assets 图集资源,react-native 热更新(android)

本文详细介绍了如何在Android版的react-native应用中实现热更新,包括修改`getJSBundleFile()`方法加载本地更新的index.android.bundle,下载并解压更新包,以及实现增量更新的方法,如使用google-diff-match-patch生成和应用补丁文件,以减少更新体积和时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

react-native 热更新(android)

react-native代码分为android部分,js部分,图片资源文件

其中android部分代码是不支持热更新的,支持更新的只有js代码和图片资源文件

react项目生成app时,所有js文件都被压缩到index.android.bundle文件中,该文件和图片资源都位于assets目录下,app启动时,MainActivity首先加载index.android.bundle,转换为对应的原生试图显示到rootView

所以,react热更新就是在app启动时从服务器下载新的index.android.bundle和图片资源,然后加载新的index.android.bundle文件

更新index.android.bundle

react-native提供了getJSBundleFile()来修改index.android.bundle文件的加载路径,复写该方法,返回本地更新后的index.android.bundle文件路径

0.29及以后版本:在你的MainApplication中增加如下代码:

import cn.reactnative.modules.update.UpdateContext;

public class MainApplication extends Application implements ReactApplication {

public static final String JS_BUNDLE_LOCAL_PATH = Environment.getExternalStorageDirectory().toString() + File.separator + "patches/index.android.bundle";

private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {

@Override

protected String getJSBundleFile() {

File file = new File(JS_BUNDLE_LOCAL_PATH);

if(file != null && file.exists()){

return JS_BUNDLE_LOCAL_PATH;

}else{

return super.getJSBundleFile();

}

}

// ... 其它代码

}

}

0.28及以前版本:在你的MainActivity中增加如下代码:

// ... 其它代码

import cn.reactnative.modules.update.UpdateContext;

public class MainActivity extends ReactActivity {

public static final String JS_BUNDLE_LOCAL_PATH = Environment.getExternalStorageDirectory().toString() + File.separator + "patches/index.android.bundle";

@Override

protected String getJSBundleFile() {

File file = new File(JS_BUNDLE_LOCAL_PATH);

if(file != null && file.exists()){

return JS_BUNDLE_LOCAL_PATH;

}else{

return super.getJSBundleFile();

}

}

// ... 其它代码

}

index.android.bundle文件路径变更后,图片默认从该文件同目录下加载,所以图片资源要和index.android.bundle文件一起打包下载到手机SD存储目录

生成index.android.bundle

项目目录下新建bundle目录

执行react-native bundle –entry-file index.android.js –bundle-output ./bundle/index.android.bundle –platform android –assets-dest ./bundle –dev false命令生成index.android.bundle文件和图片资源到bundle目录下

将index.android.bundle文件和图片资源压缩并上传至服务器供app启动时更新使用

19c1123901acf9f09de2dd09dc99a486.png

下载压缩包到手机

复写MainActivity中onCreate方法,下载包含index.android.bundle文件和图片资源的压缩包到手机本地存储并解压,存储目录和getJSBundleFile()方法的返回值一致,这样,下次启动app时,就会加载更新后的index.android.bundle文件和图片资源

增量更新

由于生成的index.android.bundle文件最少也有几百KB,加上所有的图片资源,导致更新的压缩包体积较大,更新时间较长,且会浪费用户较多流量,所以必须采用增量更新的方法,即只下载js变更部分和新增或修改的图片资源

index.android.bundle文件增量更新

初始项目发布时,生成并保留一份index.android.bundle文件文件

有版本更新时,生成新的index.android.bundle文件,使用google-diff-match-patch对比两个文件,并生成差异补丁文件

手机app下载补丁文件,再使用google-diff-match-patch和assets目录下的初始版本合并,生成新的index.android.bundle文件

图片资源增量更新

1.修改图片加载的js源码:

RemoteUpdateDemo\node_modules\react-native\Libraries\Image\AssetSourceResolver.js

修改isLoadedFromFileSystem方法(是否从index.android.bundle文件目录下加载图片)

isLoadedFromFileSystem(): boolean {

var imgFolder = getAssetPathInDrawableFolder(this.asset);

var imgName = imgFolder.substr(imgFolder.indexOf("/")+1);

var isPatchImg = PatchImages.indexOf("|"+imgName+"|") > -1;

return !!this.bundlePath && isPatchImg;

}

其中PatchImages是自定义字符串,其中包含所有更新和修改的图片名称

//补丁更新图片名称,中间和两边用|分割

var PatchImages = "|test1.png|test2.png|";

注意:生成bundle目录时,图片资源都会放在统一目录下(drawable-mdpi),如果引用图片包含其它路径,例如require(“./img/test1.png”),图片在img目录下,则图片加载时会自动将img目录转换为图片名称,加载”img_test1.png”图片,此时更新图片名称需要修改为”img_test1.png”

下面贴上MainActivity代码示例:

public class MainActivity extends ReactActivity {

private static final String TAG = "MainReactActivity";

public static final String JS_Patch_REMOTE_URL = "http://1.filesys.applinzi.com/patches.zip";

public static final String JS_BUNDLE_LOCAL_FILE = "index.android.bundle";

public static final String JS_BUNDLE_LOCAL_PATH = Environment.getExternalStorageDirectory().toString() + File.separator + "patches/" + JS_BUNDLE_LOCAL_FILE;

public static final String JS_PATCH_LOCAL_FILE = Environment.getExternalStorageDirectory().toString() + File.separator + "patches/patches.pat";

public static final String JS_PATCH_LOCAL_FOLDER = Environment.getExternalStorageDirectory().toString() + File.separator + "patches";

public static final String JS_PATCH_LOCAL_PATH = Environment.getExternalStorageDirectory().toString() + File.separator + "patches.zip";

private CompleteReceiver mDownloadCompleteReceiver;

private long mDownloadId;

/**

* Returns the name of the main component registered from JavaScript.

* This is used to schedule rendering of the component.

*/

@Override

protected String getMainComponentName() {

return "RemoteUpdateDemo";

}

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

initDownloadManager();

updateBundle();

}

private class CompleteReceiver extends BroadcastReceiver {

@Override

public void onReceive(Context context, Intent intent) {

long completeDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);

if(completeDownloadId == mDownloadId){

onJSBundleLoadedFromServer();

}

}

};

private void initDownloadManager() {

mDownloadCompleteReceiver = new CompleteReceiver();

registerReceiver(mDownloadCompleteReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

}

private void updateBundle() {

// Should add version check here, if bundle file

// is the newest , we do not need to update

File file = new File(JS_BUNDLE_LOCAL_PATH);

if(file != null && file.exists()){

Log.i(TAG, "newest bundle exists !");

return;

}

//Toast.makeText(BaseReactActivity.this, "Start downloading update", Toast.LENGTH_SHORT).show();

DownloadManager.Request request = new DownloadManager.Request(Uri.parse(JS_Patch_REMOTE_URL));

request.setDestinationUri(Uri.parse("file://" + JS_PATCH_LOCAL_PATH));

DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);

mDownloadId = dm.enqueue(request);

Log.i(TAG, "start download remote bundle");

}

private void onJSBundleLoadedFromServer() {

File file = new File(JS_PATCH_LOCAL_PATH);

if(file == null || !file.exists()){

Log.i(TAG, "download error, check URL or network state");

return;

}

try {

ZipInputStream inZip = new ZipInputStream(new FileInputStream(JS_PATCH_LOCAL_PATH));

ZipEntry zipEntry;

String szName = "";

try {

while((zipEntry = inZip.getNextEntry()) != null){

szName = zipEntry.getName();

if(zipEntry.isDirectory()){

szName = szName.substring(0,szName.length()-1);

File folder = new File(JS_PATCH_LOCAL_FOLDER+File.separator+szName);

folder.mkdirs();

}else{

File file1 = new File(JS_PATCH_LOCAL_FOLDER+File.separator+szName);

file1.createNewFile();

FileOutputStream fos = new FileOutputStream(file1);

int len;

byte[] buffer = new byte[1024];

while((len = inZip.read(buffer)) != -1){

fos.write(buffer, 0 , len);

fos.flush();

}

fos.close();

}

}

} catch (IOException e) {

e.printStackTrace();

}

inZip.close();

} catch (FileNotFoundException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

}

String bundle1 = getJsBundleFromAssets();

String pathesStr = getStringFromText(JS_PATCH_LOCAL_FILE);

diff_match_patch dmp = new diff_match_patch();

LinkedListpathes = (LinkedList) dmp.patch_fromText(pathesStr);

Object[] bundle2 = dmp.patch_apply(pathes, bundle1);

try{

Writer w = new FileWriter(JS_BUNDLE_LOCAL_PATH);

String newJsBundle = (String) bundle2[0];

w.write(newJsBundle);

w.close();

File patchFile = new File(JS_PATCH_LOCAL_PATH);

patchFile.delete();

}catch (Exception e){

e.getMessage();

}

}

private String getJsBundleFromAssets(){

String result = "";

try {

InputStream is = getAssets().open(JS_BUNDLE_LOCAL_FILE);

int size = is.available();

byte[] buffer = new byte[size];

is.read(buffer);

is.close();

result = new String(buffer,"UTF-8");

Log.i(TAG, "get JS_BUNDLE_LOCAL_FILE success!!!");

} catch (IOException e) {

e.printStackTrace();

}

return result;

}

private String getStringFromText(String path){

String result = "";

try {

Reader reader = new FileReader(path);

int ch = reader.read();

StringBuffer sb = new StringBuffer();

while(ch!=-1){

sb.append((char) ch);

ch = reader.read();

}

reader.close();

result = sb.toString();

Log.i(TAG, "get JS_BUNDLE_LOCAL_FILE success!!!"+path);

} catch (IOException e) {

e.printStackTrace();

}

return result;

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值