Rn热更新以及增量更新操作
原文链接:http://blog.csdn.net/qq_22329521/article/details/65631947
不是增量更新,Rn的热更新,流程是下载服务器端上的一个解压包到本地 解压到应用的文件目录
这是一个打包后的apk文件,在Rn中我们的js代码都是打包后存放在assets目录中,其中index.android.bundle,可以理解我们js写后打包的代码文件
其中Rn加载bundle 的文件的代码片段在ReactNativeHost,在MainApplication中就为我们初始化好了
protected ReactInstanceManager createReactInstanceManager() {
ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
.setApplication(mApplication)
.setJSMainModuleName(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setRedBoxHandler(getRedBoxHandler())
.setUIImplementationProvider(getUIImplementationProvider())
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE);
for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}
//这是可以重写的方法,为我们提供重写获取bundleFile的方法
String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
//加载assets目录下的文件
builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
return builder.build();
}
public Builder setBundleAssetName(String bundleAssetName) {
mJSBundleAssetUrl = (bundleAssetName == null ? null : "assets://" + bundleAssetName);
mJSBundleLoader = null;
return this;
}
开工
首先为我们旧的应用打包
http://reactnative.cn/docs/0.42/signed-apk-android.html#content(react-native 中文网打包教程)
注意点
- keystore放在android/app的目录下
安装apk
之后修改代码 生成我们新的jsbundle 和图片资源文件(更新必须是要附带图片的即使旧版本的资源已经有了,也要重新下载)
react-native bundle --platform android --dev false --reset-cache --entry-file index.android.js --bundle-output E:\test\index.android.bundle --assets-dest E:\test
生成后的 文件 对其进行生成压缩包
注意点
- 因为使用的zipinputStream 这个api 如果是生成rar解压包后改成zip 可能获取不到getNextEntry() 所以最好是直接生成zip格式的解压包
代码部分
private String bundleParentPath = null;
private String bundlePath = null;
private String bundleName = "index.android.bundle";
private File bundleFile = null;
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
//无视这个
new GankViewManager()
);
}
@Override
protected String getJSMainModuleName() {
return super.getJSMainModuleName();
}
@Nullable
@Override
protected String getBundleAssetName() {
String bundleName = "index.android.bundle";
if (bundleFile != null && bundleFile.exists()) {
Log.d(TAG, "assets bundle exit");
return null;
}
// Logger.d("assets bundle does not exit");
return bundleName;
}
@Nullable
@Override
protected String getJSBundleFile() {
if (bundleFile != null && bundleFile.exists()) {
Log.d(TAG, "js bundle file " + bundleFile.getPath());
return bundleFile.getPath();
}
return null;
}
};
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
//adb push到sd卡中
File file = new File(Environment.getExternalStorageDirectory().getAbsoluteFile() + "/test.zip");
if (file.exists()) {
try {
ZipUtils.unzip(Environment.getExternalStorageDirectory().getAbsoluteFile() + "/test.zip", Environment.getExternalStorageDirectory().getAbsoluteFile() + "/bundle");
} catch (Exception e) {
e.printStackTrace();
}
}
bundleParentPath = Environment.getExternalStorageDirectory().getAbsoluteFile() + "/bundle";
bundlePath = bundleParentPath + File.separator + bundleName;
bundleFile = new File(bundlePath);
}
public class ZipUtils {
private final static int BUFFER_SIZE = 1 << 12;
public static void unzip(String zipFilePath, String destDirectory) throws Exception {
File destDir = new File(destDirectory);
if (destDir.exists()) {
destDir.delete();
}
destDir.mkdir();
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zipFilePath));
ZipEntry zipEntry = zipInputStream.getNextEntry();
while (zipEntry != null) {
String filePath = destDirectory + File.separator + zipEntry.getName();
if (!zipEntry.isDirectory()) {
extractFiles(zipInputStream, filePath);
} else {
File dir = new File(filePath);
dir.mkdir();
}
zipInputStream.closeEntry();
zipEntry = zipInputStream.getNextEntry();
}
zipInputStream.close();
}
private static void extractFiles(ZipInputStream inputStream, String path) throws IOException {
BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(path));
byte[] bytes = new byte[BUFFER_SIZE];
int read = 0;
while ((read = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, read);
}
outputStream.close();
}
}
上面的方式是在Application中替换掉加载的JSBundle ,图片资源和代码最好在一个目录下
如果文件被情况,默认加载assets下的原始的bundle
注意点
- 原始的Android 代码打包成dex是没法做热更新的
增量更新(暂未实现)
- index.android.bundle文件增量更新:使用Google的google-diff-match-patch对比老版本的index.android.bundle文件和新版本的index.android.bundle文件生成一个补丁包,客户端下载后与assets中的文件合并,由于google-diff-match-patch 适合字符串文本的对比,在这里 使用的jbdiff这个来进行更新 https://github.com/jdesbonnet/jbdiff/tree/master/src/ie/wombat/jbdiff,需要注意的是 在Android中assets中的文件操作,如果单纯是文件的修改可以实现, 在assets中通过InputStream的方式还未实现
- 资源的增量更新,需要修改内部的image加载的方式
资源的增量更新 需要看到图片的加载方法
//这样加载一张图片 内部的代码
<Image source={require('./imgs/test.png')} />
在//image.android.js 中
render: function() {
const source = resolveAssetSource(this.props.source);
const loadingIndicatorSource = resolveAssetSource(this.props.loadingIndicatorSource);
....
}
//继续查看 resolveAssetSource
function resolveAssetSource(source: any): ?ResolvedAssetSource {
if (typeof source === 'object') {
return source;
}
var asset = AssetRegistry.getAssetByID(source);
if (!asset) {
return null;
}
//主要是AssetSourceResolver 这个对象传递了,这里看出非网络图片的时候,加载图片的方式和bundle的路径有关
const resolver = new AssetSourceResolver(getDevServerURL(), getBundleSourcePath(), asset);
//这里应该是图片变换才会走的
if (_customSourceTransformer) {
return _customSourceTransformer(resolver);
}
//最后来到defaultAsset这个方法
return resolver.defaultAsset();
}
//是否是网络图片
function getDevServerURL(): ?string {
if (_serverURL === undefined) {
var scriptURL = SourceCode.scriptURL;
var match = scriptURL && scriptURL.match(/^https?:\/\/.*?\//);
if (match) {
// Bundle was loaded from network
_serverURL = match[0];
} else {
// Bundle was loaded from file
_serverURL = null;
}
}
return _serverURL;
}
//加载bunle的路径
function getBundleSourcePath(): ?string {
if (_bundleSourcePath === undefined) {
const scriptURL = SourceCode.scriptURL;
if (!scriptURL) {
// scriptURL is falsy, we have nothing to go on here
_bundleSourcePath = null;
return _bundleSourcePath;
}
if (scriptURL.startsWith('assets://')) {
// running from within assets, no offline path to use
_bundleSourcePath = null;
return _bundleSourcePath;
}
if (scriptURL.startsWith('file://')) {
// cut off the protocol
_bundleSourcePath = scriptURL.substring(7, scriptURL.lastIndexOf('/') + 1);
} else {
_bundleSourcePath = scriptURL.substring(0, scriptURL.lastIndexOf('/') + 1);
}
}
return _bundleSourcePath;
}
defaultAsset(): ResolvedAssetSource {
//这里开始加载网络图片
if (this.isLoadedFromServer()) {
return this.assetServerURL();
}
if (Platform.OS === 'android') {
//加载本地图片,如果是离线文件 加载drawableFolderInBundle这个方法,而这个方法是bundle和资源文件在一个目录下
return this.isLoadedFromFileSystem() ?
this.drawableFolderInBundle() :
this.resourceIdentifierWithoutScale();
} else {
return this.scaledAssetPathInBundle();
}
}
drawableFolderInBundle(): ResolvedAssetSource {
const path = this.bundlePath || '';
return this.fromSource(
'file://' + path + getAssetPathInDrawableFolder(this.asset)
);
}
如果要实现资源的热更新,思路是修改代码加载图片的路径问题
参考文章:http://blog.csdn.net/shandian000/article/details/54582603
http://blog.csdn.net/u011050541/article/details/52703209
http://www.cnblogs.com/liubei/p/RNUpdate.html