ReactNative熱更新&拆包


目錄

  • 1)全量熱更新-Android
  • 2)拆包增量更新-Android
  • 3)圖片增量更新-Android
  • 4)全量熱更新-iOS

流程圖

1)全量熱更新-Android

全量熱更新

-打更新包bundle(包括更新的圖片和代碼)

react-native bundle --entry-file index.android.js --bundle-output ./bundle/index.android.bundle --platform android --assets-dest ./bundle --dev false
  • 運行此命令會將代碼和圖片打入根目錄下的bundle文件夾碧信,將這些文件壓縮至zip包
    => patches.zip
    =>將zip包放入遠程文件服務(wù)器待下載


    打更新包bundle

-根據(jù)業(yè)務(wù)判斷是否需要更新

private void checkVersion() {
  if (true) {
     // 有最新版本
     Toast.makeText(this, "開始下載", Toast.LENGTH_SHORT).show();
     initDownloadManager();  //開啟廣播接收器
     downLoadBundle();   //開始下載任務(wù)
  }
}

-下載zip 至指定的sdcard地址

    //注冊廣播接收器
    private void initDownloadManager() {
        mDownloadReceiver = new DownloadReceiver();
        registerReceiver(mDownloadReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
    }

    //下載任務(wù)
    private void downLoadBundle() {
        // 1.檢查是否存在pat壓縮包,存在則刪除
        // /storage/emulated/0/Android/data/包名/cache/patches.zip
        zipfile = new File(FileConstant.get_JS_PATCH_LOCAL_PATH(this));
        if(zipfile != null && zipfile.exists()) {
            zipfile.delete();
        }
        // 2.下載
        DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
        //遠程下載地址http://192.168.1.127/patches.zip
        DownloadManager.Request request = new DownloadManager
                .Request(Uri.parse(FileConstant.JS_BUNDLE_REMOTE_URL));
        request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE);
        request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE| DownloadManager.Request.NETWORK_WIFI);
        //下載目標(biāo)地址 /storage/emulated/0/Android/data/包名/cache/patches.zip
        request.setDestinationUri(Uri.parse("file://"+ FileConstant.get_JS_PATCH_LOCAL_PATH(this)));
        mDownloadId = downloadManager.enqueue(request);
    }

-解析zip并寫入sdcard

    private class DownloadReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            //下載完成,收到廣播
            long completeDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
            if(completeDownloadId == mDownloadId){
                // 1.解壓并寫入sdcard對應(yīng)地址
                RefreshUpdateUtils.decompression(getApplicationContext());
                zipfile.delete();
            }
        }
    }
//~/RefreshUpdateUtils.java
    //解析壓縮包瘫里,并寫入手機存儲位置
    public static void decompression(Context context) {
        try {
            //從下載目標(biāo)地址 /storage/emulated/0/Android/data/包名/cache/patches.zip 獲取壓縮包
            ZipInputStream inZip = new ZipInputStream(new FileInputStream(FileConstant.get_JS_PATCH_LOCAL_PATH(context)));
            ZipEntry zipEntry;
            String szName;
            try {
                while((zipEntry = inZip.getNextEntry()) != null) {

                    szName = zipEntry.getName();
                    //如果是目錄則創(chuàng)建,并寫入/storage/emulated/0/Android/data/包名/cache/patches/目錄下
                    if(zipEntry.isDirectory()) {

                        szName = szName.substring(0,szName.length()-1);
                        File folder = new File(FileConstant.get_JS_PATCH_LOCAL_FOLDER(context) + File.separator + szName);
                        folder.mkdirs();

                    }
                    //如果是文件則創(chuàng)建,并寫入/storage/emulated/0/Android/data/包名/cache/patches/目錄下
                    else{
                        File folder = new File(FileConstant.get_JS_PATCH_LOCAL_FOLDER(context) + File.separator);
                        if (!folder.exists()){
                            folder.mkdir();
                        }
                        File file1 = new File(FileConstant.get_JS_PATCH_LOCAL_FOLDER(context) + File.separator + szName);

                        boolean s = file1.createNewFile();
                        FileOutputStream fos = new FileOutputStream(file1);
                        int len;
                        byte[] buffer = new byte[1024];

                        while((len = inZip.read(buffer)) != -1) {
                            fos.write(buffer, 0 , len);
                            fos.flush();
                        }

                        fos.close();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            inZip.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

-RN調(diào)用JSBundle的時候判斷,當(dāng)sdcard對應(yīng)位置的bundle不為空時加載sdcard中的bundle蓬衡,否則加載原包內(nèi)Assets位置的bundle


public class MainApplication extends Application implements ReactApplication {
    private ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
            //Debug模式唇礁,這個模式才能在JS里作調(diào)試
            return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
            //返回帶有官方已有的package的集合
            return Arrays.<ReactPackage>asList(
                    new MainReactPackage(),
                    new MyReactPackage()  //加入自定義的Package類
            );
        }

        @Nullable
        @Override
        protected String getJSBundleFile() {
            //判斷sdcard中是否存在bundle碎捺,存在則加載路鹰,不存在則加載Assets中的bundle
            //路徑 /storage/emulated/0/Android/data/包名/cache/patches/index.android.bundle
            File file = new File (FileConstant.get_JS_BUNDLE_LOCAL_PATH(getApplicationContext()));
            if(file != null && file.exists()) {
                return FileConstant.get_JS_BUNDLE_LOCAL_PATH(getApplicationContext());
            } else {
                return super.getJSBundleFile();
            }
        }
    };

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        SoLoader.init(this, /* native exopackage */ false);
    }
}

-注意點:

  • 1-退出的時候需要殺死進程,否則不會初始化Application就無法更換bundle的加載路徑了
@Override
  protected void onDestroy() {
    super.onDestroy();
    //殺死進程收厨,否則就算退出App晋柱,App處于空進程并未銷毀,再次打開也不會初始化Application
    //從而也不會執(zhí)行g(shù)etJSBundleFile去更換bundle的加載路徑 !!!
    android.os.Process.killProcess(android.os.Process.myPid());
    //解除廣播接收器
    unregisterReceiver(mDownloadReceiver);
}
  • 2-權(quán)限诵叁,由于我們采用App擴展存儲方式雁竞,若無需兼容6.0以下,則無需申請權(quán)限拧额。
<!--代碼中使用getExternalCacheDir(), API >=19 是不需要申請的碑诉,若需兼容6.0以下則需寫此權(quán)限 -->
<!--但寫此權(quán)限若不加maxSdkVersion="18",會導(dǎo)致6.0已上機型會在設(shè)置中看到此權(quán)限開關(guān)势腮,從而可能會關(guān)閉此權(quán)限-->>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        android:maxSdkVersion="18"/>
  • 3-不能在開發(fā)環(huán)境調(diào)試联贩,需要打包調(diào)測
//創(chuàng)建assets目錄 ./android/app/src/main/assets
//創(chuàng)建離線bundle和打包本地資源
react-native bundle --entry-file index.android.js --bundle-output ./android/app/src/main/assets/index.android.bundle --platform android --assets-dest ./android/app/src/main/res/ --dev false
//打簽名包即可
cd android && ./gradlew assembleRelease
//進入目錄安裝apk  ./android/app/build/outputs/apk/release
adb install app-release.apk 

-其他代碼

~/FileConstant.java

public class FileConstant {
    //遠程下載服務(wù)地址
    public static final String JS_BUNDLE_REMOTE_URL = "http://192.168.1.127/patches.zip";
    //本地bundle文件名
    public static final String JS_BUNDLE_LOCAL_FILE = "index.android.bundle";

    //sdcard中bundle的加載路徑
    public static String get_JS_BUNDLE_LOCAL_PATH(Context context){
        return context.getExternalCacheDir().getPath() + File.separator+ "patches/index.android.bundle";
    }
    //sdcard中下載后文件的存放文件夾路徑
    public static String get_JS_PATCH_LOCAL_FOLDER(Context context){
        return context.getExternalCacheDir().getPath() + File.separator+ "patches";
    }
    //sdcard中下載的zip包存放位置
    public static String get_JS_PATCH_LOCAL_PATH(Context context){
        return context.getExternalCacheDir().getPath() + File.separator+ "patches.zip";
    }
    //sdcard中下載后的增量包pat存放位置
    public static String get_JS_PATCH_LOCAL_FILE(Context context){
        return context.getExternalCacheDir().getPath() + File.separator+ "patches/patches.pat";
    }
}

2)拆包增量更新-Android

  • 雖然通過zip壓縮減小了一部分bundle體積,但是每次需要熱更新去打全量包在不更新圖片的情況下再小也有幾百kb捎拯,其中業(yè)務(wù)部分的代碼也只占一部分,著實浪費且不科學(xué)。
  • 所以在每次原生迭代版本發(fā)布時署照,保留其附屬的RN版本bundle祸泪,并在此原生版本周期內(nèi)需要熱更新時,生成新的bundle建芙,使用google-diff-match-patch與原版本bundle比對没隘,生成差異化補丁。
  • App判斷有熱更新時禁荸,下載此補丁右蒲,與Assets中的初始版本合并,生成新的index.android.bundle文件寫入sdcard中赶熟。

-打更新包bundle(同-1)

-生成差異化補丁文件

將初始版本old.bundle和熱更版本new.bundle進行比對瑰妄,生成patches.pat
=> 將pat和圖片壓縮成 patches.zip
=> 將zip包放入遠程文件服務(wù)器待下載


生成patches.pat

patches.pat
    public static void main(String[] args) {
        String o = getStringFromPat("/Users/tugaofeng/Desktop/old.bundle");
        String n = getStringFromPat("/Users/tugaofeng/Desktop/new.bundle");
        // 對比
        diff_match_patch dmp = new diff_match_patch();
        LinkedList<diff_match_patch.Diff> diffs = dmp.diff_main(o, n);
        // 生成差異補丁包
        LinkedList<diff_match_patch.Patch> patches = dmp.patch_make(diffs);
        // 解析補丁包
        String patchesStr = dmp.patch_toText(patches);

        try {
            // 將補丁文件寫入到某個位置
            Files.write(Paths.get("/Users/tugaofeng/Desktop/patches.pat"), patchesStr.getBytes());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

-根據(jù)業(yè)務(wù)判斷是否需要更新(同-1)

-下載zip 至指定的sdcard地址(同-1)

-解析zip并寫入sdcard

    private class DownloadReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            //下載完成,收到廣播
            long completeDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
            if(completeDownloadId == mDownloadId){
                // 1.解壓并寫入sdcard對應(yīng)地址
                RefreshUpdateUtils.decompression(getApplicationContext());
                zipfile.delete();

                // 2.將下載好的patches文件與assets目錄下的原index.android.bundle合并映砖,得到新的
                // bundle文件,并寫入sdcard中
                mergePatAndAsset();
            }
        }
    }

-將Assets內(nèi)的index.android.bundle和下載完成的差異化補丁pat合并间坐,并生成新的index.android.bundle寫入sdcard對應(yīng)位置

//拆包增量更新bundle
    private void mergePatAndAsset() {
        // 1.獲取本地Assets目錄下的bunlde
        String assetsBundle = RefreshUpdateUtils.getJsBundleFromAssets(getApplicationContext());
        // 2.獲取.pat文件字符串
        // /storage/emulated/0/Android/data/包名/cache/patches/patches.pat
        String patcheStr = RefreshUpdateUtils.getStringFromPat(FileConstant.get_JS_PATCH_LOCAL_FILE(this));
        if (patcheStr == null || "".equals(patcheStr)){
            return;
        }
        // 3.初始化 dmp
        diff_match_patch dmp = new diff_match_patch();
        // 4.轉(zhuǎn)換pat
        LinkedList<diff_match_patch.Patch> pathes = (LinkedList<diff_match_patch.Patch>) dmp.patch_fromText(patcheStr);
        // 5.與assets目錄下的bundle合并,生成新的bundle
        Object[] bundleArray = dmp.patch_apply(pathes,assetsBundle);
        // 6.保存新的bundle
        // 至/storage/emulated/0/Android/data/包名/cache/patches/index.android.bundle
        try {
            Writer writer = new FileWriter(FileConstant.get_JS_BUNDLE_LOCAL_PATH(this));
            String newBundle = (String) bundleArray[0];
            writer.write(newBundle);
            writer.close();
            // 7.刪除.pat文件
            // 路徑為/storage/emulated/0/Android/data/包名/cache/patches/patches.pat
            File patFile = new File(FileConstant.get_JS_PATCH_LOCAL_FILE(this));
            patFile.delete();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

-其他代碼

~/diff_match_patch.java

//~/RefreshUpdateUtils.java
    //將.pat or bundle文件轉(zhuǎn)換為String
    public static String getStringFromPat(String patPath) {
        FileReader reader = null;
        String result = "";
        try {
            reader = new FileReader(patPath);
            int ch = reader.read();
            StringBuilder sb = new StringBuilder();
            while (ch != -1) {
                sb.append((char)ch);
                ch  = reader.read();
            }
            reader.close();
            result = sb.toString();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    //從本地Assets獲取bundle
    public static String getJsBundleFromAssets(Context context) {
        String result = "";
        try {
            InputStream is = context.getAssets().open(FileConstant.JS_BUNDLE_LOCAL_FILE);
            int size = is.available();
            byte[] buffer = new byte[size];
            is.read(buffer);
            is.close();
            result = new String(buffer,"UTF-8");

        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

3)圖片增量更新-Android

圖片增量更新需要修改RN源碼邑退。

-修改RN源碼竹宋。
注意:RN庫版本升級時別忘了修改。地技。

渲染圖片的方法在:node_modules / react-native / Libraries / Image /AssetSourceResolver.js 下:

defaultAsset(): ResolvedAssetSource {  
  if (this.isLoadedFromServer()) {  
    return this.assetServerURL();  
  }  
  
  if (Platform.OS === 'android') {  
    return this.isLoadedFromFileSystem() ?  
//存在離線Bundle文件時蜈七,從Bundle文件所在目錄加載圖片
      this.drawableFolderInBundle() :  
//否則從Asset資源目錄下加載
      this.resourceIdentifierWithoutScale();  
  } else {  
    return this.scaledAssetPathInBundle();  
  }  
}  

對源碼做如下修改:

...
import type { PackagerAsset } from 'AssetRegistry';
// 1-新增全局變量
// !!!注意:每次基于某個原生版本的RN熱更版本新增的圖片都要在此處新增加(不是覆蓋哦)
// 比如原生版本1.0.0,RN熱更版本1.0.0-1時新增a.png
//var patchImgNames = '|a.png|';
// 比如原生版本1.0.0莫矗,RN熱更版本1.0.0-2時新增b.png
//var patchImgNames = '|a.png|b.png|';
// 比如原生版本2.0.0(2.0的原生版本asset里已經(jīng)會包含a和b.png)宪潮,暫無RN熱更版本時
//var patchImgNames = '';
var patchImgNames = '|src_res_images_offer_message_red.png|src_res_images_banner_default.png|'; 

...
// 2-修改此函數(shù)
  isLoadedFromFileSystem(): boolean {
    // return !!this.bundlePath;  //注釋此處,新增如下代碼
    var imgFolder = getAssetPathInDrawableFolder(this.asset);  
    var imgName = imgFolder.substr(imgFolder.indexOf("/") + 1);  
    var isPatchImg = patchImgNames.indexOf("|"+imgName+"|") > -1;  
    return !!this.bundlePath && isPatchImg; 
  }

-打增量代碼包和增量圖片

=> 打更新包
=> 生成差異化補丁文件pat
=> 將pat和本次熱更新增的圖片壓縮成patches.zip
=> 將zip包放入遠程文件服務(wù)器待下載


第一次熱更包 1.0.0-1

第二次熱更包1.0.0-2
  • 注意:圖片每次熱更的時候可以只將當(dāng)次熱更新增的圖片打入zip包中趣苏,而不需要像修改源碼全局變量patchImgNames一樣需要追溯當(dāng)次原生版本的所有熱更圖片的文件名 !!!

-效果展示

版本1.0.0熱更至1.0.0-1.gif
版本1.0.0-1的sdcard圖片目錄.png

版本1.0.0-1熱更至1.0.0-2.gif

版本1.0.0-2的sdcard圖片目錄.png

4)全量熱更新-iOS

demo

-修改podfile狡相,新增SSZipArchive【解壓】和AFNetworking【文件下載】

  pod 'SSZipArchive'
  pod 'AFNetworking', '~> 3.0'
cd /ios && pod install

-打更新包bundle(包括更新的圖片和代碼)

react-native bundle --entry-file index.ios.js --platform ios --dev false --bundle-output release_ios/main.jsbundle --assets-dest release_ios/
  • 運行此命令會將代碼和圖片打入根目錄下的release_ios文件夾,將這些文件壓縮至zip包
    => patches.zip
    =>將zip包放入遠程文件服務(wù)器待下載

-創(chuàng)建bundle存放路徑,使用plist文件去存儲版本號和下載路徑

//創(chuàng)建bundle路徑
-(void)createPath{
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:[self getVersionPlistPath]]) {
        return;
    }
    
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
    NSString *path = [paths lastObject];
    NSString *directryPath = [path stringByAppendingPathComponent:@"IOSBundle"];
    [fileManager createDirectoryAtPath:directryPath withIntermediateDirectories:YES attributes:nil error:nil];
    NSString *filePath = [directryPath stringByAppendingPathComponent:@"Version.plist"];
    [fileManager createFileAtPath:filePath contents:nil attributes:nil];
}

-根據(jù)業(yè)務(wù)判斷是否需要更新

//獲取版本信息
-(void)getAppVersion{
    
    //從服務(wù)器上獲取版本信息,與本地plist存儲的版本進行比較
    //1.獲取本地plist文件的版本號 
    NSString* plistPath=[self getVersionPlistPath];
    NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
    
    NSInteger localV=[data[@"bundleVersion"]integerValue];
  
    //本地plist的版本號
    printf("%ld ", (long)localV);

    //保留業(yè)務(wù)食磕,根據(jù)當(dāng)前熱更版本號與本地比對尽棕,進行判斷是否下載
    if(true){
        //下載bundle文件 存儲在 Doucuments/IOSBundle/下
        NSString*url=@"http://192.168.1.127/patches.zip";
        [[DownLoadTool defaultDownLoadTool] downLoadWithUrl:url];
    }
}

-下載zip 至指定沙盒路徑地址

-(void)downLoadWithUrl:(NSString*)url{
    //根據(jù)url下載相關(guān)文件
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
    NSURL *URL = [NSURL URLWithString:url];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
        //獲取下載進度
        NSLog(@"Progress is %f", downloadProgress.fractionCompleted);
    } destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
        //有返回值的block,返回文件存儲路徑
        NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
        
        // file:///Users/tugaofeng/Library/Developer/CoreSimulator/Devices/C4A3CBA7-3313-4EF1-A281-FF04064041B0/data/Containers/Data/Application/20FC6C6B-1397-45C7-A7C5-836EA272EE1C/Documents/kiOSFileName
        NSURL* targetPathUrl = [documentsDirectoryURL URLByAppendingPathComponent:@"IOSBundle"];
        return [targetPathUrl URLByAppendingPathComponent:[response suggestedFilename]];
        
    } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
        if(error){
            //下載出現(xiàn)錯誤
            NSLog(@"%@",error);
            
        }else{
            // [self showPromptWithStr:@"更新完畢彬伦。請重新啟動******滔悉!"];
            //下載成功
            //  file:///Users/tugaofeng/Library/Developer/CoreSimulator/Devices/C4A3CBA7-3313-4EF1-A281-FF04064041B0/data/Containers/Data/Application/20FC6C6B-1397-45C7-A7C5-836EA272EE1C/Documents/kiOSFileName/patches.zip
            NSLog(@"File downloaded to: %@", filePath);
            self.zipPath = [[filePath absoluteString] substringFromIndex:7];
            //下載成功后更新本地存儲信息
            NSDictionary*infoDic=@{@"bundleVersion":@3,@"downloadUrl":url};
            [UpdateDataLoader sharedInstance].versionInfo=infoDic;
            
            [[UpdateDataLoader sharedInstance] writeAppVersionInfoWithDictiony:[UpdateDataLoader sharedInstance].versionInfo];
            
            //解壓并刪除壓縮包
            [self unZip];
            [self deleteZip]; 
        }
    }];
    [downloadTask resume];
}

-解壓&刪除壓縮包

//解壓壓縮包
-(BOOL)unZip{
    if (self.zipPath == nil) {
        return NO;
    }
//Users/tugaofeng/Library/Developer/CoreSimulator/Devices/C4A3CBA7-3313-4EF1-A281-FF04064041B0/data/Containers/Data/Application/20FC6C6B-1397-45C7-A7C5-836EA272EE1C/Documents/kiOSFileName/patches.zip
    NSString *zipPath = self.zipPath;
    
    // /Users/tugaofeng/Library/Developer/CoreSimulator/Devices/C4A3CBA7-3313-4EF1-A281-FF04064041B0/data/Containers/Data/Application/20FC6C6B-1397-45C7-A7C5-836EA272EE1C/Documents/IOSBundle
    NSString *destinationPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]stringByAppendingString:@"/IOSBundle"];
    BOOL success = [SSZipArchive unzipFileAtPath:zipPath
                                   toDestination:destinationPath];
    return success;  
}  
//刪除壓縮包  
-(void)deleteZip{  
    NSError* error = nil;  
    [[NSFileManager defaultManager] removeItemAtPath:self.zipPath error:&error];  
}  
下載前沙盒文件

下載解壓后沙盒文件

-RN調(diào)用JSBundle的時候判斷,當(dāng)沙盒對應(yīng)位置的bundle不為空時加載其bundle单绑,否則加載原包內(nèi)的bundle

    NSURL *jsCodeLocation;
    
    NSString* iOSBundlePath = [[UpdateDataLoader sharedInstance] iOSFileBundlePath];
    NSString* filePath = [iOSBundlePath stringByAppendingPathComponent:@"/main.jsbundle"];
    if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
            jsCodeLocation = [NSURL URLWithString:[iOSBundlePath stringByAppendingString:@"/main.jsbundle"]];
    
    }else{
        jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
    }

參考資料

React Native 實現(xiàn)熱部署回官、差異化增量熱更新
React-Native開發(fā)iOS篇-熱更新的代碼實現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市搂橙,隨后出現(xiàn)的幾起案子歉提,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件苔巨,死亡現(xiàn)場離奇詭異版扩,居然都是意外死亡,警方通過查閱死者的電腦和手機侄泽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門礁芦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悼尾,你說我怎么就攤上這事柿扣。” “怎么了闺魏?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵未状,是天一觀的道長。 經(jīng)常有香客問我舷胜,道長娩践,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任烹骨,我火速辦了婚禮翻伺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沮焕。我一直安慰自己吨岭,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布峦树。 她就那樣靜靜地躺著辣辫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪魁巩。 梳的紋絲不亂的頭發(fā)上急灭,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音谷遂,去河邊找鬼葬馋。 笑死,一個胖子當(dāng)著我的面吹牛肾扰,可吹牛的內(nèi)容都是我干的畴嘶。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼集晚,長吁一口氣:“原來是場噩夢啊……” “哼窗悯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起偷拔,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤蒋院,失蹤者是張志新(化名)和其女友劉穎亏钩,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體悦污,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡铸屉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年钉蒲,在試婚紗的時候發(fā)現(xiàn)自己被綠了切端。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡顷啼,死狀恐怖踏枣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情钙蒙,我是刑警寧澤茵瀑,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站躬厌,受9級特大地震影響马昨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扛施,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一鸿捧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疙渣,春花似錦匙奴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至啦租,卻和暖如春哗伯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背篷角。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工焊刹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人内地。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓伴澄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親阱缓。 傳聞我的和親對象是個殘疾皇子非凌,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350