前言
用react-native(一下简称RN)开发的app的更新方案有很多,其中比较火的是热更新方案,有官方推荐的pushy和微软的code-push
文档很详细,接入也比较简单
这里主要介绍一种最传统的更新方案,也是很多原生开发在使用的方案——全量更新
全量更新
顾名思义,即每次更新通过http去下载新版本包去然后去做一个覆盖安装,这种做法在更新迭代中会避免很多不必要的麻烦,而且在这个5G都要到来的时代,网络资源大小的限制也显得不那么重要。
步骤
获取当前APP版本号
react-native是不提供获取版本号模块的,所以我们需要自己去写一个原生模块去获取当前app的版本号。
传送门:原生模块
新建一个模块文件夹
新建一个文件夹在android/app/src/main/java/com/your-app-name/下,这里我取名叫appinfo
新建一个类
在appinfo新建一个类为AppInfoModule.java
package com.your-app-name.appinfo;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import com.facebook.react.uimanager.IllegalViewOperationException;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import java.util.Map;
import java.util.HashMap;
public class AppInfoModule extends ReactContextBaseJavaModule {
public AppInfoModule(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "AppInfo"; //后面需要调用的模块名
}
@ReactMethod
public void getVersion(Callback successCallback) { //后面需要调用的方法名,不需要传参
try {
PackageInfo info = getPackageInfo();
if(info != null){
successCallback.invoke(info.versionName); //这里我是一回调函数的形式返回,也可以用promise的方式
} else {
successCallback.invoke("");
}
} catch (IllegalViewOperationException e){
successCallback.invoke("");
}
}
private PackageInfo getPackageInfo(){
PackageManager manager = getReactApplicationContext().getPackageManager();
PackageInfo info = null;
try{
info = manager.getPackageInfo(getReactApplicationContext().getPackageName(),0);
return info;
} catch (Exception e){
e.printStackTrace();
} finally {
return info;
}
}
}
注册模块
用于在JavaScript 中访问,新建一个AppInfoPackage.java
package com.your-app-name.appinfo;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AppInfoPackage implements ReactPackage {
@Override
public List createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List createNativeModules(
ReactApplicationContext reactContext) {
List modules = new ArrayList<>();
modules.add(new AppInfoModule(reactContext)); //你的模块
return modules;
}
}
暴露方法
暴露该模块,修改android/app/src/main/java/com/your-app-name/MainApplication.java文件的getPackages方法
import com.your-app-name.appinfo.AppInfoPackage;
......
@Override
protected List getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List packages = new PackageList(this).getPackages();
packages.add(new AppInfoPackage()); //导入注册好的模块
return packages;
}
......
JavaScript调用
NativeModules.AppInfo.getVersion((version) => {
console.log(version);
//output 1.1.0
});
版本校验
写一个版本比较接口,把获取到的版本号提交上去,以此来判断是否需要更新
提供一个php的版本比较方法:
private function compareVersion($v1,$v2)
{
if(empty($v2)){
return false;
}
$l1 = explode('.',$v1);
$l2 = explode('.',$v2);
$len = count($l1) > count($l2) ? count($l1): count($l2);
for ($i = 0; $i < $len; $i++) {
$n1 = $l1[$i] ? $l1[$i] : 0;
$n2 = $l2[$i] ? $l2[$i] : 0;
if ($n1 > $n2) {
return true;
} else if ($n1 < $n2) {
return false;
}
}
return false;
}
下载新版本
版本校验需要更新,得到更新的地址后去下载保存到本机
获取读取存储的权限
......
(async () => {
try {
const permissions = [
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
];
const granteds = await PermissionsAndroid.requestMultiple(permissions);
if (granteds["android.permission.ACCESS_FINE_LOCATION"] === "granted" && granteds["android.permission.READ_EXTERNAL_STORAGE"] === "granted" && granteds["android.permission.WRITE_EXTERNAL_STORAGE"] === "granted") {
} else {
ToastAndroid.show('授权被拒绝', ToastAndroid.LONG)
}
} catch (err) {
ToastAndroid.show(err.toString(), ToastAndroid.LONG);
}
})();
react-native-fs下载
接入文档react-native-fs
import RNFS from 'react-native-fs';
const downloadDest = `${RNFS.DownloadDirectoryPath}/app.apk`;
const options = {
fromUrl: UPDATEURL, //更新包的地址
toFile: downloadDest, //下载后保存的地址
background: true,
begin: (res) => {
}
};
const ret = RNFS.downloadFile(options);
ret.promise.then(res => {
// 下载成功
}).catch(err => {
ToastAndroid.show(err, ToastAndroid.LONG);
});
安装
下载成功后,考虑到用户体验,还得有一个自动安装吧~
当然去打开指定路径的文件也是需要我们自己写原生模块的。
按照上面的流程,我新建了update模块和UpdateModule.java类
这里流程很上面的获取版本号一致,我直接贴UpdateModule.java的代码
package com.your-app-name.update;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
//使用FileProvider需要一定的配置
import androidx.core.content.FileProvider;
import java.io.File;
import java.util.Map;
import java.util.HashMap;
public class UpdateModule extends ReactContextBaseJavaModule {
private ReactApplicationContext context;
public UpdateModule(ReactApplicationContext reactContext) {
super(reactContext);
context = reactContext;
}
@Override
public String getName() {
return "Update";
}
@ReactMethod
public void installApp(final String path, Callback successCallback) {
try {
if (Build.VERSION.SDK_INT >= 24) { //android 7.0以后,处于安全考虑必须使用FileProvider打开文件
Uri contentUri = FileProvider.getUriForFile(context, "com.your-app-name.fileprovider", new File(path));
Intent intent = new Intent(Intent.ACTION_VIEW)
.setDataAndType(contentUri, "application/vnd.android.package-archive");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
} else {
Intent intent = new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.parse("file://" + path), "application/vnd.android.package-archive").setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
successCallback.invoke("安装成功");
} catch (Exception e) {
successCallback.invoke("安装失败");
}
}
}
更新下逻辑代码,下载成功后自动安装
......
ret.promise.then(res => {
// 下载成功
NativeModules.Update.installApp(downloadDest, (res) => {
// 安装成功
});
}).catch(err => {
ToastAndroid.show(err, ToastAndroid.LONG);
});
......