以前寫過另外一篇文章:Ionic 熱更新實踐筆記,講了 cordova-hot-code-push-plugin 的使用詳細方法。
依賴cordova-hot-code-push-plugin 熱更新,在升級到ionic4之后都會遇到白屏的問題,經過調試代碼,發現是由于cordova-plugin-ionic-webview升級導致的
cordova-plugin-ionic-webview升級到2.x以上會有如下影響
- iOS 最低兼容之10+
- android最低兼容 4.4+
- GCDWebServer 不支持帶轉義的字符,比如 cordova-hot-code-push-plugin 的存儲位置是 Application Support, 在URL轉義后變成 Application%20Support , 此時GCDWebServer無法找到該路徑,所以會白屏
- 1.0時GCDWebServer的默認localserver 根目錄是APP的根目錄,而2.x之后改成了包中www目錄,熱更新后新的目錄在 Application Support對應的www中,與localserver的根目錄不符,所以此時也無法找到
針對以上基礎改動,我們可以采取如下措施
1. iOS 打包target 改為10+
根據蘋果官網的統計:https://developer.apple.com/support/app-store/
iOS 10 以上的版本占據了95%的市場份額,相對來說兼容10以下的性價比太低
2. android 兼容 至4.4+
根據安卓官方統計:https://developer.android.com/about/dashboards?hl=zh-cn
4.4以下的版本份額不足4%,所以低版本的也可以不給予考慮
3. 改動 cordova-hot-code-push-plugin的存儲位置
將 cordova-hot-code-push-plugin 存儲位置改為非 Application Support 的目錄,比如cacheDirectory。此處只需要改動iOS對應的代碼,如下:
在 HCPFilesStructure.m的 pluginRootFolder方法中做如下改動
NSURL *supportDir = [fileManager applicationSupportDirectory];
改為
NSURL *supportDir = [fileManager applicationCacheDirectory];
4. 在熱更新插件啟動完畢和熱更新文件下載完畢后重設localserver根目錄,并重啟localserver
安卓改動:
只需要改動 redirectToLocalStorageIndexPage方法,使用到了java的反射機制
private void redirectToLocalStorageIndexPage() {
final String indexPage = getStartingPage();
// remove query and fragment parameters from the index page path
// TODO: cleanup this fragment
String strippedIndexPage = indexPage;
if (strippedIndexPage.contains("#") || strippedIndexPage.contains("?")) {
int idx = strippedIndexPage.lastIndexOf("?");
if (idx >= 0) {
strippedIndexPage = strippedIndexPage.substring(0, idx);
} else {
idx = strippedIndexPage.lastIndexOf("#");
strippedIndexPage = strippedIndexPage.substring(0, idx);
}
}
// make sure, that index page exists
String external = Paths.get(fileStructure.getWwwFolder(), strippedIndexPage);
if (!new File(external).exists()) {
Log.d("CHCP", "External starting page not found. Aborting page change.");
return;
}
try {
Log.d("CHCP", "begin restart app");
String basePath = fileStructure.getWwwFolder();
// 嘗試重置本地服務器根目錄為當前熱更新后的外置存儲路徑
Class[] cArg = new Class[1];
cArg[0] = String.class;
// 此處重置loacalserver的根目錄
webView.getEngine().getClass().getDeclaredMethod("setServerBasePath", cArg).invoke(webView.getEngine(),
basePath);
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
Log.d("CHCP", "Loading external page: " + external);
}
iOS改動:
iOS的改動有兩處,一處是熱更新拆件加載完畢后和熱更新文件下載完成后都需要重定向localserver的根目錄
為了與其他插件耦合,采用了objective-c的
添加如下方法:
/**
* 切換本地服務根目錄到外存儲目錄
*/
-(void) switchServerBaseToExternalPath{
NSString * basePath = [_filesStructure.wwwFolder.absoluteString stringByReplacingOccurrencesOfString:@"file://" withString:@""];
// 先要確保webVieEngine能響應以下兩個方法
if([self.webViewEngine respondsToSelector:@selector(setServerPath:)] && [self.webViewEngine respondsToSelector:@selector(basePath)]){
// 先判斷之前的本地服務根目錄是否與將要切換的路徑相同,如果不相同則切換,否則不切換
NSString * preBasePath = [self.webViewEngine performSelector:@selector(basePath)];
if( ![preBasePath isEqualToString:basePath] && [[NSFileManager defaultManager] fileExistsAtPath:basePath]){
dispatch_async(dispatch_get_main_queue(), ^{
// 此處需要在主線程中調用,否則會出現意想不到的錯誤
[self.webViewEngine performSelector:@selector(setServerPath:) withObject:basePath];
});
}
NSLog(@"reset the base server success, start reload app");
}else{
// 如果不能響應,則不需要再調用切換了,保持APP在未更新狀態
NSLog(@"cannot reset the base server, keep current page");
}
}
并在改動resetIndexPageToExternalStorage的代碼如下:
/**
* Redirect user to the index page that is located on the external storage.
*/
- (void)resetIndexPageToExternalStorage {
NSString *indexPageStripped = [self indexPageFromConfigXml];
NSRange r = [indexPageStripped rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"?#"] options:0];
if (r.location != NSNotFound) {
indexPageStripped = [indexPageStripped substringWithRange:NSMakeRange(0, r.location)];
}
NSURL *indexPageExternalURL = [self appendWwwFolderPathToPath:indexPageStripped];
if (![[NSFileManager defaultManager] fileExistsAtPath:indexPageExternalURL.path]) {
return;
}
// rewrite starting page www folder path: should load from external storage
if ([self.viewController isKindOfClass:[CDVViewController class]]) {
// 在此處重置localserver
[self switchServerBaseToExternalPath];
} else {
NSLog(@"HotCodePushError: Can't make starting page to be from external storage. Main controller should be of type CDVViewController.");
}
}
fork了一份 **cordova-hot-code-push-plugin **的代碼,并做了相應的改動,如果想用可以直接fork一下然后自己打npm的包,地址:https://github.com/amosbaby/cordova-hot-code-push
當然, 我也帶了一個npm的包,名字叫做 teh-hot-code-push-plugin
安裝方法:
cordova plugin add teh-hot-code-push-plugin
其他與原來 插件沒啥區別。親測可用,如有問題,隨時可以提issues或者評論。