Rn熱更新以及增量更新操作
原文鏈接:http://blog.csdn.net/qq_22329521/article/details/65631947
不是增量更新脊串,Rn的熱更新状知,流程是下載服務(wù)器端上的一個解壓包到本地 解壓到應(yīng)用的文件目錄
這是一個打包后的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;
}
開工
首先為我們舊的應(yīng)用打包
http://reactnative.cn/docs/0.42/signed-apk-android.html#content(react-native 中文網(wǎng)打包教程)
注意點
- keystore放在android/app的目錄下
安裝apk
之后修改代碼 生成我們新的jsbundle 和圖片資源文件(更新必須是要附帶圖片的即使舊版本的資源已經(jīng)有了沪哺,也要重新下載)
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是沒法做熱更新的
增量更新(暫未實現(xiàn))
- 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中的文件操作页慷,如果單純是文件的修改可以實現(xiàn), 在assets中通過InputStream的方式還未實現(xiàn)
- 資源的增量更新胁附,需要修改內(nèi)部的image加載的方式
資源的增量更新 需要看到圖片的加載方法
//這樣加載一張圖片 內(nèi)部的代碼
<Image source={require('./imgs/test.png')} />
在//image.android.js 中
render: function() {
const source = resolveAssetSource(this.props.source);
const loadingIndicatorSource = resolveAssetSource(this.props.loadingIndicatorSource);
....
}
//繼續(xù)查看 resolveAssetSource
function resolveAssetSource(source: any): ?ResolvedAssetSource {
if (typeof source === 'object') {
return source;
}
var asset = AssetRegistry.getAssetByID(source);
if (!asset) {
return null;
}
//主要是AssetSourceResolver 這個對象傳遞了酒繁,這里看出非網(wǎng)絡(luò)圖片的時候,加載圖片的方式和bundle的路徑有關(guān)
const resolver = new AssetSourceResolver(getDevServerURL(), getBundleSourcePath(), asset);
//這里應(yīng)該是圖片變換才會走的
if (_customSourceTransformer) {
return _customSourceTransformer(resolver);
}
//最后來到defaultAsset這個方法
return resolver.defaultAsset();
}
//是否是網(wǎng)絡(luò)圖片
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 {
//這里開始加載網(wǎng)絡(luò)圖片
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)
);
}
如果要實現(xiàn)資源的熱更新,思路是修改代碼加載圖片的路徑問題
參考文章:http://blog.csdn.net/shandian000/article/details/54582603
http://blog.csdn.net/u011050541/article/details/52703209
http://www.cnblogs.com/liubei/p/RNUpdate.html