前言
用react-native(一下簡(jiǎn)稱RN)開發(fā)的app的更新方案有很多,其中比較火的是熱更新方案,有官方推薦的pushy和微軟的code-push
文檔很詳細(xì),接入也比較簡(jiǎn)單
這里主要介紹一種最傳統(tǒng)的更新方案,也是很多原生開發(fā)在使用的方案——全量更新
全量更新
顧名思義,即每次更新通過http去下載新版本包去然后去做一個(gè)覆蓋安裝,這種做法在更新迭代中會(huì)避免很多不必要的麻煩,而且在這個(gè)5G都要到來的時(shí)代,網(wǎng)絡(luò)資源大小的限制也顯得不那么重要。
步驟
獲取當(dāng)前APP版本號(hào)
react-native是不提供獲取版本號(hào)模塊的,所以我們需要自己去寫一個(gè)原生模塊去獲取當(dāng)前app的版本號(hào)。
傳送門:原生模塊
新建一個(gè)模塊文件夾
新建一個(gè)文件夾在android/app/src/main/java/com/your-app-name/下,這里我取名叫appinfo
新建一個(gè)類
在appinfo新建一個(gè)類為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"; //后面需要調(diào)用的模塊名
}
@ReactMethod
public void getVersion(Callback successCallback) { //后面需要調(diào)用的方法名,不需要傳參
try {
PackageInfo info = getPackageInfo();
if(info != null){
successCallback.invoke(info.versionName); //這里我是一回調(diào)函數(shù)的形式返回,也可以用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;
}
}
}
注冊(cè)模塊
用于在JavaScript 中訪問,新建一個(gè)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<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> 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<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new AppInfoPackage()); //導(dǎo)入注冊(cè)好的模塊
return packages;
}
......
JavaScript調(diào)用
NativeModules.AppInfo.getVersion((version) => {
console.log(version);
//output 1.1.0
});
版本校驗(yàn)
寫一個(gè)版本比較接口,把獲取到的版本號(hào)提交上去,以此來判斷是否需要更新
提供一個(gè)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;
}
下載新版本
版本校驗(yàn)需要更新,得到更新的地址后去下載保存到本機(jī)
獲取讀取存儲(chǔ)的權(quán)限
......
(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('授權(quán)被拒絕', 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);
});
安裝
下載成功后,考慮到用戶體驗(yàn),還得有一個(gè)自動(dòng)安裝吧~
當(dāng)然去打開指定路徑的文件也是需要我們自己寫原生模塊的。
按照上面的流程,我新建了update模塊和UpdateModule.java類
這里流程很上面的獲取版本號(hào)一致,我直接貼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("安裝失敗");
}
}
}
更新下邏輯代碼,下載成功后自動(dòng)安裝
......
ret.promise.then(res => {
// 下載成功
NativeModules.Update.installApp(downloadDest, (res) => {
// 安裝成功
});
}).catch(err => {
ToastAndroid.show(err, ToastAndroid.LONG);
});
......