第18课:热更新

知识点:

  • 原理
  • React-Native-Pushy
  • 自定义 Android 下载
  • 自定义 iOS 下载

原理

RN 的热更新非常简单,在 App 打开的时候检测 JS 文件是否需要升级,如果需要升级就下载最新的文件,然后使用新的 JS 启动 App 就可以达到更新最新的 UI 的目的。

enter image description here

第三方热更新

ReactNative 中文网推出了一个热更新的包,这个更新方式比较简单,所有新更新全部提交到中文网,个人只需要负责开发就好了。在用户启动 App 的时候更新逻辑也不复杂,体验也还可以,这里介绍一下怎么安装这个包。

集成 pushy 的代码全部放在新分支 add-pushy 中,要看代码的可以先切个分支了。

首先要注册一个账号,详见这里,在中文网注册一个账号并不复杂,注册完了之后顺便新建一个应用吧,Android 和 iOS 是分开的,需要单独创建。

enter image description here

在项目根目录安装需要的包,这里第一个是全局安装命令,只有在第一次需要执行。

$ npm install -g react-native-update-cli
$ npm install --save react-native-update
$ react-native link react-native-update

如果有使用旧版 RN 的需要看一下下面的对照关系,这里我直接安装就可以了,目前最新的版本是 5.0.0。

React Native 版本React-Native-update 版本
0.26及以下1.0.x
0.27 - 0.282.x
0.29 - 0.333.x
0.34 - 0.454.x
0.46及以上5.x

安装命令可以参考下面的这个命令:npm install --save react-native-update@5.x。

Android

在 Android 中添加新的方法,该方法可以将 bundle 的文件从新加的包中处理一次再返回。

enter image description here

iOS

在工程 Target 的 Build Phases→Link Binary with Libraries 中加入 libz.tbd、libbz2.1.0.tbd:

enter image description here

修改 bundle 的获取方式:

// ... 其他代码

#import "RCTHotUpdate.h"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if DEBUG
  // 原来的jsCodeLocation保留在这里
  jsCodeLocation = ..........
#else
  // 非DEBUG情况下启用热更新
  jsCodeLocation=[RCTHotUpdate bundleURL];
#endif
  // ... 其他代码
}

enter image description here

注意,在项目根目录下登录 pushy。

enter image description here

根目录下会新建一个 .update 文件,将该文件加入 Git 排除目录,不要将这个敏感文件共享到公网去。

如果之前没有在注册之后创建 App,这里还可以使用命令创建,命令创建 Android 或者 iOS 的 App,这里的应用名字就输入我们的项目名称就好了。

$ pushy createApp --platform ios
App Name: <输入应用名字>
$ pushy createApp --platform android
App Name: <输入应用名字>

enter image description here

如果你已经在网页端或者其他地方创建过应用,也可以直接选择应用,可以看到提示网上新建的 App 和刚才命令创建的 App。

enter image description here

选择之后就可以看到当前目录下新建了 update.json 文件,该文件配置了应用的对应关系。

{
    "ios": {
        "appId": 1,
        "appKey": "<一串随机字符串>"
    },
    "android": {
        "appId": 2,
        "appKey": "<一串随机字符串>"
    }
}

使用 JS 去获取新版本的可以参考官网的例子,这个比较简单就不再介绍了。

https://github.com/reactnativecn/react-native-pushy/blob/master/docs/guide2.md

使用 IDE 打包 Android 或者 iOS 之后也可以直接发布到 pushy 上。

pushy uploadIpa <your-package.ipa>
pushy uploadApk android/app/build/outputs/apk/app-release.apk

在根目录下使用命令上传新 bundlepushy bundle --platform <ios|android>

如果遇到下面的错误,请安装一下 metro-bundler:

Cannot find module 'metro-bundler/src/babelRegisterOnly'

上传之后会提示是否发布这个新 bundle,后面还会提示是否绑定包。这里由于是第一次提交,是没有可绑定的对象的,取消就好了。

enter image description here

enter image description here

也可以直接使用命令,让用户更新最新的包:pushy update --platform <ios|android>。

自定义 Android 下载

自定义请求地址这里写了一个默认值“http://www.guofangchao.com/down”,这是一个不存在的地址,每次都会返回 404,我们的逻辑是:

  • 从 App 拿到 bundle 的 MD5 值,放在请求的 header 里;
  • 服务器根据 MD5 判断是否需要下载文件;
  • 需要则直接下载文件,否则返回 httpcode 级别的 404。

安卓自定义的代码放在 android-push 分支下,有需要的请切换分支。

首先需要新建一个 load 页,该页面主要用来承载 bundle 的下载更新功能,进入首页之后会销毁该页面。

enter image description here

页面样式这里先不去了解,我们主要了解一下原生代码的逻辑。

enter image description here

onCreate 是进入页面的方法,这里执行初始化的操作,最后调用我们自定义的 DownBundle 方法去请求 bundle。

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_load);

        //设置组件对象
        progressBar = (ProgressBar) findViewById(R.id.progressBar);
        tv_update = (TextView) findViewById(R.id.tv_update);
        iv_arrow = (ImageView) findViewById(R.id.iv_arrow);
        //进入下载节奏
        DownBundle();
    }

我们来实现一下自定义的下载方法,这里简单的做一下下载。

//下载文件,如果出错就启动
private void DownBundle(){
    String token=getBundleMD5();
    Request req=new Request.Builder().addHeader("token",token).url("http://www.guofangchao.com/down").build();
    new OkHttpClient().newCall(req).enqueue(new Callback() {
        @Override
        public void onFailure(Call call, IOException e) {
            //下载失败直接启动app
            startApp();
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            tv_update.setVisibility(View.VISIBLE);
            progressBar.setVisibility(View.VISIBLE);
            iv_arrow.setVisibility(View.VISIBLE);
            progressBar.setProgress(0);

            //下载成功
            InputStream is = null;
            byte[] buf = new byte[2048];
            int len = 0;
            FileOutputStream fos = null;
            // 储存下载文件的目录
            File file = new File(getFilesDir().getAbsolutePath()+"/index.android.bundle");
           try{
               is = response.body().byteStream();
               long total = response.body().contentLength();
               fos = new FileOutputStream(file);
               long sum = 0;
               while ((len = is.read(buf)) != -1) {
                   fos.write(buf, 0, len);
                   sum += len;
                   int progress = (int) (sum * 1.0f / total * 100);
                   // 下载中更新进度条
                   progressBar.setProgress(progress);
               }
               fos.flush();
           }catch (Exception e){
               startApp();
           }finally {
               try {
                   if (is != null)
                       is.close();
               } catch (IOException e) {
               }
               try {
                   if (fos != null)
                       fos.close();
               } catch (IOException e) {
               }
               startApp();
           }
        }
    });
}

自定义 iOS 热更新

这里切换了新的分支 ios-push,在项目下添加一个 Utils 文件夹:

enter image description here

在文件夹里添加 MD5 计算方法,右键选择添加一个新文件。

enter image description here

在弹出的地方选择 CocaaTouchClass 选项。

enter image description here

单击“下一步”按钮,输入我们的文件名,也是后面用到的类名,继续直到完成。

enter image description here

在 Filehash.h 中我们添加三个接口,在后面的 m 后缀名的文件中我们再实现这三个方法。

#import <Foundation/Foundation.h>

@interface FileHash : NSObject

+ (NSString *)md5HashOfFileAtPath:(NSString *)filePath;
+ (NSString *)sha1HashOfFileAtPath:(NSString *)filePath;
+ (NSString *)sha512HashOfFileAtPath:(NSString *)filePath;

@end

在 AppDelegate.m 中引用刚才的方法 #import "FileHash.h"。

这里改造启动方法,在调试模式下不参与下载。

enter image description here

这里使用 iOS 7 之后添加的方法去下载文件,如果打算支持低版本的 iOS 的话可能需要换个别的方法了。

enter image description here

这里使用的方法没有添加进度条,有需要的可以了解一下使用代理监听下载进度。

如果对方法前面的 +- 符号不理解的,这里提醒一下:可以简单默认 + 符号代表的是静态方法,调用要加方法所在的类;- 符号代表内部方法,内部调用使用 self,外部调用要加实例化之后的对象名称。

总结

这里使用的是全量下载方式,对于 App 逐渐增大的项目来说是有一些弊端的,这里提供一些优化方案。

  • 使用 zip 压缩:服务器返回的是压缩文件,App 需要在下载完成之后解压缩才能继续,这里使用 zip 的压缩算法减少了下载包的大小。
  • 将 JS 文件和图片分开:之前的方法会比较方便,但是当图片比较多的时候图片所占体积太大了,这个时候就需要把图片和 JS 分开下载,这里使用 zip 解压可以非常方便的实现到图片和 JS 分开打包的实现方式。
  • 使用 diff 算法:可以使用现成的 diff 算法比较 JS 的更新部分,将更新的 JS 打包成一个补丁,这样基本上就是最极致的更新了,也是 App 增量更新的主要实现方式。

其他还有一个 code-push 的更新方式,不过这个服务器是在海外,在下载速度上很不理想,国内基本不考虑了。

评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符 “速评一下”
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页